我正在编写一个需要国际化的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>
问题是:
我希望这很清楚,有人可以帮助我! 谢谢!
编辑: 我使用GlassFish 4并手动将Mojarra更新为2.2.6
答案 0 :(得分:3)
目前发布的代码中存在2个技术问题:
您在一个仅在视图渲染时间可用的变量上使用binding
。 binding
属性在视图构建期间运行,而不是在视图渲染时间期间运行。在此特定情况下,执行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?
您没有保存回发组件的状态。您应该删除languages
属性,并将getter和setter委托给getStateHelper()
。另请参阅How to save state when extending UIComponentBase。
然而,整体方法很笨拙。您不需要功能要求的支持组件。只需将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>
那么它应该像预期的那样工作。请注意使用括号符号[]
引用动态映射键的可能性。这可能是您解决方案的全部关键(您似乎没有意识到这一点,因此致力于一个过于复杂的解决方案)。