6

在尝试在我的 JSF/Primefaces Web 应用程序中实现简单的 html5 属性“autofocus”时,我注意到组件不会将所有未知属性传递给最终标记的事实。我可以理解其中的原因,因为组件可以是 html 标记的复杂组合,如果组件尚未明确定义属性,则不清楚将其放置在何处。

但对我来说最好的解决方案是支持自动对焦(以及我可能希望在我的应用程序中支持的 primefaces 尚未定义的任何其他可能类型的属性)。

我已经看到为 JSF 2.0 UIInput 组件添加自定义属性 (HTML5) 支持,但这似乎适用于基本的 JSF 组件并且不适用于 PrimeFaces 组件。

如何扩展 Primefaces 的组件/渲染以支持这一点?

4

3 回答 3

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源代码在这里)理论上也应该在 PrimeFaces 组件上正常工作。然而,这个问题迫使我再看一遍,我很尴尬地看到里面的component论点在里面ResponseWriter#startElement()(见第 74 行,它应该是代替的)。我不确定这是故意还是 PrimeFaces 渲染器设计中的疏忽,但您可以使用它来获取它。null<p:inputText>InputTextRendererwriter.startElement("input", inputText)UIComponent#getCurrentComponent()


更新:这是在 OmniFaces 1.5 中修复的。


应该注意的是,即将到来的 JSF 2.2 将支持通过新的passthrough命名空间或<f:passThroughAttribute>标签在视图中定义自定义属性。另请参阅JSF 2.2 中有哪些新功能?- HTML5 传递属性

因此,所以:

<html ... xmlns:p="http://java.sun.com/jsf/passthrough">
...
<h:inputText ... p:autofocus="true" />

(您可能希望使用a而不是p作为命名空间前缀以避免与 PrimeFaces 的默认命名空间发生冲突)

或者:

<h:inputText ...>
    <f:passThroughAttribute name="autofocus" value="true" />
</h:inputText>
于 2013-03-22T18:15:32.957 回答
3

我找到的解决方案是扩展和重新实现输入渲染器的 encodeMarkup 方法。我想要一个更通用的解决方案,但是在查看 Primefaces 源代码后,我没有看到任何用于组件渲染器添加自定义属性的通用挂钩。标记写在encodeMarkup(FacesContext context, InputText inputText)渲染器的方法中。它调用类层次结构,renderPassThruAttributes(FacesContext context, UIComponent component, String[] attributes)但它只从org.primefaces.util.HTML.

就我而言,我希望支持 InputMask、InputText、InputTextarea 和 Password 组件上的“自动对焦”属性。此外,每个组件的实现都是相同的,因此我将介绍如何在 InputText 组件上实现“自动对焦”,但如何扩展它以支持更多属性和更多组件应该很明显。

要扩展/覆盖渲染器,您需要有可用的 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 中的 autofocus 不使用值,但如果没有给出值,JSF 会抛出错误,因此在添加此类属性时请务必定义一些丢弃值。

于 2013-03-22T16:21:13.937 回答
2

JSF 2.2 还提供了专为 HTML5 及更高版本设计的传递属性功能,因此当 PrimeFaces 正式支持 JSF 2.2 时,您可以将任何属性从组件传递到 html 元素。

于 2013-03-23T20:34:09.067 回答