Node Roundup: Cluster Live, Nib, Capsule

27 Apr 2011 | By Alex Young | Comments | Tags node modules css

Node v0.4.7

Node v0.4.7 is out. This release includes bug fixes and documentation improvements. If you’re interested in reading more on the bug fixes, the numbers in the release notes refer to the GitHub issue tracker, for example: #897. Because the comments in the release notes are short it can be illuminating to read more.

Cluster Live

TJ Holowaychuk released Cluster Live (GitHub: visionmedia / cluster-live, MIT License), which is a realtime administration and statistics plugin for Cluster. If you’ve been holding off on Cluster, this is probably the sort of usage TJ envisioned for it and may convince you to use it.

It needs a bit of configuration to get it going:

var live = require('cluster-live');

cluster(server)
  .set('workers', 6)
  .use(cluster.debug())
  .use(cluster.stats({ connections: true, lightRequests: true }))
  .use(live())
  .listen(3000);

I really like the simplified design TJ has used for the interface, and if you look at cluster-live’s Stylus templates you can see how he’s split them up into separate files, using @import to load each one. More useful lessons for those of you who are interested in Stylus can be found in these files.

Extend Sylus with a Nib

Speaking of Stylus… TJ wrote Extend Sylus with a Nib which is a post about Nib (MIT License), a library of Stylus extensions. Nib comes with lots of useful shortcuts for dealing with CSS gradients and even buttons — these buttons can be generated very quickly with Nib:

You’ve probably seen these in some of TJ’s recent projects and examples. Rather than writing a bunch of CSS or Stylus, these buttons can be created with Nib using functions:

.bold
  bold-button()

.bold-alternate
  bold-button(glow: #00ABFA)

An optional dependency is node-canvas which Nib will use to generate data URIs containing smooth linear gradients.

Capsule

Capsule (MIT License) by Henrik Joreteg is a Node framework that uses Socket.io and Backbone.js to synchronise models between the client and server. He’s written some documentation for Capsule.models.js and Capsule.views.js, and the README includes a detailed example of how to set things up for the server and browser.

It’s hard to see how useful this framework is without an application example, but Henrik suggests reading Re-using Backbone.js Models on the server with Node.js and Socket.io to build real-time apps which covers some of the concepts behind Capsule.

He also mentions an unreleased library called Thoonk.js which aims to improve the scalability of this approach by using Redis’s clustering and pub/sub capabilities.

jQuery Roundup: jQuery UI 1.8.12, IndexedDB, Apprise

26 Apr 2011 | By Alex Young | Comments | Tags jquery plugins ui database indexeddb

jQuery UI 1.8.12

jQuery UI 1.8.12 is out. Upgrading should be fairly simple as the upgrade guide says there are now backwards incompatible changes. This version includes bug fixes for Resizable, Autocomplete, Datepicker, Dialog, Progressbar, Slider, and Tabs.

IndexedDB

IndexedDB by Parashuram provides a simple chained API for the Indexed Database API. This specification is still a working draft, but Firefox 4, Chrome, and potentially IE support it.

Indexed Database API is a client-side database for simple values and hierarchical objects. It supersedes Web SQL Database which is considered deprecated. Parashuram’s jQuery plugin makes a flatter API, which looks like this:

$.indexeddb('BookShop-1').objectStore('BookList').openCursor().each(write);

The alternative is nested callbacks for each operation, which makes this seem pretty slick by comparison. I have a feeling we could even lose openCursor, because it seems implicit to me, but that’s up to the author.

Apprise

Apprise (License) is an alternative to alert(). Basic usage is just like alert, but it can also prompt for values or act like confirm(). It looks a bit like Firefox 4’s alert boxes.

It doesn’t depend on jQuery UI, which means you don’t need to add a full UI library just to get prettier popup boxes.

Node Tutorial Part 22: Backbone (Again)

25 Apr 2011 | By Alex Young | Comments | Tags server node tutorials lmawa nodepad backbone.js

Welcome to part 22 of Let’s Make a Web App, a tutorial series about building a web app with Node. This series will walk you through the major areas you’ll need to face when building your own applications. These tutorials are tagged with lmawa.

Click to show previous tutorials.

Search

I hadn’t finished rebuilding the search interface using Backbone. The old code used simple jQuery events and Ajax to manage the search field, form submission, and document list. The search text input management looked like this:

$('input[name="s"]').focus(function() {
  var element = $(this);
  if (element.val() === 'Search')
    element.val('');
});

$('input[name="s"]').blur(function() {
  var element = $(this);
  if (element.val().length === 0)
    element.val('Search');
});

This translates quite easily to Backbone using a view and some events:

var SearchView = Backbone.View.extend({
  el: $('#header .search'),

  events: {
    'focus input[name="s"]': 'focus',
    'blur input[name="s"]': 'blur'
  },

  focus: function(e) {
    var element = $(e.currentTarget);
    if (element.val() === 'Search')
      element.val('');
  },

  blur: function(e) {
    var element = $(e.currentTarget);
    if (element.val().length === 0)
      element.val('Search');
  }
}

The view’s main element is the search form, and the focus and blur events are bound in a similar way to the old code.

Next I instantiate the class in AppView, which acts as a main container view:

AppView = Backbone.View.extend({
  initialize: function() {
    this.documentList = new DocumentList();
    this.searchView = new SearchView();
  }
});

Working With DocumentList

The existing DocumentList manages the left-hand-side list of documents, which the search code needs to work with. Using the old code could have confused things a little bit here, because it altered the raw HTML rather than working through Backbone.

This is the perfect place for the Show All link that appears when a search is active:

DocumentList = Backbone.View.extend({
  el: $('#document-list'),
  Collection: Documents,

  // This supports the search system:
  events: {
    'click #show-all': 'showAll',
  },

  initialize: function() {
    _.bindAll(this, 'render', 'addDocument', 'showAll');
    this.Collection.bind('refresh', this.render);
  },
  
  // ...

  showAll: function(e) {
    e.preventDefault();
    this.el.html('');
    this.Collection.fetch();
    appView.searchView.reset();
  }

This code is mostly the same, but it adds the showAll method and event binding. Using this.Collection.fetch will reload all the documents from the server.

As an aside: I was looking at a way of making this work with a subclassed version of the DocumentList collection that uses /search.json instead of the usual document list URL, but I couldn’t find a way of making Backbone POST with a request parameter.

Searching

To actually search, I’ve used the original jQuery Ajax call:

SearchView = Backbone.View.extend({
  el: $('#header .search'),

  events: {
    'focus input[name="s"]': 'focus',
    'blur input[name="s"]': 'blur',
    'submit': 'submit'
  },

  initialize: function(model) {
    _.bindAll(this, 'search', 'reset');
  },

  focus: function(e) {
    var element = $(e.currentTarget);
    if (element.val() === 'Search')
      element.val('');
  },

  blur: function(e) {
    var element = $(e.currentTarget);
    if (element.val().length === 0)
      element.val('Search');
  },

  submit: function(e) {
    e.preventDefault();
    this.search($('input[name="s"]').val());
  },

  reset: function() {
    this.el.find("input[name='s']").val('Search');
  },

  search: function(value) {
    $.post('/search.json', { s: value }, function(results) {
      appView.documentList.el.html('<li><a id="show-all" href="#">Show All</a></li>');

      if (results.length === 0) {
        alert('No results found');
      } else {
        for (var i = 0; i < results.length; i++) {
          var d = new Document(results[i]);
          appView.documentList.addDocument(d);
        }
      }
    }, 'json');
  }
});

I’ve also added the reset method which sets the search input to Search after pressing Show All.

Notice that DocumentList.addDocument is reused here. In a larger application, this kind of reuse is very important and is where Backbone starts to show its value. Another thing to note is SearchView could easily be in its own file, allowing us to avoid a monolithic client-side JavaScript file that becomes hard to manage.

Conclusion

Writing good code with client-side frameworks like Backbone requires a lot of work, but it does take away some of the drudgery when working with data and binding it to interfaces. The search code looks cleaner than it did before, but even though it’s quite simple it would be interesting to refactor it.

This week’s commit was 2b8e083.

Spine, dat.gui, WebSonic

22 Apr 2011 | By Alex Young | Comments | Tags graphics libraries mvc webgl

Spine

Spine (GitHub: maccman / spine, License) by Alex MacCaw is an MVC library that’s a lot like Backbone.js. The controller API is based on Backbone, so if you’re familiar with that it shouldn’t take too much work to get going with Spine.

Spine models have validations, persistence, serialization, and an event API. It’s a small library, throwing away some of Backbone’s functionality (it’s designed not to require collections for example). I’m not sure how flexible or extensible it is, but the models and controllers have events that can be bound to for everything I could think of.

Models look like this:

var User = Spine.Class.create({
  name: 'Tonje',

  init: function(name) {
    this.name = name;
  }
});

var user = User.inst();

The inst method is used to instantiate models. I kept thinking I should call init, but inst calls it instead and does all the Object.create stuff to maintain the prototype inheritance chain.

Update:

@alex_young good point about the API – I’ve changed it to .init() rather than inst(). It’s backwards compatible with the previous version.

- @maccman

dat.gui

dat.gui (GitHub: dataarts / dat.gui from the Data Arts Team is a controller library. It creates and manages DAT.GUI panels which contain UI controls. The controls can be observed and manipulated. This makes a lot more sense when looking at the example of dat.gui’s homepage!

It seems like something that would work well with data visualisation or Processing inspired graphical projects.

WebSonic

WebSonic (GitHub: Coreh / WebSonic) by Marco Aurélio is a WebGL game engine example that features Sonic the Hedgehog. It’s not exactly a fully-playable game, but it’s extremely interesting. The project’s README on GitHub includes some technical details, with comments from Marco about what he thinks could be improved or requires extra browser support for (like joystick support).

Let's Make a Framework: Writing CSS Properties

21 Apr 2011 | By Alex Young | Comments | Tags frameworks tutorials lmaf documentation dom css

Welcome to part 59 of Let’s Make a Framework, the ongoing series about building a JavaScript framework.

These articles are tagged with lmaf. The project we’re creating is called Turing. Documentation is available at turingjs.com.

I had some feedback on last week’s commit from John-David Dalton which can be viewed here: commit 9dc312. The changes he suggested help keep behaviour relative to the element’s document rather than the global one. I tested both suggestions in IE/Firefox/WebKit.

Writing CSS

Last week we were doing this sort of thing:

turing('#dom-test').css('background-color');

Now CSS properties can be read, how about writing them? I discussed how jQuery implements writing styles back in part 54. The algorithm works like this:

  1. Check the element has the right nodeType
  2. Get the CSS property name, correcting case
  3. Make sure that NaN and null values aren’t set
  4. If a number was passed in, add px to the value (except for certain CSS properties)
  5. Write the value using the element’s style property

This process is actually fairly simple. Even the cases where px shouldn’t be added are properties that aren’t usually manipulated:

// Exclude the following css properties to add px
cssNumber: {
  "zIndex": true,
  "fontWeight": true,
  "opacity": true,
  "zoom": true,
  "lineHeight": true
}

It’s easier to pass numbers rather than remembering to use ‘10px’. The reason this is an object with properties set to true is to make it easy to use: jQuery.cssNumber[property] is nice and succinct.

Tests

These are the tests I want to pass:

'test writing style properties': function() {
  var element = turing.dom.get('#dom-test')[0],
      expected = element.currentStyle ? '#f5f5f5' : 'rgb(245, 245, 245)';

  turing.dom.css(element, { 'background-color': expected, 'width': 1000 });

  assert.equal(turing.dom.css(element, 'background-color'), expected);
  assert.equal(turing.dom.css(element, 'backgroundColor'), expected);
  assert.equal(turing.dom.css(element, 'width'), '1000px');
},

'test chained writing style properties': function() {
  var element = turing.dom.get('#dom-test')[0],
      expected = element.currentStyle ? '#f1f1f1' : 'rgb(241, 241, 241)';

  turing('#dom-test').css({ 'background-color': expected });

  assert.equal(turing('#dom-test').css('background-color'), expected);
  assert.equal(turing('#dom-test').css('backgroundColor'), expected);
}

As usual I’m testing both the “modular” API and chained API. The first test includes a numerical value that should automatically get px set. I haven’t tested all the edge cases because I don’t think it would help you learn anything about DOM programming.

Implementation

The original dom.css method I defined already detected cases where styles should be written, so I’ve added a loop to iterate over an object with a list of styles:

/**
 * Gets or sets style values.
 *
 * @param {Object} element A DOM element 
 * @returns {Object} The style value
 */
dom.css = function(element, options) {
  if (typeof options === 'string') {
    return getStyle(element, options);
  } else {
    for (var property in options) {
      if (options.hasOwnProperty(property)) {
        setStyle(element, property, options[property]);
      }
    }
  }
};

Next, setStyle’s implementation is dependent on browser:

if (document.documentElement.currentStyle) {
  getStyle = function(element, property) {
    return element.currentStyle[camelCase(property)];
  };

  setStyle = function(element, property, value) {
    return setStyleProperty(element, camelCase(property), value);
  };
} else if (document.defaultView.getComputedStyle) {
  getStyle = function(element, property) {
    return element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue(uncamel(property));
  };

  setStyle = function(element, property, value) {
    return setStyleProperty(element, uncamel(property), value);
  };
}

The currentStyle detection lets us determine if we should always camelCase properties or not. Next, setStyleProperty does the real work:

function setStyleProperty(element, property, value) {
  if (invalidCSSNode(element)) {
    return;
  }

  if (typeof value === 'number' && !cssNumericalProperty[property]) {
    value += 'px';
  }

  element.style[property] = value;
}

This uses cssNumericalProperty which is the same as jQuery’s cssNumber object. I also created invalidCSSNode to detect if the element’s style can be written to:

function invalidCSSNode(element) {
  return !element || element.nodeType === nodeTypes.TEXT_NODE || element.nodeType === nodeTypes.COMMENT_NODE || !element.style;
}

I’ve used the nodeTypes object again to make this more readable.

Conclusion

The tests pass in IE, Firefox, and Chrome/Safari, so I’m happy. It would be possible to take reading and writing CSS properties a lot further than I have here — colour values could be unified across browsers, and maybe even the document’s stylesheets could be manipulated.

This week’s code is commit c6a2ee8.

Node Roundup: Nedis, Redisify, Node on iOS

20 Apr 2011 | By Alex Young | Comments | Tags node modules redis v8 iOS

Node v0.4.6

Node v0.4.6 was released last week. v8 has been updated to 3.1.8.10, which reminded me — you can track v8 releases at Google Code’s v8 repository. Amongst other things there’s a full v8 changelog which may be of interest.

Nedis

Nedis (MIT License, npm: nedis) by TJ Holowaychuk is a Redis server implemented with Node. TJ has posted some benchmarks of it:

SET
nedis: ops 25048, per second 5009.6
redis: ops 54850, per second 10970

GET
nedis: ops 32729, per second 6545.8
redis: ops 54714, per second 10942.8

… although he says he built this for fun. He’s written a blog post with more details: Redis Implemented With Node and mentions the background of the project:

[…] however as our team grows larger, and as we add more non-technical team members over at LearnBoost I figured it would be nice help prevent the need for compiling development dependencies.

Redisify

Redisify (MIT License, npm: redisify) by Jonah Fox adds Redis capaibilites to objects:

var User = {
  key: "Users"
};

User.redis = redisify(client);

User.redis('get', 'xx', function(val) {
  // redis "get Users:xx"
  // User == this
});

This method could actually be named anything — one of Jonah’s examples refers to it as db which reads well. He’s written a blog post about the library: Redisfy your objects.

Node on iOS

I saw this blog post by Nathan Rajlich called NodeJS on iOS. He’s started node-iOS which is an effort to bring native iOS bindings to Node. You’ll require a jailbroken iPhone to use this, but as someone who does a fair bit of Objective-C I find this very interesting.

jQuery Roundup: 1.6, customSelect, bubbleBox

19 Apr 2011 | By Alex Young | Comments | Tags jquery plugins ui

jQuery 1.6 Beta 1

jQuery 1.6 beta 1 has been released. In this version, .attr() has been rewritten, and there are a lot of fixes across the framework which are covered in the announcement.

The jQuery blog also featured a post clarifying “official plugins”: A Change in the Roadmap.

Because of your concerns and ours, we’ve decided to eliminate the notion of Official Plugins altogether.

The jQuery UI project will take ownership over plugins on which it has a current or future dependency: Templating, Globalization, and bgiframe.

customSelect

customSelect (GitHub: rixth/customSelect, MIT) by Tom Rix is a jQuery UI widget that makes select elements prettier. It’ll change them to a drop-down list of radio buttons, or checkboxes for multiple selects. It’s the exact kind of thing I’m always being asked to add by my clients (I already intend on using it for a project).

Usage:

$('selector').customSelect();

Tom posted some extra info about this to his Forrst account: Introducing jQuery customSelect.

bubbleBox

bubbleBox (GitHub: rixth/bubbleBox, MIT) also by Tom Rix is a jQuery UI widget for managing lists. He’s even written some Jasmine unit tests for it!

He wrote a post on Forrst for this too: Introducing jQuery bubbleBox

Node Tutorial Part 21: Connection Management

18 Apr 2011 | By Alex Young | Comments | Tags server node tutorials lmawa nodepad

Welcome to part 21 of Let’s Make a Web App, a tutorial series about building a web app with Node. This series will walk you through the major areas you’ll need to face when building your own applications. These tutorials are tagged with lmawa.

Click to show previous tutorials.

Package Updates

I’ve updated all the packages to the latest versions. Express is now on 2.2.2!

HTML Preview

In the Backbone.js tutorials we already added a stub to DocumentControls called showHTML. This is meant to show the HTML preview, which can be obtained by viewing a document with .html appended to the URL. I added a convenience function to the model for getting the right URL:

  Document = Backbone.Model.extend({
    Collection: Documents,

    url: function() {
      return this.urlWithFormat('json');
    },

    urlWithFormat: function(format) {
      return this.get('id') ? '/documents/' + this.get('id') + '.' + format : '/documents.json';
    },

    // ...

Then added the event 'click #html-button': 'showHTML' to DocumentControls and updated showHTML:

showHTML: function(e) {
  var model = this.model;
  e.preventDefault();
  $.get(this.model.urlWithFormat('html'), function(data) {
    console.log($(window).height());
    $('#html-container').html(data);
    $('#html-container').dialog({
      title: model.get('title'),
      autoOpen: true,
      modal: true,
      width: $(window).width() * 0.95,
      height: $(window).height() * 0.90
    });
  });
}

I’m using dialog from jQuery UI to display the contents of the HTML document.

Express Responses

It was possible to make some of my server-side methods hang, like this one:

app.post('/search.:format?', loadUser, function(req, res) {
  Document.find({ user_id: req.currentUser.id, keywords: req.body.s ? req.body.s : null },
                [], { sort: ['title', 'descending'] },
                function(err, documents) {
    switch (req.params.format) {
      case 'json':
        res.send(documents.map(function(d) {
          return { title: d.title, id: d._id };
        }));
      break;
    }
  });
});

This is poor style because an unrecognised format would cause the app to hang. You should avoid this as much as possible when building Express apps and always send something back to the browser:

app.post('/search.:format?', loadUser, function(req, res) {
  Document.find({ user_id: req.currentUser.id, keywords: req.body.s ? req.body.s : null },
                [], { sort: ['title', 'descending'] },
                function(err, documents) {
    switch (req.params.format) {
      case 'json':
        res.send(documents.map(function(d) {
          return { title: d.title, id: d._id };
        }));
      break;

      // Here
      default:
        res.send('Format not available', 400);
      break;
    }
  });
});

TJ Holowaychuk pointed this out to me in the comments.

I’m not sure if a 400 Bad Request is the best way to respond though. What about 406 Not Acceptable? I’m sure there’s a standard convention, but I read through part of rfc2616 and couldn’t decide.

An additional way to safeguard against hung connections is by using middleware to disconnect them after a period of time. The Connect-timeout middleware by Guillermo Rauch could be used to do this. Long-running connections are still allowed with this middleware by using the clearTimeout method which gets added to the request objects.

It’s fairly easy to use:

var connectTimeout = require('connect-timeout@0.0.1'),
    // ...

app.configure(function() {
  app.use(connectTimeout({ time: 10000 }));
  // ...

There are options for error code and time.

This week’s code is commit 2fde220.

PhiloGL, GLGE

15 Apr 2011 | By Alex Young | Comments | Tags webgl graphics animation

PhiloGL

PhiloGL (GitHub: senchalabs / philogl, License) by Nicolas Garcia Belmonte is a WebGL framework for data visualisation, game development, and making cool stuff.

The modules include support for shaders, scene building, events, effects and tweening, and even web workers. Nicolas has pushed the idea of idiomatic JavaScript, so the API feels like popular JavaScript frameworks:

PhiloGL('name', {
  textures: {
    src: ['texture.gif'],
    parameters: [{
      name: 'TEXTURE_MAG_FILTER',
      value: 'LINEAR'
    }, {
      name: 'TEXTURE_MIN_FILTER',
      value: 'LINEAR_MIPMAP_NEAREST',
      generateMipmap: true
    }]
  },
  events: {
    // Keyboard events would go here
    // ...

I’ve adapted this PhiloGL snippet from lesson10-canvas which is based on a tutorial from Learning WebGL.

If you’re interested in using this for presenting data, there’s an example called Surface Explorer which can plot parametric equations in 3D.

GLGE

GLGE (GitHub: supereggbert / GLGE) by Paul Brunt attempts to make WebGL more accessible. It has some advanced features, like skeletal animations, LOD, culling, 2D filters, normal mapping, materials — take a look through the API documentation to see more.

There’s a great Sky Fog Demo which showcases some of these features. The API reminds me more of a Java API, which may actually suit game developers looking to work with WebGL.

Let's Make a Framework: DOM Manipulation Part 4

14 Apr 2011 | By Alex Young | Comments | Tags frameworks tutorials lmaf documentation dom css

Welcome to part 58 of Let’s Make a Framework, the ongoing series about building a JavaScript framework.

If you haven’t been following along, these articles are tagged with lmaf. The project we’re creating is called Turing. Documentation is available at turingjs.com.

Last week I implemented read and write functionality for text nodes. Along the way I had to build an empty method to clean out nodes before changing them.

I don’t know about you, but I find researching the implementations of these DOM methods fascinating. It might not be as glamorous as animations, but it underpins everything we do in client-side JavaScript.

This week I want to look at reading style values.

Reading Style Values

A few weeks ago I researched how jQuery implements its css() method, in Part 54, CSS Manipulation. Most frameworks implement something similar in some form, Prototype for example has setStyle and getStyle.

The essence of reading styles is to use currentStyle or getComputedStyle based on browser support. The getComputedStyle method was introduced in DOM level 2, and is intended to provide read access to computed style values. jQuery makes this a bit more friendly by allowing for both camelcase names and the hyphenated ones most of us are used to.

camelCase

The easiest way to do this in JavaScript is:

'backgroundColor'.replace(/-([a-z])/ig, function(all, letter) { return letter.toUpperCase(); })

… which is what jQuery does.

/**
 * Converts property names with hyphens to camelCase.
 *
 * @param {String} text A property name
 * @returns {String} text A camelCase property name
 */
function camelCase(text) {
  if (typeof text !== 'string') return;
  return text.replace(/-([a-z])/ig, function(all, letter) { return letter.toUpperCase(); });
};

We also need to go the other way. IE’s properties are not in camelCase!

/**
 * Converts property names in camelCase to ones with hyphens.
 *
 * @param {String} text A property name
 * @returns {String} text A camelCase property name
 */
function uncamel(text) {
  if (typeof text !== 'string') return;
  return text.replace(/([A-Z])/g, '-$1').toLowerCase();
};

Getting Style Values

I want this test to pass:

'test reading style properties': function() {
  var element = turing.dom.get('#dom-test')[0];
  assert.equal(turing.dom.css(element, 'background-color'), 'rgb(240, 240, 240)');
}

The turing.dom.css(element, propertyName) signature will be used to read CSS properties directly. I’ll also add a chained version with the more succinct syntax:

'test chained reading style properties': function() {
  assert.equal(turing('#dom-test').css('background-color'), 'rgb(240, 240, 240)');
}

But there’s a catch. IE will return #f0f0f0 instead, so the revised tests look like this (with camelCase assertions added as well):

'test reading style properties': function() {
  var element = turing.dom.get('#dom-test')[0],
      expected = element.currentStyle ? '#f0f0f0' : 'rgb(240, 240, 240)';
  assert.equal(turing.dom.css(element, 'background-color'), expected);
  assert.equal(turing.dom.css(element, 'backgroundColor'), expected);
},

'test chained reading style properties': function() {
  var element = turing.dom.get('#dom-test')[0],
      expected = element.currentStyle ? '#f0f0f0' : 'rgb(240, 240, 240)';
  assert.equal(turing('#dom-test').css('background-color'), expected);
  assert.equal(turing('#dom-test').css('backgroundColor'), expected);
}

Ideally our CSS API would return the same values. Maybe this could be something for a future iteration?

The way I’ve implemented this is to set up a getStyle functional internal to the turing.dom module. One is IE-friendly and the other is for W3C browsers:

if (document.documentElement.currentStyle) {
  getStyle = function(element, property) {
    return element.currentStyle[camelCase(property)];
  };
} else if (window.getComputedStyle) {
  getStyle = function(element, property) {
    return document.defaultView.getComputedStyle(element, null).getPropertyValue(uncamel(property));
  };
}

/**
 * Gets or sets style values.
 *
 * @param {Object} element A DOM element 
 * @returns {Object} The style value
 */
dom.css = function(element, options) {
  if (typeof options === 'string') {
    return getStyle(element, options);
  }
};

Set isn’t implemented yet of course. The chained hooks look like this:

turing.domChain = {
  /**
   * Get or set styles.
   *
   * @param {Objects} options Either options for a style to set or a property name
   * @returns {Object} `this` or the style property
   */
  css: function(options) {
    if (typeof options === 'string') {
      return this.elements.length > 0 ? getStyle(this.elements[0], options) : null;
    } else {
      for (var i = 0; i < this.elements.length; i++) {
        dom.css(this[i], options);
      }
    }
    return this;
  },

Conclusion

On the surface reading CSS properties looks easy, but it takes some effort to create a consistent API across browsers. A production framework would go deeper than this implementation — take a look at jQuery’s CSS module for more details.

This week’s code was commit 9dc312f.

References

Node Roundup: npm link, mongoq, EventEmitter

13 Apr 2011 | By Alex Young | Comments | Tags node modules mongo npm

npm link

Isaac Schlueter posted about npm link on the Node blog. This is an npm command for use during development — link installing a package will install it using its package.json file, without having to continually rebuild it as you work on it.

The link command has been refactored in npm 1.0, and Isaac explains the thinking behind the new implementation.

mongoq

MongoDB client libraries always seem to be too complicated. Defining schemas might be nice for application code that requires validation and other architectural features, but most of the time I just want to quickly dump or fetch data without thinking about the schema or model classes. I thought that was kind of what NoSQL document-based databases were all about?

So the next time I’m feeling lazy I’m going to try mongoq by zzdhidden. It’s a small MongoDB client library based on node-mongodb-native, and the syntax seems simple enough:

var db = require('mongoq'),
    testdb = db('mongodb://fred:foobar@localhost/testdb');

testdb.collection('people').insert({ name: 'Alex', occupation: 'Wizard' }, function(err, doc) {
  // etc...
});

Notice that it uses the standard connection string, which means you can stick database authentication details in there.

EventEmitter

EventEmitter (MIT/GPL) by Oliver Caldwell is a version of Node’s EventEmitter for browsers. Oliver has written a fair bit of open source client-side JavaScript, including the Spark framework.

If you look at events.js in Node and compare it to EventEmitter.js you can see that they’re similar but Oliver’s version has been somewhat simplified.

jQuery Roundup: Easie, jquery-qrcode, tmpload

12 Apr 2011 | By Alex Young | Comments | Tags jquery plugins templating animations

Easie

jquery.easie.js (GitHub: jaukia / easie) by Janne Aukia provides CSS3-compatible cubic-Bézier easings for jQuery:

$('selector').animate({ top: 100 }, $.easie(0.25,0.1,0.25,1.0));

There are also predefined easings, with names like easieEaseIn. The easie project page has animated examples of these, with an interactive graphical Bézier curve for changing the parameters.

jquery-qrcode

jquery-qrcode (GitHub: jeromeetienne / jquery-qrcode) by Jerome Etienne generates HTML-based qrcodes, which means they should show up on practically any browser without depending on any server-side image generation.

Like all good jQuery plugins, it’s extremely easy to use:

jquery('#qrcode').qrcode('this plugin is great');

tmpload

tmpload (GitHub: markdalgleish / tmpload) by Mark Dalgleish can load and cache templates intelligently.

Downloading a remote template looks like this:

$.tmpload('search', 'path/to/search.tmpl');

And I thought this deferred example was neat:

$.when(
  $.tmpload('search'),
  $.getJSON('path/to/data.json')
).then(function(tmpl, data){
  $.tmpl(tmpl, data).appendTo('#target');
});

Node Tutorial Part 20: Backbone.js

11 Apr 2011 | By Alex Young | Comments | Tags server node tutorials lmawa nodepad

Welcome to part 20 of Let’s Make a Web App, a tutorial series about building a web app with Node. This series will walk you through the major areas you’ll need to face when building your own applications. These tutorials are tagged with lmawa.

Click to show previous tutorials.

Backbone Persistence

I haven’t yet hooked up our interface and models to Backbone’s persistence layer. I generally work by relying on model.save(attributes) and model.destroy(). The save method knows when to create or update based on if the id attribute has been set — remember this, because prior to this tutorial Nodepad was using _id which confuses Backbone.

The add/remove document toolbar could use a skeleton Backbone view like this:

ListToolBar = Backbone.View.extend({
  el: $('#left .toolbar'),

  events: {
    'click #create-document': 'add',
    'click #delete-document': 'remove'
  },

  initialize: function(model) {
    _.bindAll(this, 'add', 'remove');
    this.model = model;
  },

  add: function(e) {
    // TODO: Create a new document
  },

  remove: function(e) {
    e.preventDefault();
    if (confirm('Are you sure you want to delete that document?')) {
      this.model.destroy();
    }
  }
});

The destroy method will delete documents using a HTTP DELETE, the same way as my previous jQuery implementation. We actually need to instantiate ListToolBar with a model though, so where should that happen? I decided to put it in the DocumentRow view:

open: function() {
  $('#document-list .selected').removeClass('selected');
  $(this.el).addClass('selected');
  this.model.display();
  this.toolbar = new ListToolBar(this.model);
}

Every time a document is selected, a toolbar will be instantiated. Now there’s a relationship between the toolbar view and the current document.

We still need to create a new document when the + button is pressed… This is basically a case of instantiating a document and calling save:

add: function(e) {
  e.preventDefault();
  var d = new Document({ title: 'Untitled Document', data: '' });
  d.save();

  // Add it to the collection
  Documents.add(d);

  // addDocument is a new method I've added to DocumentList which just appends
  // the right elements to the unordered list
  appView.documentList.addDocument(d);

  // Trigger an open
  d.rowView.open();
}

There’s some housekeeping going on there, but notice that we basically just call save to make Backbone do all the boring Ajax work for us.

DocumentControls

I’ve also added a view called DocumentControls which manages updating documents. By now nothing in this should really surprise you:

DocumentControls = Backbone.View.extend({
  el: $('#controls'),

  events: {
    'click #save-button': 'save'
  },

  initialize: function(model) {
    _.bindAll(this, 'save', 'showHTML');
    this.model = model;
  },

  save: function(e) {
    this.model.set({
      title: $('input.title').val(),
      data: $('#editor').val()
    });
    
    this.model.save();
    this.model.rowView.render();
    e.preventDefault();
  },

  showHTML: function(e) {
    e.preventDefault();
    // TODO
  }
});

The line that reads this.model.rowView.render() is just triggering the DocumentRow to update its contents. For clarity, it reads like this:

render: function() {
  $(this.el).html(this.template({
    id: this.model.id,
    title: this.model.get('title')
  }));
  return this;
}

Conclusion

I hope it’s now clear that working with Backbone and REST APIs can be less work than a mess of Ajax calls and CSS selectors.

This week’s code was commit 7d5cc3d.

Win A Hacker Monthly Subscription

08 Apr 2011 | By Alex Young | Comments | Tags magazines security

Hacker Monthly is a printed and digital magazine that collects the best posts from Hacker News. Issue 11 has this on the cover:

($=[$=[]][(__=!$+$)[_=-~-~-~$]+({}+$)[_/_]+ ($$=($_=!''+$)[_/_]+$_[+$])])()[__[_/_]+__ [_+~$]+$_[_]+$$](_/_)

You may have seen this obtuse piece of JavaScript before — it will display an alert and pop up the browser cookie. Adam Cecchetti reverse engineers this code and explains exactly how it works. I’m fascinated by security, so I enjoyed reading this rare and detailed JavaScript security-related post.

If you’d like to read more from Hacker Monthly, we’ve got 5 digital subscriptions to give away — worth $29 each! Just post a comment with your favourite JavaScript one-liner, and I’ll select 5 winners. Make sure your comment has a way for us to get in touch (a link to a contact form or Twitter account would suffice).

Winners

The response to this was overwhelming, there were some great examples of JavaScript’s strengths, weaknesses, and obscure features.

If you’re a winner, I’ve followed you on Twitter/Facebook, so please follow back and direct message me with an email address that I can send the Hacker Monthly folks.

These are the winners:

א=-~-~[],ב=-~א,ג=({})+[],ד=[].א+[],ה=![]+[],ו=!![]+[],ד[א]+ה[ב-א]+ד[ב+א]+ה[א]+([][ג[א+ב]+ג[ב-א]+ד[ב-א]+ה[ב]+ג[ב+ב]+ו[ב-א]+ו[א]+ג[א+ב]+ג[ב+ב]+ג[ב-א]+ו[ב - א]]+[])[(א<<א)+א+ב]+ג[ב]+ה[ב]

- Golmote

(function ___(__){return function(_){return _!='?' && ___(__+_) || __+'!';};})('d')('a')('i')('l')('y')('j')('s')(' ')('r')('o')('c')('k')('s')('?')

- Florian Cargoet

// From jQuery's $.each():
for ( var value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}

- tkazec

Quite a simple one, posted by John Resig, but it felt like a real epiphany when I first saw it:

Array.max = function() { return Math.max.apply(Math, arguments); };
Array.min = function() { return Math.min.apply(Math, arguments); };

- Gary Chambers

// Remove local scope access from eval:
;(function(){var oldEval=eval;eval=function eval() {oldEval.apply(this,arguments)}})();

- Bradley

Francois Laberge’s WebGL Examples

Francois Laberge sent me some cool 3D examples:

He’s working on some interesting WebGL related projects and posts about them on endergen.com.

Let's Make a Framework: DOM Manipulation Part 3

07 Apr 2011 | By Alex Young | Comments | Tags frameworks tutorials lmaf documentation dom css

Welcome to part 57 of Let’s Make a Framework, the ongoing series about building a JavaScript framework.

If you haven’t been following along, these articles are tagged with lmaf. The project we’re creating is called Turing. Documentation is available at turingjs.com.

Last week I implemented some DOM methods, like append and html for reading innerHTML. There are still yet more DOM manipulation methods that we can build on the work we’ve done so far.

text

The text method should work like html, in that it either returns the text contents of an element or sets them. Given this test:

'test text nodes can be read': function() {
  assert.ok(turing('#dom-html-read-test').text().match(/Example/));
}

And this HTML:

<div id="dom-html-read-test">
  <p>Example</p>
</div>

Then it should be fairly easy to write something that extracts the text nodes.

jQuery’s Implementation

Let’s look at how jQuery does it:

Sizzle.getText = function( elems ) {
	var ret = "", elem;

	for ( var i = 0; elems[i]; i++ ) {
		elem = elems[i];

		// Get the text from text nodes and CDATA nodes
		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
			ret += elem.nodeValue;

		// Traverse everything else, except comment nodes
		} else if ( elem.nodeType !== 8 ) {
			ret += Sizzle.getText( elem.childNodes );
		}
	}

	return ret;
};

This is from Sizzle. It will get the text node of all the descendants recursively.

The comments help follow what happens based on each type of node (else the nodeType integer values would be confusing). This could actually be expressed using Node:

  if (elem.nodeType === Node.TEXT_NODE
      || elem.nodeType === Node.CDATA_SECTION_NODE) {
};

Unfortunately, not all browsers support this. I thought it might be more friendly for readers if we set up an object to store node types:

nodeTypes = {
  ELEMENT_NODE:                  1,
  ATTRIBUTE_NODE:                2,
  TEXT_NODE:                     3,
  CDATA_SECTION_NODE:            4,
  ENTITY_REFERENCE_NODE:         5,
  ENTITY_NODE:                   6,
  PROCESSING_INSTRUCTION_NODE:   7,
  COMMENT_NODE:                  8,
  DOCUMENT_NODE:                 9,
  DOCUMENT_TYPE_NODE:            10,
  DOCUMENT_FRAGMENT_NODE:        11,
  NOTATION_NODE:                 12
};

So I ended up implementing a similar function to jQuery:

function getText(elements) {
  var results = '', element, i;

  for (i = 0; elements[i]; i++) {
    element = elements[i];
    if (element.nodeType === nodeTypes.TEXT_NODE 
        || element.nodeType === nodeTypes.CDATA_SECTION_NODE) {
      results += element.nodeValue;
    } else if (element.nodeType !== nodeTypes.COMMENT_NODE) {
      results += getText(element.childNodes);
    }
  }

  return results;
};

I also set up some short functions for turing.dom and the chained API:

/**
 * Set or get text nodes.
 *
 * @param {Object} element A DOM element
 * @param {String} text A string containing text
 */
dom.text = function(element, text) {
  if (arguments.length === 1) {
    return getText(element);
  }
};

// ...

turing.domChain = {
  // ...

  /**
   * Get or set text nodes.  Applied to every element.
   *
   * @param {String} text A string containing text to set
   * @returns {Object} `this` or the text content
   */
  text: function(text) {
    if (arguments.length === 0) {
      return this.elements.length === 0 ? null : getText(this.elements);
    } else {
      for (var i = 0; i < this.elements.length; i++) {
      }
    }
    return this;
  }

Like reading innerHTML, working with text nodes is fairly straightforward.

Writing Text

jQuery writes to text nodes like this:

return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );

The key thing here is it uses empty to clear the node first.

jQuery has a lot of features like caching that it has to handle, which makes empty more complex than our simple case. Given this test:

'test nodes can be emptied': function() {
  turing.dom.empty(turing.dom.get('#dom-html-empty-test')[0]);
  assert.equal(turing.dom.get('#dom-html-empty-test')[0].innerHTML, '');
}

The bare minimum required to implement the equivalent functionality is quite simple:

/**
 * Empty nodes.
 *
 * @param {Object} element A DOM element
 */
dom.empty = function(element) {
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
};

A test for writing text might look like this:

'test chained text nodes can be written': function() {
  turing('#dom-text-write-test p').text('Written again');
  assert.ok(turing.dom.get('#dom-text-write-test p')[0].innerHTML.match(/Written again/));
}

And an implementation is fairly basic DOM stuff, with appendChild and createTextNode, combined with dom.empty:

/**
 * Set or get text nodes.
 *
 * @param {Object} element A DOM element
 * @param {String} text A string containing text
 */
dom.text = function(element, text) {
  if (arguments.length === 1) {
    return getText(element);
  } else {
    dom.empty(element);
    element.appendChild(document.createTextNode(text));
  }
};

// Chained:

/**
 * Get or set text nodes.  Applied to every element.
 *
 * @param {String} text A string containing text to set
 * @returns {Object} `this` or the text content
 */
text: function(text) {
  if (arguments.length === 0) {
    return this.elements.length === 0 ? null : getText(this.elements);
  } else {
    for (var i = 0; i < this.elements.length; i++) {
      dom.text(this.elements[i], text);
    }
  }
  return this;
}

I tested this in WebKit, IE 6, and Firefox.

Conclusion

Dealing with text nodes is simpler than HTML, but it requires a little bit of care to make it feel as straightforward as it should be. jQuery’s implementation is more complicated than my tutorial because it does a lot of housekeeping in the background, but the basic principles are outlined here.

This week’s code is in commit 2c7ca.

Node Roundup: 0.4.5, Asset, Haraka

06 Apr 2011 | By Alex Young | Comments | Tags node modules email

Node 0.4.5

Node 0.4.5 has been released, which has yet more TLS improvements, bug fixes, and it now uses V8 3.1.8.8. One of the comments on the Node Blog reads:

Big thanks for yet another awesome Nodejs release. The TLS performance improvements are staggering!

- Superstructor

Sounds worth checking out to me!

Asset

Asset by TJ Holowaychuk is a client-side script downloader. This would download jQuery and Raphael to ./public:

asset raphael jquery@1.4.3

The GitHub page at visionmedia / asset has full usage details, and it can be installed with npm install asset. The list of available assets are in assets.json — these can be searched with search [query].

TJ also posted a screencast about three.js and Cluster visualisation. He hasn’t actually released the code, but it’s inspirational material if you’re looking for WebGL ideas.

Haraka

I’m often surprised by the lack of interesting non-web software built with Node, considering how potentially easy it is to create extremely fast socket-based servers. So I was happy to find Haraka by baudehlo which is a Node email server. The author suggests that it shouldn’t be used to replace existing SMTP servers, but instead sit in front of them and extend them with new features. Haraka has a plugin system to attempt to make this easier than working with a typical SMTPd.

The Haraka plugin documentation demonstrates how to create a plugin. Plugins are based around event hooks and callbacks, so they should be fairly easy to write.

If you’re creating interesting servers with Node I’d love to hear about it!

jQuery Roundup: 1.5.2, Waypoints, Sausage

05 Apr 2011 | By Alex Young | Comments | Tags jquery plugins

jQuery 1.5.2 Released

jQuery 1.5.2 was released on March 31st, just as planned. Who says IT projects are late and over-budget?! This release includes 18 bug fixes.

jQuery Waypoints

jQuery Waypoints (GitHub: imakewebthings / jquery-waypoints, MIT/GPL) by Caleb Troughton triggers a function when scrolling reaches an element. This could be used to create infinite scrolling, sticky elements, and even scrolling analytics).

Waypoints works by applying a callback to an element:

$('.entry').waypoint(function() {
  alert('You have scrolled to an entry.');
});

Sausage

Sausage (GitHub: christophercliff / sausage) by Christopher Cliff is a jQuery UI widget for presenting context alongside infinite scrolling pages. There’s a nice example of Sausage showing CouchDB documentation, with section annotations along the right-hand-side.

That example uses this JavaScript:

$(window)
    .sausage({
        content: function (i, $page) {
            return '<span class="sausage-span">'
                + $page.find('.anchor').first().text()
                + '</span>';
        }
    });

Each section of text in the CouchDB documentation is wrapped with an element with a class of page, so the $page part would really look like this: $('.page').find('.anchor').first().text(). That lets Sausage find the title of each section to use for its navigation.

Node Tutorial Part 19: Backbone.js

04 Apr 2011 | By Alex Young | Comments | Tags server node tutorials lmawa nodepad

Welcome to part 19 of Let’s Make a Web App, a tutorial series about building a web app with Node. This series will walk you through the major areas you’ll need to face when building your own applications. These tutorials are tagged with lmawa.

Click to show previous tutorials.

Backbone.js

Backbone.js is a library for writing client-side JavaScript. It provides base classes for models, collections, and views. If you’ve ever worked on a project with a library like jQuery and found client-side code becomes unwieldy and hard to navigate, Backbone.js can help!

Nodepad’s simple structure means we’re mostly interested in Backbone’s models, collections, and the persistence layer. The persistence layer is the part that talks to the server, communicating using JSON. The views are also useful, however. As Backbone’s documentation says:

It’s all too easy to create JavaScript applications that end up as tangled piles of jQuery selectors and callbacks.

To put it simply, a Backbone application uses models to interact with data, collections to manage sets of models, and views to link events to models and generate dynamic HTML based on templates. The templates are typically hidden HTML, so in our case we’d write stubs in our Jade templates with display: none, then Backbone views would be used to clone and populate these HTML fragments with data.

Planning a Backbone Application

Planning a Backbone application is a bit like planning server-side software — separate out the data from the views and controllers.

Our application consists of:

  1. A document with an ID, title, and body
  2. A document title row
  3. A list of titles which can be selected, added to, and deleted

This already sounds like Backbone primitives:

  1. Backbone.Model, Document: A document with an ID, title, and body
  2. Backbone.View, DocumentRow: A document title row
  3. Backbone.View, DocumentList: A list of titles which can be selected, added to, and deleted
  4. Backbone.Collection, Documents: A collection of documents

Models

Defining models is simple. Use Backbone.Model.extend to create a new model for your application:

var Document = Backbone.Model.extend({
});

We can’t actually do very much with this yet, and there also needs to be a collection of models. Collections can be reusable or almost like a singleton (for collections that are used once). In this case we just want one list of documents, so I like to create this type of collection by instantiating a Backbone.Collection:

var Documents = new Backbone.Collection();
Documents.url = '/documents/titles.json';

Each model and collection has a url property — it can be a function or a string. Our collection will always use the same URL. And the reason I’m getting the titles and IDs rather than /documents.json is because we can load the full document on demand.

I usually use functions when I need to use data to construct the URL:

var Document = Backbone.Model.extend({
  Collection: Documents,

  url: function() {
    return '/documents/' + this.get('_id') + '.json';
  }
});

As you can see, when loading a Document the URL will contain our Mongo object’s ID.

There’s something slightly fiddly still left to do. While we could use Documents.fetch() to load the titles, the Backbone authors suggest writing it into the template on the server-side to cut down on the extra Ajax request. We can do this in views/layout.jade like this:

script(type='text/javascript')
  Documents.refresh(!{JSON.stringify(documents)});

The part that reads !{JSON.stringify(documents)} is actually server-side, and Documents.refresh() just overwrites all of the collection’s data in the browser.

Views

The two main views are DocumentRow and DocumentList. The DocumentRow view requires some Jade changes as well. It also uses Backbone.View’s events property to watch for clicks on the document titles:

var DocumentRow = Backbone.View.extend({
  tagName: 'li',

  events: {
    'click a': 'open'
  },

  template: _.template($('#document-row-template').html()),

  initialize: function() {
    _.bindAll(this, 'render');
  },

  open: function() {
    $('#document-list .selected').removeClass('selected');
    $(this.el).addClass('selected');
    this.model.display();
  },

  render: function() {
    $(this.el).html(this.template({
      id: this.model.id,
      title: this.model.get('title')
    }));
    return this;
  }
});

The corresponding Jade template looks like this:

ul#document-list
  li#document-row-template(style='display: none')
    a(id='document_{{ id }}') {{ title }}

The document-row-template part will be hidden and used to generate several instances of DocumentRow. The events property in DocumentRow is Backbone’s convention for mapping events to methods. The left-most part, click is the event, then the rest of the string is used as the selector to observe. The Underscore method, bindAll is used to make sure this refers to an instance of DocumentRow when render is called from an event.

I’m using the standard Underscore _.template method to insert each document’s value:

_.templateSettings = {
  interpolate : /\{\{(.+?)\}\}/g
};

To implement displaying a document it simply populates the form fields:

Document = Backbone.Model.extend({
  Collection: Documents,

  url: function() {
    return '/documents/' + this.get('_id') + '.json';
  },

  display: function() {
    this.fetch({
      success: function(model, response) {
        $('#editor-container input.title').val(model.get('title'));
        $('#editor').val(model.get('data'));
      }
    });
  }
});

Conclusion

I’ve only scratched the surface of Backbone here, but you can see some of the major components of the library. When using Backbone with a larger project it may make sense to keep each class in its own file. In general it can certainly help improve over a monolithic soup of selectors and callbacks.

I’ll continue implementing Nodepad’s client-side code with Backbone next week.

This week’s commit was 8e86240.

Minecraft and JavaScript, Tempo, Janpu

01 Apr 2011 | By Alex Young | Comments | Tags webgl graphics games templating games

There’s not a doubt in my mind that a version of Minecraft in JavaScript is possible. Imagine it… WebGL graphics and a fast Node server!

AlteredQualia has some impressive WebGL demos that use three.js. I seem to remember covering three.js on this blog before, but the author has really been pushing it to new heights lately. The Minecraft demo really caught my imagination.

There’s also Minecraft-JS (demo) which is a 2D version of the game.

As far as the server is concerned there’s Nodecraft by Jeremy Apthorp. Development appears to have stalled, but Jeremy made an interesting stab at building something.

Node-backed WebGL games will be huge — please send us your games and demos!

Tempo

Tempo (GitHub: twigkit / tempo) by Stefan Olafsson and Tyler Tate is a client-side JSON rendering engine that works without a framework like jQuery. By passing Tempo an element ID and some data, it can generate suitable HTML from a fragment like this:

<ol id="tweets">
    <li data-template>
        <img src="" />
        <h3></h3>
        <p></p>
    </li>
    <li data-template-fallback>Sorry, JavaScript required!</li>
</ol>

The authors suggest that this makes Ajax content easier to work with, which makes a lot of sense to me. There’s nothing worse than dealing with plain JavaScript string handling when you really want convenient interpolation. I also like the fact it degrades appropriately, as seen in the example above.

Janpu

Janpu by Michal Budzynski and Krystian Siemiatkowski is a little JavaScript game with a great big UNICEF donation link to help the relief efforts in Japan.

Let's Make a Framework: DOM Manipulation Part 2

31 Mar 2011 | By Alex Young | Comments | Tags frameworks tutorials lmaf documentation dom css

Welcome to part 56 of Let’s Make a Framework, the ongoing series about building a JavaScript framework.

If you haven’t been following along, these articles are tagged with lmaf. The project we’re creating is called Turing. Documentation is available at turingjs.com.

Last week I looked at how to build a cross-browser innerHTML API. In this week’s post I’ll continue looking at DOM manipulation.

Reading HTML

I want to read HTML like this:

'test HTML can be read': function() {
  assert.ok(turing('#dom-html-read-test').html().match(/<p>Example/));
}

Given this HTML:

<div id="dom-html-read-test">
  <p>Example</p>
</div>

This won’t work right now. The dom module html methods need to determine if we’re reading or writing, and return a suitable result when accessed through the chained API. This can be done based on arguments:

/**
 * Set or get innerHTML.
 *
 * @param {Object} element A DOM element
 * @param {String} html A string containing HTML
 */
dom.html = function(element, html) {
  if (arguments.length === 1)
    return element.innerHTML;

  try {
    element.innerHTML = html;
  } catch (e) {
    dom.replace(element, html);
  }
};

// Elsewhere...

turing.domChain = {
  // ...

  /**
   * Chained DOM manipulation.  Applied to every element.
   *
   * @param {String} html A string containing HTML
   * @returns {Object} `this`
   */
  html: function(html) {
    if (arguments.length === 0) {
      return this.elements.length === 0 ? null : dom.html(this[0]);
    } else {
      for (var i = 0; i < this.elements.length; i++) {
        dom.html(this[i], html);
      }
    }
    return this;
  },

In the case of a chain, it returns the first element’s innerHTML.

The results may be slightly different in each browser (IE will make node names uppercase), but this should pass:

'test HTML can be read': function() {
  assert.ok(turing('#dom-html-read-test').html().match(/<p>Example/i));
}

Implementing append()

To support appending HTML, I decided to refactor the dom.replace function to make it generic. Previously it looked like this:

/**
 * Replaces the content of an element.
 *
 * @param {Object} element A DOM element
 * @param {String} html A string containing HTML
 */
dom.replace = function(element, html) {
  var context = document,
      isTable = element.nodeName === 'TABLE',
      insert,
      div;

  div = context.createElement('div');
  div.innerHTML = '<' + element.nodeName + '>' + html + '</' + element.nodeName + '>';
  insert = isTable ? div.lastChild.lastChild : div.lastChild;

  element.replaceChild(insert, element.firstChild);
  div = null;
};

The line that uses replaceChild can be replaced with a callback to make this code reusable:

manipulateDOM = function(element, html, callback) {
  var context = document,
      isTable = element.nodeName === 'TABLE',
      shim,
      div;

  div = context.createElement('div');
  div.innerHTML = '<' + element.nodeName + '>' + html + '</' + element.nodeName + '>';
  shim = isTable ? div.lastChild.lastChild : div.lastChild;
  callback(isTable ? element.lastChild : element, shim);
  div = null;
};

Now append can be implemented using appendChild in the callback:

dom.append = function(element, html) {
  manipulateDOM(element, html, function(insertTo, shim) {
    insertTo.appendChild(shim.firstChild);
  });
};

I wrote some tests for this during development to see if it actually worked:

'test HTML can be appended': function() {
  turing('#dom-html-append').append('<p>Example 2</p>');
  assert.ok(turing('#dom-html-append').html().match(/Example[^E]*Example 2/));
},

'test HTML can be appended to tables': function() {
  turing('#dom-table-append').append('<tr><td>X2</td></tr>');
  assert.ok(turing('#dom-table-append').html().match(/X1[^X]*X2/));
}

This is the corresponding HTML:

<table id="dom-table-append">
  <tr>
    <td>X1</td>
  </tr>
</table>
<div id="dom-html-append">
  <p>Example</p>
</div>

I use case-insensitive regular expressions to test the results in innerHTML.

Conclusion

This pattern of DOM manipulation is based on jQuery, which I explored back in part 53. The main reason manipulateDOM exists is just to turn text into HTML, but it also has to manage hidden insertion of tbody which can make IE behave confusingly.

There’s a lot of things left to look at in this area, in particular CSS and attribute manipulation APIs. I’ve tested what I’ve done so far in IE6, just for kicks!