Slide 1

Slide 1 text

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}}

{{=it.title}}
  • {{=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" ] }

Slide 21

Slide 21 text

loading the template $.when( $.get( "tmpl/detail.dot", function( tmpl ) { detailTmpl = doT.template( tmpl ); }, "text" ) ).then( init );

Slide 22

Slide 22 text

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.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

Slide 48

Slide 48 text

loading on the client $.when( $.get( "tmpl/result_detail.dot", function( tmpl ) { partials.result_detail = tmpl; }, "text" ) ).then( $.get( "tmpl/detail.dot", function( tmpl ) { detailTmpl = doT.compile( tmpl, partials ); }, "text" ) ).then( init );

Slide 49

Slide 49 text

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]