components - How to create dynamic JSF form fields -
i have found similar questions this one, there many ways can done made me more confused.
we getting xml
file reading. xml
contains information on form fields needs presented.
so created custom dynamicfield.java
has information need:
public class dynamicfield { private string label; // label of field private string fieldkey; // key identify field private string fieldvalue; // value of field private string type; // can input,radio,selectbox etc // getters + setters. }
so have list<dynamicfield>
.
i want iterate through list , populate form fields looks this:
<h:datatable value="#{dynamicfields}" var="field"> <my:somecustomcomponent value="#{field}" /> </h:datatable>
the <my:somecustomcomponent>
return appropriate jsf form components (i.e. label, inputtext)
another approach display <my:somecustomcomponent>
, return htmldatatable
form elements. (i think maybe easier do).
which approach best? can show me links or code shows how can create this? prefer complete code examples, , not answers "you need subclass of javax.faces.component.uicomponent
".
since origin not xml, javabean, , other answer doesn't deserve edited totally different flavor (it may still useful future references others), i'll add answer based on javabean-origin.
i see 3 options when origin javabean.
make use of jsf
rendered
attribute or jstl<c:choose>
/<c:if>
tags conditionally render or build desired component(s). below example usingrendered
attribute:<ui:repeat value="#{bean.fields}" var="field"> <div class="field"> <h:inputtext value="#{bean.values[field.name]}" rendered="#{field.type == 'text'}" /> <h:inputsecret value="#{bean.values[field.name]}" rendered="#{field.type == 'secret'}" /> <h:inputtextarea value="#{bean.values[field.name]}" rendered="#{field.type == 'textarea'}" /> <h:selectoneradio value="#{bean.values[field.name]}" rendered="#{field.type == 'radio'}"> <f:selectitems value="#{field.options}" /> </h:selectoneradio> <h:selectonemenu value="#{bean.values[field.name]}" rendered="#{field.type == 'selectone'}"> <f:selectitems value="#{field.options}" /> </h:selectonemenu> <h:selectmanymenu value="#{bean.values[field.name]}" rendered="#{field.type == 'selectmany'}"> <f:selectitems value="#{field.options}" /> </h:selectmanymenu> <h:selectbooleancheckbox value="#{bean.values[field.name]}" rendered="#{field.type == 'checkone'}" /> <h:selectmanycheckbox value="#{bean.values[field.name]}" rendered="#{field.type == 'checkmany'}"> <f:selectitems value="#{field.options}" /> </h:selectmanycheckbox> </div> </ui:repeat>
an example of jstl approach can found @ how make grid of jsf composite component? no, jstl absolutely not "bad practice". myth leftover jsf 1.x era , continues long because starters didn't understand lifecycle , powers of jstl. point, can use jstl when model behind
#{bean.fields}
in above snippet not ever change during @ least jsf view scope. see jstl in jsf2 facelets... makes sense? instead, usingbinding
bean property still "bad practice".as
<ui:repeat><div>
, doesn't matter iterating component use, can use<h:datatable>
in initial question, or component library specific iterating component, such<p:datagrid>
or<p:datalist>
. refactor if necessary big chunk of code include or tagfile.as collecting submitted values,
#{bean.values}
should pointmap<string, object>
precreated.hashmap
suffices. may want prepopulate map in case of controls can set multiple values. should prepopulatelist<object>
value. note expectfield#gettype()
enum
since eases processing in java code side. can useswitch
statement instead of nastyif/else
block.create components programmatically in
postaddtoview
event listener:<h:form id="form"> <f:event type="postaddtoview" listener="#{bean.populateform}" /> </h:form>
with:
public void populateform(componentsystemevent event) { htmlform form = (htmlform) event.getcomponent(); (field field : fields) { switch (field.gettype()) { // it's easiest if it's enum. case text: uiinput input = new htmlinputtext(); input.setid(field.getname()); // must unique! input.setvalueexpression("value", createvalueexpression("#{bean.values['" + field.getname() + "']}", string.class)); form.getchildren().add(input); break; case secret: uiinput input = new htmlinputsecret(); // etc... } } }
(note: not create
htmlform
yourself! use jsf-created one, 1 nevernull
)this guarantees tree populated @ right moment, , keeps getters free of business logic, , avoids potential "duplicate component id" trouble when
#{bean}
in broader scope request scope (so can safely use e.g. view scoped bean here), , keeps bean free ofuicomponent
properties in turn avoids potential serialization trouble , memory leaking when component held property of serializable bean.if you're still on jsf 1.x
<f:event>
not available, instead bind form component request (not session!) scoped bean viabinding
<h:form id="form" binding="#{bean.form}" />
and lazily populate in getter of form:
public htmlform getform() { if (form == null) { form = new htmlform(); // ... (continue code above) } return form; }
when using
binding
, it's important understand ui components request scoped , should absolutely not assigned property of bean in broader scope. see how 'binding' attribute work in jsf? when , how should used?create custom component custom renderer. not going post complete examples since that's lot of code after tight-coupled , application-specific mess.
pros , cons of each option should clear. goes easy , best maintainable hard , least maintainable , subsequently least reuseable best reuseable. it's pick whatever best suits functional requirement , current situation.
noted should there absolutely nothing only possible in java (way #2) , impossible in xhtml+xml (way #1). possible in xhtml+xml in java. lot of starters underestimate xhtml+xml (particularly <ui:repeat>
, jstl) in dynamically creating components , incorrectly think java "one , only" way, while ends in brittle , confusing code.
Comments
Post a Comment