Jaml: beautiful HTML generation for JavaScript

Generating HTML with JavaScript has always been ugly. Hella ugly. It usually involves writing streams of hard-to-maintain code which just concatenates a bunch of strings together and spits them out in an ugly mess.

Wouldn’t it be awesome if we could do something pretty like this:

div(
  h1("Some title"),
  p("Some exciting paragraph text"),
  br(),

  ul(
    li("First item"),
    li("Second item"),
    li("Third item")
  )
);

And have it output something beautiful like this:

<div>
  <h1>Some title</h1>
  <p>Some exciting paragraph text</p>
  <br />
  <ul>
    <li>First item</li>
    <li>Second item</li>
    <li>Third item</li>
  </ul>
</div>

With Jaml, we can do exactly that. Jaml is a simple library inspired by the excellent Haml library for Ruby. It works by first defining a template using an intuitive set of tag functions, and then rendering it to appear as pretty HTML. Here’s an example of how we’d do that with the template above:

Jaml.register('simple', function() {
  div(
    h1("Some title"),
    p("Some exciting paragraph text"),
    br(),

    ul(
      li("First item"),
      li("Second item"),
      li("Third item")
    )
  );
});

Jaml.render('simple');

All we need to do is call Jaml.register with a template name and the template source. Jaml then stores this for later use, allowing us to render it later using Jaml.render(). Rendering with Jaml gives us the nicely formatted, indented HTML displayed above.

So we’ve got a nice way of specifying reusable templates and then rendering them prettily, but we can do more. Usually we want to inject some data into our template before rendering it – like this:

Jaml.register('product', function(product) {
  div({cls: 'product'},
    h1(product.title),

    p(product.description),

    img({src: product.thumbUrl}),
    a({href: product.imageUrl}, 'View larger image'),

    form(
      label({'for': 'quantity'}, "Quantity"),
      input({type: 'text', name: 'quantity', id: 'quantity', value: 1}),

      input({type: 'submit', value: 'Add to Cart'})
    )
  );
});

In this example our template takes an argument, which we’ve called product. We could have called this anything, but in this case the template is for a product in an ecommerce store so product makes sense. Inside our template we have access to the product variable, and can output data from it.

Let’s render it with a Product from our database:

//this is the product we will be rendering
var bsg = {
  title      : 'Battlestar Galactica DVDs',
  thumbUrl   : 'thumbnail.png',
  imageUrl   : 'image.png',
  description: 'Best. Show. Evar.'
};

Jaml.render('product', bsg);

The output from rendering this template with the product looks like this:

<div class="product">
  <h1>Battlestar Galactica DVDs</h1>
  <p>Best. Show. Evar.</p>
  <img src="thumbnail.png" />
  <a href="image.png">View larger image</a>
  <form>
    <label for="quantity">Quantity</label>
    <input type="text" name="quantity" id="quantity" value="1"></input>
    <input type="submit" value="Add to Cart"></input>
  </form>
</div>

Cool – we’ve got an object oriented declaration of an HTML template which is cleanly separated from our data. How about we define another template, this time for a category which will contain our products:

Jaml.register('category', function(category) {
  div({cls: 'category'},
    h1(category.name),
    p(category.products.length + " products in this category:"),

    div({cls: 'products'},
      Jaml.render('product', category.products)
    )
  );
});

Our category template references our product template, achieving something rather like a partial in Ruby on Rails. This obviously allows us to keep our templates DRY and to easily render a hypothetical Category page like this:

//here's a second product
var snowWhite = {
  title      : 'Snow White',
  description: 'not so great actually',
  thumbUrl   : 'thumbnail.png',
  imageUrl   : 'image.png'
};

//and a category
var category = {
  name    : 'Doovde',
  products: [bsg, snowWhite]
}

Jaml.render('category', category);

All we’ve done is render the ‘category’ template with our ‘Doovde’ category, which contains an array of products. These were passed into the ‘product’ template to produce the following output:

<div class="category">
  <h1>Doovde</h1>
  <p>2 products in this category:</p>
  <div class="products"><div class="product">
  <h1>Battlestar Galactica DVDs</h1>
  <p>Best. Show. Evar.</p>
  <img src="thumbnail.png" />
  <a href="image.png">View larger image</a>
  <form>
    <label for="quantity">Quantity</label>
    <input type="text" name="quantity" id="quantity" value="1"></input>
    <input type="submit" value="Add to Cart"></input>
  </form>
</div>
<div class="product">
  <h1>Snow White</h1>
  <p>not so great actually</p>
  <img src="thumbnail.png" />
  <a href="image.png">View larger image</a>
  <form>
    <label for="quantity">Quantity</label>
    <input type="text" name="quantity" id="quantity" value="1"></input>
    <input type="submit" value="Add to Cart"></input>
  </form>
</div>
</div>
</div>

You can see live examples of all of the above at http://edspencer.github.com/jaml.

Jaml currently sports a few hacks and is not particularly efficient. It is presented as a proof of concept, though all the output above is true output from the library. As always, all of the code is up on Github, and contributions are welcome :)

Jaml would be suitable for emulating a Rails-style directory structure inside a server side JavaScript framework – each Jaml template could occupy its own file, with the template name coming from the file name. This is roughly how Rails and other MVC frameworks work currently, and it eliminates the need for the Jaml.register lines. Alternatively, the templates could still be stored server side and simply pulled down and evaluated for client side rendering.

Happy rendering!

34 Responses to Jaml: beautiful HTML generation for JavaScript

  1. lorenzo jorqura says:

    This should work really nice with CouchDb!

  2. I do love it. I tested before ActiveView from ActiveJS: http://www.activejs.org/
    Yours looks far easier.
    I’ll fork it right away.

  3. Well, actually I like ActiveView better: Less pollution of the global scope, less hacks (Jaml.renderer.render…) and possibilities of extending the element creation function (to use with jQuery or other libraries on the client side).

  4. Ed Spencer says:

    There is only 1 global – the Jaml object. It just feels like more because it’s using the with keyword. With it usually demonised as evil… and it usually is, but in this case it makes the templates look prettier and feel more intuitive so I decided it was a fair tradeoff.

    I don’t think Jaml.Renderer is a hack :) It’s done this way because I have plans to extend this with helper functions and a few other bits, so it might look a little over-engineered for the moment.

  5. craig says:

    You should take a look at Groovy’s MarkupBuilder (http://groovy.codehaus.org/Builders). Of course I realize running on the JVM or using Groovy may not be what you want, but I think it at least makes for an interesting comparison.

  6. Arno Nyhm says:

    nice :-)

    i would like to see it as a JAVA class … but i think that would not so easy to convert …

  7. Kailden says:

    This reminds me of CL-WHO for common lisp.

  8. Pingback: Jaml, bonita forma de generar HTML desde Javascript | aNieto2K

  9. Pingback: Jaml, bonita forma de generar HTML desde Javascript : Blogografia

  10. Pingback: DBJ.ORG » xJAML

  11. In the context of client-side use this is very similar to MochiKit’s set of dom-building tag functions (DIV(), P(), etc.), however I think that the way you’ve implemented registration and re-use of templates takes it to the next level.

    I will be looking to use jaml in the future; thanks!

  12. Pingback: Jaml, linda forma de generar HTML con Javascript | BufferOverflow

  13. brendan says:

    This looks great; it’s like StringTemplate but for the client side! I will definitely be using this for my next web project.

  14. Pingback: Generar HTML desde Javascript | Omeyas Web

  15. joseanpg says:

    Perhaps this fragment could be improved

    (function() {
    var tags = Jaml.Template.prototype.tags;

    for (var i = tags.length - 1; i >= 0; i--){
    var tagName = tags[i];
    var fn = function(tagName) {
    return function(attrs) {
    ...
    };
    };
    Jaml.Template.prototype[tagName] = fn(tagName);
    };
    })();

    They are creating 37 instances of the factory fn all alike with the same lexical scope. I think it would be better to get that factory outside the loop


    (function() {
    var fn = function(tagName) {
    return function(attrs) {
    ...
    };
    };

    var tags = Jaml.Template.prototype.tags;
    for (var i = tags.length - 1; i >= 0; i--){
    var tagName = tags[i];
    Jaml.Template.prototype[tagName] = fn(tagName);
    };
    })();

  16. Pingback: dev.enekoalonso.com : Mooml: Mootools markup language (intro)

  17. nunzio says:

    yes, but the seo?

  18. Mark says:

    That some sweet syntax right there. Is there anyway to access loop info (index, isFirst, isLast) for conditional output? Also, is there any way to process arbitrary functions as parameters – these functions could create different output depending on object properties and would be responsible for returning a Template Object. Also – congratulations on becoming lead developer of ExtJS – any plans to integrate this style of templating into the library, or stick with the XTemplate class?

  19. Jonathan Leech says:

    @Arno – I have been cooking up something similar for Java in my spare cycles. Think JAXB + declarative syntax. Sprinkle in some JSR 305 annotations, and you’ve got static type checking of XML – validation happens at compile time (vs. at runtime or never).

  20. devsmt says:

    I’m looking for a good idea on how to build markup since years… this templating system makes a lot of sense tome. fantastic work, thanks!

  21. Poetro says:

    And also the attributes are not escaped, so if i say:
    a({‘href:” ‘title’ : ‘some” rel=”on the moon’}, ‘link’)
    it will result:

    link

    instead of the expected:

    link

    Which means a totally different thing.

  22. andypan says:

    jaml just like actionscript to bulid flash :)

    maybe useful

  23. Pingback: Javascript HTML Templating: EJS vs JAML

  24. Pingback: Jaml updates : Ed Spencer

  25. Franz says:

    The examples look nice, but cleverly spare out the most tricky task: rendering of dynamic html tables. Dynamic means here that you don’t know the exact number of rows and columns, they entirely depend on your data structure (e.g. nested array).

    In the current version of Jaml you can only tackle this task with an endless number of subtemplates (one for the table head, one for table body, one for each table row, once for each table cell). What I am missing is something like an .each method, like

    td.each([‘item 1′, item2′, ‘item3′}) ==> ‘Item 1Item 2Item 3′

  26. Looks like domplate, no?

  27. ramen says:

    Absolute crap! Who needs this stupidity?!

    Why another layer when you can write in first place in html?

  28. Pingback: Links der Woche XXXVI - html5,css3,drupal,wordpress,javascript - Webworking

  29. Olle Jonsson says:

    Neat, and all.

    Prior art: MochiKit’s DOM module: http://mochikit.com/doc/html/MochiKit/DOM.html

  30. Mr Foo says:

    jade-lang.com is far better, this is a cheap knock off.

  31. Ed Spencer says:

    @Mr Foo – I created and forgot about Jaml before Node even existed, never mind Jade. Neither is a knock off of the other (Jade is a beautiful syntax though)

  32. Pingback: Builder Syntax: A Timeless Way of Building | Atomic Spin

  33. Fotinula says:

    Jaml looks very promising so far…I’m currently playing with it, but can’t find a way to include element (I’ve included it in tags array, but still error appear). Any ideas will be appreciated. Also I wanted to ask if there is possibility to pass more than 2 parameters to Jaml.render function. Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 2,605 other followers

%d bloggers like this: