【问题标题】:JSF Composite Component for MultilingualString object多语言字符串对象的 JSF 复合组件
【发布时间】:2014-07-22 10:52:37
【问题描述】:

我正在编写一个需要国际化的 JSF 应用程序。为此,我创建了一个 MultilingualString :

public class MultilingualString {
    /* The Language class is basically a wrapper for a java.util.Locale */
    private Map<Language, String> strings;

    /* business methods, getters, setters */
}

现在,有多个表单需要填充 MultilingualString,每次我需要将这样的对象放入表单时重复 c:forEach 循环是非常难看的。所以我听说了 JSF 复合组件,并尝试为此编写一个。

这是我的 inputMultilingualString.xhtml :

<ui:component xmlns:h="http://xmlns.jcp.org/jsf/html"
              xmlns:composite="http://xmlns.jcp.org/jsf/composite"
              xmlns:f="http://xmlns.jcp.org/jsf/core"
              xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <composite:interface componentType="inputMultilingualString">
        <composite:attribute name="value" required="true"
                             type="com.tob.entities.internationalization.MultilingualString"/>
        <composite:attribute name="languages" type="java.util.List" default="#{null}"/>
    </composite:interface>

    <composite:implementation>
        <f:event type="preRenderComponent" listener="#{cc.init}"/>
        <h:dataTable id="#{cc.clientId}" value="#{cc.languages}" var="language">
            <h:column>
                <h:outputLabel value="#{language}"/>
            </h:column>
            <h:column>
                <h:inputText binding="#{cc.inputs[language]}"/>
            </h:column>
        </h:dataTable>
    </composite:implementation>
</ui:component>

所以我希望 value 属性是 MultilingualString 的一个实例,而 language 属性是语言列表的一个实例。 如果语言属性为空,我希望复合组件在 dataTable 中为 MultilingualString 中包含的地图中的每个条目显示一行。

现在这是我在 InputMultilingualString.java 中的“支持组件”:

@FacesComponent(value = "inputMultilingualString", createTag = true)
public class InputMultilingualString extends UIInput implements NamingContainer {

    private final Map<Language, UIInput> inputs = new HashMap();
    private List<Language> languages;

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

    public void init() {
        List<Language> ls = (List<Language>) this.getAttributes().get("languages");
        MultilingualString ms = (MultilingualString) this.getValue();

        /* Setting languages */
        if (ls != null) {
            this.setLanguages(ls);
        } else {
            this.languages = new ArrayList();
            this.languages.addAll(ms.getStrings().keySet());
        }

        /* Initializing inputs */
        UIInput tmp;
        for (Language l : this.languages) {
            tmp = new UIInput();
            tmp.setValue(ms.getString(l));//
            this.inputs.put(l, tmp);
        }
    }

    @Override
    public String getSubmittedValue() {
        String ret = new String();

        for (Map.Entry<Language, UIInput> entry : this.inputs.entrySet()) {
            if (entry.getValue() != null) {
                if (!ret.isEmpty()) {
                    ret += ',';
                }
                ret += entry.getKey().getLanguageTag(); // NullPointerException here when the form is submitted
                ret += "=" + entry.getValue().getSubmittedValue();
            }
        }
        return (ret);
    }

    @Override
    protected Object getConvertedValue(FacesContext context, Object submittedValue) {
        MultilingualString ms = (MultilingualString) this.getValue();
        String[] entries = ((String) submittedValue).split(",");
        String[] pair;
        Language language;

        for (String entry : entries) {
            pair = entry.split("=");
            language = new Language();
            language.setLanguageTag(pair[0]);
            ms.addString(language, pair[1]);
        }
        return (ms);
    }

    public List<Language> getLanguages() {
        return (this.languages);
    }

    public void setLanguages(List<Language> languages) {
        this.languages = languages;
    }

    public Map<Language, UIInput> getInputs() {
        return (this.inputs);
    }
}

为了实现我想要显示输入的语言的规则,我向支持组件添加了一个语言属性,并在 preRenderComponent 事件上调用的 init 方法中对其进行了初始化。 语言列表已正确初始化。

这是我使用复合组件的方式:

<ui:composition template="/Templates/Common.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:h="http://xmlns.jcp.org/jsf/html"
                xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
                xmlns:tob="http://xmlns.jcp.org/jsf/composite/components"
                xmlns:p="http://primefaces.org/ui">
    <ui:define name="content">
        <h:form id="testForm">
            <tob:inputMultilingualString value="#{testBean.ms}" languages="#{testBean.languages}"/>
            <!-- The testBean.ms contains :
                     [English]  => string-English
                     [français] => string-français
                     [русский]  => string-русский

                 And the testBean.languages contains a list of Language objects for English, French, and Russian -->
            <p:commandButton value="Submit" action="#{testBean.submit()}"/>
        </h:form>
    </ui:define>
</ui:composition>

问题是:

  • 如果作为值输入的 MultilingualString 已经包含一些字符串,它们不会显示在 inputText 中,因为如果您填写 inputText 值属性(我阅读了article of BalusC on the topic,他不需要填写值属性使他的下拉列表具有正确的值)。我在 stackoverflow 上的某处读到了 BalusC 的答案,如果 inputText 的绑定属性中引用的 UIInput 被评估为 null,则创建它,这就是为什么我尝试在 init 方法中初始化它们,但到目前为止没有运气。
  • 当我提交表单时,我在 getSubmittedValue() 方法中的 getKey() 调用中收到 NullPointerException。这怎么可能?

我希望这很清楚,有人可以帮助我! 谢谢!

编辑: 我正在使用 GlassFish 4,并手动将 Mojarra 更新为 2.2.6

【问题讨论】:

  • 整个页面根本不应该显示,但在页面加载期间已经失败,binding 出现 EL 异常。在视图构建期间,您不能在 null 的渲染时间变量上使用 binding。您真的在代码中使用了&lt;h:dataTable&gt; 吗?这实际上不是&lt;c:forEach&gt; 吗?我可以指出回发期间描述的失败的实际错误,但是给定的代码在显示期间已经失败,所以我现在很困惑。
  • @BalusC 我复制粘贴了我在问题中编写的代码,并且页面确实显示了。所以我知道这里不可能使用&lt;h:dataTable&gt;。但是&lt;c:forEach&gt; 的问题不会相同吗?我仍然无法初始化我的Map&lt;Language, UIInput&gt;
  • 什么 JSF impl/version?它在 Mojarra 2.2.6 上失败。但无论如何,要求是理解的。这一切都是可能的,不需要支持组件,我会给出答案。
  • @BalusC 就是这个:Mojarra 2.2.6。 GlassFish 开始输出样本:Initialisation de Mojarra 2.2.6 ( 20140304-1537 https://svn.java.net/svn/mojarra~svn/tags/2.2.6@12949)

标签: jsf jsf-2 composite-component


【解决方案1】:

目前发布的代码存在2个技术问题:

  1. 您正在对仅在视图渲染期间可用的变量使用bindingbinding 属性在视图构建期间运行,而不是在视图渲染期间运行。在这种特殊情况下,当binding 被执行时,#{language}null。另见How does the 'binding' attribute work in JSF? When and how should it be used?。此外,您似乎期望会生成多个 &lt;h:inputText&gt; 组件,但事实并非如此。只有一个在渲染视图期间被多次重用。只有当您使用&lt;c:forEach&gt; 而不是&lt;h:dataTable&gt; 时,才会在物理上生成多个&lt;h:inputText&gt; 组件。另见JSTL in JSF2 Facelets... makes sense?

  2. 您没有为回发保存组件的状态。您应该删除languages 属性并让getter 和setter 委托给getStateHelper()。另见How to save state when extending UIComponentBase

但是,整体方法很笨拙。您不需要功能需求的支持组件。只需在MultilingualString 中添加一个List&lt;Languages&gt; getter 并将其直接用作languages 属性的default

所以,如果你把这个添加到MultilingualString:

public List<Language> getLanguages() {
    return new ArrayList<>(strings.keySet());
}

然后通过#{cc.attrs}引用属性:

<cc:interface>
    <cc:attribute name="value" required="true" type="com.tob.entities.internationalization.MultilingualString"/>
    <cc:attribute name="languages" type="java.util.List" default="#{cc.attrs.value.languages}" />
</cc:interface>

<cc:implementation>
    <h:dataTable value="#{cc.attrs.languages}" var="language">
        <h:column>
            <h:outputLabel value="#{language}"/>
        </h:column>
        <h:column>
            <h:inputText value="#{cc.attrs.value.strings[language]}" />
        </h:column>
    </h:dataTable>
</cc:implementation>

然后它应该按预期工作。请注意使用大括号符号[] 引用动态映射键的可能性。这可能是您解决方案的全部关键(您似乎没有意识到这一点,因此正在努力寻求过于复杂的解决方案)。

【讨论】:

  • 确实,看到您的回答,最初的方法显然过于复杂。感谢您的回答和MultilingualString.getLanguages() 技巧!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-25
  • 1970-01-01
  • 2011-06-24
  • 2015-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多