The component blockUI is used here as reference implementation. It prevents user activity with any part of the page as long as an Ajax request / response cycle is running. We need a component class BlockUI.java, a renderer class BlockUIRenderer.java and an JavaScript blockUI.js. The component class is trivial. It has attributes "widgetVar", "for", "forElement", "source" und getter / setter. More interesting are the renderer and the script.
BlockUIRenderer.java
public class BlockUIRenderer extends CoreRenderer { private static final Log LOG = LogFactory.getLog(BlockUIRenderer.class); @Override public void encodeEnd(FacesContext fc, UIComponent component) throws IOException { encodeMarkup(fc, component); encodeScript(fc, component); } public void encodeMarkup(FacesContext fc, UIComponent component) throws IOException { ResponseWriter writer = fc.getResponseWriter(); writer.startElement("div", null); writer.writeAttribute("id", component.getClientId(fc) + "_content", null); writer.writeAttribute("style", "display: none;", null); renderChildren(fc, component); writer.endElement("div"); } protected void encodeScript(FacesContext fc, UIComponent component) throws IOException { ResponseWriter writer = fc.getResponseWriter(); BlockUI blockUI = (BlockUI) component; String clientId = blockUI.getClientId(fc); String widgetVar = blockUI.resolveWidgetVar(); String source; UIComponent sourceComponent = blockUI.findComponent(blockUI.getSource()); if (sourceComponent == null) { source = ""; LOG.warn("Source component could not be found for 'source' = " + blockUI.getSource()); } else { source = sourceComponent.getClientId(fc); } String target; if (blockUI.getFor() != null) { UIComponent targetComponent = blockUI.findComponent(blockUI.getFor()); if (targetComponent == null) { target = ""; LOG.warn("Target component could not be found for 'for' = " + blockUI.getFor()); } else { target = targetComponent.getClientId(fc); } } else if (blockUI.getForElement() != null) { target = blockUI.getForElement(); } else { target = ""; LOG.warn("Target is missing, either 'for' or 'forElement' attribute is required"); } UIComponent facet = blockUI.getFacet("events"); // collect all f:param List<UIParameter> uiParams = new ArrayList<UIParameter>(); if (facet instanceof UIParameter) { // f:facet has one child and that's f:param uiParams.add((UIParameter) facet); } else if (facet != null && facet.getChildren() != null) { // f:facet has no or more than one child (all children included into an UIPanel) for (UIComponent kid : facet.getChildren()) { if (kid instanceof UIParameter) { uiParams.add((UIParameter) kid); } } } // build a regular expression for event key, value pair. String eventRegExp; if (uiParams.isEmpty()) { // no or empty "events" facet means all events of the given source are accepted eventRegExp = "/" + Constants.PARTIAL_SOURCE_PARAM + "=" + source + "(.)*$/"; } else { StringBuffer sb = new StringBuffer("/"); sb.append(source); sb.append("_"); Iterator<UIParameter> iter = uiParams.iterator(); while (iter.hasNext()) { UIParameter param = iter.next(); // not set event name / value means any name / value is accepted sb.append("("); sb.append(param.getName() != null ? param.getName() : "(.)*"); sb.append("="); sb.append(param.getValue() != null ? param.getValue() : "(.)*"); sb.append("$)"); if (iter.hasNext()) { sb.append("|"); } } sb.append("/"); eventRegExp = sb.toString(); } writer.startElement("script", blockUI); writer.writeAttribute("type", "text/javascript", null); writer.write(widgetVar + " = new JsfToolkit.widget.BlockUI('" + ComponentUtils.escapeJQueryId(clientId) + "','" + ComponentUtils.escapeJQueryId(source) + "','" + ComponentUtils.escapeJQueryId(target) + "'," + eventRegExp + ");"); writer.write(widgetVar + ".setupAjaxSend();"); writer.write(widgetVar + ".setupAjaxComplete();"); writer.endElement("script"); } @Override public boolean getRendersChildren() { return true; } @Override public void encodeChildren(FacesContext fc, UIComponent component) throws IOException { // nothing to do } }The most important thing is the building of regular expressions. If we are not interesting in any special events, we will only check whether the source component in available in passed Ajax parameters (see the JS script below). The regular expression using for this check is
/javax.faces.source=<source>(.)*$/where <source> is the clientId of the source component. Radio buttons and checkboxes I mentioned in the part I send e.g.
javax.faces.source=accessLevels_1 javax.faces.source=accessLevels_2 javax.faces.source=accessRights_1 javax.faces.source=accessRights_2The regular expressions should be therefore
/javax.faces.source=accessLevels(.)*$/ /javax.faces.source=accessRights(.)*$/If we interesting in specified events, we build a regular expression for each event and concatenate them with the logical OR-operator. A regular expression for each event looks as follows
/<source>_(<event name>=<event value>)$/If the event name or value is not important, we use (.)* for any strings (empty is also allowed). Some examples:
/dtPropTemplates_(filtering=true)$|dtPropTemplates_(sorting=true)$/ /treeSocs_(action=SELECT)$/ /selectedGroupsUsers_(instantSelectedRowIndex=(.)*)$/Corresponding Ajax requests have parameters (Ajax options):
dtPropTemplates_filtering=true dtPropTemplates_sorting=true treeSocs_action=SELECT selectedGroupsUsers_instantSelectedRowIndex=<rowId> // <rowId> is the internal Id of the currently selected row
blockUI.js
The script provides two functions to register ajaxSend and ajaxComplete events where I check Ajax options in order to find the appropriate event. The check uses mentioned above regular expressions.
JsfToolkit.widget.BlockUI = function(id, source, target, regExp) { var clientId = id; var sourceId = source; var targetId = target; var eventRegExp = regExp; // global settings jQuery.blockUI.defaults.theme = true; jQuery.blockUI.defaults.fadeIn = 0; jQuery.blockUI.defaults.fadeOut = 0; jQuery.blockUI.defaults.applyPlatformOpacityRules = false; /* public access */ this.setupAjaxSend = function () { jQuery(sourceId).ajaxSend(function(event, xhr, ajaxOptions) { // first, check if event should be handled if (isAppropriateEvent(ajaxOptions)) { var targetEl = jQuery(targetId); // second, check if the target element has been found if (targetEl.length > 0) { // block the target element targetEl.block({message: jQuery(clientId + "_content").html()}); // get the current counter var blocksCount = targetEl.data("blockUI.blocksCount"); if (typeof blocksCount === 'undefined') { blocksCount = 0; } // increase the counter targetEl.data("blockUI.blocksCount", blocksCount+1); } } }); } this.setupAjaxComplete = function () { jQuery(sourceId).ajaxComplete(function(event, xhr, ajaxOptions) { // first, check if event should be handled if (isAppropriateEvent(ajaxOptions)) { var targetEl = jQuery(targetId); // second, check if the target element has been found if (targetEl.length > 0) { // get the current counter var blocksCount = targetEl.data("blockUI.blocksCount"); // check the counter if (typeof blocksCount !== 'undefined') { if (blocksCount == 1) { // unblock the target element and reset the counter jQuery(targetId).unblock(); targetEl.data("blockUI.blocksCount", 0); } else if (blocksCount > 1) { // only decrease the counter targetEl.data("blockUI.blocksCount", blocksCount-1); } } } } }); } /* private access */ var isAppropriateEvent = function (ajaxOptions) { if (typeof ajaxOptions === 'undefined' || ajaxOptions == null || typeof ajaxOptions.data === 'undefined' || ajaxOptions.data == null) { return false; } // split options around ampersands var params = ajaxOptions.data.split(/&/g); // loop over the ajax options and try to match events for (var i = 0; i < params.length; i++) { if (eventRegExp.test(params[i])) { return true; } } return false; } }ajaxSend increase a counter bound to target element and ajaxComplete decrease the counter. This is necessary if many sources blocks the same target. The target is unblocked if the counter is 1. The parsing of Ajax options is the main part and the clou of the question how to find any Ajax events to be able to fire client-side callbacks.
well, very nice!
ReplyDeletecould you post the source code blockUI.java?
There just setter / getter. Here the code http://paste.kde.org/49351/ You need yet jquery.blockUI.js from the blockUI homepage.
ReplyDeleteHello, link is broken, can you give an actual one?
ReplyDeleteThanks!
http://paste.kde.org/67027/
ReplyDeleteHello, it don't work for me, i use the PrimefacesM2-SNAP, could you post the complete source-package?
ReplyDeletePrimeFaces M2 uses other events at some places (I mentioned this problem in the first part of this post). You should look into JS files and figure out their names.
ReplyDelete