Integrate 3rd Party Javascript Libraries In ZK

From Documentation
DocumentationSmall Talks2012NovemberIntegrate 3rd Party Javascript Libraries In ZK
Integrate 3rd Party Javascript Libraries In ZK

Author
Ashish Dasnurkar, Engineer, Potix Corporation
Date
November 23, 2012
Version
ZK 6.0 and later

Overview

ZK's Server+Client fusion architecture gives application developers lot of flexibility to control their application UI on the client side. Using ZK's client side programming techniques application developers can make their applications more responsive, add UI effects or use third party Javascript libraries hence controlling UI on the client side. In this smalltalk I am going to demonstrate to you how you can integrate a third party Javascript library and leverage client side programming techniques in your applications.

Common tasks involved

Generally there are few common tasks that application developers need to do in order to make use of third party Javascript libraries such as

  • Include library source
  • Setup basic components
  • Prepare data for library
  • Call initialization function for configuring library
  • Calling library functions
  • Send data to server

Note: Not all of these needs to be performed. Some of these tasks are optional and really depends on the 3rd party Javascript library that you want to integrate.

We will take a look how these common tasks can be easily performed by demonstrating a sample.

Integrate Infovis Javascript Toolkit Demo

For this article I will use existing SpaceTree demo sample from Infovis JavascriptToolkit demos page and show you how it can be rewritten as a ZK ZUML page.

Here is a demo of the sample that I have created

As you can see on first page load, a simple tree visualization is loaded. To add a new node to the tree you can enter simple node text value in the textbox and hit Add button to append this newly created tree node to the root node.

This SpaceTree sample has three components; a div to contain the tree visualization, a textbox to enter new node's text value and a button to add a new node to the tree.

Details

Lets dig into the implementation details now.

Include library source

You can include javascript files using script xml processing instruction on your ZUML page. For this sample I downloaded Infovis toolkit Javascript library file and included it as shown below

	<?script src="jit.js" ?>

Also for better organization of source code I have created a separate JS file for my custom code for this sample and included it similarly

	<?script src="sample1.js" ?>

Setup basic components

To show the SpaceTree layout visualization we need a div component. I have also added textbox and button for adding a new node feature.

<zk>
	<window id="mainWin">
		<hlayout>
			<div id="infovis" width="600px" height="600px"/>
			<textbox id="nodeTxt" />
			<button id="add" label="Add"/>	
			<button id="remove" label="Refresh"/>
		</hlayout>
	</window>
</zk>

Prepare data for library

In most cases of using 3rd party Javascript libraries you would need to prepare some data that will be later manipulated and used on the client side. For example in this sample of SpaceTree I need tree nodes data that I can use to build this particular tree.

There are various ways in which you can bring data from server. You can do a simple Ajax request to server and get JSON data back, implement an Initiator or if you are already using ZK MVC then have your Composer to prepare the data. For this sample purpose I will demonstrate the later way i.e. I will prepare Tree node data in my Composer.

First apply a Composer to mainWin Window

...
	<window id="mainWin" apply="org.zkoss.internal.SpaceTreeComposer">
...

and override doBeforeComposeChildren(T) method. doBeforeComposeChildren(T) is a lifecycle callback method that ZK will invoke during ZUML page processing before any of the children components of mainWin window component.

public class SpaceTreeComposer extends SelectorComposer<Window> {
	
	String treeData;
	
	public void doBeforeComposeChildren(Window win) {
                super.doBeforeComposeChildren(win);
		// TODO: Prepare tree data
	}

Infovis SpaceTree layout accepts JSON data. For this sample I will implement a Javabean representing SpaceTree node and implement JSONAware interface.

For representing each node of Tree I define following SpaceTreeNode Javabean

public class SpaceTreeNode implements JSONAware {

	String id;            // Uniquely identifies tree node
	String name;          // Node text to be displayed 
	JSONObject data;      // Raw data
	JSONArray children;   // Children nodes

        // constructor

	public String toJSONString() {
		Map<String, Object> map = new HashMap<String, Object>();
        map.put("id", id);
        map.put("name", name);
        map.put("data", data);
        map.put("children", children);
	return JSONObject.toJSONString(map);
	}

	// getters and setters for the SpaceTreeNode fields

}

Implemented JSONAware interface for the SpaceTreeNode allows me to easily transform tree node structure into JSON string that can be sent to client for evaluation and further processing.

Below is how I create some simple SpaceTreeNode s and convert the whole structure into JSON string by calling toJSONString() on the root node.

	public void doBeforeComposeChildren(Window win) {
		JSONArray firstNodeChildren = new JSONArray();
		firstNodeChildren.add(new SpaceTreeNode("node11", "Node 11", new JSONObject(), null));
		firstNodeChildren.add(new SpaceTreeNode("node12", "Node 12", new JSONObject(), null));
		firstNodeChildren.add(new SpaceTreeNode("node13", "Node 13", new JSONObject(), null));

		SpaceTreeNode firstNode = new SpaceTreeNode("node1", "Node 1", new JSONObject(), firstNodeChildren);

		// similarly create secondNode and thirdNode

		JSONArray rootChildren = new JSONArray();
		rootChildren.add(firstNode);
		rootChildren.add(secondNode);
		rootChildren.add(thirdNode);

		SpaceTreeNode root = new SpaceTreeNode("ROOT", "ROOT", new JSONObject(), rootChildren);
		treeData = root.toJSONString();		// Converts tree node structure by recursively calling toJSONString() on each node
		...
	}

Initilizate/Configure library

Sometimes you may need to initialize 3rd party libraries before they are ready to use so the library has a chance to configure itself to support client environment. In case of SpaceTree layout of Infovis Javascript toolkit I need to create an instance of ST and optionally setup some default style for tree nodes. I have defined an init() Javascript function for this purpose. Additionally I am also going to load our tree node data into ST using the its loadJSON() API.

var st;

function init(treeData){

    //init Spacetree
    //Create a new ST instance
    var wrapper = '' + zk.Widget.$("$infovis").uuid;
    st = new $jit.ST({
        'injectInto': wrapper, // pass the DOM element 
        // additional options for tree node style nad/or callback function while SpaceTree rendering, check SpaceTree API docs

    });

    // Load tree node data as passed from server
    st.loadJSON(jq.evalJSON(treeData));
    st.compute();
    
    //optional: make a translation of the tree
    st.geom.translate(new $jit.Complex(-200, 0), "current");
    
    //Emulate a click on the root node. 
    st.onClick(st.root);
} // end of init()

Generally init() function like this is invoked on document ready status. You can use zk.afterMount to call init function shown below.

<script>
	zk.afterMount(function() {
		init();
	});
</script>

zk.afterMount will insure your function is called after all ZK javascript codes are executed and client-side APIs are available and all peer widgets and created and initialized before calling your function.

However in ZK, developer can have even finer control over the timing of execution for such function. For eg. instead of calling init() in zk.afterMount you can delay the execution until the specific component widget is attached to the DOM tree. This can be achieved by listening to onBind event of a component. onBind event is fired on client side whenever component widget is ready and its DOM elements are attached to the DOM tree.

You can register for onBind event on client side like below

<zk xmlns:w="client">
	
		...
			<div id="infovis" width="600px" height="600px" w:onBind="init('${$composer.treeData}')"/>
		...

Note: treeData is composer field and is being accessed here through EL expression.

or on server side like shown below

	public void doAfterCompose(Window comp) throws Exception {
		super.doAfterCompose(comp);
		// treeData is already prepared on doBeforeComposeChildren as shown above
		infovis.setWidgetListener("onBind", "init('" + treeData + "');");
	}

Calling library functions

Typically you will have some Javascript code that will be executed while handling certain ZK component widget events or you can even invoke Javascript code from server side. I will demonstrate both ways to call SpaceTree API for adding sub tree structure.

To add a new node or a sub tree to existing SpaceTree layout I need to call addSubtree API. addSubtree takes a JSON object representing the subtree structure and few other options (refer to SpaceTree docs for complete description).

Invoke from client-side

I have registered a client-side onClieck event listener for add button as shown below

<zk xmlns:w="client">
...
	<button id="add" label="Add" w:onClick="addNode()"/>
...

add button onClick event listener calls addNode() function which gets the new node's text value from the nodeTxt textbox component using ZK's client-side API and prepares a JSON representation of this new node and passes it on to the SpaceTree#addSubtree API to do the actual add and update SpaceTree layout as shown below

function addNode() {
	var nodeTxtValue = zk.Widget.$("$nodeTxt").getValue();
	var newNodeJSON = {
	        id: "ROOT",
	        name: "ROOT",
	        data: {},
	        children: [{
	        	id: nodeTxtValue,
	            name: nodeTxtValue,
	            data: {},
	            children: []
	        }]    
	        }
	
	st.addSubtree(newNodeJSON, "animate", {
        hideLabels: false,
        onComplete: function() {
            console.log("subtree added");
		}
    });
}

zk.Widget.$() function returns component widget reference and after that you can invoke any component specific APIs on it. For eg. here we call Textbox component's getValue() API to retrieve text value entered in the nodeTxt textbox component.

Note that st is SpaceTree layout instance that we already setup and configured in the init() function described above.

Invoke from server-side

Instead of client-side onClick event listener I can also define a server side listener in my SpaceTreeComposer as shown below.

	@Listen("onClick= #add")
	public void addNode() {
		System.out.println("Adding node on server side!!!");
		JSONArray c = new JSONArray();
		c.add(new SpaceTreeNode(nodeTxt.getValue(), nodeTxt.getValue(), new JSONObject(), new JSONArray()));
		Clients.evalJavaScript("addNode(\'" + new SpaceTreeNode("ROOT", "ROOT", new JSONObject(), c).toJSONString() + "\')");
	}

On server-side we can use SpaceTreeNode defined above to create the subtree structure and invoke toJSONString() API to get the JSON string representation of it. For calling addNode() function defined in javascript we can use Clients.evalJavascript() API and pass the subtree JSON representation as an argument. Here is the addNode javascript function that receives this subtree JSON string as argument and passes it on to the addSubtree API

function addNode(newTextNode) {
	var newNodeJSON = jq.evalJSON(newTextNode); // evaluates JSON string to JSON object 
	st.addSubtree(newNodeJSON, "animate", {
        hideLabels: false,
        onComplete: function() {
            console.log("subtree added");
		}
    });
}

Send data to server

Lets see how to communicate with server by implementing tree node remove feature for first level nodes and sending removed node id back to server using zAu.send. To demonstrate this I have setup a call back function that will be invoked when users click on first level nodes during onCreateLabel function that I passed to SpaceTree constructor. Below is the related code snippet and please note the comments

function init(treeData){
...
var wrapper = '' + zk.Widget.$("$infovis").$n().id;
    st = new $jit.ST({
        'injectInto': wrapper,
...
        //This method is triggered on label
        //creation. This means that for each node
        //this method is triggered only once.
        //This method is useful for adding event
        //handlers to each node label.
        onCreateLabel: function(label, node){
...
            if(node._depth == 1) {
                style.cursor = 'pointer';
                label.onclick = function() {
                    if(!removing) {
                        removing = true;
                        console.log("removing subtree...");  
                        //remove the subtree
                        st.removeSubtree(label.id, true, 'animate', {
                            hideLabels: false,
                            // This is a callback function that will be invoked by st.removeSubtree once the node is successfully removed from tree
                            onComplete: function() {
                              removing = false;
                              zAu.send(new zk.Event(zk.Widget.$('$infovis'), "onRemove", {'' : {'data' : {'nodeId': label.id}}}, {toServer:true}));
                              console.log("subtree removed");
                            }
                        });
                    }
                };
            };
...

and on server side you can isten to this event in your composer as shown below

	@Listen("onRemove= #infovis")
	public void nodeRemoved(Event evt) {
		System.out.println("data received" + evt.getData());
	}

Summary

In this smalltalk I demonstrated how application developers can easily integrate 3rd party Javascript libraries in their ZK application by describing common tasks involved in such a process and different techniques useful for easier integration.

Downloads

You can get complete source for the example described in this smalltalk from its github repository Either do

git clone https://github.com/kachhalimbu/jslibinteg

or download zip archive from downloads section and import project into Eclipse as "Exsisting Maven Project"


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.