Template specialization with Krakenjs Apps

By

Template Specialization is a mechanism to dynamically switch partials in your webpage at render time, based on the context information. Very common scenarios where you would need this is when you want different flavors of the same page for:

  • Country specific customization
  • A/B testing different designs
  • Adapting to various devices

Paypal runs into the above cases quite often. Most web applications in Paypal are using our open-sourced framework krakenjs on top of express/node.js. So, the mechanism for template specialization was very much inspired by the config driven approach in kraken apps. If you are not familiar with krakenjs, I’d recommend you take a quick peek at the krakenjs github repo and/or generate a kraken app to understand what I mean by ‘config driven approach’.  My example will be using dust templates to demonstrate the feature.

The main problems to solve were:

  1. A way to specify template maps for a set of context rules. I ended up writing a simple rule parsing module, Karka, which, when provided a json-based rule spec, and the context information at run-time, will resolve a partial to another when the rules match.
  2. A way to integrate the above mentioned rules into the page render workflow in the app, so that the view engine will give a chance to switch the partial in the page if a set of rules match.

After some experiments, I arrived at what I call the 3 step recipe to including specialization in the render workflow. The recipe should be applicable to any view engine (with or without kraken) in express applications (Check out my talk at JSConf 2014 on how to approach specialization without kraken)

  1. Intercept the render workflow by adding a wrapper for the view engine.
  2. Using karka (or any rule parser of your choice) generate the template map at request time using the context information, then stash it back into the context.
  3. Using the hook in your templating engine (which will let you know whenever it encounters partials and gives you an opportunity to do any customization before rendering it ), switch the template if a mapping is found.

If you are using kraken + dustjs, the above recipe has already been implemented and available ready-made for use. With some simple wiring you can see it working.

Lets see how to wire it up into an app using kraken@1.0 with the following super simple example.

  1. Generate a kraken 1.0 app (The example below uses generator-kraken@1.1.1)
  2. Add a simple karka rule spec into the file ‘config/specialization.json’ in the generated app.
{
     "whoami": [
         {
             "is": "jekyll",
             "when": {
                 "guy.is": "good",
                 "guy.looks": "respectable",
                 "guy.known.for": "philanthropy"
             }
         },
         {
             "is": "hyde",
             "when": {
                 "guy.is": "evil",
                 "guy.looks": "pre-human",
                 "guy.known.for": "violence"
             }
         }
     ]
}

Interpreting the rule spec:  ‘whoami’  will be replaced by ‘jekyll’  or ‘hyde’ when the corresponding ‘when’ clause is satisfied.

3. Wire the specialization rule-spec to be read by the view engine, by adding the following line into config/config.json file.

"specialization": "import:./specialization"

4. Change your ‘public/templates/index.dust’ to look like the following:

{>"layouts/master" /}

{<body}
 <h1>{@pre type="content" key="greeting"/}</h1>
 {>"whoami" /}
{/body}

Add  ‘public/templates/whoami.dust’ , ‘public/templates/jekyll.dust’ , ‘public/templates/hyde.dust’

{! This is whoami.dust !}
<div>
Who AM I ?????
</div>
{! This is jekyll.dust !}
<div>
 I am the good one
</div>
{! This is hyde.dust !}
<div>
 I am the evil one
</div>

5.  You want your request context to have the following to be able to see the specialization of ‘whoami’ to ‘jekyll’ :

{
    .....
    .....
    guy : {
        is: 'good',
        looks: 'respectable',
        known: {
            for: 'philanthropy'
        }
    }
    ..... 
    .....  
}

So lets try setting this context information in our sample app in  controllers/index.js.

 router.get('/', function (req, res) {
     model.guy = {
         is: 'good',
         looks: 'respectable',
         known: {
             for: 'philanthropy'
         }
     };
     res.render('index', model);
 });

What I am doing above is leveraging the model that I pass to express ‘res.render’ to set the context information to see specialization for jekyll.dust. Internally express merges res.locals and the model data to pass to the view engine. So you can instead set the values in ‘res.locals’ as well. You can also try to set the rules for ‘hyde’ in model.guy above to see specialization to hyde.dust.

Now you are ready to see it working. Open a terminal window and start the app.

$cd path/to/app
$node .

Hit ‘http://localhost:8000’ on your browser and you will see the specialized partial, per the context information. Here is the sample I created, with the exact same steps above.

The above example does not demonstrate more practical things like styling partials differently, or specializing while doing a client side render. A more comprehensive example here.

PayPal products span multiple devices and hundreds of locales and hence specialization could be the way to solve customization requirements in the views cleanly.

Poornima Venkatakrishnan

Poornima is a web/mobile developer at heart and loves playing with new technologies, solving difficult problems and working on game changing projects. When not coding, she is a trained indian classical dancer and, most importantly, a proud mom of a toddler.