用于MultilingualString对象的JSF Composite Component

时间:2014-06-02 10:37:12

标签: jsf jsf-2 composite-component

我正在编写一个需要国际化的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 Composite Components,我试图为此编写一个。

这是我的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属性作为List of Language的一个实例。 如果languages属性为null,我希望te复合组件在multiTualString中包含的地图中的每个条目的dataTable中显示一行。

现在这是我的&#34;支持组件&#34;在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值属性一样(我读了article of BalusC on the topic并且他不需要填充value属性以使其下拉列表具有正确的值)。我在stackoverflow上的某个地方阅读了BalusC的答案,如果它被评估为null,则会创建inputText的binding属性中引用的UIInput,这就是为什么我尝试在init中初始化它们的原因方法,但到目前为止没有运气。
  • 当我提交表单时,我在getSubmittedValue()方法的getKey()调用中得到NullPointerException。怎么可能?

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

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

1 个答案:

答案 0 :(得分:3)

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

  1. 您在一个仅在视图渲染时间可用的变量上使用bindingbinding属性在视图构建期间运行,而不是在视图渲染时间期间运行。在此特定情况下,执行binding时,#{language}null。另见How does the 'binding' attribute work in JSF? When and how should it be used?。此外,您似乎期望生成多个<h:inputText>组件,但这不是真的。只有一个在渲染视图期间多次重复使用。只有当您使用<c:forEach>而不是<h:dataTable>时,才会生成多个<h:inputText>组件。另请参阅JSTL in JSF2 Facelets... makes sense?

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

  3. 然而,整体方法很笨拙。您不需要功能要求的支持组件。只需将List<Languages> getter添加到MultilingualString,然后将其直接用作default属性的languages

    因此,如果您将其添加到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>
    

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