Last update: 2011, Mar. 11
Introduction
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 Single Page Interface, because in this paradigm
page fragments are replaced on demand usually loading new components.
Reconciling 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>
<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>
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.pack.js must be public as:
http://localhost:8084/inexperiments/hybridcs/jcarousel/lib/jquery.jcarousel.pack.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);
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 />
<div id="carouselContainerId" itsnat:nocache="true"> style="height:130px">
<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>
</div>
<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>
<br />
<a href="index.jsp">RETURN</a>
</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;
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.