Monday, November 11, 2013

PrimeFaces Extensions 1.1.0 released. Introduction to flexible grid layout.

We are glad to announce the next release 1.1.0 of PrimeFaces Extensions. Beside fixed issues (full list on GitHub) we added a new JSF component FluidGrid. FluidGrid is a cascading grid layout component. It works by placing elements in optimal position based on available vertical space, sort of like a mason fitting stones in a wall. It is responsive, meaning the grid will reflow as the window size changes. Modern web sites are not imaginable without a responsive design. Good designed web sites look well on every device and with every screen resolution. The basic prerequisite for that is a dynamic fluid layout which fits any browser sizes. The new component FluidGrid allows to set up a nice tight grid with items that have variable heights and widths. Items inside of FluidGrid can have any content: text, images, links, input fields, etc. They can be defined in a static or in a dynamic way as in data iteration components.

Let me show some features to get a first impression what the component can. The first example demonstrates a simple usage with static items. Static items are defined by the tag pe:fluidGridItem which can be placed multiple times below the main tag pe:fluidGrid. Items can have different width / height specified by style classes. The main container for the pe:fluidGrid has the style class pe-fluidgrid and the container of the pe:fluidGridItem has the style class pe-fluidgrid-item.
<pe:fluidGrid hGutter="10" vGutter="10">
    <pe:fluidGridItem styleClass="ui-widget-header">Item 1</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header w2 h2">Item 2</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header h3">Item 3</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header h2">Item 4</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header w3">Item 5</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header">Item 6</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header">Item 7</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header h2">Item 8</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header w2 h3">Item 9</pe:fluidGridItem>
    <pe:fluidGridItem styleClass="ui-widget-header">Item 10</pe:fluidGridItem>
    ... etc. ...
</pe:fluidGrid>
            
<h:outputStylesheet id="fluidGridCSS">
    .pe-fluidgrid {
        max-width: 900px;
    }
    
    .pe-fluidgrid .pe-fluidgrid-item {
        width:  60px;
        height: 60px;
        border-radius: 5px;
        padding-top: 0.5em;
        text-align: center;
    }
    
    .pe-fluidgrid-item.w2 {width: 130px;}
    .pe-fluidgrid-item.w3 {width: 200px;}    
    .pe-fluidgrid-item.h2 {height: 100px;}
    .pe-fluidgrid-item.h3 {height: 130px;}
</h:outputStylesheet>
In the example we used two attributes hGutter and vGutter for horizontal and vertical space between items. The result look like


After browser resizing all items are rearranged according to the available browser window size. No horizontal scrollbar appears.


Dynamic items can be put in a collection or list of FluidGridItem instances. A FluidGridItem instance contains a data object (of any data types) and an optional property type to match the type attribute in pe:fluidGridItem (see the last example with dynamic form). Dynamic items can be accessed in XHTML via the value attribute and exposed via the var attribute.
<pe:fluidGrid value="#{fluidGridDynamicController.images}" var="image" fitWidth="true" hasImages="true">
    <pe:fluidGridItem>
        <h:graphicImage library="images" name="fluidgrid/#{image.name}"/>
    </pe:fluidGridItem>
</pe:fluidGrid>
The bean looks like
@ManagedBean
@ViewScoped
public class FluidGridDynamicController implements Serializable {

    private List<FluidGridItem> images;

    @PostConstruct
    protected void initialize() {
        images = new ArrayList<FluidGridItem>();

        for (int j = 0; j < 3; j++) {
            for (int i = 1; i <= 10; i++) {
                images.add(new FluidGridItem(new Image(i + ".jpeg")));
            }
        }
    }

    public List<FluidGridItem> getImages() {
        return images;
    }
}
The result and more details can be seen in the showcase (second use case).

The next example demonstrates how to specifies elements which are stamped within the grid layout. These are special layout elements which will not be laid out by FluidGrid. Rather, FluidGrid will layout items below stamped elements. To specify stamped elements, use the stamp attribute which can be any search expression supported by the PrimeFaces Search Expression Framework. The XHTML code looks as follows
<pe:fluidGrid stamp="@(.pe-fluidgrid .stamp)" resizeBound="false" widgetVar="fluidGridWdgt">
    <div class="stamp"></div>
    
    <pe:fluidGridItem styleClass="ui-widget-header"/>
    <pe:fluidGridItem styleClass="ui-widget-header w2 h2"/>
    <pe:fluidGridItem styleClass="ui-widget-header h3"/>
    <pe:fluidGridItem styleClass="ui-widget-header h2"/>
    <pe:fluidGridItem styleClass="ui-widget-header w3"/>
    <pe:fluidGridItem styleClass="ui-widget-header"/>
    <pe:fluidGridItem styleClass="ui-widget-header"/>
    <pe:fluidGridItem styleClass="ui-widget-header h2"/>
    <pe:fluidGridItem styleClass="ui-widget-header w2 h3"/>
    <pe:fluidGridItem styleClass="ui-widget-header"/>
    ... etc. ...
</pe:fluidGrid>
More infos can be seen in the showcase (third use case). The next screenshots show a grid layout with a stamped element and without stamping after the button "Toggle stumped" was pressed.


 

Now the last, the most important example, the FluidGrid was implemented for. The example demonstrates how to design a responsive dynamic grid with input fields. This is similar to the DynaForm component in PrimeFaces Extensions, but the grid is not a fixed table in this case. It is responsive! The grid layout with input fields is placed within the center pane in pe:layout. The results before and after resizing the center layout pane are shown below. Try to resize the browser window of this example to see it in action.



The XHTML code looks like
<pe:fluidGrid id="fluidGrid" value="#{fluidGridDynaFormController.items}" var="data"
              resizeBound="false" hGutter="20" widgetVar="fluidGridWdgt">
    <pe:fluidGridItem type="input">
        <div class="dynaFormLabel">
            <p:outputLabel for="txt" value="#{data.label}"/>
        </div>
        <p:inputText id="txt" value="#{data.value}" required="#{data.required}"/>
    </pe:fluidGridItem>
    <pe:fluidGridItem type="select" styleClass="select">
        <div class="dynaFormLabel">
            <p:outputLabel for="menu" value="#{data.label}"/>
        </div>
        <p:selectOneMenu id="menu" value="#{data.value}" required="#{data.required}">
            <f:selectItems value="#{data.selectItems}"/>
        </p:selectOneMenu>
    </pe:fluidGridItem>
    <pe:fluidGridItem type="calendar" styleClass="calendar">
        <div class="dynaFormLabel">
            <p:outputLabel for="cal" value="#{data.label}"/>
        </div>
        <p:calendar id="cal" value="#{data.value}" required="#{data.required}" showOn="button"/>
    </pe:fluidGridItem>
</pe:fluidGrid>
The bean and model classes are straightforward.
@ManagedBean
@ViewScoped
public class FluidGridDynaFormController implements Serializable {

    private List<FluidGridItem> items;

    @PostConstruct
    protected void initialize() {
        items = new ArrayList<FluidGridItem>();

        List<SelectItem> selectItems = new ArrayList<SelectItem>();
        selectItems.add(new SelectItem("1", "Label 1"));
        selectItems.add(new SelectItem("2", "Label 2"));
        selectItems.add(new SelectItem("3", "Label 3"));

        items.add(new FluidGridItem(new DynamicField("First Label", null, true, null), "input"));
        items.add(new FluidGridItem(new DynamicField("Second Label", "Some default value", false, null), "input"));
        items.add(new FluidGridItem(new DynamicField("Third Label", null, false, selectItems), "select"));
        items.add(new FluidGridItem(new DynamicField("Fourth Label", "2", false, selectItems), "select"));
        items.add(new FluidGridItem(new DynamicField("Fifth Label", null, true, null), "calendar"));
        items.add(new FluidGridItem(new DynamicField("Sixth Label", new Date(), false, null), "calendar"));
        items.add(new FluidGridItem(new DynamicField("Seventh Label", null, false, null), "input"));
        items.add(new FluidGridItem(new DynamicField("Eighth Label", null, false, selectItems), "select"));
        items.add(new FluidGridItem(new DynamicField("Ninth Label", null, false, null), "calendar"));
    }

    public List<FluidGridItem> getItems() {
        return items;
    }
}

public class DynamicField implements Serializable {

    private String label;
    private Object value;
    private boolean required;
    private List<SelectItem> selectItems;

    public DynamicField() {
    }

    public DynamicField(String label, Object value, boolean required, List<SelectItem> selectItems) {
        this.label = label;
        this.value = value;
        this.required = required;
        this.selectItems = selectItems;
    }

    // getter / setter methods
    ...
}
Have fun with this release! Tip: it is also worth to check a new example for our JsonConverter. The example shows how to pre-initialize any UI forms on initial page load (GET request) with default values passed in URL. Any data types are supported (also Java Generics!).