Friday, June 7, 2013

Metawidget and Node.js

Inspired by a recent forum post and blog the next release of Metawidget (3.4) will be available as a Node.js module. This lets you use Metawidget to perform server side UI generation using JavaScript, if that's your requirement.

To install, simply use npm:

npm install metawidget

Then use the Metawidget API as normal:

var metawidget = require( 'metawidget' );
...
var mw = new metawidget.Metawidget( element );
mw.toInspect = {
   name: "Joe Bloggs",
   "DOB": "1/1/2001"
};
mw.buildWidgets();


// Print what was rendered

console.log( element.toString() );

This will render:

<table>
   <tbody>
      <tr id="table-name-row">
         <th id="table-name-label-cell"><label for="name" id="table-name-label">Name:</label></th>
         <td id="table-name-cell"><input type="text" id="name" name="name" value="Joe Bloggs"/></td>
         <td></td>
      </tr><tr id="table-DOB-row">
         <th id="table-DOB-label-cell"><label for="DOB" id="table-DOB-label">DOB:</label></th>
         <td id="table-DOB-cell"><input type="text" id="DOB" name="DOB" value="1/1/2001"/></td>
         <td></td>
      </tr>
   </tbody>
</table>

Which of course can be customized using Metawidget's pipeline architecture.

Metawidget must be used in combination with a DOM implementation. This can either be jsdom, envjs, or even a simple implementation of your own. For example:

var simpleDocument = {
   createElement: function( elementName ) {

      return {
         nodeType: 1,
         tagName: elementName.toUpperCase(),
         attributes: [],
         childNodes: [],
         setAttribute: function( name, value ) {

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               if ( this.attributes[loop].nodeName === name ) {
                  this.attributes[loop].nodeValue = value;
                  return;
               }
            }

            this.attributes.push( {
               nodeName: name,
               nodeValue: value
            } );
         },
         hasAttribute: function( name ) {

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               if ( this.attributes[loop].nodeName === name ) {
                  return true;
               }
            }

            return false;
         },
         getAttribute: function( name ) {

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               if ( this.attributes[loop].nodeName === name ) {
                  return this.attributes[loop].nodeValue;
               }
            }

            return null;
         },
         appendChild: function( childNode ) {

            this.childNodes.push( childNode );
         },
         cloneNode: function() {

            var clone = simpleDocument.createElement( elementName );

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               var attribute = this.attributes[loop];
               clone.setAttribute( attribute.nodeName, attribute.nodeValue );
            }
            for ( var loop = 0, length = this.childNodes.length; loop < length; loop++ ) {
               clone.appendChild( this.childNodes[loop].cloneNode() );
            }
            return clone;
         },
         removeChild: function( childNode ) {

            for ( var loop = 0, length = this.childNodes.length; loop < length; loop++ ) {
               if ( this.childNodes[loop] === childNode ) {
                  this.childNodes.splice( loop, 1 );
                  return childNode;
               }
            }

            throw new Error( "childNode not found: " + childNode );
         },
         ownerDocument: this,
         toString: function() {

            var toString = "<" + elementName;

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               var attribute = this.attributes[loop];
               toString += ' ' + attribute.nodeName + '="' + attribute.nodeValue + '"';
            }

            if ( this.value !== undefined ) {
               toString += ' value="' + this.value + '"';
            }

            toString += ">";

            for ( var loop = 0, length = this.childNodes.length; loop < length; loop++ ) {
               toString += this.childNodes[loop].toString();
            }

            if ( this.innerHTML !== undefined ) {
               toString += this.innerHTML;
            }

            toString += "</" + elementName + ">";
            return toString;
         }
      };
   },
   createTextNode: function( data ) {

      return {
         nodeType: 3,
         toString: function() {

            return data;
         }
      }
   }
};

var element = simpleDocument.createElement( 'div' );

Metawidget must be wrapped around a DOM element. The Metawidget constructor takes this element, and thereafter always uses element.ownerDocument rather than referencing any global document object.

0 comments: