htmlCanvas is a small JavaScript library written by Nicolas Petton. It lets you build UI-widgets and dynamically create HTML content in JavaScript. And it's only 236 LOC.
You use it instead of template engines such as Handlebars, Mustach, Closure Templates, etc. But it's not exactly a template engine.
The basic metaphor used is one of painting on a canvas using brushes. The canvas is the DOM and the brushes HTML 'tags'.
The code to render a div that contains a header and body div can look like this:
Originally htmlCanvas was a part of Amber.js but Nicolas Petton rewrote it in JavaScript for Företagsplatsen. I don´t know the complete background, but I have been told that htmlCanvas is inspired by counterparts in Smalltalk frameworks such as Seaside and Illiad.
Why is it better?
Instead of a different format/language such in Handlebar templates, it's just plain JavaScript code. This means that you can break out parts and assign them to variables, create functions that create parts, manipulating the results, etc., just like regular code. You also don´t have to worry about simple errors such as closing HTML tags, quotes around attributes, etc., as the framework takes care of all that.
Sample TODO App
Let's demonstrate by a small sample app. I've implemented a TODO app since that seems popular. The complete code is available on jsfiddle (http://jsfiddle.net/whenrik/SYZgp/)
Task widget
First we need a widget for a single task. It should have a name label and a checkbox to mark the task as completed.
function task(spec) {
var that = htmlCanvas.widget();
var name = spec.name || 'n/a';
var isDone = spec.isDone;
that.isDone = function() {
return isDone;
};
that.renderOn = function(html) {
html.div().id(id).addClass('task').render(that.renderContentOn);
};
that.renderContentOn = function(html) {
var checkbox = html.input().type('checkbox')
.change(toggle);
var title = html.span(name);
if (isDone) {
checkbox.setAttribute('checked', 'checked');
title.css('text-decoration', 'line-through');
}
};
that.update = function() {
var canvas = htmlCanvas.htmlCanvas(jQuery('#' + id));
canvas.root.asJQuery().empty();
that.renderContentOn(canvas);
};
function toggle() {
isDone = !isDone;
that.update();
}
return that;
}
If you are familiar with Douglas Crockford you should recognize the style. Otherwise I recommend you to have a look at http://www.crockford.com/
var that = htmlCanvas.widget();
That gives us a widget that we can extend. Widget has three methods and is very simple. The most interesting method is 'renderOn' that we need to override to render our HTML. The two other methods is used to attach the widget to the DOM.
Each widget must implement 'renderOn'. It's a method that accept a htmlCanvas. Here's where the DSL come to play. The canvas have methods (tag brushes) for all HTML elements (a, h1, form, input, etc).
In the code above we render a div, sets it's id and assign 'task' to the class attribute. The rendering of the content of the div is delegated to another method 'that.renderContentOn'. I delegate since I need to reuse it later when we want to update the content.
that.renderContentOn = function(html) {
var checkbox = html.input().type('checkbox')
.change(toggle);
var title = html.span(name);
if (isDone) {
checkbox.setAttribute('checked', 'checked');
title.css('text-decoration', 'line-through');
}
};
As you can see from 'checkbox' and 'title' above it's all JavaScript and you can assign parts to variables and modify them.
We could use JQuery to update the checkbox and the title. But in this case I re-render the whole content of the div.
To do that I create a new canvas with the root set to the div rendered in 'renderOn'. I then clear it using JQuery empty() and re-render the content using the 'that.renderContentOn' we wrote earlier.
Ok. Let's make a list of tasks.
function taskList(spec) {
var that = htmlCanvas.widget();
var tasks = spec.tasks || [];
that.renderOn = function(html) {
html.div({id: 'todos'},
// title
html.h1('Todos'),
// new task input field
html.form(
html.input({ type: "text", placeholder: "What needs to be done?"})
).submit(function() {
that.addTask(jQuery(this).find('input').val());
return false;
}),
// list of tasks
html.div({ id: 'tasks' }, tasks),
// footer with clear completed button
html.div({id : 'footer'},
html.a({id: 'clear'}, 'Clear completed')
.href('#').click(that.clearCompleted)
)
);
jQuery('input[type=text]').focus();
};
that.addTask = function(name) {
var newTask = task({ name: name});
tasks.push(newTask);
that.update();
};
that.update = function(html) {
var canvas = htmlCanvas.htmlCanvas(jQuery('#tasks'));
canvas.root.asJQuery().empty();
canvas.render(tasks);
};
that.clearCompleted = function() {
tasks = tasks.filter(function(task) {
return !task.isDone();
});
that.update();
};
return that;
}
The code is similar to that in Task widget but I use a slightly different style. Instead of using methods to modify attributes the first argument is an object literal that contains all attributes of the tag. IMO easier to read.
And finally the code to instantiate the task list and add it to the DOM
jQuery(document).ready(function() {
taskList({
id: 'tasks',
tasks: [
task({name: 'Make your bed.' }),
task({name: 'Do the dishes' }),
task({name: 'Clean the bathroom' }),
task({name: 'Put things away and toss trash'}),
task({name: 'Vacuum'})
]
}).appendTo('body');
});
You can find htmlCanvas here: https://github.com/whenrik/htmlCanvas/blob/master/htmlCanvas.js
Nicolas Petton has a whole range of other projects that are really interesting. Amber.js for example, an implementation of Smalltalk in JavaScript. Another project is Smalltalkhub which is sort of GitHub for smalltalkers.
Since the fall of 2011, he works with us at Företagsplatsen as a consultant remote from France.