While performing CRUD operations using JSF/PrimeFaces, a common method that resets managed bean fields/properties is generally needed which is to be invoked basically after one such operation is successfully completed so that the fields in the backing bean are reset to their initial (default) value.
Imaginary code :
@Named
@ViewScoped
public class Bean extends LazyDataModel<Entity> implements Serializable {
@Inject
private Service service; // EJB.
// Holds a list of selected rows in a <p:dataTable>.
private List<Entity> selectedValues; // Getter & setter.
private String someField; // Getter & setter.
// Other fields depending upon the business requirement.
public Bean() {}
@PostConstruct
private void init() {
// Do something.
}
@Override
public List<Entity> load(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {
setRowCount(service.rowCount());
// Other complex logic as and when required.
return service.getList(first, pageSize, map, filters); // Returns a List<Entity>.
}
// Resets fields to their default value.
public void reset() {
someField = null;
selectedValues = null;
// Reset other fields to their default value.
}
// Add (insert submitted values to the database).
// This method is basically bound to an action(Listener) of <p:commandButton>.
public void submit() {
if (service.insert(someField)) {
// Add a FacesMessge to indicate a success.
reset(); // Calling reset.
} else {
// Add a FacesMessge to indicate a failure.
}
}
// Update the database using submitted values.
public void onRowEdit(RowEditEvent event) {
if (event.getObject() instanceof Entity) {
Entity entity = (Entity) event.getObject();
Entity newEntity = service.update(entity);
if (newEntity != null) {
// Update the model.
// Other things like adding a FacesMessage to indicate a success.
} else {
// Add a FacesMessage to warn against the null entity returned by the service layer.
}
} else {
// Add a FacesMessage to indicate a failure.
}
reset(); // Finally reset the fields to their initial/default value.
}
// Similarly, performing the delete operation also requires to call the reset() method.
}
The submit()
method performing "insert" is basically associated with JSF/PrimeFaces command components like <p/h:commandButton>
or <p/h:commandLink>
. Such as.
<p:inputText value="#{bean.someField}"/>
<p:commandButton value="Submit"
actionListener="#{bean.submit}"
oncomplete="if(args && !args.validationFailed) {updateTable();}"/>
<!-- Updating a p:dataTable in question after the above p:commandButton completes. -->
<p:remoteCommand name="updateTable" update="dataTable" process="@this"/>
The following AJAX events associated with a <p:dataTable>
also require to call the reset()
method.
<p:ajax event="rowEdit"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.onRowEdit}"/>
<p:ajax event="rowEditCancel"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
<p:ajax event="page"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
<p:ajax event="sort"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
<p:ajax event="filter"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
As can be seen, the reset()
method needs to be memorized carefully as it is invoked from several places. The way is somewhat difficult to maintain.
Does there exist a way to invoke such a common method automatically after each POST request performing one of the CRUD operations has finished its job successfully?
JSF doesn't have any tag for this.
Ideally, you'd like to have something like a <f:event type="postInvokeAction" listener="#{bean.reset}">
directly attached to the <p:dataTable>
. But that event doesn't exist in JSF 2.2. OmniFaces has one, but it's only supported on UIViewRoot
, UIForm
, UIInput
and UICommand
.
The <f:phaseListener>
comes close, but it gets attached to UIViewRoot
directly, even though you place it inside <p:dataTable>
. And, it requires a whole PhaseListener
implementation.
The <f:view afterPhase>
seems your best bet. You only need some additional checking on the phase ID and the source component.
<p:dataTable binding="#{table}" ...>
<f:view afterPhase="#{bean.reset(table)}" />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
...
</p:dataTable>
public void reset(UIData table) {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() != PhaseId.INVOKE_APPLICATION) {
return;
}
String source = context.getExternalContext().getRequestParameterMap().get("javax.faces.source");
if (!table.getClientId(context).equals(source)) {
return;
}
// Reset logic here.
// ...
}
(binding
could if necessary be replaced by a hardcoded table client ID)
I understand that this is awkward. So, for the upcoming OmniFaces 2.2 I have altered the existing InvokeActionEventListener
to support this use case too. It now supports being attached on any UIComponent
.
<p:dataTable ...>
<f:event type="postInvokeAction" listener="#{bean.reset}" />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
...
</p:dataTable>
public void reset() {
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With