多语言字符串对象的 JSF 复合组件

Posted

技术标签:

【中文标题】多语言字符串对象的 JSF 复合组件【英文标题】:JSF Composite Component for MultilingualString object 【发布时间】: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,他不需要填写值属性使他的下拉列表具有正确的值)。我在 *** 上的某处读到了 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) 【参考方案1】:

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

    您正在对仅在视图渲染期间可用的变量使用bindingbinding 属性在视图构建期间运行,而不是在视图渲染期间运行。在这种特殊情况下,当binding 被执行时,#languagenull。另见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?

    您没有为回发保存组件的状态。您应该删除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() 技巧!

以上是关于多语言字符串对象的 JSF 复合组件的主要内容,如果未能解决你的问题,请参考以下文章

JSF ui:composition 和复合组件的问题

存储多语言字符串的最佳实践

C# 创建多语言设置

有没有办法将 React 组件作为字符串而不是对象返回?

MFC/C#.NET混合模式本地化(多语言/多语言应用)

python脚本中的多语言支持