Jan 12, 2008

Adding helpers to JST

  1. Introduction
  2. Creating helpers
  3. Helpers and templating automation
    1. Declare the namespaces
    2. Parser wrapper
    3. templateObject overlay
    4. Helpers definition
  4. Conclusion

As I said in this article, I recently used JST. After playing around with it for 2 or 3 days, I came up with the conclusion that an abstraction layer is needed, except if you want to rewrite the whole code every time !

Table of content

JST documentation about the templateObject.process function says :

Note that the '''contextObject''' can contain any JavaScript object, including strings, numbers, date, objects and functions. So, calling ${groupCalender(new Date())} would call contextObject.groupCalender(new Date()). Of course, you would have to supply the groupCalender() function, which should return a string value.

So, we've got a way to define our helpers !
Let's try it :

// As usual our namespace
var App = App || {};

/**
 * a little binding utility relying on closure.
 * 
 * This function can be improved by handling the 
 * merge of given parameters on binding and given 
 * parameters on call so that :
 * <pre>
 *   var myBindedFunc = App.bind( myFunc, myContext, param1, param2 );
 *   myBindedFunc ( param3 );
 * <pre>
 * results being the same as :
 * <pre>
 *   var myBindedFunc = App.bind( myFunc, myContext );
 *   myBindedFunc ( param1, param2, param3 );
 * <pre>
 * or, using the call utility :
 * <pre>
 *   myFunc.call( myContext, param1, param2, param3 );
 * <pre>
 *
 * @param {Function} afCallback function to apply
 * @param {Object}   aoContext the context to apply the function in
 *
 * @return {Function}
 */
App.bind = function( afCallback, aoContext  ){
  /* make a copy */
  var _fCallback = afCallback;
  var _oContext = aoContext;

  /* create the closure */
  return function(){
    return _fCallback.apply(_oContext, arguments);
  };
}

//let's define our helpers
App.Helpers = {
  /**
   * used as an helper, writes 'foo'
   */
  test:function(){
     return 'foo';
  },
  /**
   * increments a value, to demonstrate 
   * the binding necessity for helpers
   * that needs to access main context
   */
  count:App.bind(function(){
    App._counter = App._counter || 0;
    return App._counter++;
  },window)
};



/* parse and process */
function parse_and_process(){
  var dest = document.getElementById('jst_add_helpers_template_test_1_dest');
  var tpl = document.getElementById('jst_add_helpers_template_test_1_template');
  dest.innerHTML = TrimPath.parseTemplate( tpl.innerHTML).process( App.Helpers );
}

Ready to test ?

Give it a try !

Here will come the processed template

So, It seems that works, but why the hell did I use a binding ?
Well, it's all about contexts, the template is executed in process function's first parameter context. That means you are enclosed in this context, so you can't access the other variables ( such as App in the count helper's case ).

So, whenever we need to create an helper that has to access other namespaces, it is necessary to bind it to needed context. Application field of such a technique can be:

  • Internationalization (to access datas that are not presents)
  • add custom events
  • register or modify variables
  • ...

Well, That's cool, we've done with helpers definition understanding, but it can be exhausting to select helpers every-time you call a template, let's see how to make it simple.

Table of content

As usual, automation means a new layer. We will need as usual a main namespace, and following sub-namespace.

App
What a surprise ! Just the same as usual :-). Well, the main namespace
App.Templates
The template engine wrapper namespace
App.Templates.Helpers
the templates helpers namespace
App.Templates.templateObject
Our templateObject overlay

Table of content

The parser wrapper needs first to keep a trace of our parsed templates, so let's have a storage facility :

/* namespace */
var App;
App.Templates = App.Templates || {};

/* utilities */
/**
 * copy all the properties from aoSource to aoDestination
 * @param {Object} aoDestination
 * @param {Object} aoSource
 * @return {Object}
 */
App.extend = function(aoDestination, aoSource){
 for (var property in aoSource) 
          aoDestination[property] = aoSource[property];
 return aoDestination;
};

/* storage utilities */
App.Templates.stored = {};
/**
 * stores a template
 * 
 * @param {String} asTemplate the template name
 * @param {OGF.Templates.templateObject} asTemplate the parsed template
 * 
 * @return {OGF.Templates.templateObject}
 */
App.Templates.store = function( asTemplateName, aoTemplate ){
 return App.Templates.stored[asTemplateName] = aoTemplate;
};
/**
 * reads a template
 * 
 * @param {String} asTemplate the template name
 * 
 * @return {OGF.Templates.templateObject}
 */
App.Templates.find = function( asTemplateName ){
 return App.Templates.stored[asTemplateName];
};

Easy isn't it ? Now the parsing layer :

/* parser */
/**
 * parses a template
 * this function is a wrapper for the template.
 * 
 * @param {String} asTemplate the template string to parse
 * 
 * @return {OGF.Templates.templateObject}
 */
App.Templates.parse = function( asTemplate ){
 return new App.Templates.templateObject( TrimPath.parseTemplate(asTemplate) );
};
/**
 * parses a template and store it.
 * This function main goal is to be binded on the request return.
 * 
 * @param {String} asTemplate the template string to parse
 * 
 * @return {OGF.Templates.templateObject}
 */
App.Templates.parseAndStore = function( asName, asTemplate ){
    App.Template.store(
   asName, 
   App.Templates.parse( asTemplate )
 );
};

Well, you maybe noticed that we used an undeclared class called App.Templates.templateObject. Let's declare it.

Table of content

This class is our abstraction layer over the TrimPath template object.
It just adds our helpers definition to the evaluation context.

/**
 * @constructor
 * @classDescription the template object wrapper for OGF
 * @param {templateObject} aoTemplate the template object
 */
App.Templates.templateObject = function(aoTemplate){
 this.initialize(aoTemplate);
}
App.Templates.templateObject.prototype = {
 /**
  * the template object reference
  * @var {templateObject}
  */
 _template:null,
 /**
  * initializes the object
  * @param {templateObject} aoTemplate the template object 
  */
 initialize:function(aoTemplate){
  this._template = aoTemplate;
 },
 process:function( aoDatas ){
  var _oDatas = {};
  if(App.Templates.Helpers)
  {
   _oDatas = App.extend( _oDatas, App.Templates.Helpers);
  }
  
  _oDatas = App.extend( _oDatas, aoDatas || {});
  return this._template.process(_oDatas);
 }
};

You maybe noticed I deported the initialization function to one in the prototype, it's a simple trick to be able to port it later on your favorite class implementation (such as Dean Edwards' base / base2, prototype class implementation or any other )

Table of content

Well, to define helpers you just need to add them to the App.Templates.Helpers namespace. Here are some of mine :

App.Templates.Helpers = {
  /**
   * include an already compiled template
   *
   * @param {Object} asTplName
   * @param {Object} context
   */
  include:(function( asTplName, aoContext){
    return App.Templates.find(asTplName).process(aoContext);
  }).bind(window),
  /**
   * write a tag opening with the given tagname and the given properties
   * example :
   * ${%open_tag('div', {id:"foo",class:"bar",style:{width:'50%',background:'#000',color:'#fff'}})%}
   * I'm currently looking for a solution to make it more readable
   */
  open_tag:function( asTagName, aoProperties ){
    var _oProperties = aoProperties || {};
    var _aProperties = [asTagName];
 
 for( var _i in _oProperties)
 {
   var _val = '';
   if(typeof(_oProperties[_i]) == 'object')
   {
     _val = [];
     for(var _j in _oProperties[_i] )
  {
     _val.push(_j+':'+_oProperties[_i][_j]);
  }
  _val = _val.join(';');
   }
   else
   {
     _val = _oProperties[_i];
   }
   
   _aProperties.push(_i+'="'+_val+'"');
 }

    return '<'+_aProperties.join(" ")+'>';
  }
}

To conclude, I'll say that I use this for the binding when I include libraries into a closure (As I do for every code in this blog) to avoid polluting the main namespace with utilities functions or prototype extensions (just as used in the JST code with the array prototype for IE5 bugfix)

Table of content

Some improvements can be done, such as utilities functions to register new helpers or a function to update the element with template result (and optionally evaluate scripts).
You can also allow some options to determines which modifiers to use, to auto-load them...

Table of content

Jan 8, 2008

Extend the elements

  1. Introduction
  2. How to extend the elements : available possibilities
    1. Prototype extension
    2. Extension when selecting
    3. Create a wrapper class
  3. Conclusion

It can sometimes be hard to make cross platform applications because of the different implementations. Attaching an event on a object can become hell if you don't use an abstraction layer. First of all, let's see the normal cross-browser approach :

function myEventFunc ( aeEvent )
{
  // just get the event if we are using IE
  var _eEvent = aeEvent || window.event ;
 
  // now do what we want to do
  /* ... */
}

/* now attach this function on the elements */
if( document.getElementById )
{
 var _eElt = document.getElementById ('myElement');
 if( _eElt.addEventListener ) // W3C (Gecko, webkit, Opera)
 {
   _eElt.addEventListener( "myEventName", myEventFunc, false );
 }
 else if( _eElt.attachEvent ) // MSIE
 {
    _eElt.attachEvent ( "myEventName", myEventFunc, false );
 }
 else
 {
   throw new Error('Your browser is very old ! please upgrade.');
 }
}
else
{
 throw new Error('Your browser is very old ! please upgrade.');
}

Well, let's see what solutions we have to avoid such a big amount of code.

Table of content

Every library used a way to get rid of such compatibility problems, and are using a single interface to do stuff.

First thing that comes to mind to add new behavior to an existing class is the prototype extension. Even if it can pose problems sometimes (such as for arrays), it can be a nice way to achieve our goal. The HTML elements class is supposed to be HTMLElement, so extending the HTMLElement.prototype should do the trick.

If quite every browser allows this extension, Internet Explorer doesn't like to make web developer's life easy, so this can do the stuff in quite every case, except for the major browser.

You can find more informations and workaround at following locations :

Table of content

As the prototype extension isn't supported natively for every browser, let's see another way. We still need to extend elements, so when will be the perfect time ? When using them of course. Which process is always used before using an HTMLElement ? selection !

To extend elements we'll need a namespace with all our functions and a selection function. Let's have a piece of code to illustrate :

/**
* the application namespace
*/
var App = {};
/**
* the element functions namespace
*/
App.HTMLElement = {
   addEvent: function(aElement, asEvent, acCallback, abBubbles){
     var _bBubbles = abBubbles || false;
     if(aElement.addEventListener ) // W3C (Gecko, webkit, Opera)
     {
       aElement.addEventListener( asEvent, acCallback, _bBubbles );
     }
     else if( aElement.attachEvent ) // MSIE
     {
        aElement.attachEvent ( 'on'+asEvent, acCallback, _bBubbles );
     }
     else
     {
       throw new Error('Your browser is very old ! please upgrade.');
     }
   }
};
/**
* our simple selector
* @param {String} asElementId the id of the element
* @return {HTMLElement}
*/
App.get = function(asElementId){
   var _eElement = document.getElementById(asElementId);
   if( ! _eElement.extended )
   {
     for(var _i in App.HTMLElement)
     {
       _eElement[_i] = (function(aMethod,eElement){
          var _method = aMethod;
          var _elt = eElement;
          return function(){
             /* transforms the arguments into an array */
             var _args = [_elt];
             for(var _j in arguments)
             {
                _args.push( arguments[ _j ] );
             }
             /* forces the context */
             return _method.apply( _elt, _args );
          }
       })(App.HTMLElement[_i], _eElement);
     }
      _eElement.extended = true;
   }
   return _eElement;
};

Well, this piece of code should work, let's try it :

Pass the mouse over me.

Here is the code I used to set this behavior up

 
/**
 * load event to initialize the div
 */
function extends_elts_test1_init(){
  App.get('extend_elts_test_1').addEvent('mouseout', extends_elts_test1_out).addEvent('mouseover',extends_elts_test1_over);
}
/**
 * function attached to the mouseout event of the element
 */
function extends_elts_test1_out(){
  App.get('extend_elts_test_1').style.backgroundColor = "#ff0";
}
/**
 * function attached to the mouseover event of the element
 */
function extends_elts_test1_over(){
  App.get('extend_elts_test_1').style.backgroundColor = "#0ff";
}

/* now register the load event */
if(window.addEventListener)
{
  window.addEventListener('load',extends_elts_test1_init,false);
}
else if(window.attachEvent)
{
  window.attachEvent('onload',extends_elts_test1_init,false);
}

To conclude on the elements extension, let's say that's prototype's way of doing. You can improve this code by mixing it with the HTMLElement.prototype extension. Just call the namespace HTMLElement, declare it as a new object and extend the prototype if it isn't natively done.

Well, this method is nice, but making a closure for every function every time an object is extended seems to be a bit a heavy way isn't it ? let's see what else can be done.

Table of content

Previous approach was based on selection, let's keep it, but this time we'll build a complete wrapper around it, a wrapper that references our element and implements our new methods.

/**
 * our namespace, as usual
 */
var App = {};
/**
 * the elements methods
 */
App.Element = function( anElement ){
  this._element = anElement ;
};
App.Element.prototype = {
  /**
   * the element instance
   * @var {HTMLElement}
   */
  _element : null,
  /**
   * cross-platform event observer
   * @param {String}   asEvent    the event name
   * @param {Function} acCallback the callback
   * @param {Boolean}  abBubbles  the bubbling flag [optionnal]
   *
   * @return {App2.Element}
   */
  addEvent: function( asEvent, acCallback, abBubbles){
     var _bBubbles = abBubbles || false;
     if(this._element.addEventListener ) // W3C (Gecko, webkit, Opera)
     {
       this._element.addEventListener( asEvent, acCallback, _bBubbles );
     }
     else if( this._element.attachEvent ) // MSIE
     {
        this._element.attachEvent ( 'on'+asEvent, acCallback, _bBubbles );
     }
     else
     {
       throw new Error('Your browser is very old ! please upgrade.');
     }
     return this;
  },
  /** 
   * set a style property. needed for the demo.
   * @param {String} asProperty the property name
   * @param {String} asValue    the new property value
   *
   * @return {App2.Element}
   */
  setStyle:function( asProperty, asValue ){
    this._element.style[asProperty] = asValue ;
    return this;
  }
};
/**
 * once again the selection function
 * @param {String} asElementId the element to select id
 * @return {App.Element}
 */
App.get = function( asElementId ){
  var _Element = document.getElementById( asElementId );
  return new App.Element( _Element );
}

Want a try ? let's go using quite the same code as earlier, except for the style property setting, I used the newly created wrapper.

Pass the mouse over me.

This way is cleaner, but needs more code to be done, because you have to implement every method, none are given by default !
This approach is the one chose by Ext framework, and seems to be the fastest at execution time ( I'm still impressed with the result they achieved with this framework ).

Table of content

Any method you choose, first improvement will be to add a method for registering new "native" methods. Whatever, except if you are embarrassed with some library functionalities (such as prototype Array extension), or if application is so specific that no library fit your needs, it is always better to rely on existing maintained code.

Table of content

Jan 7, 2008

Javascript template engine : why and which ?

  1. Introduction
  2. State of art
  3. The choice
  4. Playing around

Hi there, as I needed a template engine for one of my customer, I looked around to find the one that was the best fitting my needs. My requirements where the following :

  • Cross-browser
  • Easy to learn
  • Allow easy internationalization
  • Library independent
  • Well documented and maintained
  • Possibility to add custom helpers and modifiers
  • Process relies on a compilation (to avoid multiple compilation)

Well, let's see what's on the place.

Table of content

Several client side templates engines are available, here are some that kept my attention :

Table of content

I finally decided to use JST that seemed to be the one that best fits my needs. MModifiers are simple to declare and to use for variables modification (such as upper-case or html sanitize-ing), and macros seems to be the way to declare helpers.
For the Internationalization, as I am using JSON datas it seems to be a simple workaround that can be achieved using a little layer aver the templateObject.

An other point that kept my attention is that syntax is most likely the same as the Javascript one, and that's a good point not to have to learn a new syntax.
Last but not least, it is possible to use inline JavaScript statements.

Table of content

Here comes the interesting part, it's time for tests ;-)
I'll keep you in touch with further posts including some of my code, but for now, I'll just give the documentation link

Table of content