用于MultilingualString对象的JSF复合组件



我正在编写一个需要国际化的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的一个实例,languages属性是List of Language的一个实例。如果languages属性为空,我希望复合组件在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);
    }
}

为了实现我想要在哪种语言上显示输入的规则,我向支持组件添加了一个languages属性,并在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值属性(我阅读了BalusC关于该主题的文章,他不需要填充值属性以使他的下拉列表具有正确的值)。我读到一个BalusC的答案在stackoverflow的某个地方,在inputText的绑定属性中引用的UIInput是创建的,如果它被评估为null,这就是为什么我试图在init方法中初始化它们,但到目前为止没有运气。
  • 当我提交表单时,我在getSubmittedValue()方法的getKey()调用中获得NullPointerException。这怎么可能呢?

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

编辑:我使用的是GlassFish 4,我手动更新了Mojarra到2.2.6

目前发布的代码中有两个技术问题:

  1. 你在一个变量上使用binding,这个变量只在视图渲染时可用。binding属性在视图构建时运行,而不是在视图呈现时运行。在本例中,执行binding时,#{language}null。参见'binding'属性在JSF中工作吗?何时以及如何使用?此外,您似乎期望生成多个<h:inputText>组件,但事实并非如此。只有一个在呈现视图期间被多次重用。只有当您使用<c:forEach>而不是<h:dataTable>时,才会生成多个物理上的<h:inputText>组件。参见JSF2 Facelets中的JSTL…有道理吗?

  2. 您没有为回发保存组件的状态。您应该删除languages属性,并让getter和setter委托给getStateHelper()。参见扩展UIComponentBase时如何保存状态

然而,整体方法是笨拙的。您不需要一个支持组件来满足功能需求。只需在MultilingualString上添加List<Languages> 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>

那么它应该按预期工作。请注意,可以使用大括号符号[]来引用动态映射键。这可能是你的解决方案的全部关键(你似乎没有意识到这一点,因此朝着一个过于复杂的解决方案努力)。

相关内容

  • 没有找到相关文章

最新更新