Gerd has posted 5 posts at DZone. View Full User Profile

Clean Up Your JavaScript with ztemplates and jQuery

01.04.2012
| 1594 views |
  • submit to reddit

When writing webapps that make heavy use of javascript it gets hard to keep track of the javascript used, especially when the webapp loads content dynamically like in AJAX applications. The problem is that every container page has to be aware of the javascript needed by all the dynamcally loaded component markup.

The problems are:

  • Which javascript must be loaded
  • How to prevent duplicate variable assignments 
  • dependencies between javascript libraries

In such cases a framework for dynamic javascript loading comes handy, because it relieves the container from the need to know what script is needed in the component. The component declares the needed script and is called after the script is available.

This article describes how to write dynamic javascript enabled webapps with the ztemplates java webframework. Beginning with version 2.3.0 ztemplates includes a small javascript library named zscript that makes dynamic JavaScript loading easy. zscript is fully integrated into ztemplates.

See Dynamic Javascript Loading with zscript and jquery published on dzone for a short introduction to zscript.

The library uses jquery, so make sure to include your preferred version.

To use add the following lines to your html head section: 

  <head>
    <script type="text/javascript" src="jquery-x.x.x.js"></script>
    <script type="text/javascript" src="${contextPath}/ztemplates/zscript.js"></script>
    <script type="text/javascript" src="${contextPath}/ztemplates/zscript-definitions.js"></script>
  </head> 


Now for each reusable piece of javascript functionality you want to make available to your webapp add a annotated class and a template containing the javascript. The annotation ensures that ztemplates can find the javascript. ztemplates makes the script available to the zscript library and adds it to zscript-definitions.js

Define a java class and annotate it with @ZScriptDefinition. Provide a name to the annotation. The name must match the name of the variable defined in the javascript snippet. The class-name does not matter, but You may find it useful to name your script classes ZScript_name so you can easily find them.

For example to define a javascript variable (keeping a service object) named 'user' create a java class.

 

@ZRenderer(ZVelocityRenderer.class)
//this defines a javascript object called 'user'
@ZScriptDefinition("user")
public class ZScript_user
{
}

and a javascript template in ZScript_user.vm at the same location as the class:

if(typeof user=='undefined') {
var user = function() {
    //private area
    var loggedIn;

    function isLoggedIn() {
        return loggedIn;
    }
    
    function setLoggedIn(loggedInParam) {
        loggedIn = loggedInParam;
    }
   
   //public area contains methods that can be used from outside
    return {
        isLoggedIn: function () {
            return isLoggedIn();
        },
        setLoggedIn: function (loggedIn) {
            setLoggedIn(loggedIn);
        }
    };
}();
}

This code calls a method and assigns the return value to a variable called user. This happens only if the variable has not already been created. The return value is a collection of functions that are available for calling. The other functions are private.


To use the javascript from your html page write this to your javascript tag:

<script>
zscript.requires(['user'], function(){
   user.setLoggedIn(true);
   if(user.isLoggedIn()){
     alert('logged in!');
   }
});

zscript.requires(['mylib'], function(){
   mylib.doSomething();
});

</script>

This states that the javascript library 'user' is required in the callback body, so pass the name as first parameter to the requires method. The second parameter is a callback that will be called as soon as the 'user' library is available (which could also be immediately if the library has already been loaded).

If the code contains more than one call to zscript.requires() the order in which the callbacks are called is preserved. In the example the callback for 'user' is always called before the callback for 'mylib'.


You may define other javascripts like this:

 

zscript.define('mylib', '/js/mylib.js'); //map the name 'mylib' to the url

ztemplates will ensure the javascript is loaded whenever you use it in a zscript.requires(['mylib'], function(){}) call.

Be aware of the cross-domain restrictions placed upon the locations of your scripts, so best load them from the same server as your html.

Because there is no defined order in which the scripts are loaded at runtime the libraries should not contain logic that references other dynamically loaded libraries:

var user = function() {
    var loggedIn;

    //Declare all the libraries used by this library here, will be executed before requires callbacks
    zscript.requiresInScript(['dialog', 'mylib' ]);
  
    //wrong, mylib may not be loaded
    mylib.doSomething();

    function isLoggedIn() {
                //OK, because dependency has been declared above and call is in function                 mylib.doSomething();
        return loggedIn;
    }
    
    function setLoggedIn(loggedInParam) {
                //OK, as dependency has been declared above.
                dialog.show('Something');
        loggedIn = loggedInParam;
    }    
    
    return {
        isLoggedIn: function () {
            return isLoggedIn();
        },
        setLoggedIn: function (loggedIn) {
            return setLoggedIn(loggedIn);
        }
    };
}();


Note the if(typeof user=='undefined') condition that ensures that the variable is created only once.

Using this pattern you get a clean separation between java, javascript and html and don't have to worry about script tags in your html header markup or duplicate instantiations of variables. All needed javascript is loaded on demand, or if you don't want that you can instruct ztemplates to collect all javascript in zscript-definitions.js

Weblinks:

Published at DZone with permission of its author, Gerd Ziegler.

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