If you are writing HTML and JavaScript on your PC and testing the output in your browser without setting up a server, you will probably get some error messages about Cross Origin Requests. Your browser will render HTML, run JavaScript, jQuery or AngularJS web app, and ... block requests. This is a common behavior of many web browsers due to possible cross site attacks. That is understandable because it is not clever to allow someone to read your hard drive from your web browser. Right?
More concrete example. Assume, you develop an AngularJS web app with custom directives. Maybe you're quickly testing some AngularJS features without any web server. AngularJS loads HTML files of custom directives on page initial load. All files are loaded by AJAX from your PC, so that you will see an error message such as "XMLHttpRequest cannot load file:///C:/somefolder/somefile.html". I had e.g. these messages in the browser console:
This is because your browser doesn't allow AJAX requests for file:/// Obviously, you need to serve all files over HTTP. The simplest solution would be to install a web server for Chrome. It serves web pages from a local folder over the network, using HTTP and comes exactly to rescue. After installing this small server app from the Google Web Store, you will see a Web Server icon. I have e.g. something like
Open the web server for Chrome and choose a directory to serve static content. In our case, this is the main folder of your project.
It is now able to stream large files and handle range requests. It also sets mime types correctly. Navigate to http://127.0.0.1:8887 You will see all content of you project. Assume, the main file is called myapp.html. After choosing this file, you will see the URL http://127.0.0.1:8887/myapp.html The web app is fine now because all separate files for AngularJS's includes or directives are streamed down by AJAX over HTTP. Changes in files are visible on-the-fly without any restarts or redeployments.
Have fun!
Showing posts with label Ajax. Show all posts
Showing posts with label Ajax. Show all posts
Saturday, March 7, 2015
HTTP web server for Chrome to test local web applications
Monday, April 23, 2012
DWR in JSF? Easy, with PrimeFaces Extensions.
Everybody knows DWR (Direct Web Remoting). DWR enables Java on the server and JavaScript in a browser to interact and call each other. It has several modules for Spring, Struts, Guice, etc. integration. There is also an JSF integration, but it's not fit enough to be used in JSF 2. It requires an expensive XML configuration or a configuration via annotations (@RemoteProxy, @RemoteMethod). The configuration describes a mapping between Java methods and JavaScript functions. JSF 2 standard lacks the ability to call arbitrary methods on JSF managed beans as well. There are some attempts to push up this intention and intergrate this technique in JSF 2, but nothing happend until now. PrimeFaces component library has a component RemoteCommand. Unfortunately, but it doesn't allow to call any Java method with any parameters. Passing parameters is not comfortable with this implementation. They have to be passed as
1) Assign JavaScript parameters to a property in a bean. It can be done in a convenient way with pe:remoteCommand and pe:assignableParam. AssignableParam specifies a name of the parameter and an EL expression for a corresponding Java method (setter) the parameter will be applied to. It allows to attach converters as well. The code below shows how it works.
2) Call a remote Java method with any parameters from JavaScript. It can be done with pe:remoteCommand and pe:methodParam, pe:methodSignature. This is a DWR like approach with zero configuration. MethodParam specifies a name of the parameter and MethodSignature specifies a comma or space separated list with full qualified class names. Class names should match passed parameters in the same order as they were defined. The code below shows how it works.
Described features are available in the PF Extensions 0.5-SNAPSHOT.
Edit: DWR allows a reverse call from Java to JavaScript as well. It works bidirectional. Well, it's possible with Atmosphere integration or PrimeFaces Push too. Furthermore, PrimeFaces provides a server side way to execute JavaScript when the ajax request completes. Example: RequestContext.getCurrentInstance().execute("dialog.hide()");
// call a JavaScript function bound to RemoteCommand with parameters
myFunction({param1: 'value1', param2: 'value2'});
and extracted from the current request on the server side. No JSF converters, validators can be applied, etc. Fortunately we have PrimeFaces Extensions project :-) which brings an own implementation of RemoteCommand. It covers two important use cases.1) Assign JavaScript parameters to a property in a bean. It can be done in a convenient way with pe:remoteCommand and pe:assignableParam. AssignableParam specifies a name of the parameter and an EL expression for a corresponding Java method (setter) the parameter will be applied to. It allows to attach converters as well. The code below shows how it works.
<p:growl id="growl" showDetail="true" />
<pe:remoteCommand id="applyDataCommand" name="applyData" process="@this" update="subject date circle growl"
actionListener="#{remoteCommandController.parametersAssigned}">
<pe:assignableParam name="subject" assignTo="#{remoteCommandController.subject}"/>
<pe:assignableParam name="date" assignTo="#{remoteCommandController.date}">
<f:convertDateTime type="both" dateStyle="short" locale="en"/>
</pe:assignableParam>
<pe:assignableParam name="circle" assignTo="#{remoteCommandController.circle}">
<pe:convertJson />
</pe:assignableParam>
</pe:remoteCommand>
<script type="text/javascript">
circle = {
radius: 50,
backgroundColor: "#FF0000",
borderColor: "#DDDDDD",
scaleFactor: 1.2
};
circle2 = {
...
};
</script>
<h:outputLabel for="subject" value="Subject: " />
<h:outputText id="subject" value="#{remoteCommandController.subject}" />
<h:outputLabel for="date" value="Date: " />
<h:outputText id="date" value="#{remoteCommandController.date}" />
<h:outputLabel for="circle" value="Circle: " />
<h:outputText id="circle" value="#{remoteCommandController.circle.radius} -
#{remoteCommandController.circle.backgroundColor} -
#{remoteCommandController.circle.borderColor} -
#{remoteCommandController.circle.scaleFactor}" />
<p:commandButton value="Apply Data" type="button"
onclick="applyData('hello world', '5/14/07 12:55:42 PM', JSON.stringify(circle))" />
<p:commandButton value="Apply Second Data" type="button"
onclick="applyData('hello user', '7/11/01 11:55:42 PM', JSON.stringify(circle2))" />
You see that three parameters are passed to the JavaScript function applyData which is bound to the pe:remoteCommand via name attribute. Every pe:assignableParam takes a parameter and assign to the corresponding bean property via assignTo attribute. You can also see that an attached date converter converts date string to a date object (java.util.Date) and an JSON converter (from the PF Extensions project) converts a JavaScript object (circle) to a model Java class (Circle.java). The bean (controller class) and the Circle.java (model class) look simple:
@ManagedBean
@RequestScoped
public class RemoteCommandController {
private String subject;
private Date date;
private Circle circle;
// getter, setter
public void parametersAssigned() {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "ActionListener called", "Parameters assigned");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
}
public class Circle implements Serializable {
private int radius;
private String backgroundColor;
private String borderColor;
private double scaleFactor;
// getter, setter
}
Picture:2) Call a remote Java method with any parameters from JavaScript. It can be done with pe:remoteCommand and pe:methodParam, pe:methodSignature. This is a DWR like approach with zero configuration. MethodParam specifies a name of the parameter and MethodSignature specifies a comma or space separated list with full qualified class names. Class names should match passed parameters in the same order as they were defined. The code below shows how it works.
<p:growl id="growl" showDetail="true" />
<pe:remoteCommand id="applyDataCommand" name="applyData" process="@this" update="growl"
actionListener="#{remoteCommandController.printMethodParams}">
<pe:methodSignature parameters="java.lang.String, java.util.Date, org.primefaces.extensions.showcase.model.Circle" />
<pe:methodParam name="subject"/>
<pe:methodParam name="date">
<f:convertDateTime type="both" dateStyle="short" locale="en"/>
</pe:methodParam>
<pe:methodParam name="circle">
<pe:convertJson />
</pe:methodParam>
</pe:remoteCommand>
<script type="text/javascript">
circle = {
radius: 50,
backgroundColor: "#FF0000",
borderColor: "#DDDDDD",
scaleFactor: 1.2
};
circle2 = {
...
};
</script>
<p:commandButton value="Apply Data" type="button"
onclick="applyData('hello world', '5/14/07 12:55:42 PM', JSON.stringify(circle))" />
<p:commandButton value="Apply Second Data" type="button"
onclick="applyData('hello user', '7/11/01 11:55:42 PM', JSON.stringify(circle2))" />
The bean method printMethodParams (defined as actionListener) will be called when user pushes one of the both command button. The method generates a message which is shown as growl popup.
@ManagedBean
@RequestScoped
public class RemoteCommandController {
public void printMethodParams(String subject, Date date, Circle circle) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "ActionListener called",
"Subject: " + subject + ", Date: " + date + ", Circle - backgroundColor: " + circle.getBackgroundColor());
FacesContext.getCurrentInstance().addMessage(null, msg);
}
}
Converters are used as well. They can be also defined as usual in JSF by converter attribute in pe:assignableParam / pe:methodParam.Described features are available in the PF Extensions 0.5-SNAPSHOT.
Edit: DWR allows a reverse call from Java to JavaScript as well. It works bidirectional. Well, it's possible with Atmosphere integration or PrimeFaces Push too. Furthermore, PrimeFaces provides a server side way to execute JavaScript when the ajax request completes. Example: RequestContext.getCurrentInstance().execute("dialog.hide()");
Labels:
Ajax,
JavaScript,
JSF,
PrimeFaces Extensions,
Web
Thursday, July 21, 2011
Unified JSF Connection (Ajax) Status Indicator
Real JSF web applications consist of various ajaxifed components. The good practise is to tell users that they should be waiting for something to finish. A global Ajax Status Indicator seems to be very handy in ajaxifed applications. Such components exist e.g. in RichFaces, ICEFaces and PrimeFaces. If you only use one of these component libraries, you can skip this post :-) But what is about if you use a mix composed of standard f:ajax with any standard and not standard components + ajaxified components from any libraries? I have counted f:ajax and PrimeFaces' p:ajax appearances in my applications. Approximately 50% to 50%. Probably you used f:ajax too because p:ajax had issues in early PrimeFaces versions. The question is now how to adjust p:ajaxStatus in order to consider standard Ajax requests in addition to PrimeFaces Ajax requests based on jQuery Ajax? We can add global callbacks for event handling from standard JSF
I took connection icons from ICEFaces showcase.
jsf.ajax.addOnEvent(callback) jsf.ajax.addOnError(callback)Let me extend PrimeFaces AjaxStatus, register new global callbacks and show an example in action. At first we have to add the standard JS library for Ajax and our extended script ajaxstatus.js.
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
@ResourceDependencies({
@ResourceDependency(library="primefaces", name="jquery/jquery.js"),
@ResourceDependency(library="primefaces", name="core/core.js"),
@ResourceDependency(library="primefaces", name="ajaxstatus/ajaxstatus.js"),
@ResourceDependency(library="javax.faces", name="jsf.js"),
@ResourceDependency(library="js", name="ajaxstatus.js")
})
public class AjaxStatus extends org.primefaces.component.ajaxstatus.AjaxStatus
{
}
Renderer class is simpleimport javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import java.io.IOException;
public class AjaxStatusRenderer extends org.primefaces.component.ajaxstatus.AjaxStatusRenderer
{
protected void encodeScript(FacesContext context, org.primefaces.component.ajaxstatus.AjaxStatus st)
throws IOException {
AjaxStatus status = (AjaxStatus)st;
ResponseWriter writer = context.getResponseWriter();
String clientId = status.getClientId(context);
String widgetVar = status.resolveWidgetVar();
writer.startElement("script", null);
writer.writeAttribute("type", "text/javascript", null);
writer.write(widgetVar + " = new PrimeFaces.widget.ExtendedAjaxStatus('" + clientId + "');");
encodeCallback(context, status, widgetVar, "ajaxSend", "onprestart", AjaxStatus.PRESTART_FACET);
encodeCallback(context, status, widgetVar, "ajaxStart", "onstart", AjaxStatus.START_FACET);
encodeCallback(context, status, widgetVar, "ajaxError", "onerror", AjaxStatus.ERROR_FACET);
encodeCallback(context, status, widgetVar, "ajaxSuccess", "onsuccess", AjaxStatus.SUCCESS_FACET);
encodeCallback(context, status, widgetVar, "ajaxComplete", "oncomplete", AjaxStatus.COMPLETE_FACET);
writer.endElement("script");
}
}
Note: Prestart event "ajaxSend" is not supported in the standard Ajax, but it's rarely used anyway. "ajaxStart" is normally enough. JavaScript ajaxstatus.js is a little bit complicate. I register there global handlers for Ajax events by jsf.ajax.addOnError / jsf.ajax.addOnEvent.PrimeFaces.widget.ExtendedAjaxStatus = function(id) {
this.id = id;
this.jqId = PrimeFaces.escapeClientId(this.id);
}
PrimeFaces.widget.ExtendedAjaxStatus.prototype.bindFacet = function(eventName, facetToShow) {
var _self = this;
// jQuery
jQuery(document).bind(eventName, function() {
_self.showFacet(facetToShow);
});
// Standard
if (eventName == "ajaxError") {
jsf.ajax.addOnError(this.processAjaxOnErrorFacet(facetToShow));
} else {
jsf.ajax.addOnEvent(this.processAjaxOnEventFacet(eventName, facetToShow));
}
}
PrimeFaces.widget.ExtendedAjaxStatus.prototype.bindCallback = function(eventName, fn) {
// jQuery
jQuery(document).bind(eventName, fn);
// Standard
if (eventName == "ajaxError") {
jsf.ajax.addOnError(this.processAjaxOnErrorCallback(fn));
} else {
jsf.ajax.addOnEvent(this.processAjaxOnEventCallback(eventName, fn));
}
}
PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnEventFacet = function(eventName, facetToShow) {
var _self = this;
function processEvent(data) {
if (eventName == "ajaxStart" && data.status == "begin") {
_self.showFacet(facetToShow);
} else if (eventName == "ajaxComplete" && data.status == "complete") {
_self.showFacet(facetToShow);
} else if (eventName == "ajaxSuccess" && data.status == "success") {
_self.showFacet(facetToShow);
}
}
return processEvent;
}
PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnErrorFacet = function(facetToShow) {
var _self = this;
function processEvent() {
_self.showFacet(facetToShow);
}
return processEvent;
}
PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnEventCallback = function(eventName, fn) {
function processEvent(data) {
if (eventName == "ajaxStart" && data.status == "begin") {
fn();
} else if (eventName == "ajaxComplete" && data.status == "complete") {
fn();
} else if (eventName == "ajaxSuccess" && data.status == "success") {
fn();
}
}
return processEvent;
}
PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnErrorCallback = function(fn) {
function processEvent() {
fn();
}
return processEvent;
}
PrimeFaces.widget.ExtendedAjaxStatus.prototype.showFacet = function(facetToShow) {
jQuery(this.jqId).children().hide();
jQuery(this.jqId + '_' + facetToShow).show();
}
Using on the page along with JS handling could be<p:ajaxStatus onstart="ajaxOnStartIndicator()" onerror="ajaxOnErrorIndicator()" onsuccess="ajaxOnSuccessIndicator()"/>
<h:graphicImage id="ajaxIndicatorActive" library="img" name="connect_active.gif"
style="display: none;" styleClass="ajaxIndicator"/>
<h:graphicImage id="ajaxIndicatorCaution" library="img" name="connect_caution.gif"
style="display: none;" styleClass="ajaxIndicator" title="Connection problem" alt="Connection problem"/>
function ajaxOnStartIndicator() {
document.body.style.cursor = 'wait';
jQuery("#ajaxIndicatorCaution").css("display", "none");
jQuery("#ajaxIndicatorActive").css("display", "block");
// get the current counter
var jqDoc = jQuery(document);
var requestCount = jqDoc.data("ajaxStatus.requestCount");
if (typeof requestCount === 'undefined') {
requestCount = 0;
}
// increase the counter
jqDoc.data("ajaxStatus.requestCount", requestCount+1);
}
function ajaxOnSuccessIndicator() {
// get the current counter
var jqDoc = jQuery(document);
var requestCount = jqDoc.data("ajaxStatus.requestCount");
// check the counter
if (typeof requestCount !== 'undefined') {
if (requestCount == 1) {
// hide indicators
document.body.style.cursor = 'auto';
jQuery("#ajaxIndicatorActive").css("display", "none");
jQuery("#ajaxIndicatorCaution").css("display", "none");
jqDoc.data("ajaxStatus.requestCount", 0);
} else if (requestCount > 1) {
// only decrease the counter
jqDoc.data("ajaxStatus.requestCount", requestCount-1);
}
}
}
function ajaxOnErrorIndicator() {
document.body.style.cursor = 'auto';
jQuery("#ajaxIndicatorActive").css("display", "none");
jQuery("#ajaxIndicatorCaution").css("display", "block");
// reset counter
jQuery(document).data("ajaxStatus.requestCount", 0);
}
The implementation is smart enough and avoid collisions with parallel running Ajax requests (s. counters). Furthermore, the cursor is set to 'wait' if an Ajax request is running. It's how desktop applications behaves.I took connection icons from ICEFaces showcase.
Sunday, May 8, 2011
Prevent multiple clicks in JSF
Preventation of multiple clicks on buttons / links is a common and important task during web development. There are many discussions about double click problem in JSF I will try to cover all relevant approaches.
1) Server-side solution.
Server-side solutions are normally based on token concept, phase listeners, filters or other complex tricks. Here is just one example for the server-side preventation of double-form submit Server-side solutions have disadvantages. Every request is handled by a special phase listener, filter or something near it, even if we are not interesting in multiple clicks preventation. There are problems in a cluster environment if session data get replicated. This a real problem I faced many years ago - our server-side solution didn't work proper if the web application was running in a cluster environment. Client-side solutions seems to be simpler and are more reliable.
2) Disable button / link on click.
The simplest client-side solution consists in writing this.disabled=true in onclick. An example:
3) Ajax queue and request delaying.
This is probably the best solution at all. Many popular component libraries already have this solution for the multiple clicks preventation. OpenFaces has e.g. a nice concept in order to avoid frequent requests. OpenFaces' Ajax framework sends only one request in case of several events during the short period of time specified with the "delay" attribute. RichFaces has a4j:queue and a4j:attachQueue. These components have an attribute "requestDelay". This attribute specifies the number of milliseconds to wait before sending a request in order to combine similar requests. The greater the value the fewer requests will be sent when fast typing. Similar requests in the queue are "combined" while waiting for the request delay. This feature results in just one request from a component being sent. For more details see RichFaces showcase and try to do double clicks to see they are "combined" to just one. Unfortunately, but PrimeFaces doesn't have similar feature what would prevent sending of miltiple requests from the same component like buttons / links, datatable filtering on keyup if user types very fast (important for large tables), etc.
4) Using jQuery's bind / unbind methods.
Ajax queue prevents double clicks, but doesn't ensure sending of slow multiple clicks. I have implemented my own solution for PrimeFaces' buttons / links. The idea is to bind onlick handler bind('click', function() {..}) and call unbind('click') within the handler :-) I have also tried jQuery .one() method but it didn't work as desired. We should prepare the renderer class at first. I have these lines there (only short outline for button renderer to get the idea):
1) Server-side solution.
Server-side solutions are normally based on token concept, phase listeners, filters or other complex tricks. Here is just one example for the server-side preventation of double-form submit Server-side solutions have disadvantages. Every request is handled by a special phase listener, filter or something near it, even if we are not interesting in multiple clicks preventation. There are problems in a cluster environment if session data get replicated. This a real problem I faced many years ago - our server-side solution didn't work proper if the web application was running in a cluster environment. Client-side solutions seems to be simpler and are more reliable.
2) Disable button / link on click.
The simplest client-side solution consists in writing this.disabled=true in onclick. An example:
<h:commandbutton action="..." onclick="this.disabled=true;" value="...">I had kinda troubles with this solution and ajaxified buttons like PrimeFaces' p:commandButton - it didn't work. Furthermore the entire button gets frozen and doesn't respond anymore. We want to have a still responding button or link if user clicks on it multiple times.
3) Ajax queue and request delaying.
This is probably the best solution at all. Many popular component libraries already have this solution for the multiple clicks preventation. OpenFaces has e.g. a nice concept in order to avoid frequent requests. OpenFaces' Ajax framework sends only one request in case of several events during the short period of time specified with the "delay" attribute. RichFaces has a4j:queue and a4j:attachQueue. These components have an attribute "requestDelay". This attribute specifies the number of milliseconds to wait before sending a request in order to combine similar requests. The greater the value the fewer requests will be sent when fast typing. Similar requests in the queue are "combined" while waiting for the request delay. This feature results in just one request from a component being sent. For more details see RichFaces showcase and try to do double clicks to see they are "combined" to just one. Unfortunately, but PrimeFaces doesn't have similar feature what would prevent sending of miltiple requests from the same component like buttons / links, datatable filtering on keyup if user types very fast (important for large tables), etc.
4) Using jQuery's bind / unbind methods.
Ajax queue prevents double clicks, but doesn't ensure sending of slow multiple clicks. I have implemented my own solution for PrimeFaces' buttons / links. The idea is to bind onlick handler bind('click', function() {..}) and call unbind('click') within the handler :-) I have also tried jQuery .one() method but it didn't work as desired. We should prepare the renderer class at first. I have these lines there (only short outline for button renderer to get the idea):
writer.write(button.resolveWidgetVar() +
" = new PrimeFaces.widget.ExtendedCommandButton('" + clientId + "', {");
...
writer.write("});");
if (button.isPreventMultiplyClicks()) {
writer.write(button.resolveWidgetVar() + ".bindOneClick('" + clientId + "', function(){" + onclick + "});");
}
ExtendedCommandButton is an extended widget based on the PrimeFaces' one. bindOneClick is a method in this widget and onclick is an onlick handler generated by PrimeFaces for Ajax / non Ajax requests. The extension looks as follows:PrimeFaces.widget.ExtendedCommandButton = function(id, cfg) {
PrimeFaces.widget.CommandButton(id, cfg);
}
jQuery.extend(PrimeFaces.widget.ExtendedCommandButton.prototype, PrimeFaces.widget.CommandButton.prototype);
PrimeFaces.widget.ExtendedCommandButton.prototype.bindOneClick = function(clientId, commandFunc) {
var buttonEl = jQuery(PrimeFaces.escapeClientId(clientId));
buttonEl.data('commandFunc', commandFunc);
buttonEl.unbind('click');
buttonEl.bind('click', function() {
buttonEl.unbind('click');
commandFunc();
});
}
One problem still exists - if user stays on the same page (in case of Ajax updates) and the button is not updated, it doesn't respond more by reason of unbind. We should "reinitialize" it. Therefore I added this method too:PrimeFaces.widget.ExtendedCommandButton.prototype.reinitialize = function(clientId) {
var buttonEl = jQuery(PrimeFaces.escapeClientId(clientId));
buttonEl.unbind('click');
buttonEl.bind('click', function() {
buttonEl.unbind('click');
buttonEl.data('commandFunc')();
});
}
So, if we navigate to another page (a non-Ajax request), we can write now:<p:commandButton preventMultiplyClicks="true" ajax="false" ...>If user stays on the same page (an Ajax request), a call PrimeFaces.widget.ExtendedCommandButton.reinitialize(clientId) is necessary in "oncomplete" to get multi-click preventation working again. Like this
<p:commandButton id="myButton" preventMultiplyClicks="true" ...
oncomplete="PrimeFaces.widget.ExtendedCommandLink.reinitialize('myButton')">
Or you can try to include the button Id to the "update" attribute<p:commandButton preventMultiplyClicks="true" update="@this,..." ...>p:commandLink will be updated and reinitialized again. We can also extend the button renderer a little bit and reinitialize it automatically (if it's not in the Ajax update region). Result: the button / link will be a quite usually PrimeFaces' button / link with a new attribute for multiple clicks. No differences otherwise.
<p:commandLink "general PF attributes" preventMultiplyClicks="true">
Wednesday, April 13, 2011
Hooking of PrimeFaces Ajax calls as a kind of callbacks. Part II.
This is the continuation of the part I where I intended to add client-side callbacks for any PrimeFaces Ajax calls. PrimeFaces components normally have callbacks like onstart and oncomplete, but not for all events. PrimeFaces tree has e.g. onselectStart and onselectComplete, but what is about onexpandStart and onexpandComplete in dynamic trees? I guess, all such callbacks would blow up the component interface. I believe all existing component libraries mimic each other and have countless callbacks and listeners as component attributes. Why do they need them at all for Ajax calls? There is f:ajax and derivations like p:ajax which already have "listener", "execute", "render" attributes and provide all Ajax callbacks with "onevent" attribute automatically. Do they really need to blow up component attributes or would be a simple attaching of Ajax behaviors enough? Well. It's not the topic of this post.
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.
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.
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.
Saturday, April 9, 2011
Hooking of PrimeFaces Ajax calls as a kind of callbacks. Part I.
Using PrimeFaces I have needed client-side callbacks for events causing Ajax calls like sorting, pagination, filtering of datatable or tree node expansion. I can understand that it's impossible to define all kinds of callbacks. Therefore I was looking for a generic way and believe I have found it. I'm able now to "add" any callbacks to any components. Firstly some words about PrimeFaces Ajax driven events. A PrimeFaces Ajax request triggered by any component has a parameter describing the event. It consist of component's clientId, underline, name and value, e.g. myTabview_tabChange=true. We are especially interesting in names and values. They are e.g.
jQuery has a capability to register callbacks for Ajax events. There are ajaxStart .... ajaxComplete events in jQuery. With ajaxStart you can register a handler to be called when the first Ajax request begins, with ajaxComplete a handler to be called when Ajax requests complete, etc. We can use them to catch PrimeFaces Ajax calls and check passed parameters in order to figure out whether the event occurs we are interesting in.
I'm going to demonstrate this approach on basis of a component I have implemented recently. This component calls blockUI and it's based on the jQuery plugin blockUI. By means of this component we can block any piece of page during various Ajax calls. Very handy if you have e.g. a large datatable and sorting, filtering, pagination takes much time. The component has the following parameters: "widgetVar" for the underlying widget (as all PrimeFaces components have it), "for", "forElement" to specify target component or HTML element which gets blocked and "source" to specify source component which causes an Ajax request. Events, we are interesting in, are specified via a facet "events" and f:param within the facet. I'm going to start with facelets part and show three examples.
Sometimes we are not interesting in any special events. In this case no f:facet for events is required. Assume we have a table, radio buttons and checkboxes. A click on any table row should block the radio buttons and checkboxes. A click on any radio button or checkbox should block the table, radio buttons and checkboxes. Remaining part of the page should stay untouched. Note: tables should be wrapped with a div element if they have to be blocked because a table can't be blocked nice in all browsers.
The event instantSelectedRowIndex doesn't have a specified value. That means we are not interesting in a value (everything is valid). Unfortunately Ajax handlers can not be bound with jQuery .live() method. Therefore I had to wrap radio button and checkboxes with a h:panelGroup to get blockUI updated every time together with corresponding radio button and checkboxes.
The second part will cover the implementation and reveal more details. Stay tuned.
Datatable: name="filtering" value="true" name="sorting" value="true" name="paging" value="true" Tree: name="action" value="SELECT" name="action" value="EXPAND" TabView: name="tabChange" value="true"This anatomy is not disclosed. Name / value pairs are undocumented, so that you need to look into JS files (for something like params[this.id + '_tabChange'] = true;) or corresponding component renderers to figure out what are the parameters of any event. The other problem is that a name / value pair can be changed in next releases. The new tree (since PrimeFaces 3.x) will have e.g.
name="instantSelection" value="<Node Id>" name="expandNode" value="<Node Id>"It would be very nice and appreciated by community if the PrimeFaces team could standardize and document all Ajax driven events. Please :-)
jQuery has a capability to register callbacks for Ajax events. There are ajaxStart .... ajaxComplete events in jQuery. With ajaxStart you can register a handler to be called when the first Ajax request begins, with ajaxComplete a handler to be called when Ajax requests complete, etc. We can use them to catch PrimeFaces Ajax calls and check passed parameters in order to figure out whether the event occurs we are interesting in.
I'm going to demonstrate this approach on basis of a component I have implemented recently. This component calls blockUI and it's based on the jQuery plugin blockUI. By means of this component we can block any piece of page during various Ajax calls. Very handy if you have e.g. a large datatable and sorting, filtering, pagination takes much time. The component has the following parameters: "widgetVar" for the underlying widget (as all PrimeFaces components have it), "for", "forElement" to specify target component or HTML element which gets blocked and "source" to specify source component which causes an Ajax request. Events, we are interesting in, are specified via a facet "events" and f:param within the facet. I'm going to start with facelets part and show three examples.
Playing together with p:dataTable
<h:panelGroup id="dtPropTemplatesGroup" layout="block">
<p:dataTable id="dtPropTemplates" var="propTemplate"
value="#{propTemplates.templates}" paginator="true" rows="20" ...>
...
</p:dataTable>
<jtcomp:blockUI source="dtPropTemplates" for="dtPropTemplates">
<f:facet name="events">
<f:param name="filtering" value="true"/>
<f:param name="paging" value="true"/>
<f:param name="sorting" value="true"/>
</f:facet>
<h:panelGrid columns="2">
<h:graphicImage library="themes" name="app/images/ajaxIndicator.gif"/>
<h:outputText value="Please wait, data are processing ..."/>
</h:panelGrid>
</jtcomp:blockUI>
</h:panelGroup>
Filtering, paging and sorting events display a half transparent overlay over the same table and a rotating Ajax indicator with the message "Please wait, data are processing ...".Playing together with p:tree
<h:panelGroup id="treeSocsGroup" layout="block">
<p:tree id="treeSocs" value="#{socsBean.socsRootNode}" var="socNode"
dynamic="true" cache="true" selectionMode="single"
nodeSelectListener="#{socsBean.onNodeSelect}"
nodeExpandListener="#{socsBean.onNodeExpand}" ...>
<p:treeNode>
<h:outputText value="#{socNode.displayName}" escape="false"/>
</p:treeNode>
<p:treeNode type="leafSoc" styleClass="leafSoc">
<h:outputText value="#{socNode.displayName}" styleClass="leafContent"/>
</p:treeNode>
</p:tree>
<jtcomp:blockUI source="treeSocs" forElement="overviewSocsRightGroup">
<f:facet name="events">
<f:param name="action" value="SELECT"/>
</f:facet>
// the same stuff as above
</jtcomp:blockUI>
<h:panelGroup id="overviewSocsRightGroup" layout="block">
// menus, tables and other stuff
</h:panelGroup>
</h:panelGroup>
A click on any tree node blocks the panelGroup with id "overviewSocsRightGroup" and the mentioned above overlay, indicator and message are displayed.Playing together with p:ajax
Sometimes we are not interesting in any special events. In this case no f:facet for events is required. Assume we have a table, radio buttons and checkboxes. A click on any table row should block the radio buttons and checkboxes. A click on any radio button or checkbox should block the table, radio buttons and checkboxes. Remaining part of the page should stay untouched. Note: tables should be wrapped with a div element if they have to be blocked because a table can't be blocked nice in all browsers.
<h:panelGroup id="rightsGroup" layout="block">
<h:panelGroup id="selectedGroupsUsersGroup">
<p:dataTable id="selectedGroupsUsers" var="selGrUs" value="#{rightsBean.selectedGroupsUsers}" dynamic="false"
selection="#{rightsBean.selectedGroupUser}" selectionMode="single" update="levelsRightsGrid">
...
</p:dataTable>
<jtcomp:blockUI source="selectedGroupsUsers" forElement="levelsRightsPanel">
<f:facet name="events">
<f:param name="instantSelectedRowIndex"/>
</f:facet>
// the same stuff as above
</jtcomp:blockUI>
</h:panelGroup>
<h:panelGrid id="levelsRightsGrid" columns="2">
<h:panelGroup id="accessLevelsGroup">
<h:selectOneRadio id="accessLevels" value="#{rightsBean.level}" layout="pageDirection">
<f:selectItem itemLabel="Custom" itemValue="Custom" itemDisabled="true"/>
<f:selectItems value="#{rightsBean.selectableLevels}"/>
<p:ajax event="click" process="@this" update="accessRightsGroup,selectedGroupsUsersGroup"/>
</h:selectOneRadio>
<jtcomp:blockUI source="accessLevels" for="rightsGroup">
// the same stuff as above
</jtcomp:blockUI>
</h:panelGroup>
<h:panelGroup id="accessRightsGroup">
<h:selectManyCheckbox id="accessRights" value="#{rightsBean.selectedAccessRights}" layout="pageDirection">
<f:selectItems value="#{rightsBean.accessRights}"/>
<p:ajax event="click" process="@this" update="accessLevelsGroup,selectedGroupsUsersGroup"/>
</h:selectManyCheckbox>
<jtcomp:blockUI source="accessRights" for="rightsGroup">
// the same stuff as above
</jtcomp:blockUI>
</h:panelGroup>
</h:panelGrid>
</h:panelGroup>
The event instantSelectedRowIndex doesn't have a specified value. That means we are not interesting in a value (everything is valid). Unfortunately Ajax handlers can not be bound with jQuery .live() method. Therefore I had to wrap radio button and checkboxes with a h:panelGroup to get blockUI updated every time together with corresponding radio button and checkboxes.
The second part will cover the implementation and reveal more details. Stay tuned.
Tuesday, October 19, 2010
JSF Ajax redirect after session timeout
JSF 2 has a facility to be able to do Ajax redirects. The specification says - an element <redirect url="redirect url"/> found in the response causes a redirect to the URL "redirect url". JSF component libraries often write other "redirect" elements into response, but the end result is the same - a redirect happens. It works great in case of JSF Ajax redirects.
A web application normally has a security filter - an own solution or from Spring framework or something else. Such security filter does a redirect if user is not authenticated to use requested web resources. This is not an JSF redirect. The filter doesn't pass current request to the FacesServlet at all. Here is an example:
A possible solution would be to pass "not authenticated" request through FacesServlet. We need to write an Authenticator class which stores the page URL we want to be redirected to in the request scope.
A web application normally has a security filter - an own solution or from Spring framework or something else. Such security filter does a redirect if user is not authenticated to use requested web resources. This is not an JSF redirect. The filter doesn't pass current request to the FacesServlet at all. Here is an example:
public class SecurityFilter implements Filter {
private Authenticator authenticator;
private String loginPage;
public void init(FilterConfig config) throws ServletException {
...
// create an Authenticator
authenticator = AuthenticatorFactory.createAuthenticator(config);
loginPage = getLoginPage(config);
...
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest hReq = (HttpServletRequest) request;
HttpServletResponse hRes = (HttpServletResponse) response;
...
// get user roles
Collection roles = ...
// get principal
Principal principal = hReq.getUserPrincipal();
if (!roles.isEmpty() && principal == null) {
// user needs to be authenticated
boolean bRet = authenticator.showLogin(hReq, hRes);
if (!bRet) {
// redirect the a login page or error sending
...
}
}
...
}
}
Problem: indeed an Ajax request is redirected to a specified page after session timeout, but the response can not be proper treated on the client-side. We have a quite regular redirect in this case and the specified page in the response. Client-side Ajax code doesn't expect the entire page coming back.A possible solution would be to pass "not authenticated" request through FacesServlet. We need to write an Authenticator class which stores the page URL we want to be redirected to in the request scope.
public class FormAuthenticator {
...
public boolean showLogin(HttpServletRequest request,
HttpServletResponse response, String loginPage) throws IOException {
...
request.setAttribute("web.secfilter.authenticator.showLogin", loginPage);
return true;
}
}
The method showLogin returns true in order to avoid a redirect by filter. Let us execute the redirect by JSF! The call gets now a third parameter "loginPage".authenticator.showLogin(hReq, hRes, loginPage);If you use Spring security you should have an entry point in your XML configuration file like this one
<beans:bean id="authenticationProcessingFilterEntryPoint"
class="com.xyz.webapp.security.AuthenticationProcessingFilterEntryPoint">
<beans:property name="loginFormUrl" value="/login.html"/>
<beans:property name="serverSideRedirect" value="true"/>
</beans:bean>
The class AuthenticationProcessingFilterEntryPoint is derived from Spring's one/**
* Extended Spring AuthenticationProcessingFilterEntryPoint for playing together with JSF Ajax redirects.
*/
public class AuthenticationProcessingFilterEntryPoint extends
org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint
{
public static final String ATTRIBUTE_LOGIN_PAGE = "web.secfilter.authenticator.showLogin";
public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
request.setAttribute(ATTRIBUTE_LOGIN_PAGE, getLoginFormUrl());
super.commence(request, response, authException);
}
}
On the JSF side we need a special phase listener to do the redirect by ExternalContext. We check at first the buffered page URL in the beforePhase. If it was set - an JSF redirect is needed and will be done./**
* Phase listener for the Restore View Phase which manages display of login page.
*/
public class SecurityPhaseListener implements PhaseListener
{
private static final Log LOG = LogFactory.getLog(SecurityPhaseListener.class);
public void afterPhase(PhaseEvent event) { ; }
public void beforePhase(PhaseEvent event) {
FacesContext fc = event.getFacesContext();
String loginPage = (String) fc.getExternalContext().getRequestMap().
get("web.secfilter.authenticator.showLogin");
if (StringUtils.isNotBlank(loginPage)) {
doRedirect(fc, loginPage);
}
}
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
/**
* Does a regular or ajax redirect.
*/
public void doRedirect(FacesContext fc, String redirectPage)
throws FacesException {
ExternalContext ec = fc.getExternalContext();
try {
if (ec.isResponseCommitted()) {
// redirect is not possible
return;
}
// fix for renderer kit (Mojarra's and PrimeFaces's ajax redirect)
if ((RequestContext.getCurrentInstance().isAjaxRequest()
|| fc.getPartialViewContext().isPartialRequest())
&& fc.getResponseWriter() == null
&& fc.getRenderKit() == null) {
ServletResponse response = (ServletResponse) ec.getResponse();
ServletRequest request = (ServletRequest) ec.getRequest();
response.setCharacterEncoding(request.getCharacterEncoding());
RenderKitFactory factory = (RenderKitFactory)
FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
RenderKit renderKit = factory.getRenderKit(fc,
fc.getApplication().getViewHandler().calculateRenderKitId(fc));
ResponseWriter responseWriter =
renderKit.createResponseWriter(
response.getWriter(), null, request.getCharacterEncoding());
fc.setResponseWriter(responseWriter);
}
ec.redirect(ec.getRequestContextPath() +
(redirectPage != null ? redirectPage : ""));
} catch (IOException e) {
LOG.error("Redirect to the specified page '" +
redirectPage + "' failed");
throw new FacesException(e);
}
}
}
It works in both cases - for Ajax and regular request.
Subscribe to:
Posts (Atom)








