Jsf 在以编程方式添加的复合组件中回发后,不会呈现脚本

Jsf 在以编程方式添加的复合组件中回发后,不会呈现脚本,jsf,composite-component,omnifaces,Jsf,Composite Component,Omnifaces,我有一个复合组件: <h:outputScript target="body"> defineMask("#{cc.clientId}", "#{cc.attrs.mask}"); </h:outputScript> inputMask.xhtml <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:composite="http://xmlns.jcp.

我有一个复合组件:

<h:outputScript target="body">
    defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
inputMask.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:composite="http://xmlns.jcp.org/jsf/composite"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">

      <composite:interface>
        <composite:attribute name="value" />
        <composite:attribute name="mask" type="java.lang.String" required="true" />
        <composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
      </composite:interface>

      <composite:implementation>
        <h:outputScript library="script" name="inputmask.js" target="head" />

        <h:inputText id="mascara">
            <c:if test="#{cc.getValueExpression('value') != null}">
                <f:attribute name="value" value="#{cc.attrs.value}" />
            </c:if>
            <f:converter converterId="#{cc.attrs.converterId}" />
            <f:attribute name="mask" value="#{cc.attrs.mask}" />
        </h:inputText>

        <h:outputScript target="body">
            defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
        </h:outputScript>
      </composite:implementation>
</html>
正确的是
“inputMask”
,而不是
“inputMask.xhtml”
。但正如我之前告诉过你的,当我使用这段代码时,会出现以下错误:

Caused by: javax.faces.FacesException: Cannot remove the same component twice: form:a123:j_idt2
所以我怀疑id形式为a123:j_idt2的组件是复合组件中存在的h:outputScript之一。因此,我将复合组件代码更改为:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:composite="http://xmlns.jcp.org/jsf/composite"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">

      <composite:interface componentType="inputMask">
        <composite:attribute name="value" />
        <composite:attribute name="mask" type="java.lang.String" required="true" />
        <composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
      </composite:interface>

      <composite:implementation>
        <h:inputText id="mascara">
            <c:if test="#{cc.getValueExpression('value') != null}">
                <f:attribute name="value" value="#{cc.attrs.value}" />
            </c:if>
            <f:converter converterId="#{cc.attrs.converterId}" />
            <f:attribute name="mask" value="#{cc.attrs.mask}" />
        </h:inputText>

        <script type="text/javascript">
            defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
        </script>
      </composite:implementation>
</html>
但这有点难看,似乎没有必要。同样,如果组件不是以编程方式包含的,那么我不需要支持组件。这似乎是JSF中的一个bug。你们中的一些人可以测试并确认这一点吗?我的意思是,测试以编程方式添加脚本的复合组件在回发时是否会丢失脚本

p.S.:我正在使用OmniFaces 2.4和Mojarra 2.2.13。

解决方案(解决方法)是从复合组件中删除所有脚本,并为其创建一个支持组件,以精确执行JSF应该执行的操作:

package br.edu.company.project.view.inputmask;

import java.io.IOException;
import java.util.Map;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.omnifaces.util.FacesLocal;

@FacesComponent("inputMask")
public class InputMask extends UIInput implements NamingContainer
{
    private static final String SCRIPT_FILE_WRITTEN =
        "br.edu.company.project.SCRIPT_FILE_WRITTEN";

    @Override
    public String getFamily()
    {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException
    {
        writeScriptFileIfNotWrittenYet(context);

        super.encodeBegin(context);
    }

    @Override
    public void encodeEnd(FacesContext context) throws IOException
    {
        super.encodeEnd(context);

        writeMaskDefinition(context);
    }

    private void writeScriptFileIfNotWrittenYet(FacesContext context) throws IOException
    {
        if (FacesLocal.getRequestMap(context).putIfAbsent(
            SCRIPT_FILE_WRITTEN, true) == null)
        {
            writeScript(context, w -> w.writeAttribute(
                "src", "resources/script/inputmask.js", null));
        }
    }

    private void writeMaskDefinition(FacesContext context) throws IOException
    {
        writeScript(context, w -> w.writeText(String.format(
            "defineMask('%s', '%s');", getClientId(),
            getAttributes().get("mask")), null));
    }

    private void writeScript(FacesContext context, WriteAction writeAction)
        throws IOException
    {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("script", this);
        writer.writeAttribute("type", "text/javascript", null);
        writeAction.execute(writer);
        writer.endElement("script");
    }

    @FunctionalInterface
    private static interface WriteAction
    {
        void execute(ResponseWriter writer) throws IOException;
    }
}
同样,如果您的复合组件不以编程方式包含,您也不需要这样做。在本例中,JSF按预期工作,您不需要支持组件

如果有人有时间,我想最好向Mojarra团队提交一份bug报告

if (Components.findComponent("form:a123") == null)
{
    Map<String, String> attributes = new HashMap<>();
    attributes.put("value", "#{bean.cpf}");
    attributes.put("mask", "999.999.999-99");
    includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);
}
<h:outputScript target="body">
    defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask", "a123", attributes);
Caused by: javax.faces.FacesException: Cannot remove the same component twice: form:a123:j_idt2
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:composite="http://xmlns.jcp.org/jsf/composite"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">

      <composite:interface componentType="inputMask">
        <composite:attribute name="value" />
        <composite:attribute name="mask" type="java.lang.String" required="true" />
        <composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
      </composite:interface>

      <composite:implementation>
        <h:inputText id="mascara">
            <c:if test="#{cc.getValueExpression('value') != null}">
                <f:attribute name="value" value="#{cc.attrs.value}" />
            </c:if>
            <f:converter converterId="#{cc.attrs.converterId}" />
            <f:attribute name="mask" value="#{cc.attrs.mask}" />
        </h:inputText>

        <script type="text/javascript">
            defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
        </script>
      </composite:implementation>
</html>
@Override
public void encodeEnd(FacesContext context) throws IOException
{
    super.encodeEnd(context);

    ResponseWriter writer = context.getResponseWriter();
    writer.startElement("script", this);
    writer.writeText(String.format("defineMask('%s', '%s');",
        getClientId(), getAttributes().get("mask")), null);
    writer.endElement("script");
}
package br.edu.company.project.view.inputmask;

import java.io.IOException;
import java.util.Map;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.omnifaces.util.FacesLocal;

@FacesComponent("inputMask")
public class InputMask extends UIInput implements NamingContainer
{
    private static final String SCRIPT_FILE_WRITTEN =
        "br.edu.company.project.SCRIPT_FILE_WRITTEN";

    @Override
    public String getFamily()
    {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException
    {
        writeScriptFileIfNotWrittenYet(context);

        super.encodeBegin(context);
    }

    @Override
    public void encodeEnd(FacesContext context) throws IOException
    {
        super.encodeEnd(context);

        writeMaskDefinition(context);
    }

    private void writeScriptFileIfNotWrittenYet(FacesContext context) throws IOException
    {
        if (FacesLocal.getRequestMap(context).putIfAbsent(
            SCRIPT_FILE_WRITTEN, true) == null)
        {
            writeScript(context, w -> w.writeAttribute(
                "src", "resources/script/inputmask.js", null));
        }
    }

    private void writeMaskDefinition(FacesContext context) throws IOException
    {
        writeScript(context, w -> w.writeText(String.format(
            "defineMask('%s', '%s');", getClientId(),
            getAttributes().get("mask")), null));
    }

    private void writeScript(FacesContext context, WriteAction writeAction)
        throws IOException
    {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("script", this);
        writer.writeAttribute("type", "text/javascript", null);
        writeAction.execute(writer);
        writer.endElement("script");
    }

    @FunctionalInterface
    private static interface WriteAction
    {
        void execute(ResponseWriter writer) throws IOException;
    }
}