Extending PrimeFaces: AJAX

User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

This is the last of a series of posts (Extending PrimeFaces components is the first one, here the second one) with the purpose of explaining how to use PrimeFaces as an API to create new JSF components. Our main focus in this article is to add AJAX listeners to our components. The component that we are going to analyse together is the Switch from StrazzFaces library: a simple wrapper of one of the Drinks Toolkit widgets, used to manage a boolean value.

Component

Let’s start, as always, with the component code:

public class Switch extends UIInput implements Widget, ClientBehaviorHolder {

	public static final String COMPONENT_TYPE = "it.strazz.faces.Switch";
	public static final String COMPONENT_FAMILY = "it.strazz.faces.components";
	static final Collection<String> AVAIABLE_TYPES = Collections.unmodifiableCollection(Arrays.asList("rocker", "arc", "side", "circle", "rect", "toggle"));
	static final String DEFAULT_TYPE = "toggle";
	private static final Collection<String> EVENT_NAMES = Collections.unmodifiableCollection(Arrays.asList("change"));

	@Override
	public Collection<String> getEventNames() {
		return EVENT_NAMES;
	}

	@Override
	public String getDefaultEventName() {
		return "change";
	}

	@Override

	public String getFamily() {
		return COMPONENT_FAMILY;
	}

	public String getWidgetVar() {
		return (String) getStateHelper().eval(PropertyKeys.widgetVar, null);
	}

	public void setWidgetVar(String _widgetVar) {
		getStateHelper().put(PropertyKeys.widgetVar, _widgetVar);
	}

	public String getType() {
		return (String) getStateHelper().eval(PropertyKeys.type, DEFAULT_TYPE);
	}

	public void setType(String _widgetVar) {
		getStateHelper().put(PropertyKeys.type, _widgetVar);

	}

	...
}

The principal feature of this component is to implement the ClientBehaviorHolder interface: using this interface we are declaring that this component is capable of managing ClientBehavior objects. A ClientBehavior is an object that represents a client behaviour defined by a script, in a nutshell it represents a <p:ajax/> tag. In the previous code we have implemented two methods: getEventsName and getDefaultEventName. The first one lists the AJAX events that are valid for our component (the event is defined by the ‘event’ attribute in the <p:ajax/> tag), the second one simply defines the default event (when not specified on the tag). In our case the only supported event is “change”.

The other methods of the interfaces let us add/read ClientBehaviors, but they are implemented in the class UIComponentBase: the base class of almost all the JSF components.

Renderer

Let’s move on to the Renderer code:

public class SwitchRenderer extends CoreRenderer {

	public static final String RENDERER_TYPE = "it.strazz.faces.SwitchRenderer";

	@Override
	public void decode(FacesContext context, UIComponent component) {
		Switch switchComponent = (Switch) component;
		decodeBehaviors(context, switchComponent);

		String submittedValue = (String) context.getExternalContext().getRequestParameterMap().get(switchComponent.getClientId(context) + "_hidden");
		switchComponent.setSubmittedValue(Boolean.parseBoolean(submittedValue));

	}

	@Override

	public void encodeEnd(FacesContext context, UIComponent component) throws IOException {

		Switch switchComponent = (Switch) component;
		encodeMarkup(context, switchComponent);
		encodeScript(context, switchComponent);

	}

	private void encodeScript(FacesContext context, Switch switchComponent) throws IOException {

		String clientId = switchComponent.getClientId();

		String widgetVar = switchComponent.resolveWidgetVar();

		WidgetBuilder wb = getWidgetBuilder(context);



		wb.init("Switch", widgetVar, clientId);

		wb.attr("widgetName", widgetVar);

		wb.attr("type", getSwitchType(switchComponent));

		encodeClientBehaviors(context, switchComponent);

		wb.finish();

	}

	private void encodeMarkup(FacesContext context, Switch switchComponent) throws IOException {

		...

	}

}

As you can notice from the Renderer code, all we have to do to manage the ClientBehaviors is to invoke two methods: encodeClientBehaviors and decodeClientBehaviors. These methods, inherited from the PrimeFaces CoreRenderer, will link the AJAX functions to the JavaScript widget (encode), and will transform the AJAX call into a Java method invocation on the server (decode). It’s interesting to notice that we have to write no code for achieving this goal, PrimeFaces gives us a complete “out of the box” solution.

Widget

The last element that we are going to analyze is the JavaScript widget:

PrimeFaces.widget.Switch = PrimeFaces.widget.BaseWidget.extend({

	init : function(cfg) {

		this._super(cfg);

		this.input = jQuery(this.jqId+"_hidden");
		this.name = cfg.widgetName;
		this.type = cfg.type;

		var that = this;

		
		jQuery(document).ready(function(){

			if(!that.switchComponent){

				that.switchComponent = Drinks.createElement('switch', {"id":that.id+'_switch'});

				that.switchComponent.setAttribute("onchange", "eval(\"PF('" + that.name + "').onchange(this.value);\");");			

			}

	        });

		}
		

	},

	onchange: function(value){

		this.input.val(value === 1);

		if(this.cfg.behaviors && this.cfg.behaviors.change) {

			this.cfg.behaviors.change.call(this.input);

		}

	}

});

To make an AJAX call, the only thing that we have to do is to invoke the right JavaScript function in the behaviour object of our widget. The purpose on the encodeClientBehaviors method it’s just to populate this object.

Example

Once the component is ready, its usage it’s similar to any other PrimeFaces component, as we can see in the next example:

<p:outputPanel layout="block" styleClass="container" id="container">

	<div class="row">

		<div class="col-md-2">

			<s:switch value="#{switchBean.value}">

				<p:ajax update="container" />

			</s:switch>

		</div>

		<div class="col-md-10">

			<p>The Lights are #{switchBean.value ? 'On' : 'Off'}!</p>

		</div>

	</div>

</p:outputPanel>

The result (here a live demo) is this:

primefaces extension

primefaces extension 

Conclusions

Let’s recap the logical flow of the operations needed to add an AJAX listener to our component:

  • For each <p.ajax/> tag inside our component a ClientBehavior instance is created.
  • The renderer create the widget and links each ClientBehavior to a JavaScript function (encode).
  • The widget wait for user interaction and, in case, will invoke the corresponding function.
  • The renderer convert the client event into a server method (decode).

All of these operations needs a very small amount of code. All the hard work it’s done by PrimeFaces classes.

With this post I finished my little tutorial on how to create new JSF components using PrimeFaces. You can look at the code of this and other components at the StrazzFaces GitHub repository

Author:

Francesco Strazzullo - software architect and one of the committer in the Primefaces Extensions project.


Advertisement