jsf 2 - JSF Composite Component for MultilingualString object -
i'm writing jsf application needs internationalized. so, created multilingualstring :
public class multilingualstring { /* language class wrapper java.util.locale */ private map<language, string> strings; /* business methods, getters, setters */ }
now, there multiple forms needs fill multilingualstring, , it's quite ugly repeat c:foreach loop each time need put such object in form. heard jsf composite components, , tried write 1 purpose.
here 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>
so want value attribute instance of multilingualstring , languages attribute instance of list of language. if languages attribute null, want te composite component display row in datatable each entry in map contained in multilingualstring.
now here "backing component" in 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; (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(); (map.entry<language, uiinput> entry : this.inputs.entryset()) { if (entry.getvalue() != null) { if (!ret.isempty()) { ret += ','; } ret += entry.getkey().getlanguagetag(); // nullpointerexception here when form 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; (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); } }
in order implement rule on languages want display input, added languages attribute backing component , initialized in init method called on prerendercomponent event. languages list correctly initialized.
here how use composite component :
<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}"/> <!-- testbean.ms contains : [english] => string-english [français] => string-français [русский] => string-русский , testbean.languages contains list of language objects english, french, , russian --> <p:commandbutton value="submit" action="#{testbean.submit()}"/> </h:form> </ui:define> </ui:composition>
the problems :
- if multilingualstring entered value contains strings, not displayed in inputtext if fill inputtext value attribute (i read article of balusc on topic , doesn't need fill value attribute make drop down lists have correct value). read on balusc's answer somewhere on stackoverflow uiinput referenced in binding attribute of inputtext created if it's evaluated null, that's why tried initialized them in init method, far no luck.
- when submit form, nullpointerexception @ getkey() call in getsubmittedvalue() method. how possible ?
i hope clear , can me ! !
edit : i'm using glassfish 4 , manually updated mojarra 2.2.6
there 2 technical problems in code posted far:
you're using
binding
on variable available during view render time.binding
attribute runs during view build time, not during view render time. in particular case, whenbinding
executed,#{language}
null
. see how 'binding' attribute work in jsf? when , how should used?. seem expect multiple<h:inputtext>
components generated, that's not true. there's 1 reused multiple times during rendering view. when have used<c:foreach>
instead of<h:datatable>
, indeed physically multiple<h:inputtext>
components generated. see jstl in jsf2 facelets... makes sense?you're not saving state of component postback. should removing
languages
property , let getter , setter delegategetstatehelper()
. see how save state when extending uicomponentbase.
however, overall approach clumsy. don't need backing component functional requirement. add list<languages>
getter multilingualstring
, use directly default
of languages
attribute.
so, if add multilingualstring
:
public list<language> getlanguages() { return new arraylist<>(strings.keyset()); }
and reference attribtues via #{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>
then should work intented. note possibility use brace notation []
reference dynamic map key. that's perhaps whole key solution (you seems not aware of , hence working towards overcomplicated solution).
Comments
Post a Comment