DRAFT

the video is forthcoming and the examples haven't been fully tested

All about PURE

TL;DR

Start watching the video.

Open the CodePen.io collection

Skip down the page a little bit and read the last few sections.

Rant about DOM-unaware templates

Straight up:

The problem with most of the template systems out there is that they

  • are based on text templating
  • have no knowledge of html
  • have no knowledge of the DOM
  • don't work with html-aware systems such as Jade and Slim
  • confuse designers
  • make it easy for designers to screw up
  • have DSL syntax and all of that crazy {# {{ {{{ %{ {{% <% <? {{= #{ mess going on (isn't HTML bad enough not to have to throw in additional crapola?
  • make validation even more difficult
  • the push programming logic into the templates

We've already had PHP and decided that it is a train wreck.

So why do we keep creating mini PHP languages over and over and over again?

Granted the {{ (handlebar / mustache / bracket) genra of languages are far superior than many of the others because they don't look like html so it's more clear that they are just text templates which have no knowledge of html and they generally have far less logic... but they still have no knowledge of html and they still have logic!

Enter PURE

PURE is a template system that

  • can work both client-side and server side
  • is DOM-aware
  • only works with valid html (and is incapable of producing invalid html)
  • has NO TEMPLATE LOGIC
  • works with strings (debunks the "DOM-aware systems are slow" myth)
  • is a perfect companion to Jade, Slim, and other HTML-aware renderers

If PURE is so perfect, then why don't I marry it doesn't everybody use it?

Well, since there's no canonical way to translate JSON to XML (except for one, which turns out to be more verbose than html in practice) you have to provide a "directive" to tell PURE how to map a simple object to HTML using CSS selectors.

Sounds good so far, right?

There are two caveats: The loop syntax is part of the directive (as opposed to being a class on an element), which looks awkward and since the template has no logic, you supply logic in the directive as JavaScript, whereas most template systems would use a DSL.

The pro of this is that you don't have to learn yet another language. The con is that unless you define a safe subset of JavaScript as a DSL, you would need to use JSHint and or ADSafe to verify the safety of user-uploaded directives that contain logic.

Getting Started

If you head straight to the PURE documentation you might be a little confused about which method to use when. I'll do my best here to give you some best practices to live by.

There are 3 parts to PURE:

  • The template - HTML with specific classes where data should be mapped
  • The directive - a key / value mapping between a CSS selector and data
    • The key is always a CSS selector
    • The value is either a property of an object or a loop
  • The data - objects and arrays and stuff

Use placeholders to create true-to-data templates

So here's a big beasty blob of HTML that a designer would love. Just tell them to stay away from anything with a js- prefix (i.e. never reference it in the stylesheet and don't delete it), and they can go to town.

contacts.tpl.html:

<div class="js-contacts-container">
  <ul class="js-contacts">
    <li class="js-contact">

      <a class="js-name"
        href="#">John Doe</a>

      <!-- note the `http://twitter.cam/` prefix -->
      <a class="js-twitter"
        href="http://twitter.com/">@johndoe</a>

      <!-- note the `mailto:` prefix -->
      <a class="js-email"
        href="mailto:">john.doe@email.com</a>

    </li>
  </ul>
</div>

This HTML wholly encapsulates our example contacts widget.

Render the simplest thing first (a single property)

  • Don't try to do everything at once
  • Always use an object (never a bare array)
  • Start with just a single element / field / value.

contacts.js:

var data
  , directive
  , renderer
  , $root
  ;
  
function init() {
  // this is the single LI
  $root = $('.js-contact')

  // we're simply telling PURE that all elements
  // with the `.js-twitter` class should be updated
  // with the text of `twitter` (see below)
  directive = {
  , ".js-twitter": "twitter"
  };

  renderer = $root.compile(directive);
}

function render() {
  data = {
    "twitter": "@tchvil"
  };

  // data first, then render
  $root.render(data, renderer);
}

function onDomReady() {
  init();
  render();
}

// jQuery will fire `onDomReady` when the dom is ready
$(onDomReady);

Warning: you should not attach events to $root because the root element is destroyed and recreated as part of the template process and will lose any events.

Add in the loop next

  • switch the root from being the single element container to the collection container
  • realize the conceptual difference between the use of an object as a map or an object
    • a map is an collection with (many) unique ids (it's like an array, but indexed by id instead of ordered by number)
    • an object is more like an instance of a class with a few fields (name, email, phone, etc)
  • always pass an object into pure, for clarity's sake

contacts.js:

var data
  , directive
  , renderer
  , $root
  ;
  
function init() {
  // now we're using the parent
  $root = $('ul.js-contacts');

  directive = {
    // here we specify that the LI will 
    // be a loop rather than a single field
    "li": {
      // the loop syntax is saying:
      // treat the `contacts` property of the object as an array / map
      // and refer to each element of that array / map as `contact`
      "contact <- contacts": {
        // and here we specify a classname that maps to a value
        ".js-twitter": "contact.twitter"
      }
    ]
  };

  renderer = $root.compile(directive);
}

function render() {
  // Our data is now in an array
  data = [
    {
      "twitter": "@tchvil"
    }
  ];
  // Here's the same data as a map
  /*
  data = {
    "0": {
      "twitter": "@tchvil"
    }
  };
  */

  // Note that I'm wrapping our array in an object so that the loop is clear
  $root.render({ contacts: data }, renderer);
}

function onDomReady() {
  init();
  render();
}

// jQuery will fire `onDomReady` when the dom is ready
$(onDomReady);

Then add all the crazy stuff

  • just add one thing at a time at first
  • do as much as possible to format data the way you want it templated before you send it to pure

contacts.js:

var data
  , directive
  , renderer
  , $root
  ;
  
function init() {
  $root = $('ul.js-contacts');

  directive = {
    "li": {
      "c <- contacts": {
        ".js-name": "name"
        // here we tell PURE to replace the href attribute with c.web
      , ".js-name@href": "web"
      , ".js-email": "email"
        // here we tell PURE to add c.email to the value of href
      , ".js-email@href+": "email"
      , ".js-twitter": "twitter"
        // here we use a custom function
        // to strip off the '@' of the twitter name
        // before putting it as the href value
      , ".js-twitter@href+": function (ctx) {
          console.log(ctx);
          return ctx.context.twitter.substr(1);
        }
      }
    }
  };

  renderer = $root.compile(directive);
}

function render() {
  data = [
    { name: "Mic Tchvil"
    , twitter: "@tchvil"
    , web: "http://beebole.com"
    , email: "tchvil@gmail.com"
    }
  , { name: "AJ ONeal"
    , twitter: "@coolaj86"
    , web: "http://coolaj86.com"
    , email: "coolaj86@gmail.com"
    }
  ]

  $root.render({ contacts: data }, rfn);
}

$(document).ready(compile);
$(document).ready(render);

Note that I never right HTML by hand, it's just to hard and error prone. However, for those less unfamiliar with CSS and Jade/Slim/Haml/etc, I did there.

Watch the Video

As it turns out, it's a lot easier to live code this type of thing than it is to write out a billion examples.

You'll want to have a link to the CodePen.io collection


By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )