reusable components

Putting these ideas about code organization together, we can build a reusable UI component that we can reuse across our application or in other applications.

Here is a bare-bones example of an empty widget module:

module.exports = Widget;function Widget (opts) { if (!(this instanceof Widget)) return new Widget(opts); this.element = document.createElement('div');}Widget.prototype.appendTo = function (target) { if (typeof target === 'string') target = document.querySelector(target); target.appendChild(this.element);};

Handy javascript constructor tip: you can include a this instanceof Widget check like above to let people consume your module with new Widget or Widget(). It's nice because it hides an implementation detail from your API and you still get the performance benefits and indentation wins of using prototypes.

To use this widget, just use require() to load the widget file, instantiate it, and then call .appendTo() with a css selector string or a dom element.

Like this:

var Widget = require('./widget.js');var w = Widget();w.appendTo('#container');

and now your widget will be appended to the DOM.

Creating HTML elements procedurally is fine for very simple content but gets very verbose and unclear for anything bigger. Luckily there are many transforms available to ease importing HTML into your javascript modules.

Let's extend our widget example using brfs. We can also use domify to turn the string that fs.readFileSync() returns into an html dom element:

var fs = require('fs');var domify = require('domify');var html = fs.readFileSync(__dirname + '/widget.html', 'utf8');module.exports = Widget;function Widget (opts) { if (!(this instanceof Widget)) return new Widget(opts); this.element = domify(html);}Widget.prototype.appendTo = function (target) { if (typeof target === 'string') target = document.querySelector(target); target.appendChild(this.element);};

and now our widget will load a widget.html, so let's make one:

<div class="widget"> <h1 class="name"></h1> <div class="msg"></div></div>

It's often useful to emit events. Here's how we can emit events using the built-in events module and the inherits module:

var fs = require('fs');var domify = require('domify');var inherits = require('inherits');var EventEmitter = require('events').EventEmitter;var html = fs.readFileSync(__dirname + '/widget.html', 'utf8');inherits(Widget, EventEmitter);module.exports = Widget;function Widget (opts) { if (!(this instanceof Widget)) return new Widget(opts); this.element = domify(html);}Widget.prototype.appendTo = function (target) { if (typeof target === 'string') target = document.querySelector(target); target.appendChild(this.element); this.emit('append', target);};

Now we can listen for 'append' events on our widget instance:

var Widget = require('./widget.js');var w = Widget();w.on('append', function (target) { console.log('appended to: ' + target.outerHTML);});w.appendTo('#container');

We can add more methods to our widget to set elements on the html:

var fs = require('fs');var domify = require('domify');var inherits = require('inherits');var EventEmitter = require('events').EventEmitter;var html = fs.readFileSync(__dirname + '/widget.html', 'utf8');inherits(Widget, EventEmitter);module.exports = Widget;function Widget (opts) { if (!(this instanceof Widget)) return new Widget(opts); this.element = domify(html);}Widget.prototype.appendTo = function (target) { if (typeof target === 'string') target = document.querySelector(target); target.appendChild(this.element);};Widget.prototype.setName = function (name) { this.element.querySelector('.name').textContent = name;}Widget.prototype.setMessage = function (msg) { this.element.querySelector('.msg').textContent = msg;}

If setting element attributes and content gets too verbose, check out hyperglue.

Now finally, we can toss our widget.js and widget.html into node_modules/app-widget. Since our widget uses the brfs transform, we can create a package.json with:

{ "name": "app-widget", "version": "1.0.0", "private": true, "main": "widget.js", "browserify": { "transform": [ "brfs" ] }, "dependencies": { "brfs": "^1.1.1", "inherits": "^2.0.1" }}

And now whenever we require('app-widget') from anywhere in our application, brfs will be applied to our widget.js automatically! Our widget can even maintain its own dependencies. This way we can update dependencies in one widgets without worrying about breaking changes cascading over into other widgets.

Make sure to add an exclusion in your .gitignore for node_modules/app-widget:

node_modules/*!node_modules/app-widget

You can read more about shared rendering in node and the browser if you want to learn about sharing rendering logic between node and the browser using browserify and some streaming html libraries.

results matching ""

    No results matching ""