$30 off During Our Annual Pro Sale. View Details »

Better templates from the ground up

Better templates from the ground up

It's easy to write a template. But the challenges of the architecture around templates reveal themselves very quickly, especially once you begin to consider using templates both on the client and in Node.js. Even an off-the-shelf framework that handles that for you may at some point need to be extended, and that requires thinking about what you need from a template before and after the HTML it renders. A template, once you really start to use it, is a lot more than just a way to produce some HTML.

Garann Means

July 25, 2013
Tweet

More Decks by Garann Means

Other Decks in Technology

Transcript

  1. Better templates from the
    ground up
    with Node.js
    Garann Means : @garannm

    View Slide

  2. you need templates

    View Slide

  3. you need a template
    strategy

    View Slide

  4. assumption: html
    doesn’t belong in your js

    View Slide

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

    View Slide

  6. but you want to avoid
    manipulating the dom

    View Slide

  7. how come:
    poor performance
    fragile code
    depends on hooks
    depends on hierarchy
    again, HTML and JS are tightly coupled

    View Slide

  8. you can’t avoid the dom
    when wiring up events
    when accepting user input
    when reflecting changes

    View Slide

  9. so save up your dom
    allowance

    View Slide

  10. templates are useless
    without data

    View Slide

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

    View Slide

  12. assumption: all data
    needs transforming

    View Slide

  13. for instance:
    making things plural
    currency and date formats
    user names and pronouns
    subtotaling and other arithmetic
    differences for boolean states
    language translation

    View Slide

  14. that stuff is not
    application logic
    it’s part of the display

    View Slide

  15. assumption: js doesn’t
    belong in your html

    View Slide

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

    View Slide

  17. simple template
    {{=it.title}}
    {{=it.artist.name}}


    {{=priceFormat(it.price)}}
    {{~it.info :info_item}}
    {{=info_item}}
    {{~}}

    View Slide

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

    View Slide

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

    View Slide

  20. 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"
    ]
    }

    View Slide

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

    View Slide

  22. rendering
    function loadDetail( id ) {
    if ( id.data ) {
    id = id.data.id;
    }
    $.get( "detail/" + id, function( info ) {
    $( "div.detail" ).html( detailTmpl( info ) );
    });
    }

    View Slide

  23. some data transformation
    function priceFormat( price ) {
    price = price % 1 == 0 ? price + “.00” : price;
    return "$" + price;
    }

    View Slide

  24. that’s pretty easy

    View Slide

  25. on the server
    // using Express and Consolidate
    app.get( "/product/:id", function( req, res ) {
    res.render( "detail", products[ req.params.id ] );
    });

    View Slide

  26. problem!
    no globals in Node
    ReferenceError: priceFormat is not
    defined
    helpers need to be registered

    View Slide

  27. templates increase in
    difficulty pretty quick

    View Slide

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

    View Slide

  29. templates glue the
    whole display together

    View Slide

  30. node.js can help

    View Slide

  31. manual labor

    View Slide

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

    View Slide

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

    View Slide

  34. what does that have to
    do with node?

    View Slide

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

    View Slide

  36. node is excellent for
    building js

    View Slide

  37. but why let the client have
    all the fun

    View Slide

  38. shared templates save
    you work

    View Slide

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

    View Slide

  40. but it makes sense
    templates themselves are easy
    template architecture is hard
    you want to share more than just the
    template

    View Slide

  41. twice the app with half
    the material

    View Slide

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

    View Slide

  43. partials are your best
    frenemy

    View Slide

  44. using a partial
    Modern Art

    {{~it.results :r}}
    {{#def.result_detail}}
    {{~}}

    View Slide

  45. the partial itself


    {{=r.title}}

    {{=r.artist.name}}


    {{=r.price}}


    View Slide

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

    View Slide

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

    View Slide

  48. 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 );

    View Slide

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

    View Slide

  50. we can make this work
    better.

    View Slide

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

    View Slide

  52. how do we get it?

    View Slide

  53. we write it ourselves*
    // Tmpl.js
    define( [ "require", "doT" ],
    function ( require, doT ) {
    function Tmpl() {
    return this;
    }
    return Tmpl;
    });

    View Slide

  54. 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;
    });

    View Slide

  55. 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;
    });

    View Slide

  56. 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 );
    };

    View Slide

  57. but meta info stays
    with the template

    View Slide

  58. app only knows it exists
    // some_view.js
    define( [ "results" ], function( results ) {
    function blahBlahBlah() {
    ...
    results.render({
    search_text: “Modern art”,
    results: resultsArrayFromXHR
    });
    ...
    }
    });

    View Slide

  59. 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 ) );
    };

    View Slide

  60. 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;
    }

    View Slide

  61. 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 );
    };

    View Slide

  62. so we can also do
    function aNewDetailAppeared() {
    ...
    results.partials.detail.append({
    id: 103,
    title: "Madame Pompadour",
    artist: {
    name: "Amedeo Modigliani"
    },
    price: 29.99
    });
    ...
    }

    View Slide

  63. 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 ) );
    });

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. that’s a solid day’s work
    thanks!
    @garannm / garann.com / [email protected]

    View Slide