基于 Primefaces 的 <p:diagram> 创建自定义组件
Creating custom component based on Primefaces' <p:diagram>
多年来我一直潜伏在这个帮助社区,但从未发布过,因为我通常在询问之前找到我需要的东西。
我已经阅读了那些文章:
How to render a composite component using a custom renderer?
What is the relationship between component family, component type and renderer type?
但我一直在尝试基于 Primefaces <p:diagram>
.
创建自己的组件
该组件本身几乎符合我的需要,但我需要网络浏览器能够正确解释 HTML 标签,例如 <mark>, <strong>
的 Element
的 data
属性<p:diagram>
。我还没有找到不实现我自己的组件的解决方案。
稍微了解一下 JSF 的 <h:outputText>
如何提供选项(通过 escape
标记)来解释 HTML 标签正确,我考虑将此标签及其行为添加到 <p:diagram>
组件并制作我自己的组件(稍后我可能还需要添加进一步的自定义)。
这是我的标签库:
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd" version="2.0">
<namespace>http://myNamespace</namespace>
<tag>
<tag-name>logigramme</tag-name>
<description><![CDATA[]]></description>
<component>
<component-type>myComponent.component.type</component-type>
<renderer-type>myComponentRenderer.renderer.type</renderer-type>
</component>
<attribute>
<description><![CDATA[Unique identifier of the component in a namingContainer.]]></description>
<name>id</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Boolean value to specify the rendering of the component, when set to false component will not be rendered.]]></description>
<name>rendered</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[An el expression referring to a server side UIComponent instance in a backing bean.]]></description>
<name>binding</name>
<required>false</required>
<type>javax.faces.component.UIComponent</type>
</attribute>
<attribute>
<description><![CDATA[Value of the component.]]></description>
<name>value</name>
<required>false</required>
<type>java.lang.Object</type>
</attribute>
<attribute>
<description><![CDATA[An el expression or a literal text that defines a converter for the component. When it's an EL expression, it's resolved to a converter instance.
In case it's a static text, it must refer to a converter id.]]></description>
<name>converter</name>
<required>false</required>
<type>java.faces.convert.Converter</type>
</attribute>
<attribute>
<description><![CDATA[Name of the client side widget.]]></description>
<name>widgetVar</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Name of the iterator variable used to refer each data.]]></description>
<name>var</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Inline style of the component.]]></description>
<name>style</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Style class of the component.]]></description>
<name>styleClass</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Flag indicating that characters that are sensitive in HTML and XML markup must be escaped. This flag is set to "true" by default.]]></description>
<name>escape</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
</tag>
这是我的组件:
package myPackage;
import javax.faces.component.FacesComponent;
import org.primefaces.component.diagram.Diagram;
@FacesComponent("logigramme")
public class Logigramme extends Diagram {
public static final String COMPONENT_TYPE = "myComponent.component.type";
public static final String DEFAULT_RENDERER = "myComponentRenderer.renderer.type";
protected enum PropertyKeys {
widgetVar, var, style, styleClass, escape;
String toString;
PropertyKeys(String toString) {
this.toString = toString;
}
PropertyKeys() {
}
public String toString() {
return ((this.toString != null) ? this.toString : super.toString());
}
}
public Logigramme() {
setRendererType(DEFAULT_RENDERER);
}
public String getEscape() {
return (String) getStateHelper().eval(PropertyKeys.escape, null);
}
}
这是我的自定义渲染器:
package myPackage;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import org.primefaces.component.diagram.Diagram;
import org.primefaces.component.diagram.DiagramRenderer;
import org.primefaces.model.diagram.Connection;
import org.primefaces.model.diagram.DiagramModel;
import org.primefaces.model.diagram.Element;
import org.primefaces.model.diagram.connector.Connector;
import org.primefaces.model.diagram.endpoint.EndPoint;
import org.primefaces.model.diagram.overlay.Overlay;
import org.primefaces.renderkit.CoreRenderer;
import org.primefaces.util.SharedStringBuilder;
import org.primefaces.util.WidgetBuilder;
@FacesRenderer(componentFamily = Diagram.COMPONENT_FAMILY, rendererType=Logigramme.DEFAULT_RENDERER)
public class LogigrammeRenderer extends DiagramRenderer {
@Override
public void decode(FacesContext context, UIComponent component) {
Logigramme logigramme = (Logigramme) component;
if (logigramme.isConnectRequest(context)) {
decodeNewConnection(context, logigramme);
} else if (logigramme.isDisconnectRequest(context)) {
decodeDisconnection(context, logigramme);
} else if (logigramme.isConnectionChangeRequest(context)) {
decodeConnectionChange(context, logigramme);
}
decodeBehaviors(context, component);
}
[...]
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
Logigramme logigramme = (Logigramme) component;
encodeMarkup(context, logigramme);
encodeScript(context, logigramme);
}
[...]
protected void encodeMarkup(FacesContext context, Logigramme logigramme) throws IOException {
ResponseWriter writer = context.getResponseWriter();
DiagramModel model = (DiagramModel) logigramme.getValue();
String clientId = logigramme.getClientId(context);
String style = logigramme.getStyle();
String styleClass = logigramme.getStyleClass();
styleClass = (styleClass == null) ? Logigramme.CONTAINER_CLASS : Logigramme.CONTAINER_CLASS + " " + styleClass;
UIComponent elementFacet = logigramme.getFacet("element");
Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
String var = logigramme.getVar();
Boolean escape = Boolean.valueOf(logigramme.getEscape());
writer.startElement("div", logigramme);
writer.writeAttribute("id", logigramme.getClientId(context), null);
writer.writeAttribute("class", styleClass, null);
if (style != null) {
writer.writeAttribute("style", style, null);
}
if (model != null) {
List<Element> elements = model.getElements();
if (elements != null && !elements.isEmpty()) {
for (int i = 0; i < elements.size(); i++) {
Element element = elements.get(i);
String elementClass = element.getStyleClass();
elementClass = (elementClass == null) ? Logigramme.ELEMENT_CLASS : Logigramme.ELEMENT_CLASS + " " + elementClass;
if (element.isDraggable()) {
elementClass = elementClass + " " + Logigramme.DRAGGABLE_ELEMENT_CLASS;
}
Object data = element.getData();
String x = element.getX();
String y = element.getY();
String coords = "left:" + x + ";top:" + y;
writer.startElement("div", null);
writer.writeAttribute("id", clientId + "-" + element.getId(), null);
writer.writeAttribute("class", elementClass, null);
writer.writeAttribute("style", coords, null);
if (elementFacet != null && var != null) {
requestMap.put(var, data);
elementFacet.encodeAll(context);
} else if (data != null) {
if (escape == null || escape) {
writer.writeText(data, null);
} else {
writer.write(data.toString());
}
}
writer.endElement("div");
}
}
if (var != null) {
requestMap.remove(var);
}
}
writer.endElement("div");
}
}
考虑到我在渲染器上使用了注释,我没有修改面孔-config.xml.
然后我在视图中调用我的新组件:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui" xmlns:odc="http://myNamespace">
[...]
<odc:logigramme value="#{myBean.model}" styleClass="ui-widget-content" widgetVar="logigrammeWV" escape="false"/>
myBean
是我的支持 bean,包含我的 DefaultDiagramModel(primefaces 对象)。
当我显示我的页面时,我有以下错误(我随意裁剪它,如果需要我可以提供完整的堆栈跟踪):
INFOS: Facelet[/views/incident/listeIncidents.xhtml] was modified @ 11:06:19, flushing component applied @ 11:06:15
juil. 26, 2018 11:06:19 AM com.sun.faces.application.ApplicationImpl createComponentApplyAnnotations
GRAVE: JSF1068 : Impossible d’instancier un composant dont le type est myComponent.component.type
javax.faces.FacesException: Erreur d’expression : objet nommé «myComponent.component.type» non détecté
at com.sun.faces.application.ApplicationImpl.createComponentApplyAnnotations(ApplicationImpl.java:1933)
at com.sun.faces.application.ApplicationImpl.createComponent(ApplicationImpl.java:1168)
at javax.faces.application.ApplicationWrapper.createComponent(ApplicationWrapper.java:637)
[...]
粗略地翻译一下,该错误意味着 JSF 无法实例化 myComponent.component.type
类型的组件(即 Diagram.COMPONENT_FAMILY
又名 org.primefaces.component
)
最后,我的问题是:知道我做错了什么吗?我忘了什么吗?有没有人必须根据 Primefaces 的图表创建自定义组件?
感谢你们的帮助:)
通常我会发表评论而不是回答,但我发现您的代码存在一些问题。
在我重写数据表的代码中,我必须将它添加到我的面部 - config.xml 以便它能够拾取它。我原以为您的 @FacesComponent 注释会做到这一点。
<!-- Extend PF Datatable component and rendering to fix filter map handling -->
<component>
<component-type>org.primefaces.component.DataTable</component-type>
<component-class>com.stuff.web.faces.MyDataTable</component-class>
</component>
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.DataTableRenderer</renderer-type>
<renderer-class>com.stuff.faces.MyDataTableRenderer</renderer-class>
</renderer>
</render-kit>
其次,除非您的示例被截断,否则我看不到您的 Escape 属性 的 "setter" 只有 getter。您将需要两者才能设置值。
多年来我一直潜伏在这个帮助社区,但从未发布过,因为我通常在询问之前找到我需要的东西。
我已经阅读了那些文章:
How to render a composite component using a custom renderer?
What is the relationship between component family, component type and renderer type?
但我一直在尝试基于 Primefaces <p:diagram>
.
创建自己的组件
该组件本身几乎符合我的需要,但我需要网络浏览器能够正确解释 HTML 标签,例如 <mark>, <strong>
的 Element
的 data
属性<p:diagram>
。我还没有找到不实现我自己的组件的解决方案。
稍微了解一下 JSF 的 <h:outputText>
如何提供选项(通过 escape
标记)来解释 HTML 标签正确,我考虑将此标签及其行为添加到 <p:diagram>
组件并制作我自己的组件(稍后我可能还需要添加进一步的自定义)。
这是我的标签库:
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd" version="2.0">
<namespace>http://myNamespace</namespace>
<tag>
<tag-name>logigramme</tag-name>
<description><![CDATA[]]></description>
<component>
<component-type>myComponent.component.type</component-type>
<renderer-type>myComponentRenderer.renderer.type</renderer-type>
</component>
<attribute>
<description><![CDATA[Unique identifier of the component in a namingContainer.]]></description>
<name>id</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Boolean value to specify the rendering of the component, when set to false component will not be rendered.]]></description>
<name>rendered</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[An el expression referring to a server side UIComponent instance in a backing bean.]]></description>
<name>binding</name>
<required>false</required>
<type>javax.faces.component.UIComponent</type>
</attribute>
<attribute>
<description><![CDATA[Value of the component.]]></description>
<name>value</name>
<required>false</required>
<type>java.lang.Object</type>
</attribute>
<attribute>
<description><![CDATA[An el expression or a literal text that defines a converter for the component. When it's an EL expression, it's resolved to a converter instance.
In case it's a static text, it must refer to a converter id.]]></description>
<name>converter</name>
<required>false</required>
<type>java.faces.convert.Converter</type>
</attribute>
<attribute>
<description><![CDATA[Name of the client side widget.]]></description>
<name>widgetVar</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Name of the iterator variable used to refer each data.]]></description>
<name>var</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Inline style of the component.]]></description>
<name>style</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Style class of the component.]]></description>
<name>styleClass</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Flag indicating that characters that are sensitive in HTML and XML markup must be escaped. This flag is set to "true" by default.]]></description>
<name>escape</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
</tag>
这是我的组件:
package myPackage;
import javax.faces.component.FacesComponent;
import org.primefaces.component.diagram.Diagram;
@FacesComponent("logigramme")
public class Logigramme extends Diagram {
public static final String COMPONENT_TYPE = "myComponent.component.type";
public static final String DEFAULT_RENDERER = "myComponentRenderer.renderer.type";
protected enum PropertyKeys {
widgetVar, var, style, styleClass, escape;
String toString;
PropertyKeys(String toString) {
this.toString = toString;
}
PropertyKeys() {
}
public String toString() {
return ((this.toString != null) ? this.toString : super.toString());
}
}
public Logigramme() {
setRendererType(DEFAULT_RENDERER);
}
public String getEscape() {
return (String) getStateHelper().eval(PropertyKeys.escape, null);
}
}
这是我的自定义渲染器:
package myPackage;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import org.primefaces.component.diagram.Diagram;
import org.primefaces.component.diagram.DiagramRenderer;
import org.primefaces.model.diagram.Connection;
import org.primefaces.model.diagram.DiagramModel;
import org.primefaces.model.diagram.Element;
import org.primefaces.model.diagram.connector.Connector;
import org.primefaces.model.diagram.endpoint.EndPoint;
import org.primefaces.model.diagram.overlay.Overlay;
import org.primefaces.renderkit.CoreRenderer;
import org.primefaces.util.SharedStringBuilder;
import org.primefaces.util.WidgetBuilder;
@FacesRenderer(componentFamily = Diagram.COMPONENT_FAMILY, rendererType=Logigramme.DEFAULT_RENDERER)
public class LogigrammeRenderer extends DiagramRenderer {
@Override
public void decode(FacesContext context, UIComponent component) {
Logigramme logigramme = (Logigramme) component;
if (logigramme.isConnectRequest(context)) {
decodeNewConnection(context, logigramme);
} else if (logigramme.isDisconnectRequest(context)) {
decodeDisconnection(context, logigramme);
} else if (logigramme.isConnectionChangeRequest(context)) {
decodeConnectionChange(context, logigramme);
}
decodeBehaviors(context, component);
}
[...]
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
Logigramme logigramme = (Logigramme) component;
encodeMarkup(context, logigramme);
encodeScript(context, logigramme);
}
[...]
protected void encodeMarkup(FacesContext context, Logigramme logigramme) throws IOException {
ResponseWriter writer = context.getResponseWriter();
DiagramModel model = (DiagramModel) logigramme.getValue();
String clientId = logigramme.getClientId(context);
String style = logigramme.getStyle();
String styleClass = logigramme.getStyleClass();
styleClass = (styleClass == null) ? Logigramme.CONTAINER_CLASS : Logigramme.CONTAINER_CLASS + " " + styleClass;
UIComponent elementFacet = logigramme.getFacet("element");
Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
String var = logigramme.getVar();
Boolean escape = Boolean.valueOf(logigramme.getEscape());
writer.startElement("div", logigramme);
writer.writeAttribute("id", logigramme.getClientId(context), null);
writer.writeAttribute("class", styleClass, null);
if (style != null) {
writer.writeAttribute("style", style, null);
}
if (model != null) {
List<Element> elements = model.getElements();
if (elements != null && !elements.isEmpty()) {
for (int i = 0; i < elements.size(); i++) {
Element element = elements.get(i);
String elementClass = element.getStyleClass();
elementClass = (elementClass == null) ? Logigramme.ELEMENT_CLASS : Logigramme.ELEMENT_CLASS + " " + elementClass;
if (element.isDraggable()) {
elementClass = elementClass + " " + Logigramme.DRAGGABLE_ELEMENT_CLASS;
}
Object data = element.getData();
String x = element.getX();
String y = element.getY();
String coords = "left:" + x + ";top:" + y;
writer.startElement("div", null);
writer.writeAttribute("id", clientId + "-" + element.getId(), null);
writer.writeAttribute("class", elementClass, null);
writer.writeAttribute("style", coords, null);
if (elementFacet != null && var != null) {
requestMap.put(var, data);
elementFacet.encodeAll(context);
} else if (data != null) {
if (escape == null || escape) {
writer.writeText(data, null);
} else {
writer.write(data.toString());
}
}
writer.endElement("div");
}
}
if (var != null) {
requestMap.remove(var);
}
}
writer.endElement("div");
}
}
考虑到我在渲染器上使用了注释,我没有修改面孔-config.xml.
然后我在视图中调用我的新组件:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui" xmlns:odc="http://myNamespace">
[...]
<odc:logigramme value="#{myBean.model}" styleClass="ui-widget-content" widgetVar="logigrammeWV" escape="false"/>
myBean
是我的支持 bean,包含我的 DefaultDiagramModel(primefaces 对象)。
当我显示我的页面时,我有以下错误(我随意裁剪它,如果需要我可以提供完整的堆栈跟踪):
INFOS: Facelet[/views/incident/listeIncidents.xhtml] was modified @ 11:06:19, flushing component applied @ 11:06:15
juil. 26, 2018 11:06:19 AM com.sun.faces.application.ApplicationImpl createComponentApplyAnnotations
GRAVE: JSF1068 : Impossible d’instancier un composant dont le type est myComponent.component.type
javax.faces.FacesException: Erreur d’expression : objet nommé «myComponent.component.type» non détecté
at com.sun.faces.application.ApplicationImpl.createComponentApplyAnnotations(ApplicationImpl.java:1933)
at com.sun.faces.application.ApplicationImpl.createComponent(ApplicationImpl.java:1168)
at javax.faces.application.ApplicationWrapper.createComponent(ApplicationWrapper.java:637)
[...]
粗略地翻译一下,该错误意味着 JSF 无法实例化 myComponent.component.type
类型的组件(即 Diagram.COMPONENT_FAMILY
又名 org.primefaces.component
)
最后,我的问题是:知道我做错了什么吗?我忘了什么吗?有没有人必须根据 Primefaces 的图表创建自定义组件?
感谢你们的帮助:)
通常我会发表评论而不是回答,但我发现您的代码存在一些问题。
在我重写数据表的代码中,我必须将它添加到我的面部 - config.xml 以便它能够拾取它。我原以为您的 @FacesComponent 注释会做到这一点。
<!-- Extend PF Datatable component and rendering to fix filter map handling -->
<component>
<component-type>org.primefaces.component.DataTable</component-type>
<component-class>com.stuff.web.faces.MyDataTable</component-class>
</component>
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.DataTableRenderer</renderer-type>
<renderer-class>com.stuff.faces.MyDataTableRenderer</renderer-class>
</renderer>
</render-kit>
其次,除非您的示例被截断,否则我看不到您的 Escape 属性 的 "setter" 只有 getter。您将需要两者才能设置值。