Better templates from the
ground up
with Node.js
Garann Means : @garannm
Slide 2
Slide 2 text
you need templates
Slide 3
Slide 3 text
you need a template
strategy
Slide 4
Slide 4 text
assumption: html
doesn’t belong in your js
Slide 5
Slide 5 text
how come:
it has to be escaped
it’s less modular
string or array concatenation is easy to
mess up
it makes it difficult to actually see
your HTML source
Slide 6
Slide 6 text
but you want to avoid
manipulating the dom
Slide 7
Slide 7 text
how come:
poor performance
fragile code
depends on hooks
depends on hierarchy
again, HTML and JS are tightly coupled
Slide 8
Slide 8 text
you can’t avoid the dom
when wiring up events
when accepting user input
when reflecting changes
Slide 9
Slide 9 text
so save up your dom
allowance
Slide 10
Slide 10 text
templates are useless
without data
Slide 11
Slide 11 text
it can come from anywhere
proper data models
state settings in object literals
transformed user input
notifications via XHR or web sockets to
display
even first render from the server
Slide 12
Slide 12 text
assumption: all data
needs transforming
Slide 13
Slide 13 text
for instance:
making things plural
currency and date formats
user names and pronouns
subtotaling and other arithmetic
differences for boolean states
language translation
Slide 14
Slide 14 text
that stuff is not
application logic
it’s part of the display
Slide 15
Slide 15 text
assumption: js doesn’t
belong in your html
Slide 16
Slide 16 text
getting around that
helpers and filters in templates
classes and data attributes for
behaviors
even better: modular templates
add helpers to data properties before
render
add behaviors to elements post-render
Slide 17
Slide 17 text
simple template
{{=it.title}}
{{=it.artist.name}}
{{=priceFormat(it.price)}}
{{~it.info :info_item}}
{{=info_item}}
{{~}}
Slide 18
Slide 18 text
uses
rendering some data from an XHR on the
client
rendering a whole “page” or state in a
SPA
rendering a page or view on the server
Slide 19
Slide 19 text
saved and loaded
has no meta info about how it’s used -
it’s up to the app to know
in node: have to be sure i/o is complete
before page is delivered
on client: use XHR (on init) or AMD (in
view module)
might be a partial
needs to be registered on both client
and server
Slide 20
Slide 20 text
an object that feeds it
{
id: 101,
title: "Las Dos Fridas",
artist: {
name: "Frida Kahlo"
},
price: 59.99,
info: [
'11" x 14"',
"acid-free paper",
"suitable for matting",
"limited edition"
]
}
rendering
function loadDetail( id ) {
if ( id.data ) {
id = id.data.id;
}
$.get( "detail/" + id, function( info ) {
$( "div.detail" ).html( detailTmpl( info ) );
});
}
Slide 23
Slide 23 text
some data transformation
function priceFormat( price ) {
price = price % 1 == 0 ? price + “.00” : price;
return "$" + price;
}
Slide 24
Slide 24 text
that’s pretty easy
Slide 25
Slide 25 text
on the server
// using Express and Consolidate
app.get( "/product/:id", function( req, res ) {
res.render( "detail", products[ req.params.id ] );
});
Slide 26
Slide 26 text
problem!
no globals in Node
ReferenceError: priceFormat is not
defined
helpers need to be registered
Slide 27
Slide 27 text
templates increase in
difficulty pretty quick
Slide 28
Slide 28 text
framework templates
back to easy, right?
templates may provide data binding
templates may provide behaviors
if you want to do something differently,
you’ll need to think about all the parts
Slide 29
Slide 29 text
templates glue the
whole display together
Slide 30
Slide 30 text
node.js can help
Slide 31
Slide 31 text
manual labor
Slide 32
Slide 32 text
template "“”builds"“”
loaded as text from an element or file
run through a compile function
stored as a function
calling function with data produces
hydrated markup
Slide 33
Slide 33 text
precompiling
no need to fetch template text
compiled function is just JS
can be concatenated onto the rest of
your JS
can be treated as a module if you
provide a wrapper
Slide 34
Slide 34 text
what does that have to
do with node?
Slide 35
Slide 35 text
it’s just easier
get your template engine from npm
template logic is in JS
objects and dot notation work like JS
truthy and falsey work like JS
wrappers and helpers are in the same
language
Slide 36
Slide 36 text
node is excellent for
building js
Slide 37
Slide 37 text
but why let the client have
all the fun
Slide 38
Slide 38 text
shared templates save
you work
Slide 39
Slide 39 text
*you don’t need node
zillions of flavors of Mustache
parsers for lots of other popular
template engines
client-side versions of some server-side
templating frameworks
Slide 40
Slide 40 text
but it makes sense
templates themselves are easy
template architecture is hard
you want to share more than just the
template
Slide 41
Slide 41 text
twice the app with half
the material
Slide 42
Slide 42 text
not just Single Page Apps
providing fully rendered pages from the
server
sharing states
sharing data transformation and helpers
not using two different template
languages, which will drive you mad
Slide 43
Slide 43 text
partials are your best
frenemy
Slide 44
Slide 44 text
using a partial
Modern Art
{{~it.results :r}}
{{#def.result_detail}}
{{~}}
Slide 45
Slide 45 text
the partial itself
{{=r.title}}
{{=r.artist.name}}
{{=r.price}}
Slide 46
Slide 46 text
the good
can share chrome around a variety of
displays
templates can be as granular as you want
don’t need giant conditional blocks
re-render only the pieces of the page
that change
Slide 47
Slide 47 text
the bad
need to be available and registered
before containing templates
have to be compiled before solo use
a template isn’t usually a module
so it can’t define dependencies
on the server
// throw Express easy rendering out the window
// can’t use external partials with Consolidate
// have to write it from scratch
// so.. looks basically the same as the client
Slide 50
Slide 50 text
we can make this work
better.
Slide 51
Slide 51 text
composable templates
modules that manage their dependencies
a place to map helpers and behaviors to
properties and elements
template code abstracted out of
application logic
something that works on both the client
and server
what do we want?
Slide 52
Slide 52 text
how do we get it?
Slide 53
Slide 53 text
we write it ourselves*
// Tmpl.js
define( [ "require", "doT" ],
function ( require, doT ) {
function Tmpl() {
return this;
}
return Tmpl;
});
Slide 54
Slide 54 text
should be simple to use
// results.js
define( [ "Tmpl", "resultDetail" ],
function( Tmpl, resultDetail ) {
this = new Tmpl();
this.name = "results";
this.el = "#contents";
this.partials = { detail: resultDetail };
this.init();
return this;
});
Slide 55
Slide 55 text
and to use recursively
// result_detail.js
define( [ "Tmpl", “utils” ], function( Tmpl, utils ) {
this = new Tmpl();
this.name = "result_detail";
this.config = { varname: “r” };
this.el = "#results";
this.helpers = { “price”: utils.priceFormat };
this.init();
return this;
});
Slide 56
Slide 56 text
do ugly stuff on init
this.init = function() {
this.tmpl_txt = require( "text!tmpl/" + this.name );
var def = {};
if ( this.partials ) {
for ( var p in this.partials ) {
var partial = this.partials[ p ];
def[ partial.name ] = partial.tmpl_txt;
});
}
this.tmpl_fn =
doT.template( this.tmpl_txt, this.config, def );
};
Slide 57
Slide 57 text
but meta info stays
with the template
Slide 58
Slide 58 text
app only knows it exists
// some_view.js
define( [ "results" ], function( results ) {
function blahBlahBlah() {
...
results.render({
search_text: “Modern art”,
results: resultsArrayFromXHR
});
...
}
});
Slide 59
Slide 59 text
rendering on base object
function getHTML( fn, data ) {
if ( this.helpers ) {
data = this.transform( data );
}
if ( data.length ) {
var html = "";
data.forEach( function( d ) {
html += fn( d );
});
}
return html.length ? html : fn( data );
}
this.render = function( data, $el ) {
$el = $el || $( this.el );
return $el.html( getHTML( this.tmpl_fn, data ) );
};
Slide 60
Slide 60 text
and data transformation
this.transform = function( data ) {
if ( data.length ) {
data = data.map( this.transform );
} else {
this.helpers.forEach( function( propName ) {
data[ propName ] = this.helpers[ propName ].call(
this, data[ propName ]
);
});
}
return data;
}
Slide 61
Slide 61 text
and other helpers
this.append = function( data, $el ) {
$el = $el || $( this.el );
return $el.append( getHTML( this.tmpl_fn, data ) );
};
this.prepend = function( data, $el ) {
$el = $el || $( this.el );
return $el.prepend( getHTML( this.tmpl_fn, data ) );
};
this.serve = function( data ) {
return getHTML( this.tmpl_fn, data );
};
Slide 62
Slide 62 text
so we can also do
function aNewDetailAppeared() {
...
results.partials.detail.append({
id: 103,
title: "Madame Pompadour",
artist: {
name: "Amedeo Modigliani"
},
price: 29.99
});
...
}
Slide 63
Slide 63 text
or on the server
var resultsTmpl = requirejs( “results” );
...
app.get( "/results/:search", function( req, res ) {
var data = {
search_text: req.params.search,
results: getSearchResults( req.params.search)
};
res.send( resultsTmpl.serve( data ) );
});
Slide 64
Slide 64 text
✓ composable templates
✓ modules that manage their dependencies
✓ a place to map helpers and behaviors to
properties and elements
✓ template code abstracted out of
application logic
✓ something that works on both the client
and server
Slide 65
Slide 65 text
and we can take it further
multiple templates for multiple states
data transformation can create new
properties
a custom config that changes all the
delimiters to ⚒ can be baked in
templates can wire up plugins and widget
events
Slide 66
Slide 66 text
a good foundation:
using templates is a good start
a template is more than just some HTML
letting templates manage their own meta
information keeps your app code clean
using Node keeps templates and app code
consistent
might as well build it in from the start
Slide 67
Slide 67 text
that’s a solid day’s work
thanks!
@garannm / garann.com / [email protected]