在尝试在我的JSF / Primefaces Web应用程序中实现简单的html5属性“autofocus”时,我被警告组件不会将所有未知属性传递给最终标记。我可以理解这个问题的原因,因为组件可能是html标记的复杂组合,如果属性尚未被组件定义,则不清楚放置属性的位置。
但对我来说最好的解决方案是支持自动对焦(以及我可能希望在我的应用程序中支持的任何其他可能类型的属性,而这些属性还没有定义)。
我见过Adding custom attribute (HTML5) support to JSF 2.0 UIInput component,但这似乎适用于基本的JSF组件,并不适用于PrimeFaces组件。
如何扩展Primefaces的组件/渲染以支持此功能?
答案 0 :(得分:10)
您可以创建一个RenderKit
,而不是为每个单独的组件自定义渲染器,您可以在其中提供自定义ResponseWriter
,其中startElement()
方法被覆盖以检查元素名称和/或组件实例,然后相应地编写其他属性。
以下是HTML5渲染工具包的启动示例:
public class Html5RenderKit extends RenderKitWrapper {
private RenderKit wrapped;
public Html5RenderKit(RenderKit wrapped) {
this.wrapped = wrapped;
}
@Override
public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) {
return new Html5ResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding));
}
@Override
public RenderKit getWrapped() {
return wrapped;
}
}
HTML5响应编写器:
public class Html5ResponseWriter extends ResponseWriterWrapper {
private static final String[] HTML5_INPUT_ATTRIBUTES = { "autofocus" };
private ResponseWriter wrapped;
public Html5ResponseWriter(ResponseWriter wrapped) {
this.wrapped = wrapped;
}
@Override
public ResponseWriter cloneWithWriter(Writer writer) {
return new Html5ResponseWriter(super.cloneWithWriter(writer));
}
@Override
public void startElement(String name, UIComponent component) throws IOException {
super.startElement(name, component);
if ("input".equals(name)) {
for (String attributeName : HTML5_INPUT_ATTRIBUTES) {
String attributeValue = component.getAttributes().get(attributeName);
if (attributeValue != null) {
super.writeAttribute(attributeName, attributeValue, null);
}
}
}
}
@Override
public ResponseWriter getWrapped() {
return wrapped;
}
}
要运行它,请创建此HTML5渲染工具包工厂:
public class Html5RenderKitFactory extends RenderKitFactory {
private RenderKitFactory wrapped;
public Html5RenderKitFactory(RenderKitFactory wrapped) {
this.wrapped = wrapped;
}
@Override
public void addRenderKit(String renderKitId, RenderKit renderKit) {
wrapped.addRenderKit(renderKitId, renderKit);
}
@Override
public RenderKit getRenderKit(FacesContext context, String renderKitId) {
RenderKit renderKit = wrapped.getRenderKit(context, renderKitId);
return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new Html5RenderKit(renderKit) : renderKit;
}
@Override
public Iterator<String> getRenderKitIds() {
return wrapped.getRenderKitIds();
}
}
并在faces-config.xml
:
<factory>
<render-kit-factory>com.example.Html5RenderKitFactory</render-kit-factory>
</factory>
JSF实用程序库OmniFaces还有一个这样的渲染工具包Html5RenderKit(source code here),理论上它也可以在PrimeFaces组件上正常工作。但是,这个问题迫使我再次重新审视,我很尴尬地看到component
中的ResponseWriter#startElement()
参数null
<p:inputText>
writer.startElement("input", inputText)
},它应该是UIComponent#getCurrentComponent()
而不是。我不确定这是有意还是对PrimeFaces渲染器的设计进行疏忽,但您可以使用passthrough
来获取它。
更新:这在OmniFaces 1.5中得到修复。
值得注意的是,即将推出的JSF 2.2将支持通过新的<f:passThroughAttribute>
命名空间或<html ... xmlns:p="http://java.sun.com/jsf/passthrough">
...
<h:inputText ... p:autofocus="true" />
标记在视图中定义自定义属性。另请参阅line 74 of InputTextRenderer
。
因此,所以:
a
(您可能希望使用p
而不是<h:inputText ...>
<f:passThroughAttribute name="autofocus" value="true" />
</h:inputText>
作为命名空间前缀,以避免与PrimeFaces的默认命名空间发生冲突)
或者:
{{1}}
答案 1 :(得分:3)
我找到的解决方案是为输入渲染器扩展并重新实现encodeMarkup方法。我想要一个更通用的解决方案,但在查看Primefaces源代码之后,我没有看到组件渲染器添加自定义属性的任何通用挂钩。标记在渲染器的encodeMarkup(FacesContext context, InputText inputText)
方法中写出。它将类层次结构调用到renderPassThruAttributes(FacesContext context, UIComponent component, String[] attributes)
,但它只提供来自org.primefaces.util.HTML
的静态最终String []数组。
在我的情况下,我希望支持InputMask,InputText,InputTextarea和Password组件上的'autofocus'属性。此外,每个组件的实现都是相同的,因此我将在InputText组件上实现'autofocus',但是应该明白如何扩展它以支持更多属性和更多组件。
要扩展/覆盖渲染器,您需要使用Primefaces源并找到encodeMarkup方法并复制其内容。以下是InputTextRenderer的示例:
protected void encodeMarkup(FacesContext context, InputText inputText) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String clientId = inputText.getClientId(context);
writer.startElement("input", null);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("name", clientId, null);
writer.writeAttribute("type", inputText.getType(), null);
String valueToRender = ComponentUtils.getValueToRender(context, inputText);
if(valueToRender != null) {
writer.writeAttribute("value", valueToRender , null);
}
renderPassThruAttributes(context, inputText, HTML.INPUT_TEXT_ATTRS);
if(inputText.isDisabled()) writer.writeAttribute("disabled", "disabled", null);
if(inputText.isReadonly()) writer.writeAttribute("readonly", "readonly", null);
if(inputText.getStyle() != null) writer.writeAttribute("style", inputText.getStyle(), null);
writer.writeAttribute("class", createStyleClass(inputText), "styleClass");
writer.endElement("input");
}
使用您自己的渲染器扩展/覆盖渲染器(请参阅重要代码的注释):
public class HTML5InputTextRenderer extends InputTextRenderer {
Logger log = Logger.getLogger(HTML5InputTextRenderer.class);
//Define your attributes to support here
private static final String[] html5_attributes = { "autofocus" };
protected void encodeMarkup(FacesContext context, InputText inputText) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String clientId = inputText.getClientId(context);
writer.startElement("input", null);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("name", clientId, null);
writer.writeAttribute("type", inputText.getType(), null);
String valueToRender = ComponentUtils.getValueToRender(context, inputText);
if (valueToRender != null) {
writer.writeAttribute("value", valueToRender, null);
}
renderPassThruAttributes(context, inputText, HTML.INPUT_TEXT_ATTRS);
//Make an extra call to renderPassThruAttributes with your own attributes array
renderPassThruAttributes(context, inputText, html5_attributes);
if (inputText.isDisabled())
writer.writeAttribute("disabled", "disabled", null);
if (inputText.isReadonly())
writer.writeAttribute("readonly", "readonly", null);
if (inputText.getStyle() != null)
writer.writeAttribute("style", inputText.getStyle(), null);
writer.writeAttribute("class", createStyleClass(inputText), "styleClass");
writer.endElement("input");
}
}
在faces-config.xml中配置渲染覆盖
<?xml version='1.0' encoding='UTF-8'?>
<faces-config 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-facesconfig_2_0.xsd"
version="2.0">
<!-- snip... -->
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.InputTextRenderer</renderer-type>
<renderer-class>com.mycompany.HTML5InputTextRenderer</renderer-class>
</renderer>
</render-kit>
<!-- snip... -->
</faces-config>
如果你没有在web.xml中配置faces-config,那么就是这样:
<context-param>
<param-name>javax.faces.CONFIG_FILES</param-name>
<param-value>
/WEB-INF/faces-config.xml, /faces-config.xml
</param-value>
</context-param>
然后在一些标记中使用它:
<p:inputText id="activateUserName" value="${someBean.userName}"
autofocus="on">
</p:inputText>
注意:JSF对没有值的属性不满意。虽然HTML5中的自动对焦不使用值,但如果没有给出,则JSF会抛出错误,因此请确保在添加此类属性时定义一些丢弃值。
答案 2 :(得分:2)
JSF 2.2还提供了为HTML5及更高版本设计的传递属性功能,因此当PrimeFaces正式支持JSF 2.2时,您可以将任何属性从组件传递到html元素。