【发布时间】: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。您真的在代码中使用了<h:dataTable>吗?这实际上不是<c:forEach>吗?我可以指出回发期间描述的失败的实际错误,但是给定的代码在显示期间已经失败,所以我现在很困惑。 -
@BalusC 我复制粘贴了我在问题中编写的代码,并且页面确实显示了。所以我知道这里不可能使用
<h:dataTable>。但是<c:forEach>的问题不会相同吗?我仍然无法初始化我的Map<Language, UIInput>。 -
什么 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