Specialization-example
An example kraken 1.0 app demonstrating template specialization.
What is Template Specialization ?
Template specialization is a way to dynamically switch partials in your views, to some other partial, based on some rules that you can specify in the form of a json config in your app.
Why would you want to do this ?
This may become important and very handy when:
- you are writing apps that need to be supported for multiple locales and parts of it can look different in different countries/regions
- you want part of your views to look completely different across various devices (an alternate, more flexible solution to adaptive/responsive designs)
- you want to A/B test ..... Or any other creative way you'd like to use it.
Installing this demo
Clone, install and run.
git clone git@github.com:krakenjs/kraken-example-with-specialization.git
cd kraken-examples-with-specialization
npm install
npm start
Explore the app
Visit http://localhost:8000
How to setup the app with i18n from scratch by using generator-kraken ?
Create a simple scaffolded app using generator-kraken
- Install Generator
$ npm install -g generator-kraken
- Create an app using the generator
$ yo kraken
,'""`.
hh / _ _ \
|(@)(@)| Release the Kraken!
) __ (
/,'))((`.\
(( (( )) ))
`\ `)(' /'
Tell me a bit about your application:
[?] Name: foo
[?] Description: bar
[?] Author: foobar
[?] Template library? Dust
[?] CSS preprocessor library? LESS
[?] JavaScript library? None
Adding specialization rules to your app
The rules for specializing partials in your views can be written in the form of a json config using the module karka. For the purposes of our demo:
- Add a
config/specialization.json
with the following content and copy the corresponding templates from this project in pathpublic/templates
{
"yinyang": [
{
"is": "yin",
"when": {
"energy.is": "female",
"orientation.is": "moon"
}
},
{
"is": "yang",
"when": {
"energy.is": "male",
"orientation.is": "sun"
}
}
],
"nested/yinyang": [
{
"is": "nested/yin",
"when": {
"energy.is": "female",
"orientation.is": "moon"
}
},
{
"is": "nested/yang",
"when": {
"energy.is": "male",
"orientation.is": "sun"
}
}
],
"nested/peace": [
{
"is": "nested/peace-yin",
"when": {
"energy.is": "female",
"orientation.is": "moon"
}
},
{
"is": "nested/peace-yang",
"when": {
"energy.is": "male",
"orientation.is": "sun"
}
}
]
}
What above config means is for eg: partial yingyang
will be replaced with partial yin
when the context satisfies the rules
{
"energy.is": "female",
"orientation.is": "moon"
}
Similar syntax applies to the rest of the config. To learn more about semantics of the config and other options available, please be sure to read docs here.
Including specialization into the render work flow
view engines
in config/config.json
to use makara@2
Modify "view engines": {
"dust": {
"module": "makara",
"renderer": {
"method": "js",
"arguments": [
{ "cache": true }
]
}
}
}
makara
middleware with the specialization configuration in config/config.json
Add the In the middleware
section, add:
"makara": {
"priority": 100,
"enabled": true,
"module": {
"name": "makara",
"arguments": [ {
"i18n": "config:i18n",
"specialization": "import:./specialization.json"
} ]
}
},
view engines
in config/development.json
to use makara@2
Modify Notice the difference in caching.
"view engines": {
"dust": {
"module": "makara",
"renderer": {
"method": "dust",
"arguments": [
{ "cache": false }
]
}
}
}
Set the context information before res.render
This can be done in two ways: Setting context info into res.locals (or) in the model passed to res.render(view, model) We will demo both in our test.
- Add a separate route to set the context rules at run time, so to
routes.js
add the following
router.get('/setSpcl/:type', function(req, res) {
req.session.type = req.params.type;
res.redirect('/');
});
- Add
lib/specialization.js
and add the following snippet of code
'use strict';
module.exports = function () {
return function (req, res, next) {
//Sample of setting context in res.locals
var energy;
//sample of setting context in the model
switch(req.session.type) {
case 'yin':
energy = 'female';
break;
case 'yang':
energy = 'male';
break;
}
res.locals.energy = {
is: energy
};
next();
};
};
- Add it to the middleware chain to your express app by inserting the following in
middleware
property in yourconfig/config.json
"spclContext": {
"enabled": true,
"priority": 105,
"module": {
"name": "path:./lib/specialization"
}
}
- Also in your
routes.js
modify the/
route controller to do the following
router.get('/', function (req, res) {
var orientation;
var model = new IndexModel();
//sample of setting context in the model
switch(req.session.type) {
case 'yin':
orientation = 'moon';
break;
case 'yang':
orientation = 'sun';
break;
}
model.orientation = {
is: orientation
};
res.render('index', model);
});
To see specialization on server-side render
- In your console
$ node .
- In your browser:
http://localhost:8000
- In your browser
http://localhost:8000/setSpcl/yin
(or)
http://localhost:8000/setSpcl/yang
(or)
http://localhost:8000/setSpcl/yinyang
You will see that the specialization rules will be set in the session and you will be redirected to the index page with the right specialization rules.
To see specialization on client-side render
- Change public/app.js with following:
require(['config'], function() {
require(['jquery', 'nougat'], function ($, nougat) {
console.info('required jquery and nougat');
var app = {
initialize: function () {
console.info('intialized view');
nougat.setContext($(document.body).data());
//Demonstrating specialization for
//client side rendering.
this.initializeView();
},
initializeView: function () {
$('#more').click(function () {
console.info('clicked');
nougat.viewRenderer
.render('nested/yinyang', {message: 'More Info'})
.done(function (content) {
$('#moreInfo').html(content);
});
});
}
};
app.initialize();
});
});
The above code:
- Requires/loads the javascript necessary to perform a client side render.
- Sets up a button listener to trigger the client side render.
The file
public/js/lib/nougat.js
is the client side render helper for dust.
- Set the templatePath according to locale so that you can find the compiled templates in the right path when required from the browser. In the demo case we hard code it to
US/en
. But this can be done in conjunction with i18n for right locale. Inconfig.json
"templatePath": {
"enabled":true,
"priority": 96,
"module": {
"name": "path:./lib/templatePath"
}
}
In lib/templatePath
'use strict';
module.exports = function () {
return function (req, res, next) {
//Sample of setting context in res.locals
res.locals.templatePath = 'templates/US/en';
next();
};
};
- Modify
templates/layouts/master.dust
to include the specialization map and the templatePath in a data-attribute
<body data-specialization='{_specialization|js|s}' data-template-path="{templatePath}/%s.js">
- Add dependency files (copy from
public/js/lib
in this project) and add the file frompublic/config.js
to your project as well.
Now repeat steps 1,2,3 from previous section and then click on the 'Tell Me More' button. You will see that templates/styles are different for different specializations.
You can play with the specialization rules in the config + what you set in the context to see how dust partials gets specialized.
Have Fun!!