This package has been deprecated

Author message:

this project is no longer maintained

domv

4.2.0 • Public • Published

domv

Create views as components using DOM. Run the same code on the browser and on the server.

  1. Introduction
  2. For servers
  3. For browsers
  4. Tutorial
  5. Creating a Component
  6. Subclassing
  7. HtmlDocument
  8. Modules
  9. API Reference

Introduction

This is a library that helps you create your html documents using plain javascript, instead of using a template language. No need to learn yet another language with new syntax. Using domv you can easily break up your page design into smaller components and reuse them in other projects.

In most templating languages reusability is hard because once the html is generated, you can no longer modify it easily. In domv, each component you create or use is a proper OOP class which can be subclassed or modified by calling methods or using setters. After you have set up all your components you can serialize them to html markup.

It should also help reduce coupling in your application. This is because the Components are more clearly separated from each other, there is less dependency between them.

Security is another benefit, user input is not parsed as HTML unless you explicitly want it to be.

This library has unit tests with 100% branch coverage. You can also run this test suite in the various browsers.

For servers

If you would like to use domv on your server using node.js or io.js you need to install domv for your project first:

npm install domv inherits --save

You will then need to obtain a DOM Document, domv has a convenience function to create this for you using jsdom:

var domv = require('domv');
var document = domv.createHtmlDomDocument();

For browsers

If you would like to use domv in a web browser, you can use one of two methods.

If you are already using browserify for your project, you can simply require domv:

var domv = require('domv');

Or you can generate an UMD bundle:

npm install domv
cd node_modules/domv
npm run bundle

This will generate a javascript file for you in build/domv.js, here's an example on using it:

<!DOCTYPE html>
<html>
    <head>
        <script src="domv.js"></script> 
        <script>
        (function(document, domv)
        {
             var image = domv.create(document, 'img', {src: 'foo.jpg'});
             // ...
        } (window.document, window.domv));
        </script> 
    </head>
    <body>
    </body>
</html>

Tutorial

Creating a Component

Now that you have obtained a DOM Document and have access to the domv module, you can create new Components. When using the domv API you do not have to worry about which browser or server your code is running in.

The most important part of domv is the Component class. This example shows how you can create one:

var domv = require('domv');
var image = domv.create(document, 'img', {src: 'foo.jpg', width: 200, height: 100});
console.log(image.stringifyAsHtml());
// <img src="foo.jpg" height="100" width="200">

Or you can use a convenient shorthand:

var domv = require('domv');
var div = domv.shorthand(document, 'div');
var img = domv.shorthand(document, 'img');
var component = div('columns',
    div('left', 'This is the left column!'),
    div('right',
        'This is the right column...',
        img({src: 'foo.jpg', width: 200, height: 100})
    )
);
console.log(component.stringifyAsHtml());
// <div class="columns"><div class="left">This is the left column!</div>
// <div class="right">This is the right column...
// <img src="foo.jpg" height="100" width="200"></div></div>

Each Component actually wraps a DOM Node. This is useful to overcome differences between browsers and jsdom, however the real strength is that it lets you create subclasses of Component (more on that later). Here is an example on wrapping:

var body = domv.wrap(document.body);
console.log(body !== document.body); // true
console.log(body.outerNode === document.body); // true

Note that domv.wrap will always create a new Component (this is by design):

var body = domv.wrap(document.body);
var body2 = domv.wrap(document.body);
console.log(body !== body2); // true
console.log(body.outerNode === body2.outerNode); // true

Subclassing

Subclassing Component is what makes domv useful. A Component wraps a DOM Node and by doing so it gives you a stable API to modify the DOM with. When browsers introduce new features, your old Components will still use the same stable API without any clashes in attributes or method names. A lot of DOM methods are proxied with some extra flavoring (such asappendChild, childNodes, etc).

Here is an example:

// AuthorInfo.js
'use strict';
var domv = require('domv');
 
// the constructor for AuthorInfo
function AuthorInfo(node, author)
{
    // call the constructor of our super class
    domv.Component.call(this, node, 'div');
 
    if (this.isCreationConstructor(node))
    {
        var img = this.shorthand('img');
        var h2 = this.shorthand('h2');
        var a = this.shorthand('a');
        var p = this.shorthand('p');
        var text = this.textShorthand();
 
        this.cls('AuthorInfo');
 
        this.attr('data-id', author.id);
 
        this.appendChild(
            this.avatar = img('avatar', { src: author.avatarUrl }),
            this.name = h2('name',
                this.nameLink = a(
                    { href: '/author/' + encodeURIComponent(author.id) },
                    text(author.displayName)
                )
            ),
            this.introduction = p('introduction',
                'about me: ',
                text(author.introduction)
            )
        );
    }
    else // wrapping constructor
    {
        // assertHasClass and assertSelector are used to throw an
        // Error in case invalid content is given to this constructor
        this.assertHasClass('AuthorInfo');
        this.avatar = this.assertSelector('> .avatar');
        this.name = this.assertSelector('> .name');
        this.nameLink = this.name.assertSelector('> a');
        this.introduction = this.assertSelector('> .introduction');
    }
 
    // Add a DOM event listener
    this.on('click', this._onclick);
}
 
module.exports = AuthorInfo;
 
// "inherits" module provides prototype chaining
require('inherits')(AuthorInfo, domv.Component);
 
Object.defineProperty(AuthorInfo.prototype, 'id', {
    get: function()
    {
        return this.getAttr('data-id');
    }
});
 
Object.defineProperty(AuthorInfo.prototype, 'displayName', {
    get: function()
    {
        return this.nameLink.textContent;
    },
    set: function(val)
    {
        this.nameLink.textContent = val;
    }
});
 
AuthorInfo.prototype._onclick = function(e)
{
    this.toggleClass('highlighted');
};

(If you are using IntelliJ or WebStorm, there is a File Template you can import)

The constructor of a Component subclass must always accept a node argument as its first argument (a DOM Node). If this argument is a Document the Component must create new DOM elements and attributes.

As a rule of thumb, the outer element of a Component always has a class attribute that contains the name of the Component (beginning with a capital letter). Any other class name begins with a lowercase letter. This is import because it lets you easily debug where generated HTML came from, and it is also important when making sure your CSS rules only match your own Component.

var domv = require('domv');
var AuthorInfo = require('./AuthorInfo');
var body = domv.wrap(document.body);
 
var author = {
    id: 500,
    avatarUrl: 'someone.jpg',
    displayName: 'Someone',
    introduction: 'Hi! I am someone!!!'
};
 
var authorComponent = new AuthorInfo(document, author);
body.appendChild(authorComponent);

This results in the following HTML:

<div class="AuthorInfo" data-id="500">
    <img src="someone.jpg" class="avatar">
    <h2 class="name">
        <a href="/author/500">Someone</a>
    </h2>
    <p class="introduction">about me: Hi! I am someone!!!</p>
</div>

If the node argument is a different kind of Node, usually an Element, the Component must wrap existing content:

var domv = require('domv');
var AuthorInfo = require('./AuthorInfo');
var body = domv.wrap(document.body);
 
var element = body.selector('> .AuthorInfo');
var authorComponent = new AuthorInfo(element);
authorComponent.displayName = 'I just renamed myself!';

The wrapping constructor is especially useful if you would like to modify the DOM in the browser after having received HTML from the server.

Outer and Inner nodes

Each Component has an "outer" node and an "inner" node. You can access these nodes by using the outerNode and innerNode attributes. For most components innerNode and outerNode refer to the same Node. But you can set them to something different anytime (usually in the constructor).

The outerNode is used whenever you make a change to DOM attributes. Also, if a child Component is added to a parent Component, the outerNode of the child is used.

The innerNode is used to add any children and is also used for getters such as childNodes, firstChild, etc.

var domv = require('domv');
var document = domv.wrap(domv.createHtmlDomDocument());
var foo = document.create('div', 'outer');
var fooInner = document.create('div', 'inner');
foo.appendChild(fooInner);
foo.innerNode = fooInner;
foo.attr('data-example', 'This is set on the outer node!');
var bar = document.create('span', '', 'I am a span');
foo.appendChild(bar);
console.log(foo.stringifyAsHtml());

Result:

<div class="outer" data-example="This is set on the outer node!">
    <div class="inner">
        <span>I am a span</span>
    </div>
</div>

HtmlDocument

HtmlDocument is a Component subclass which lets you create or wrap a html document. This includes the html, head, title and body elements, but it also provides a number of convenient methods to easily add style, script elements and JSON data. Unlike normal Components the node argument in HtmlDocument is optional, if not given, a new Document is constructed for you.

var domv = require('domv');
var author = {
    id: 500,
    avatarUrl: 'someone.jpg',
    displayName: 'Someone',
    introduction: 'Hi! I am someone!!!'
};
 
var html = new domv.HtmlDocument();
html.title = 'This is my page title';
html.baseURI = 'https://example.com/foo';
html.appendChild(html.create('p', 'myParagraph', 'This paragraph is added to the body'));
html.appendChild(new AuthorInfo(html.document, author));
html.addCSS('/my-bundle.css');
html.addJS('/my-bundle.js');
html.addJSONData('user-session', {userId: 1, name: 'root'});
console.log(html.stringifyAsHtml());

This gives you:

<!DOCTYPE html>
<html>
    <head>
        <base href="https://example.com/foo">
        <title>This is my page title</title>
        <link href="/my-bundle.css" rel="stylesheet" type="text/css">
        <script async="async" src="/my-bundle.js" type="text/javascript"></script> 
        <script data-identifier="user-session" type="application/json">
            {"userId":1,"name":"root"}
        </script> 
    </head>
    <body>
        <p class="myParagraph">This paragraph is added to the body</p>
        <div data-id="500" class="AuthorInfo">
            <img src="someone.jpg" class="avatar">
            <h2 class="name"><a href="/author/500">Someone</a></h2>
            <p class="introduction">about me: Hi! I am someone!!!</p>
        </div>
    </body>
</html>

And this is an example with wrapping:

var domv = require('domv');
var html = new domv.HtmlDocument(document.documentElement);
html.title = 'A new title!';
var session = html.getJSONData('user-session');
console.log('My user id is', session.userId, 'And my name is', session.name);
var authorInfo = html.assertSelector('> .AuthorInfo', AuthorInfo);
console.log('display name of the author you are viewing:', authorInfo.displayName);

Modules

Because this is all just plain javascript, it is easy to publish your Component as a node module on npm. This works especially well when you use browserify.

Most Components also need some CSS to function properly. For this purpose stylerefs is a good solution. It lets you list the CSS or LESS files your Component needs in the source code using a require call:

'use strict';
var domv = require('domv');
 
require('static-reference')('./AuthorInfo.less', 'optional', 'filter', 'keywords');
 
// the constructor for AuthorInfo
function AuthorInfo(node, author)
{
        // call the constructor of our super class
        domv.Component.call(this, node, 'div');
 
        if (this.isCreationConstructor(node))
        {
 
        ... SNIP ...

This works a lot like browserify. You simply point the stylerefs command line tool to your code and it will recursively analyze all the require() calls in your app in order to find the CSS / LESS files that you need. You can use the output of this tool to generate a single CSS bundle file. (instead of the command line tool you can also use grunt. Look at the stylerefs documentation for more details.

API Reference

You can find a description of the API in the file api.md

Package Sidebar

Install

npm i domv

Weekly Downloads

1

Version

4.2.0

License

MIT

Last publish

Collaborators

  • joris-van-der-wel