回发后以编程方式添加的复合组件中未呈现脚本 [英] Script is not rendered after postback in a composite component added programmatically

查看:45
本文介绍了回发后以编程方式添加的复合组件中未呈现脚本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个复合组件:

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>

最后一个问题:

尝试添加复合词时出错程序化的组件(未为名称定义标签")

我遇到此错误:

javax.faces.view.facelets.TagException: //C:/wildfly-10/standalone/tmp/eventos.ear.visao.war/mojarra7308315477323852505.tmp @2,127 <j:inputMask.xhtml> Tag Library supports namespace: http://xmlns.jcp.org/jsf/composite/componente, but no tag was defined for name: inputMask.xhtml

当尝试使用以下代码以编程方式添加上述复合组件时:

when trying to add the above composite component programmatically with this code:

Map<String, String> attributes = new HashMap<>();
attributes.put("mask", "999.999");
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);

但是我设法通过以下方式解决了这个问题:

but I managed to solve this problem this way:

OmniFaces 2.4(我使用的版本)中方法 Components#includeCompositeComponent 的实现是这样的:

The implementation of the method Components#includeCompositeComponent from OmniFaces 2.4 (the version I was using) is this:

public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String tagName, String id, Map<String, String> attributes) {
    String taglibURI = "http://xmlns.jcp.org/jsf/composite/" + libraryName;
    Map<String, Object> attrs = (attributes == null) ? null : new HashMap<String, Object>(attributes);

    FacesContext context = FacesContext.getCurrentInstance();
    UIComponent composite = context.getApplication().getViewHandler()
        .getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
        .createComponent(context, taglibURI, tagName, attrs);
    composite.setId(id);
    parent.getChildren().add(composite);
return composite;
}

因此,我决定尝试使用此方法的早期版本的OmniFaces(带有一些更改,从我的属性参数中进行一些更改)中的代码:

So I decided to give a try to the code from an earlier version of OmniFaces (with some change adding the attributes parameter from me) of this method:

public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String resourceName, String id, Map<String, String> attributes) {
    // Prepare.
    FacesContext context = FacesContext.getCurrentInstance();
    Application application = context.getApplication();
    FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);

    // This basically creates <ui:component> based on <composite:interface>.
    Resource resource = application.getResourceHandler().createResource(resourceName, libraryName);
    UIComponent composite = application.createComponent(context, resource);
    composite.setId(id); // Mandatory for the case composite is part of UIForm! Otherwise JSF can't find inputs.

    // This basically creates <composite:implementation>.
    UIComponent implementation = application.createComponent(UIPanel.COMPONENT_TYPE);
    implementation.setRendererType("javax.faces.Group");
    composite.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, implementation);

    if (!attributes.isEmpty()) {
        ExpressionFactory factory = application.getExpressionFactory();
        ELContext ctx = context.getELContext();
        for (Map.Entry<String, String> entry : attributes.entrySet()) {
            ValueExpression expr = factory.createValueExpression(ctx, entry.getValue(), Object.class);
            composite.setValueExpression(entry.getKey(), expr);
        }
    } 

    // Now include the composite component file in the given parent.
    parent.getChildren().add(composite);
    parent.pushComponentToEL(context, composite); // This makes #{cc} available.
    try {
        faceletContext.includeFacelet(implementation, resource.getURL());
    } catch (IOException e) {
        throw new FacesException(e);
    } finally {
        parent.popComponentFromEL(context);
    }

    return composite;
}

最后错误消失了.复合组件已动态添加到页面中.

And finally the error was gone. The composite component was dynamically added to the page.

但是出现了另一个问题.

But another problem appeared.

按钮中添加组件的操作大致如下:

The action in a button to add the component is more or less like this:

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);
}

如您所见,复合组件仅添加一次.

As you can see, the composite component is only added once.

首次添加组件时,该组件中的脚本代码:

When the component is first added, the script code that is in the component:

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

已添加到页面.在浏览器中可视化html源代码时,可以看到它.但是在回发时,此脚本代码不再呈现.不在基于html的页面中.与target="head"相同的<h:outputScript>每次都会按预期呈现,但不是每次都呈现.

is added to the page. I can see it when I visualize the html source code in the browser. But on postbacks, this script code is not rendered anymore. It's not in the genereted html page. The <h:outputScript> with target="head" is rendered everytime, as expected, but not this one.

从我的角度来看,即使在页面的回发中,上述方法中的复合组件代码的组装仍可能缺少某些东西来修复脚本代码.我真的不知道只是一个猜测而已.

From my point of view, maybe there's still someting missing in the assembling of the composite component code in the method above to fix the script code even on postbacks on the page. I really don't know. It's just a guess.

您知道发生了什么或缺少什么?

Do you know what's going on or what's missing?

----更新1 ----

我认为我确实找到了问题的根源.看来这是JSF中的一个错误,与以编程方式包含的复合组件中的脚本有关.

I think that I really found the source of the problem. It seems that it's a bug in JSF related with scripts in composite components included programatically.

这是我发现的东西:

我注意到OmniFaces包含我的复合组件的正确代码是:

I noticed that the correct code from OmniFaces to include my composite component is this:

Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask", "a123", attributes);

正确的是"inputMask",而不是"inputMask.xhtml".但是,正如我之前告诉您的那样,当我使用此代码时,我收到了以下错误消息:

The correct is "inputMask", not "inputMask.xhtml". But as I told you before, when I use this code I get this error instead:

Caused by: javax.faces.FacesException: Cannot remove the same component twice: form:a123:j_idt2

所以我怀疑ID为 form:a123:j_idt2 的组件是复合组件中存在的 h:outputScript 之一.因此,我将复合组件代码更改为此:

So I suspected that the component with the id form:a123:j_idt2 was one of the h:outputScript present in the composite component. So I changed the composite component code to this:

<!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>

删除对 h:outputScript 标记的所有引用. (当然,我将 inputmask.js 脚本放置在复合组件之外,以使该组件继续工作.)

Removing all references to the h:outputScript tag. (Of course, I placed the inputmask.js script outside the composite component for the component to continue to work).

现在,当我运行代码时,该组件最终将被无错误地添加到页面中.但是,正如我之前对OmniFaces早期版本中的代码所说的那样,该脚本仍未在回发中呈现. JSF仅在添加组件时才呈现它,从而在回发时失去它.我知道这不是预期的行为.

And now when I run the code, the component is finally added to the page without errors. But, as I said before with the code from an earlier version of OmniFaces, the script is still not rendered in postbacks. JSF only renders it when the component is added, loosing it on postbacks. I know this is not an expected behaviour.

所以,我问你:你知道我该如何解决这个脚本问题?或者在这种情况下至少可以使用任何解决方法?

So, I ask you: do you know how I can solve this script problem? Or at least any workaround I can use in this case?

谢谢.

----更新2 ----

我找到了解决方法.我是在复合组件的后备组件中完成此操作的,它一直有效,脚本始终被渲染:

I found a workaround for it. I did this in a backing component for the composite component and it worked, the script is always rendered:

@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");
}

但这有点丑陋,似乎没有必要.同样,如果不以编程方式包含该组件,则不需要后备组件.似乎是JSF中的错误.你们中的一些人可以测试并确认吗?我的意思是,测试添加了脚本的复合组件是否以编程方式在回发时丢失了脚本.

but it's kind of ugly and seems unnecessary. Again, if the component is not included programmatically, I don't need the backing component. It seems like a bug in JSF. Could some of you test and confirm this? I mean, test if a composite component with script in it added programmatically loses its script on postback.

PS::我正在使用 OmniFaces 2.4 Mojarra 2.2.13 .

推荐答案

解决方案(解决方法)是从复合组件中删除所有脚本,并创建一个支持组件以使其完全执行JSF应该做的事情:

The solution (workaround) is to remove all script from the composite component and create a backing component for it to do precisely what JSF was supposed to do:

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可以按预期工作,并且您不需要支持组件.

Again, you don't need this if your composite component won't be included programmatically. In this case, JSF works as expected and you don't need the backing component.

如果有时间,我认为最好向Mojarra团队提交错误报告.

If someone have the time, I think it would be nice to file a bug report to the Mojarra team.

这篇关于回发后以编程方式添加的复合组件中未呈现脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆