We are glad to announce a maintenance release of PrimeFaces Extensions 0.7.1. This release only contains fixed issues. The full list is on the GitHub. Updated components are Exporter, KeyFilter, Layout, TimePicker and Timeline.
The release is already available in the Maven central repository. Getting Started is as usually here. The theme of the showcase was changed to redmond. We are normally doing this to distinguish separate releases from each other.
Now it is a good time for some new components, so we will add a couple of them. Stay tuned.
Thursday, June 6, 2013
PrimeFaces Extensions 0.7.1 released
Tuesday, June 4, 2013
Multiple dynamic includes with one JSF tag
Every JSF developer knows the ui:include and ui:param tags. You can include a facelet (XHTML file) and pass an object, which will be available in the included facelet, as follows
< ColumnModel>. A ColumnModel has e.g. the attributes header and property.
Go on. Now, if we want to add a support for sorting / filtering, we can use dynamic paths which refer to specific facelet files containg sorting or / and filtering feature(s). Simple bind the src attribute to a bean property.
But come to the point! What I wrote until now was an introduction for a motivation to extend ui:include. Recently, I got a task to use a p:dataTable with dynamic columns and p:rowEditor. Like this one in the PrimeFaces showcase. The problem is only - such editing feature doesn't support p:columns. My idea was to add p:column tags multiple times, dynamically, but with different context parameters. You can imagine this as ui:include with ui:param in a loop. In the example above we intend to iterate over the List<ColumnModel>. Each loop iteration should make an instance of type ColumnModel available in the included facelet. So, I wrote a custom tag handler to include any facelet multiple times.
Note: This approach can be applied to another components and use cases. The InlcudesTagHandler works universell. For instance, I can imagine to create a dynamic Menu component in PrimeFaces without an underlying MenuModel. A list or array of some model class is still needed of course :-).
<ui:include src="/sections/columns.xhtml"> <ui:param name="columns" value="#{bean.columns}"/> </ui:include>So, you can e.g. use it within a PrimeFaces DataTable with dynamich columns (p:columns)
<p:dataTable value="#{bean.entries}" var="data" rowKey="#{data.id}" ...> ... <ui:include src="/sections/columns.xhtml"> <ui:param name="data" value="#{data}"/> <ui:param name="columns" value="#{bean.columns}"/> </ui:include> </p:dataTable>where the included facelet could contain this code
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:p="http://primefaces.org/ui" xmlns:ui="http://java.sun.com/jsf/facelets" ...> <p:columns value="#{columns}" var="column"> <f:facet name="header"> <h:outputText value="#{msgs[column.header]}"/> </f:facet> // place some input / select or complex composite component for multiple data types here. // a simple example for demonstration purpose: <p:inputText value="#{data[column.property]}"/> </p:columns> </ui:composition>#{bean.columns} refers to a List of special objects which describe the columns. I will name such objects ColumnModel. So, it is a List
<ui:include src="#{bean.columnsIncludeSrc}"> <ui:param name="data" value="#{data}"/> <ui:param name="columns" value="#{bean.columns}"/> </ui:include>The bean has something like
private boolean isFilterRight; private boolean isSortRight // setter / getter public String getColumnsIncludeSrc() { if (isFilterRight && isSortRight) { return "/include/columnsTableFilterSort.xhtml"; } else if (isFilterRight && !isSortRight) { return "/include/columnsTableFilter.xhtml"; } else if (!isFilterRight && isSortRight) { return "/include/columnsTableSort.xhtml"; } else { return "/include/columnsTable.xhtml"; } }Different facelets are included dependent on the set boolean rights. So, the decision about what file to be included is placed within a bean. To be more flexible, we can encapsulate the table in a composite component and move the decision logic to the component class.
<cc:interface componentType="xxx.component.DataTable"> <cc:attribute name="id" required="false" type="java.lang.String" shortDescription="Unique identifier of the component in a NamingContainer"/> <cc:attribute name="entries" required="true" shortDescription="The data which are shown in the datatable. This is a list of object representing one row."/> <cc:attribute name="columns" required="true" type="java.util.List" shortDescription="The columns which are shown in the datatable. This is a list of instances of type ColumnModel."/> ... </cc:interface> <cc:implementation> <p:dataTable value="#{cc.attrs.entries}" var="data" rowKey="#{data.id}" ...> ... <ui:include src="#{cc.columnsIncludeSrc}"> <ui:param name="data" value="#{data}"/> <ui:param name="columns" value="#{cc.attrs.columns}"/> </ui:include> </p:dataTable> </cc:implementation>How does ui:include work? This is a tag handler which is applied when the view is being built. In JSF 2, the component tree is built twice on POST requests, once in RESTORE_VIEW phase and once in RENDER_RESPONSE phase. On GET it is built once in the RENDER_RESPONSE phase. This behavior is specified in the JSF 2 specification and is the same in Mojarra and MyFaces. The view building in the RENDER_RESPONSE is necessary in case the page author uses conditional includes or conditional templates. So, you can be sure that the src attribute of the ui:include gets evaluated shortly before the rendering phase.
But come to the point! What I wrote until now was an introduction for a motivation to extend ui:include. Recently, I got a task to use a p:dataTable with dynamic columns and p:rowEditor. Like this one in the PrimeFaces showcase. The problem is only - such editing feature doesn't support p:columns. My idea was to add p:column tags multiple times, dynamically, but with different context parameters. You can imagine this as ui:include with ui:param in a loop. In the example above we intend to iterate over the List<ColumnModel>
package xxx.taghandler; import xxx.util.VariableMapperWrapper; import java.io.IOException; import java.util.List; import java.util.UUID; import javax.el.ExpressionFactory; import javax.el.ValueExpression; import javax.el.VariableMapper; import javax.faces.component.UIComponent; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.TagAttribute; import javax.faces.view.facelets.TagAttributeException; import javax.faces.view.facelets.TagConfig; import javax.faces.view.facelets.TagHandler; /** * Tag handler to include a facelet multiple times with different contextes (objects from "value"). * The attribute "value" can be either of type java.util.List or array. * If the "value" is null, the tag handler works as a standard ui:include. */ public class InlcudesTagHandler extends TagHandler { private final TagAttribute src; private final TagAttribute value; private final TagAttribute name; public InlcudesTagHandler(TagConfig config) { super(config); this.src = this.getRequiredAttribute("src"); this.value = this.getAttribute("value"); this.name = this.getAttribute("name"); } @Override public void apply(FaceletContext ctx, UIComponent parent) throws IOException { String path = this.src.getValue(ctx); if ((path == null) || (path.length() == 0)) { return; } // wrap the original mapper - this is important when some objects passed into include via ui:param // because ui:param invokes setVariable(...) on the set variable mappper instance VariableMapper origVarMapper = ctx.getVariableMapper(); ctx.setVariableMapper(new VariableMapperWrapper(origVarMapper)); try { this.nextHandler.apply(ctx, null); ValueExpression ve = (this.value != null) ? this.value.getValueExpression(ctx, Object.class) : null; Object objValue = (ve != null) ? ve.getValue(ctx) : null; if (objValue == null) { // include facelet only once ctx.includeFacelet(parent, path); } else { int size = 0; if (objValue instanceof List) { size = ((List) objValue).size(); } else if (objValue.getClass().isArray()) { size = ((Object[]) objValue).length; } final ExpressionFactory exprFactory = ctx.getFacesContext().getApplication().getExpressionFactory(); final String strName = this.name.getValue(ctx); // generate unique Id as a valid Java identifier and use it as variable for the provided value expression final String uniqueId = "a" + UUID.randomUUID().toString().replaceAll("-", ""); ctx.getVariableMapper().setVariable(uniqueId, ve); // include facelet multiple times StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { if ((strName != null) && (strName.length() != 0)) { // create a new value expression in the array notation and bind it to the variable "name" sb.append("#{"); sb.append(uniqueId); sb.append("["); sb.append(i); sb.append("]}"); ctx.getVariableMapper().setVariable(strName, exprFactory.createValueExpression(ctx, sb.toString(), Object.class)); } // included facelet can access the created above value expression ctx.includeFacelet(parent, path); // reset for next iteration sb.setLength(0); } } } catch (IOException e) { throw new TagAttributeException(this.tag, this.src, "Invalid path : " + path); } finally { // restore original mapper ctx.setVariableMapper(origVarMapper); } } }The most important call is ctx.includeFacelet(parent, path). The method includeFacelet(...) from the JSF API includes the facelet markup at some path relative to the current markup. The class VariableMapperWrapper is used for a name to value mapping via ui:param. For the example with columns the variable column will be mapped to expressions #{columns[0]}, #{columns[1]}, etc. before every includeFacelet(...) call as well. Well, not exactly to these expressions, at place of columns should be an unique name mapped again to the columns object (to avoid possible name collisions). The mapper class looks like as follows
package xxx.util; import java.util.HashMap; import java.util.Map; import javax.el.ELException; import javax.el.ValueExpression; import javax.el.VariableMapper; /** * Utility class for wrapping a VariableMapper. Modifications occur to the internal Map instance. * The resolving occurs first against the internal Map instance and then against the wrapped VariableMapper * if the Map doesn't contain the requested ValueExpression. */ public class VariableMapperWrapper extends VariableMapper { private final VariableMapper wrapped; private Map<String, ValueExpression> vars; public VariableMapperWrapper(VariableMapper orig) { super(); this.wrapped = orig; } @Override public ValueExpression resolveVariable(String variable) { ValueExpression ve = null; try { if (this.vars != null) { // try to resolve against the internal map ve = this.vars.get(variable); } if (ve == null) { // look in the wrapped variable mapper return this.wrapped.resolveVariable(variable); } return ve; } catch (Throwable e) { throw new ELException("Could not resolve variable: " + variable, e); } } @Override public ValueExpression setVariable(String variable, ValueExpression expression) { if (this.vars == null) { this.vars = new HashMap<String, ValueExpression>(); } return this.vars.put(variable, expression); } }Register the tag handler in a taglib XML file and you are done.
<tag> <tag-name>includes</tag-name> <handler-class>xxx.taghandler.InlcudesTagHandler</handler-class> <attribute> <description> <![CDATA[The relative path to a XHTML file to be include one or multiple times.]]> </description> <name>src</name> <required>true</required> <type>java.lang.String</type> </attribute> <attribute> <description> <![CDATA[Objects which should be available in the included XHTML files. This attribute can be either of type java.util.List or array. If it is null, the tag handler works as a standard ui:include.]]> </description> <name>value</name> <required>false</required> <type>java.lang.Object</type> </attribute> <attribute> <description> <![CDATA[The name of the parameter which points to an object of each iteration over the given value.]]> </description> <name>name</name> <required>false</required> <type>java.lang.String</type> </attribute> </tag>Now I was able to use it in a composite component as
<p:dataTable value="#{cc.attrs.entries}" var="data" rowKey="#{data.id}" ...> ... <custom:includes src="#{cc.columnsIncludeSrc}" value="#{cc.attrs.columns}" name="column"> <ui:param name="data" value="#{data}"/> </custom:includes> </p:dataTable>A typically facelet file (and the component tree) contains a quite regular p:column tag which means we are able to use all DataTable's features!
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:p="http://primefaces.org/ui" xmlns:ui="http://java.sun.com/jsf/facelets" ...> <p:column headerText="#{msgs[column.header]}"> <p:cellEditor> <f:facet name="output"> <custom:typedOutput outputType="#{column.outputTypeName}" typedData="#{column.typedData}" value="#{data[column.property]}" timeZone="#{cc.timeZone}" calendarPattern="#{cc.calendarPattern}" locale="#{cc.locale}"/> </f:facet> <f:facet name="input"> <custom:typedInput inputType="#{column.inputTypeName}" typedData="#{column.typedData}" label="#{column.inputTypeName}" value="#{data[column.property]}" onchange="highlightEditedRow(this)" timeZone="#{cc.timeZone}" calendarPattern="#{cc.calendarPattern}" locale="#{cc.locale}"/> </f:facet> </p:cellEditor> </p:column> </ui:composition>
Subscribe to:
Posts (Atom)