I'm the father of ItsNat AJAX Java web framework, JNIEasy, LAMEOnJ, JEPLayer and JEPLDroid libraries (and very old stuff like XPDOM), supporter of the Single Page Interface (SPI) paradigm, writer of the SPI manifesto and currently Android native guy. Jose Maria has posted 28 posts at DZone. You can read more from them at their website. View Full User Profile

Hybrid Client-Server Centric Programming: a Match Made In Heaven

01.26.2011
| 7565 views |
  • submit to reddit

First of all we must need to define what client-centric and server-centric programming in the web is.

Client-Centric Programming

You do client-centric programming when you make code to be executed on the client. For instance, any JavaScript code made by hand by you is client-centric programming (regardless of whether or not you use a JavaScript library). The more custom JavaScript the more client-centric is your application.

Even if you code in Java with GWT, this technology is client-centric because your code will be executed in client.

Server-centric Programming

You do server-centric programming when your code is going to be executed on the server, of course your code will generate HTML and may generate JavaScript under the hood.

Client and server centric mismatch

Both approaches have played together for years in conventional paged web sites, the server generates the page containing custom JavaScript and this custom JavaScript has provided us some kind of dynamic behavior. With the advent of AJAX even more dynamic behavior has been added to our web sites.

Notwithstanding everything has changed with Single Page Interface, in this kind of AJAX and JavaScript intensive sites/applications, web frameworks, client and server centric, tend to be too invasive with tons of already made black-boxed components. That is, there is not much room to coexist and cooperate, so you are usually compelled to select one approach and one framework. For instance you hardly can add custom JavaScript code to add some value added to server-centric components. The opposite is also true, some JavaScript based components are too closed to be extended with some kind of server-centric processing.

Another problem is many already made JavaScript components are designed only for page based applications. There is not very much support for a Single Page Interface, in this paradigm page fragments are replaced on demand usually loading new components.

How to reconcile client-server centric approaches with ItsNat

In spite of its server-centric nature, ItsNat  is highly flexible. With some care you can make web applications with zones controlled by ItsNat and zones controlled by custom JavaScript code. Custom JavaScript code can be returned as the result of AJAX requests, and attributes of any element can be changed by custom JavaScript code, for instance to provide some kind of visual effect (usually onX handlers, style and class attributes), in this case the client-server synchronization is broken (attributes modified only in client) but this is not a problem in ItsNat.

As of ItsNat v1.1 a new feature was introduced, disconnected nodes from client, server DOM nodes can be removed only in server remaining client DOM nodes intact. Later the same DOM subtree in server can be reconnected again in sync with client.

This feature is very interesting to bring a new world of cooperative client-server development. The following example shows how a typical black-boxed JavaScript component can be fully managed from server in a Single Page Interface application.

JCarousel is a beautiful jQuery based component to provide carousel motion to a list of images. Online example.

JCarousel is a "semi" black-boxed component, because some templating is possible, the list of the images is defined by us with something like:

      <ul id="carouselId" class="jcarousel-skin-tango">
<li><img src="hybridcs/img/199481236_dc98b5abb3_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481072_b4a0d09597_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481087_33ae73a8de_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481108_4359e6b971_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481143_3c148d9dd3_s.jpg" width="75" height="75" alt="" /></li>
</ul>

In some location of the page, this script must be executed to invoke JCarousel to render the carousel when the page is loaded:

jQuery(document).ready(function() {
jQuery('#carouselId').jcarousel();
}

The jCarousel layout seems to be very clean based on plain HTML, this is not true, the initial layout is just to provide the list of images, this HTML code will be fully replaced (rendered).

ItsNat just can provide the required initial layout of the jCarousel component, because the final DOM rendered by jCarousel on the client is very different to the initial DOM. This final DOM is no longer in sync with the server, therefore the DOM in server used to set up jCarousel must be ignored otherwise we can break our ItsNat application. Yes we can integrate jCarousel but once rendered we cannot change the component for instance adding new images with ItsNat… unless we rebuild the component again.

The following example shows how we can use jCarousel as a client based extension of ItsNat in client but fully managed in server.

We are going to create an ItsNat based web application.

Create an empty web application with your preferred IDE, the context name is not relevant, in this example we will use "inexperiments".

Download ItsNat and follow the instructions of “What does a new ItsNat based web application need?” 

Download jCarousel  and decompress (v0.2.7 is the version used in this tutorial).

Create a public web folder with name "hybridcs" and copy the folder "jsor-jcarousel-XXXXXX" into, and renamed to "jcarousel". For instance, the file jquery.jcarousel.min.js must be public as:

http://localhost:8084/inexperiments/hybridcs/jcarousel/lib/jquery.jcarousel.min.js

The port number may be different.

To avoid remote image loading from Flickr (just for performance), copy the images linked in jCarousel examples: 


http://static.flickr.com/66/199481236_dc98b5abb3_s.jpg
http://static.flickr.com/75/199481072_b4a0d09597_s.jpg
http://static.flickr.com/57/199481087_33ae73a8de_s.jpg
http://static.flickr.com/77/199481108_4359e6b971_s.jpg
http://static.flickr.com/58/199481143_3c148d9dd3_s.jpg
http://static.flickr.com/72/199481203_ad4cdcf109_s.jpg
http://static.flickr.com/58/199481218_264ce20da0_s.jpg
http://static.flickr.com/69/199481255_fdfe885f87_s.jpg
http://static.flickr.com/60/199480111_87d4cb3e38_s.jpg
http://static.flickr.com/70/229228324_08223b70fa_s.jpg

To the public folder "hybridcs/img"

Create a new servlet with name "inexpservlet" (this name is just an example and is not mandatory) with your preferred IDE. Default source code and registration in web.xml is fine.

Replace with the following code:

import inexp.hybridcs.HybridCSLoadListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import org.itsnat.core.ItsNatServletConfig;
import org.itsnat.core.ItsNatServletContext;
import org.itsnat.core.http.HttpServletWrapper;
import org.itsnat.core.http.ItsNatHttpServlet;
import org.itsnat.core.tmpl.ItsNatDocumentTemplate;

public class inexpservlet extends HttpServletWrapper
{
public void init(ServletConfig config) throws ServletException
{
super.init(config);
ItsNatHttpServlet itsNatServlet = getItsNatHttpServlet();
ItsNatServletConfig itsNatConfig = itsNatServlet.getItsNatServletConfig();
ItsNatServletContext itsNatCtx = itsNatConfig.getItsNatServletContext();
itsNatCtx.setMaxOpenDocumentsBySession(5);
// To limit the memory of bots identified as legitimate browsers and abusive users
String pathPrefix = getServletContext().getRealPath("/") + "/WEB-INF/";
pathPrefix = pathPrefix + "hybridcs/pages/";
ItsNatDocumentTemplate docTemplate;
docTemplate = itsNatServlet.registerItsNatDocumentTemplate("hybridcs","text/html", pathPrefix + "hybridcs.xhtml");
docTemplate.addItsNatServletRequestListener(new HybridCSLoadListener());
}
}


Add the following pure HTML template (which is registered with name “hybridcs”):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Hybrid Client-Server Centric Programming</title>

<link href="hybridcs/jcarousel/style.css" rel="stylesheet" type="text/css" />
<!-- jQuery library -->
<script type="text/javascript" src="hybridcs/jcarousel/lib/jquery-1.4.2.min.js"></script>
<!-- jCarousel library -->
<script type="text/javascript" src="hybridcs/jcarousel/lib/jquery.jcarousel.min.js"></script>
<!-- jCarousel skin stylesheet -->
<link rel="stylesheet" type="text/css" href="hybridcs/jcarousel/skins/tango/skin.css" />
</head>
<body xmlns:itsnat="http://itsnat.org/itsnat" >

<h1>Hybrid Client-Server Centric Programming</h1>

<p>This example shows how <a href="http://www.itsnat.org">ItsNat</a> can cooperate with client
JavaScript libraries like <a href="http://jquery.com/" target="_blank">jQuery</a> and black-boxed JS components like <a href="http://sorgalla.com/jcarousel/" target="_blank">jCarousel</a>.
</p>

<br />
<span id="carouselContainerId" itsnat:nocache="true">
<ul id="carouselId" class="jcarousel-skin-tango">
<li><img src="hybridcs/img/199481236_dc98b5abb3_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481072_b4a0d09597_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481087_33ae73a8de_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481108_4359e6b971_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481143_3c148d9dd3_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481203_ad4cdcf109_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481218_264ce20da0_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199481255_fdfe885f87_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/199480111_87d4cb3e38_s.jpg" width="75" height="75" alt="" /></li>
<li><img src="hybridcs/img/229228324_08223b70fa_s.jpg" width="75" height="75" alt="" /></li>
</ul>
</span>

<br/>
<div itsnat:nocache="true">
URL of the new Image: <br/>
<input id="imgURLId" type="text" size="50" /> <br/>
<button id="addImageFirstId">Add to the Begining</button>
<button id="addImageLastId">Add to the End</button>
</div>

</body>
</html>


to the file "WEB-INF/hybridcs/pages/hybridcs.xhtml"

Create the Java file HybridCSLoadListener in package inexp.hybridcs:

package inexp.hybridcs;
import org.itsnat.core.ItsNatServletRequest;
import org.itsnat.core.ItsNatServletResponse;
import org.itsnat.core.event.ItsNatServletRequestListener;
import org.itsnat.core.html.ItsNatHTMLDocument;

public class HybridCSLoadListener implements ItsNatServletRequestListener
{
public HybridCSLoadListener() { }
public void processRequest(ItsNatServletRequest request, ItsNatServletResponse response)
{
new HybridCSDocument((ItsNatHTMLDocument)request.getItsNatDocument());
}
}

The method processRequest will be called whe the page is being loaded.

And the Java file HybridCSDocument in the same package:

package inexp.hybridcs;
import org.itsnat.comp.ItsNatComponentManager;
import org.itsnat.comp.text.ItsNatHTMLInputText;
import org.itsnat.core.domutil.ElementList;
import org.itsnat.core.domutil.ItsNatTreeWalker;
import org.itsnat.core.html.ItsNatHTMLDocument;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.html.HTMLDocument;
import org.w3c.dom.html.HTMLImageElement;

public class HybridCSDocument implements EventListener
{
protected ItsNatHTMLDocument itsNatDoc;
protected Element carouselContainerElem;
protected Element carouselElem;
protected Element itemPatternElem;
protected ItsNatHTMLInputText imgURLComp;
protected Element addImageFirstElem;
protected Element addImageLastElem;
protected DocumentFragment content;
protected int size;

public HybridCSDocument(ItsNatHTMLDocument itsNatDoc)
{
this.itsNatDoc = itsNatDoc;

HTMLDocument doc = itsNatDoc.getHTMLDocument();
this.carouselContainerElem = doc.getElementById("carouselContainerId");
this.carouselElem = doc.getElementById("carouselId");
this.itemPatternElem = ItsNatTreeWalker.getFirstChildElement(carouselElem);
this.content = (DocumentFragment)itsNatDoc.disconnectChildNodesFromClient(carouselContainerElem);

ItsNatComponentManager compMgr = itsNatDoc.getItsNatComponentManager();
this.imgURLComp = (ItsNatHTMLInputText)compMgr.createItsNatComponentById("imgURLId");
HTMLImageElement firstImgElem = (HTMLImageElement)itemPatternElem.getFirstChild();
imgURLComp.setText(firstImgElem.getSrc());

this.addImageFirstElem = doc.getElementById("addImageFirstId");
((EventTarget)addImageFirstElem).addEventListener("click", this, false);
this.addImageLastElem = doc.getElementById("addImageLastId");
((EventTarget)addImageLastElem).addEventListener("click", this, false);

ElementList list = itsNatDoc.getElementGroupManager().createElementList(carouselElem,false);
this.size = list.getLength();

StringBuilder code = new StringBuilder();
code.append("jQuery(document).ready(function() {");
code.append(" jQuery('#carouselId').jcarousel(); ");
code.append(" }); ");

itsNatDoc.addCodeToSend(code.toString());
}

public void handleEvent(Event evt)
{
EventTarget currTarget = evt.getCurrentTarget();
if (currTarget == addImageFirstElem || currTarget == addImageLastElem)
{
if (size == 30)
{
itsNatDoc.addCodeToSend("alert('Too many images');");
return;
}

size++;

int index;
// Pattern: <li><img src="image url" width="75" height="75" alt="" /></li>
Element newItemElem = (Element)itemPatternElem.cloneNode(true);
HTMLImageElement newImgElem = (HTMLImageElement)newItemElem.getFirstChild();
String newUrl = imgURLComp.getText();
newImgElem.setSrc(newUrl);
if (currTarget == addImageFirstElem)
{
carouselElem.insertBefore(newItemElem, carouselElem.getFirstChild());
index = 1;
}
else
{
carouselElem.appendChild(newItemElem);
index = size;
}
carouselContainerElem.appendChild(content);
this.content = (DocumentFragment)itsNatDoc.disconnectChildNodesFromClient(carouselContainerElem);

itsNatDoc.addCodeToSend("jQuery('#carouselId').jcarousel( { start:" + index + " });");
}
}
}

As you can see most of the code is just Java W3C DOM HTML and W3C DOM Events.

The key method is ItsNatDocument. disconnectChildNodesFromClient(Node), this method removes the content of the element only on the server, the client remains the same. So jCarousel can render the carousel with no problem.

Because we have saved the initial setup of the carousel, we can restore the removed nodes, just inserting the content again into the parent element of the DOM subtree being disconnected. Before inserting, ItsNat automatically clears the content in client, in this case the jCarousel component is fully removed, now new nodes will be also added to the client and again the client is in sync with server, then we perform the required changes to the initial layout (add a new image in the specified position) and jCarousel is called again to render the new component, in this case a new parameter is provided to scroll the component to the new image.

Fortunately jCarousel detects when a component is no longer in the page with no problem and can render new components beyond page load phase.

Deploy, run and open the example with this URL (port may different)

http://localhost:8084/inexperiments/inexpservlet?itsnat_doc_name=hybridcs

See it already deployed online in action.

Conclusion

This example has shown how we can fully manage a semi-black-boxed JavaScript component from server in a Single Page Interface environment with minimum custom JavaScript.

 

Published at DZone with permission of its author, Jose Maria Arranz.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Jose Maria Arranz replied on Wed, 2011/01/26 - 7:59am

Fixed a wrong import

Jose Maria Arranz replied on Wed, 2011/01/26 - 9:58am

Changed in HTML template:

jquery-1.2.3.pack.js => jquery-1.4.2.min.js

jquery.jcarousel.pack.js => jquery.jcarousel.min.js

Removed: jquery.jcarousel.css dependency

because the current version is different to the version used for the tutorial, now the last version is used and now it works fine in WebKit browsers (Chrome, Safari...).

 

Jose Maria Arranz replied on Tue, 2013/07/02 - 1:35pm

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.