FluidGrid is a cascading grid layout component. It places elements in the optimal position based on the available vertical space, like a mason fitting stones in a wall, and is responsive: the grid reflows as the window size changes. Based on the Masonry JavaScript library.
Static items are defined by pe:fluidGridItem placed multiple times below pe:fluidGrid. Items can have
different widths and heights set by style classes. The container has the style class pe-fluidgrid and each item
pe-fluidgrid-item. The attributes hGutter and vGutter set the horizontal and vertical
space between items. Resize the browser window to test the fluid grid.
Dynamic items are put in a collection of FluidGridItem instances; each holds a data object and an optional
type that matches the type attribute of pe:fluidGridItem. The items are exposed via
value and var. hasImages="true" re-layouts the grid after all images have been loaded, so
items do not overlap. The event layoutComplete is fired after the layout and all transitions have completed.
Stamped elements are special layout elements that are not laid out by the grid; items are placed below them. They are set by
the stamp attribute, which can be any PrimeFaces search expression. The widget methods stamp() and
unstamp() toggle a stamped element and layout() triggers the layout. Inside a layout pane, set
resizeBound="false" to deactivate re-layouting on browser resize.
A responsive dynamic grid with input fields, similar to DynaForm but not a fixed table: resize the browser window. Place the labels
above the input fields for a consistent alignment. The type of pe:fluidGridItem must match the
type property of FluidGridItem.
Dynamic items whose label attribute on input components is bound to the data model via var.
Change Items replaces the grid's items to verify that labels are preserved across dynamic updates. Click Submit
with empty required fields: each validation message shows its own component's label.
<div class="card">
<h5>Static items</h5>
<p>
Static items are defined by <code>pe:fluidGridItem</code> placed multiple times below <code>pe:fluidGrid</code>. Items can have
different widths and heights set by style classes. The container has the style class <code>pe-fluidgrid</code> and each item
<code>pe-fluidgrid-item</code>. The attributes <code>hGutter</code> and <code>vGutter</code> set the horizontal and vertical
space between items. Resize the browser window to test the fluid grid.
</p>
<style>
.fg-static .pe-fluidgrid {
max-width: 900px;
}
.fg-static .pe-fluidgrid .pe-fluidgrid-item {
width: 60px;
height: 60px;
border-radius: 5px;
padding-top: 0.5em;
text-align: center;
}
.fg-static .pe-fluidgrid-item.w2 {width: 130px;}
.fg-static .pe-fluidgrid-item.w3 {width: 200px;}
.fg-static .pe-fluidgrid-item.h2 {height: 100px;}
.fg-static .pe-fluidgrid-item.h3 {height: 130px;}
</style>
<h:form id="formStatic">
<div class="fg-static">
<pe:fluidGrid hGutter="10" vGutter="10">
<pe:fluidGridItem styleClass="p-widget-header">Item 1</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w2 h2">Item 2</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h3">Item 3</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h2">Item 4</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w3">Item 5</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 6</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 7</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h2">Item 8</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w2 h3">Item 9</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 10</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h2">Item 11</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 12</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w2 h2">Item 13</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w2">Item 14</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 15</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h2">Item 16</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 17</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 18</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h3">Item 19</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h2">Item 20</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 21</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 22</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h2">Item 23</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w2 h2">Item 24</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 25</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 26</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header h3">Item 27</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 28</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w3">Item 29</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 30</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 31</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header w2">Item 32</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 33</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 34</pe:fluidGridItem>
<pe:fluidGridItem styleClass="p-widget-header">Item 35</pe:fluidGridItem>
</pe:fluidGrid>
</div>
</h:form>
</div>
<div class="card">
<h5>Dynamic items</h5>
<p>
Dynamic items are put in a collection of <code>FluidGridItem</code> instances; each holds a data object and an optional
<code>type</code> that matches the <code>type</code> attribute of <code>pe:fluidGridItem</code>. The items are exposed via
<code>value</code> and <code>var</code>. <code>hasImages="true"</code> re-layouts the grid after all images have been loaded, so
items do not overlap. The event <i>layoutComplete</i> is fired after the layout and all transitions have completed.
</p>
<style>
.fg-dynamic img {
margin-top: -4px;
}
</style>
<h:form id="formDynamic">
<p:growl id="growl" showDetail="true" showSummary="true"/>
<div class="fg-dynamic">
<pe:fluidGrid value="#{fluidGridDynamicController.images}" var="image"
fitWidth="true" hasImages="true">
<p:ajax event="layoutComplete" update="growl"
listener="#{fluidGridDynamicController.fireLayoutComplete}"/>
<pe:fluidGridItem>
<h:graphicImage library="images" name="fluidgrid/#{image.name}"/>
</pe:fluidGridItem>
</pe:fluidGrid>
</div>
</h:form>
</div>
<div class="card">
<h5>Stamped elements within layout</h5>
<p>
Stamped elements are special layout elements that are not laid out by the grid; items are placed below them. They are set by
the <code>stamp</code> attribute, which can be any PrimeFaces search expression. The widget methods <code>stamp()</code> and
<code>unstamp()</code> toggle a stamped element and <code>layout()</code> triggers the layout. Inside a layout pane, set
<code>resizeBound="false"</code> to deactivate re-layouting on browser resize.
</p>
<style>
.fg-stamped .pe-fluidgrid {
max-width: 1000px;
position: relative;
}
.fg-stamped .pe-fluidgrid .pe-fluidgrid-item {
width: 60px;
height: 60px;
background: #D26;
border: 1px solid orange;
border-radius: 5px;
opacity: 0.7;
}
.fg-stamped .pe-fluidgrid-item.w2 {width: 120px;}
.fg-stamped .pe-fluidgrid-item.w3 {width: 180px;}
.fg-stamped .pe-fluidgrid-item.h2 {height: 100px;}
.fg-stamped .pe-fluidgrid-item.h3 {height: 130px;}
.fg-stamped .pe-fluidgrid .stamp {
position: absolute;
right: 15%;
top: 10px;
width: 50%;
height: 70px;
background: orange;
border: 4px dotted black;
}
</style>
<h:form id="formStamped">
<div class="fg-stamped">
<pe:fluidGrid stamp="@(.fg-stamped .stamp)" resizeBound="false" widgetVar="fluidGridStampedWdgt">
<div class="stamp"></div>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header w2 h2"/>
<pe:fluidGridItem styleClass="p-widget-header h3"/>
<pe:fluidGridItem styleClass="p-widget-header h2"/>
<pe:fluidGridItem styleClass="p-widget-header w3"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header h2"/>
<pe:fluidGridItem styleClass="p-widget-header w2 h3"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header h2"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header w2 h2"/>
<pe:fluidGridItem styleClass="p-widget-header w2"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header h2"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header h3"/>
<pe:fluidGridItem styleClass="p-widget-header h2"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header h2"/>
<pe:fluidGridItem styleClass="p-widget-header w2 h2"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header h3"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header w3"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header w2"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
<pe:fluidGridItem styleClass="p-widget-header"/>
</pe:fluidGrid>
</div>
<p:commandButton value="Toggle stamped" type="button" style="margin-top:15px;"
onclick="toggleFluidGridStamped()"/>
</h:form>
<script>
/* <![CDATA[ */
window.fluidGridIsStamped = true;
function toggleFluidGridStamped() {
// stamp or unstamp element
if (window.fluidGridIsStamped) {
PF('fluidGridStampedWdgt').unstamp($(".fg-stamped .stamp").get());
} else {
PF('fluidGridStampedWdgt').stamp($(".fg-stamped .stamp").get());
}
// trigger layout
PF('fluidGridStampedWdgt').layout();
window.fluidGridIsStamped = !window.fluidGridIsStamped;
}
/* ]]> */
</script>
</div>
<div class="card">
<h5>Dynamic form</h5>
<p>
A responsive dynamic grid with input fields, similar to DynaForm but not a fixed table: resize the browser window. Place the labels
above the input fields for a consistent alignment. The <code>type</code> of <code>pe:fluidGridItem</code> must match the
<code>type</code> property of <code>FluidGridItem</code>.
</p>
<style>
.fg-dynaform .pe-fluidgrid-item {
width: 160px;
height: 60px;
}
.fg-dynaform .pe-fluidgrid-item input {
width: 150px;
}
.fg-dynaform .pe-fluidgrid-item.calendar input {
width: 120px;
}
.fg-dynaform .pe-fluidgrid-item.select .p-selectonemenu {
width: 134px !important;
}
.fg-dynaform .dynaFormLabel {
font-weight: bold;
margin-bottom: 7px;
}
</style>
<h:form id="formDynaform">
<p:growl id="growl" showDetail="true" showSummary="true"/>
<div class="fg-dynaform">
<pe:fluidGrid id="fluidGrid" value="#{fluidGridDynaFormController.items}" var="data"
resizeBound="false" hGutter="20" widgetVar="fluidGridDynaformWdgt" style="width:600px">
<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:datePicker id="cal" value="#{data.value}" required="#{data.required}" showIcon="true"/>
</pe:fluidGridItem>
</pe:fluidGrid>
</div>
<p:commandButton value="Submit" style="margin-top: 10px;"
process="fluidGrid" update="fluidGrid inputValues growl"
oncomplete="if(!args.validationFailed) {PF('dynaformValuesWidget').show();}"/>
<p:dialog header="Input values" widgetVar="dynaformValuesWidget">
<p:dataList id="inputValues" value="#{fluidGridDynaFormController.items}" var="item"
style="margin:10px;">
<h:outputText value="#{item.data.label} : #{item.data.value}" style="margin-right:15px;"/>
</p:dataList>
</p:dialog>
</h:form>
</div>
<div class="card">
<h5>Dynamic labels</h5>
<p>
Dynamic items whose <code>label</code> attribute on input components is bound to the data model via <code>var</code>.
<b>Change Items</b> replaces the grid's items to verify that labels are preserved across dynamic updates. Click <b>Submit</b>
with empty required fields: each validation message shows its own component's label.
</p>
<style>
.fg-dynlabels .pe-fluidgrid-item {
width: 180px;
height: 65px;
}
.fg-dynlabels .pe-fluidgrid-item input {
width: 170px;
}
.fg-dynlabels .dynLabel {
font-weight: bold;
margin-bottom: 5px;
}
</style>
<h:form id="formDynamicLabels">
<p:growl id="growl" showDetail="true" showSummary="true"/>
<p:commandButton value="Change Items" action="#{fluidGridDynamicLabelsController.changeItems}"
process="@this" update="fluidGrid" style="margin-bottom:10px"/>
<div class="fg-dynlabels">
<pe:fluidGrid id="fluidGrid" value="#{fluidGridDynamicLabelsController.items}" var="data"
resizeBound="false" hGutter="20" style="width:450px">
<pe:fluidGridItem type="input">
<div class="dynLabel">
<p:outputLabel for="txt" value="#{data.label}"/>
</div>
<p:inputText id="txt" value="#{data.value}" label="#{data.label}" required="#{data.required}"/>
</pe:fluidGridItem>
</pe:fluidGrid>
</div>
<p:commandButton value="Submit" style="margin-top: 10px;"
process="fluidGrid" update="fluidGrid inputValues growl"
oncomplete="if(!args.validationFailed) {PF('dynamicLabelsValuesWidget').show();}"/>
<p:dialog header="Submitted Values" widgetVar="dynamicLabelsValuesWidget">
<p:dataList id="inputValues" value="#{fluidGridDynamicLabelsController.items}" var="item"
style="margin:10px;">
<h:outputText value="#{item.data.label} : #{item.data.value}" style="margin-right:15px;"/>
</p:dataList>
</p:dialog>
</h:form>
</div>
package org.primefaces.extensions.showcase.controller.fluidgrid;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.event.LayoutCompleteEvent;
import org.primefaces.extensions.model.fluidgrid.FluidGridItem;
import org.primefaces.extensions.showcase.model.fluidgrid.Image;
/**
* FluidGridDynamicController
*
* @author Oleg Varaksin / last modified by $Author: $
* @version $Revision: 1.0 $
*/
@Named
@ViewScoped
public class FluidGridDynamicController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
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;
}
public void fireLayoutComplete(final LayoutCompleteEvent event) {
final FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Fluid grid has been laid out", null);
FacesContext.getCurrentInstance().addMessage(null, msg);
}
}
package org.primefaces.extensions.showcase.model.fluidgrid;
import java.io.Serial;
import java.io.Serializable;
/**
* Image
*
* @author Oleg Varaksin / last modified by $Author: $
* @version $Revision: 1.0 $
*/
public class Image implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String name;
public Image() {
}
public Image(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package org.primefaces.extensions.showcase.controller.fluidgrid;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.model.SelectItem;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.model.fluidgrid.FluidGridItem;
import org.primefaces.extensions.showcase.model.fluidgrid.DynamicField;
/**
* FluidGridDynaFormController
*
* @author Oleg Varaksin / last modified by $Author$
* @version $Revision$
*/
@Named
@ViewScoped
public class FluidGridDynaFormController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<FluidGridItem> items;
@PostConstruct
protected void initialize() {
items = new ArrayList<FluidGridItem>();
final 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;
}
}
package org.primefaces.extensions.showcase.controller.fluidgrid;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.model.fluidgrid.FluidGridItem;
@Named
@ViewScoped
public class FluidGridDynamicLabelsController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static int counter = 0;
private List<FluidGridItem> items;
@PostConstruct
protected void initialize() {
items = createItems("First", "Second", "Third", "Fourth", "Fifth");
}
public List<FluidGridItem> getItems() {
return items;
}
public void changeItems() {
counter++;
switch (counter % 3) {
case 1:
items = createItems("Alpha", "Beta", "Gamma");
break;
case 2:
items = createItems("Apple", "Banana", "Cherry", "Date");
break;
default:
items = createItems("First", "Second", "Third", "Fourth", "Fifth");
break;
}
}
private static List<FluidGridItem> createItems(String... names) {
final List<FluidGridItem> items = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
final boolean required = i % 2 == 0;
items.add(new FluidGridItem(new DynamicLabelField(names[i], null, required), "input"));
}
return items;
}
public static class DynamicLabelField implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String label;
private String value;
private boolean required;
public DynamicLabelField() {
}
public DynamicLabelField(String label, String value, boolean required) {
this.label = label;
this.value = value;
this.required = required;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
}
}