Commits

Stephen McKamey committed af7c155

- making view entry point a function rather tahn
- renaming js unit test entry
- tweaking build process

  • Participants
  • Parent commits c5ae1b7

Comments (0)

Files changed (15)

 
 	<target name="init" description="Concatenates js">
 		<echo message="Building..." />
+		<property name="lib.dir" value="${basedir}/lib" />
 		<property name="src.dir" value="${basedir}/src" />
-		<property name="lib.dir" value="${basedir}/lib" />
 		<property name="test.dir" value="${basedir}/test" />
 		<property name="bin.dir" value="${basedir}/bin" />
 		<property name="build.dir" value="${basedir}/build" />
+		<property name="jarfile" value="${build.dir}/${ant.project.name}.jar" />
+		<property name="reports.dir" value="${build.dir}/test" />
 		<property name="docs.dir" value="${build.dir}/docs" />
-		<property name="reports.dir" value="${build.dir}/test" />
-		<property name="jarfile" value="${build.dir}/${ant.project.name}.jar" />
 
 		<!-- set the classpath for the project including /bin and /lib -->
 		<path id="classpath.path">
 			<fileset file="${src.dir}/js/intro.js" />
 			<fileset file="${src.dir}/js/types.js" />
 			<fileset file="${src.dir}/js/bind.js" />
+			<fileset file="${src.dir}/js/factory.js" />
 			<fileset file="${src.dir}/js/render.js" />
 			<fileset file="${src.dir}/js/dom.js" />
-			<fileset file="${src.dir}/js/factory.js" />
 			<fileset file="${src.dir}/js/outro.js" />
 		</concat>
 		<echo message="Concatenation done." />
 		<echo message="JSLint static analysis done." />
 	</target>
 
-	<target name="jscompile" depends="jslint" description="Compile JS">
+	<target name="compile.js" depends="jslint" description="Compile JS">
 		<jscomp compilationLevel="simple" warning="verbose" debug="false" output="${build.dir}/duel.min.js">
 			<sources dir="${build.dir}">
 				<file name="duel.js"/>
 	</target>
 
 	<target name="clean" depends="init">
-		<delete dir="${docs.dir}" />
-		<delete dir="${reports.dir}" />
+		<delete dir="${build.dir}" />
 		<delete dir="${bin.dir}" />
+		<mkdir dir="${bin.dir}" />
+		<mkdir dir="${build.dir}" />
 	</target>
 
-	<target name="prepare" depends="clean">
-		<mkdir dir="${bin.dir}" />
-		<mkdir dir="${reports.dir}" />
-		<mkdir dir="${docs.dir}" />
-	</target>
-
-	<target name="compile" depends="prepare" description="Compile Java">
+	<target name="compile.src" depends="clean" description="Compile Java">
 		<javac
 			srcdir="${src.dir}"
 			destdir="${bin.dir}"
 		/>
 	</target>
 
-	<target name="jar" depends="compile" description="Package as a jar">
+	<target name="jar" depends="compile.src" description="Package as a jar">
 		<jar destfile="${jarfile}" update="true">
 			<fileset dir="${bin.dir}" />
 			<manifest>
 		</jar>
 	</target>
 
-	<target name="testcompile" depends="prepare,jar" description="Compile Java tests">
+	<target name="compile.test" depends="jar" description="Compile Java tests">
 		<javac
 			srcdir="${test.dir}"
 			destdir="${bin.dir}"
+			classpathref="classpath.path"
 			debug="${javac.debug}"
 		/>
 	</target>
 
-	<target name="test" depends="compile" description="Run Java unit tests">
-		<junit fork="yes" printsummary="no" haltonfailure="no">
+	<target name="test" depends="compile.src,compile.test" description="Run Java unit tests">
+		<mkdir dir="${reports.dir}" />
+
+		<junit fork="yes" printsummary="yes" haltonfailure="no" haltonerror="no">
 			<batchtest fork="yes" todir="${reports.dir}" >
 				<fileset dir="${bin.dir}">
-					<include name="**/*Test.class" />
+					<include name="**/*Tests.class" />
 				</fileset>
 			</batchtest>
 			<formatter type="xml" />
-			<classpath refid="classpath.path" />
+			<classpath>
+				<pathelement location="${bin.dir}" />
+				<fileset dir="${lib.dir}">
+					<include name="**/*.jar" />
+				</fileset>
+			</classpath>
 		</junit>
+	</target>
 
+	<target name="test.report" depends="test">
 		<junitreport todir="${reports.dir}">
 			<fileset dir="${reports.dir}">
-				<include name="TEST-*.xml" />
+				<include name="**/TESTS-*.xml" />
+				<include name="**/TEST-*.xml" />
 			</fileset>
 			<report todir="${reports.dir}" />
 		</junitreport>
 	</target>
 
-	<target name="docs" depends="prepare" description="Generate docs">
+	<target name="docs" depends="clean" description="Generate docs">
 		<mkdir dir="${docs.dir}" />
 		<javadoc
 			destdir="${docs.dir}"
 		</javadoc>
 	</target>
 
-	<target name="all" depends="jscompile,test">
+	<target name="all" depends="clean,compile.js,test.report">
 		<echo message="Build completed." />
 	</target>
 

File build/duel.js

  * Licensed under the MIT License (http://duelengine.org/license.txt)
  */
 
-var duel = (
-
 /**
  * @param {Window} window Window reference
- * @param {*=} undefn undefined constant
  */
-function(window, undefn) {
+(function(window) {
 
 	"use strict";
 
 	}
 
 	/**
+	 * Determines if the value is a string
+	 * 
+	 * @private
+	 * @param {*} val the object being tested
+	 * @return {boolean}
+	 */
+	function isString(val) {
+		return (typeof val === "string");
+	}
+
+	/**
+	 * Determines if the value is a function
+	 * 
+	 * @private
+	 * @param {*} val the object being tested
+	 * @return {boolean}
+	 */
+	function isFunction(val) {
+		return (typeof val === "function");
+	}
+
+	/**
 	 * Wraps a binding result with rendering methods
 	 * 
 	 * @private
 		this.value = /** @type {Array} */(view);
 	}
 
-	/**
-	 * Wraps a template definition with binding methods
-	 * 
-	 * @private
-	 * @this {View}
-	 * @param {Array|Object|string|number} view The template definition
-	 * @constructor
-	 */
-	function View(view) {
-		if (getType(view) !== ARY) {
-			// ensure is rooted element
-			view = ["", view];
-		}
-
-		/**
-		 * @type {Array}
-		 * @const
-		 * @protected
-		 */
-		// Closure Compiler type cast
-		this.value = /** @type {Array} */(view);
-	}
-
 	/* bind.js --------------------*/
-	
-	/**
-	 * @private
-	 * @constant
-	 * @type {string}
-	 */
-	var BIND_EXTERN = "bind";
 
 	/**
 	 * @private
 		var each = node[1] && node[1][EACH];
 
 		// execute code block
-		if (getType(each) === FUN) {
+		if (isFunction(each)) {
 			each = each(model, index, count);
 		}
 	
 	
 			switch (cmd) {
 				case IF:
-					if (getType(test) === FUN) {
+					if (isFunction(test)) {
 						test = test(model, index, count);
 					}
 	
 			// Closure Compiler type cast
 			c = /** @type {number} */ (bind(args[COUNT], model, index, count));
 
-		return bind(duel(v).value, m, i, c);
+		return (v && isFunction(v.getView)) ?
+			bind(v.getView(), m, i, c) : null;
 	}
-	
+
 	/**
 	 * Binds the node to model
 	 * 
 	
 		return result;
 	};
-	
+
+	/* factory.js --------------------*/
+
 	/**
-	 * Binds and wraps the result
+	 * @private
+	 * @const
+	 * @type {string}
+	 */
+	var DUEL_EXTERN = "duel";
+
+	/**
+	 * @private
+	 * @const
+	 * @type {string}
+	 */
+	var RAW_EXTERN = "raw";
+
+	/**
+	 * Renders an error as text
 	 * 
+	 * @private
+	 * @param {Error} ex The exception
+	 * @return {string}
+	 */
+	function onError(ex) {
+		return "["+ex+"]";
+	}
+
+	/**
+	 * Wraps a view definition with binding method
+	 * 
+	 * @private
+	 * @param {Array|Object|string|number} view The template definition
+	 * @return {function(*)}
+	 */
+	function factory(view) {
+		if (getType(view) !== ARY) {
+			// ensure is rooted element
+			view = ["", view];
+		}
+
+		/**
+		 * Binds and wraps the result
+		 * 
+		 * @public
+		 * @param {*} model The data item being bound
+		 * @return {Result}
+		 */
+		var self = function(model) {
+			try {
+				// Closure Compiler type cast
+				var result = bind(/** @type {Array} */(view), model, 0, 1);
+				return new Result(result);
+			} catch (ex) {
+				// handle error with context
+				return onError(ex);
+			}
+		};
+
+		/**
+		 * Gets the internal view definition
+		 * 
+		 * @private
+		 * @return {Array}
+		 */
+		self.getView = function() {
+			// Closure Compiler type cast
+			return /** @type {Array} */(view);
+		};
+
+		return self;
+	}
+
+	/**
 	 * @public
-	 * @this {View}
-	 * @param {*} model The data item being bound
+	 * @param {Array|Object|string|number|function(*,number,number):Array|Object|string} view The view template
+	 * @return {function(*)}
 	 */
-	View.prototype[BIND_EXTERN] = View.prototype.bind = function(model) {
-		var result = bind(this.value, model, 0, 1);
-		return new Result(result);
+	var duel = window[DUEL_EXTERN] = function(view) {
+		return (isFunction(view) && isFunction(view.getView)) ? view : factory(view);
 	};
 
+	/**
+	 * @public
+	 * @param {string} value Markup text
+	 * @return {Markup}
+	 */
+	duel[RAW_EXTERN] = duel.raw = function(/*string*/ value) {
+		return new Markup(value);
+	};
 
 	/* render.js --------------------*/
 	
 	 * @return {Array|Object|string|number}
 	 */
 	function htmlEncode(val) {
-		if (typeof val !== "string") {
+		if (!isString(val)) {
 			return val;
 		}
 	
 	 * @return {Array|Object|string|number}
 	 */
 	function attrEncode(val) {
-		if (typeof val !== "string") {
+		if (!isString(val)) {
 			return val;
 		}
 	
 	 * @return {string}
 	 */
 	 function render(view) {
-		var buffer = new Buffer();
-		renderElem(buffer, view);
-		return buffer.toString();
+		try {
+			var buffer = new Buffer();
+			renderElem(buffer, view);
+			return buffer.toString();
+		} catch (ex) {
+			// handle error with context
+			return onError(ex);
+		}
 	}
 
 	/**
 		return render(this.value);
 	};
 
-	/**
-	 * Returns result as HTML text
-	 * 
-	 * @public
-	 * @override
-	 * @this {Result}
-	 * @return {string}
-	 */
-	View.prototype.toString = function() {
-		return render(this.value);
-	};
-
 	/* dom.js --------------------*/
 
 	/**
 	 * @param {function(Event)} handler The event handler
 	 */
 	function addHandler(elem, name, handler) {
-		if (typeof handler === "string") {
+		if (isString(handler)) {
 			/*jslint evil:true */
 			handler = new Function("event", handler);
 			/*jslint evil:false */
 		}
 	
-		if (typeof handler === "function") {
+		if (isFunction(handler)) {
 			elem[name] = handler;
 		}
 	}
 				delete elem[key];
 			} catch (ex) {
 				// sometimes IE doesn't like deleting from DOM
-				elem[key] = undefn;
+				elem[key] = undefined;
 			}
 
-			if (typeof method !== "function") {
+			if (!isFunction(method)) {
 				try {
 					/*jslint evil:true */
 					method = new Function(""+method);
 				}
 			}
 		}
+
 		return method;
 	}
 
 	}
 
 	/**
-	 * Renders an error as a text node
-	 * 
-	 * @private
-	 * @param {Error} ex The exception
-	 * @return {Node}
-	 */
-	function onError(ex) {
-		return document.createTextNode("["+ex+"]");
-	}
-
-	/**
 	 * Applies node to DOM
 	 * 
 	 * @private
 	}
 
 	/**
+	 * Renders an error as a text node
+	 * 
+	 * @private
+	 * @param {Error} ex The exception
+	 * @return {Node}
+	 */
+	function onErrorDOM(ex) {
+		return document.createTextNode(onError(ex));
+	}
+
+	/**
 	 * Returns result as DOM objects
 	 * 
 	 * @public
 		try {
 			return patchDOM(createElement(this.value[0]), this.value);
 		} catch (ex) {
-			try {
-				// handle error with complete context
-				var err = (typeof duel.onerror === "function") ? duel.onerror : onError;
-				return err(ex, this.value);
-			} catch (ex2) {
-				return onError(ex2);
-			}
+			// handle error with context
+			return onErrorDOM(ex);
 		}
 	};
 
-	/* factory.js --------------------*/
-
-	/**
-	 * @private
-	 * @const
-	 * @type {string}
-	 */
-	var DUEL_EXTERN = "duel";
-
-	/**
-	 * @private
-	 * @const
-	 * @type {string}
-	 */
-	var RAW_EXTERN = "raw";
-
-	/**
-	 * @public
-	 * @param {Array|Object|string|number|function(*,number,number):Array|Object|string} view The view template
-	 * @return {View}
-	 */
-	var duel = window[DUEL_EXTERN] = function(view) {
-		return (view instanceof View) ? view : new View(view);
-	};
-
-	/**
-	 * @public
-	 * @param {string} value Markup text
-	 * @return {Markup}
-	 */
-	duel[RAW_EXTERN] = duel.raw = function(/*string*/ value) {
-		return new Markup(value);
-	};
-
-	return duel;
-
 })(window);

File build/duel.min.js

-var duel=function(u,F){function v(a){this.value=a}function i(a){switch(typeof a){case "object":return!a?0:a instanceof Array?2:a instanceof v?5:3;case "function":return 1;case "undefined":return 0;default:return 4}}function r(a){if(i(a)!==2)a=["",a];this.value=a}function q(a){if(i(a)!==2)a=["",a];this.value=a}function s(a,b){if(i(a)===2)switch(i(b)){case 2:if(b[0]==="")for(var d=1,c=b.length;d<c;d++)s(a,b[d]);else a.push(b);break;case 3:d=a[1];if(i(d)===3)for(c in b){if(b.hasOwnProperty(c))d[c]=b[c]}else a.splice(1,
-0,b);break;case 4:d=a.length-1;if(d>0&&i(a[d])===4)a[d]=""+a[d]+b;else a.push(""+b);break;case 0:break;default:a.push(b)}}function w(a,b,d,c){for(var e=1,f=a.length;e<f;e++){var g=a[e],m=g[1]&&g[1].test;switch(g[0]){case "$if":if(i(m)===1)m=m(b,d,c);if(!m)continue;if(g.length===3)g=g[2];else[""].concat(a.slice(2));return j(g,b,d,c);case "$else":if(g.length===2)g=g[1];else[""].concat(a.slice(1));return j(g,b,d,c)}}return null}function k(){this.value=k.FAST?"":[]}function G(a){if(typeof a!=="string")return a;
-return a.replace(/[&<>]/g,function(b){switch(b){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";default:return b}})}function H(a){if(typeof a!=="string")return a;return a.replace(/[&<>"]/g,function(b){switch(b){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";default:return b}})}function x(a,b){var d=b[0],c=b.length,e=1,f;if(d){a.append("<",d);f=b[e];if(i(f)===3){for(var g in f)if(f.hasOwnProperty(g)){a.append(" ",g);var m=f[g];m&&a.append('="',
-H(m),'"')}e++}a.append(">")}for(;e<c;e++){f=b[e];i(f)===2?x(a,f):a.append(G(f))}d&&!I[d]&&a.append("</",d,">")}function y(a){var b=new k;x(b,a);return b.toString()}function n(a){if(!a){if(l.createDocumentFragment)return l.createDocumentFragment();a=""}if(a.toLowerCase()==="style"&&l.createStyleSheet)return l.createStyleSheet();return l.createElement(a)}function t(a,b){if(b)if(a.tagName&&a.tagName.toLowerCase()==="table"&&a.tBodies)if(b.tagName){var d=b.tagName.toLowerCase();if(d&&d!=="tbody"&&d!==
-"thead"){var c=a.tBodies.length>0?a.tBodies[a.tBodies.length-1]:null;if(!c){c=n(d==="th"?"thead":"tbody");a.appendChild(c)}c.appendChild(b)}else a.canHaveChildren!==false&&a.appendChild(b)}else{if(b.nodeType===11)for(;b.firstChild;)t(a,b.removeChild(b.firstChild))}else if(a.tagName&&a.tagName.toLowerCase()==="style"&&l.createStyleSheet)a.cssText=b;else if(a.canHaveChildren!==false)a.appendChild(b);else if(a.tagName&&a.tagName.toLowerCase()==="object"&&b.tagName&&b.tagName.toLowerCase()==="param"){try{a.appendChild(b)}catch(e){}try{if(a.object)a.object[b.name]=
-b.value}catch(f){}}}function z(a,b,d){if(typeof d==="string")d=new Function("event",d);if(typeof d==="function")a[b]=d}function A(a){return!!a&&a.nodeType===3&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function B(a){if(a){for(;A(a.firstChild);)a.removeChild(a.firstChild);for(;A(a.lastChild);)a.removeChild(a.lastChild)}}function C(a,b){var d=a[b];if(d){try{delete a[b]}catch(c){a[b]=F}if(typeof d!=="function")try{d=new Function(""+d)}catch(e){d=null}}return d}function J(a){if(a){var b=C(a,"$init");b&&
-b.call(a);if(b=C(a,"$load"))setTimeout(function(){b.call(a);b=a=null},0);else b=a=null}}function D(a){return l.createTextNode("["+a+"]")}function E(a,b){for(var d=1;d<b.length;d++){var c=b[d];switch(i(c)){case 2:t(a,E(n(c[0]),c));break;case 4:t(a,l.createTextNode(""+c));break;case 3:if(a.nodeType===1){var e=a,f=c;if(f.name&&l.attachEvent)try{var g=n("<"+e.tagName+" name='"+f.name+"'>");if(e.tagName===g.tagName)e=g}catch(m){}c=void 0;for(c in f)if(f.hasOwnProperty(c)){var h=f[c];if(c&&h){c=K[c.toLowerCase()]||
-c;if(c==="style")if(typeof e.style.cssText!=="undefined")e.style.cssText=h;else e.style=h;else if(c==="class")e.className=h;else if(L[c]){z(e,c,h);o[c]&&z(e,o[c],h)}else if(i(h)===4){e.setAttribute(c,h);o[c]&&e.setAttribute(o[c],h)}else{e[c]=h;if(o[c])e[o[c]]=h}}}a=e}break;case 5:e=t;f=a;h=c;c=n("div");c.innerHTML=""+h;B(c);if(c.childNodes.length===1)c=c.firstChild;else{for(h=n("");c.firstChild;)h.appendChild(c.firstChild);c=h}e(f,c)}}B(a);J(a);if(a.nodeType===11&&a.childNodes.length===1)a=a.firstChild;
-return a}var l=u.document;v.prototype.toString=function(){return this.value};var j;j=function(a,b,d,c){var e;switch(i(a)){case 1:e=a(b,d,c);break;case 2:var f=a[0]||"";switch(f){case "$for":a=a;f=a[1]&&a[1].each;if(i(f)===1)f=f(b,d,c);a=a.length===3?a[2]:[""].concat(a.slice(2));b=[""];switch(i(f)){case 2:d=0;for(c=f.length;d<c;d++)s(b,j(a,f[d],d,c));break;case 3:for(e in f)f.hasOwnProperty(e)&&s(b,j(a,f[e],e,0))}e=b;break;case "$choose":e=w(a,b,d,c);break;case "$if":case "$else":e=w(["$choose",a],
-b,d,c);break;case "$call":var g=a[1];if(!g||!g.view)e=null;else{a=j(g.view,b,d,c);e=j(g.model,b,d,c);f=j(g.index,b,d,c);b=j(g.count,b,d,c);e=j(p(a).value,e,f,b)}break;default:e=[f];f=1;for(g=a.length;f<g;f++)s(e,j(a[f],b,d,c))}break;case 3:e={};for(f in a)if(a.hasOwnProperty(f))e[f]=j(a[f],b,d,c);break;default:e=a}return e};q.prototype.bind=q.prototype.bind=function(a){a=j(this.value,a,0,1);return new r(a)};var I={area:true,base:true,basefont:true,br:true,col:true,frame:true,hr:true,img:true,input:true,
-isindex:true,keygen:true,link:true,meta:true,param:true,source:true,wbr:true};k.FAST=!u.ScriptEngineMajorVersion;k.prototype.append=function(a,b,d){if(k.FAST){this.value+=a;if(b!=null){this.value+=b;if(d!=null)this.value+=d}}else this.value.push.apply(this.value,arguments)};k.prototype.clear=function(){this.value=k.FAST?"":[]};k.prototype.toString=function(){return k.FAST?this.value:this.value.join("")};r.prototype.toString=function(){return y(this.value)};q.prototype.toString=function(){return y(this.value)};
-var K={rowspan:"rowSpan",colspan:"colSpan",cellpadding:"cellPadding",cellspacing:"cellSpacing",tabindex:"tabIndex",accesskey:"accessKey",hidefocus:"hideFocus",usemap:"useMap",maxlength:"maxLength",readonly:"readOnly",contenteditable:"contentEditable"},o={enctype:"encoding",onscroll:"DOMMouseScroll"},L={onblur:true,onchange:true,onclick:true,ondblclick:true,onerror:true,onfocus:true,onkeydown:true,onkeypress:true,onkeyup:true,onload:true,onmousedown:true,onmouseenter:true,onmouseleave:true,onmousemove:true,
-onmouseout:true,onmouseover:true,onmouseup:true,onresize:true,onscroll:true,onselect:true,onsubmit:true,onunload:true};r.prototype.toDOM=r.prototype.toDOM=function(){try{return E(n(this.value[0]),this.value)}catch(a){try{return(typeof p.onerror==="function"?p.onerror:D)(a,this.value)}catch(b){return D(b)}}};var p=u.duel=function(a){return a instanceof q?a:new q(a)};p.raw=p.raw=function(a){return new v(a)};return p}(window);
+(function(t){function u(a){this.value=a}function j(a){switch(typeof a){case "object":return!a?0:a instanceof Array?2:a instanceof u?5:3;case "function":return 1;case "undefined":return 0;default:return 4}}function n(a){return typeof a==="function"}function q(a){if(j(a)!==2)a=["",a];this.value=a}function r(a,b){if(j(a)===2)switch(j(b)){case 2:if(b[0]==="")for(var d=1,c=b.length;d<c;d++)r(a,b[d]);else a.push(b);break;case 3:d=a[1];if(j(d)===3)for(c in b){if(b.hasOwnProperty(c))d[c]=b[c]}else a.splice(1,
+0,b);break;case 4:d=a.length-1;if(d>0&&j(a[d])===4)a[d]=""+a[d]+b;else a.push(""+b);break;case 0:break;default:a.push(b)}}function v(a,b,d,c){for(var e=1,f=a.length;e<f;e++){var g=a[e],m=g[1]&&g[1].test;switch(g[0]){case "$if":if(n(m))m=m(b,d,c);if(!m)continue;if(g.length===3)g=g[2];else[""].concat(a.slice(2));return i(g,b,d,c);case "$else":if(g.length===2)g=g[1];else[""].concat(a.slice(1));return i(g,b,d,c)}}return null}function D(a){if(j(a)!==2)a=["",a];var b=function(d){try{var c=i(a,d,0,1);return new q(c)}catch(e){return"["+
+e+"]"}};b.getView=function(){return a};return b}function k(){this.value=k.FAST?"":[]}function E(a){if(typeof a!=="string")return a;return a.replace(/[&<>]/g,function(b){switch(b){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";default:return b}})}function F(a){if(typeof a!=="string")return a;return a.replace(/[&<>"]/g,function(b){switch(b){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";default:return b}})}function w(a,b){var d=b[0],c=
+b.length,e=1,f;if(d){a.append("<",d);f=b[e];if(j(f)===3){for(var g in f)if(f.hasOwnProperty(g)){a.append(" ",g);var m=f[g];m&&a.append('="',F(m),'"')}e++}a.append(">")}for(;e<c;e++){f=b[e];j(f)===2?w(a,f):a.append(E(f))}d&&!G[d]&&a.append("</",d,">")}function o(a){if(!a){if(l.createDocumentFragment)return l.createDocumentFragment();a=""}if(a.toLowerCase()==="style"&&l.createStyleSheet)return l.createStyleSheet();return l.createElement(a)}function s(a,b){if(b)if(a.tagName&&a.tagName.toLowerCase()===
+"table"&&a.tBodies)if(b.tagName){var d=b.tagName.toLowerCase();if(d&&d!=="tbody"&&d!=="thead"){var c=a.tBodies.length>0?a.tBodies[a.tBodies.length-1]:null;if(!c){c=o(d==="th"?"thead":"tbody");a.appendChild(c)}c.appendChild(b)}else a.canHaveChildren!==false&&a.appendChild(b)}else{if(b.nodeType===11)for(;b.firstChild;)s(a,b.removeChild(b.firstChild))}else if(a.tagName&&a.tagName.toLowerCase()==="style"&&l.createStyleSheet)a.cssText=b;else if(a.canHaveChildren!==false)a.appendChild(b);else if(a.tagName&&
+a.tagName.toLowerCase()==="object"&&b.tagName&&b.tagName.toLowerCase()==="param"){try{a.appendChild(b)}catch(e){}try{if(a.object)a.object[b.name]=b.value}catch(f){}}}function x(a,b,d){if(typeof d==="string")d=new Function("event",d);if(n(d))a[b]=d}function y(a){return!!a&&a.nodeType===3&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function z(a){if(a){for(;y(a.firstChild);)a.removeChild(a.firstChild);for(;y(a.lastChild);)a.removeChild(a.lastChild)}}function A(a,b){var d=a[b];if(d){try{delete a[b]}catch(c){a[b]=
+undefined}if(!n(d))try{d=new Function(""+d)}catch(e){d=null}}return d}function H(a){if(a){var b=A(a,"$init");b&&b.call(a);if(b=A(a,"$load"))setTimeout(function(){b.call(a);b=a=null},0);else b=a=null}}function B(a,b){for(var d=1;d<b.length;d++){var c=b[d];switch(j(c)){case 2:s(a,B(o(c[0]),c));break;case 4:s(a,l.createTextNode(""+c));break;case 3:if(a.nodeType===1){var e=a,f=c;if(f.name&&l.attachEvent)try{var g=o("<"+e.tagName+" name='"+f.name+"'>");if(e.tagName===g.tagName)e=g}catch(m){}c=void 0;for(c in f)if(f.hasOwnProperty(c)){var h=
+f[c];if(c&&h){c=I[c.toLowerCase()]||c;if(c==="style")if(typeof e.style.cssText!=="undefined")e.style.cssText=h;else e.style=h;else if(c==="class")e.className=h;else if(J[c]){x(e,c,h);p[c]&&x(e,p[c],h)}else if(j(h)===4){e.setAttribute(c,h);p[c]&&e.setAttribute(p[c],h)}else{e[c]=h;if(p[c])e[p[c]]=h}}}a=e}break;case 5:e=s;f=a;h=c;c=o("div");c.innerHTML=""+h;z(c);if(c.childNodes.length===1)c=c.firstChild;else{for(h=o("");c.firstChild;)h.appendChild(c.firstChild);c=h}e(f,c)}}z(a);H(a);if(a.nodeType===
+11&&a.childNodes.length===1)a=a.firstChild;return a}var l=t.document;u.prototype.toString=function(){return this.value};var i;i=function(a,b,d,c){var e;switch(j(a)){case 1:e=a(b,d,c);break;case 2:var f=a[0]||"";switch(f){case "$for":a=a;f=a[1]&&a[1].each;if(n(f))f=f(b,d,c);a=a.length===3?a[2]:[""].concat(a.slice(2));b=[""];switch(j(f)){case 2:d=0;for(c=f.length;d<c;d++)r(b,i(a,f[d],d,c));break;case 3:for(e in f)f.hasOwnProperty(e)&&r(b,i(a,f[e],e,0))}e=b;break;case "$choose":e=v(a,b,d,c);break;case "$if":case "$else":e=
+v(["$choose",a],b,d,c);break;case "$call":var g=a[1];if(!g||!g.view)e=null;else{a=i(g.view,b,d,c);e=i(g.model,b,d,c);f=i(g.index,b,d,c);b=i(g.count,b,d,c);e=a&&n(a.getView)?i(a.getView(),e,f,b):null}break;default:e=[f];f=1;for(g=a.length;f<g;f++)r(e,i(a[f],b,d,c))}break;case 3:e={};for(f in a)if(a.hasOwnProperty(f))e[f]=i(a[f],b,d,c);break;default:e=a}return e};var C=t.duel=function(a){return n(a)&&n(a.getView)?a:D(a)};C.raw=C.raw=function(a){return new u(a)};var G={area:true,base:true,basefont:true,
+br:true,col:true,frame:true,hr:true,img:true,input:true,isindex:true,keygen:true,link:true,meta:true,param:true,source:true,wbr:true};k.FAST=!t.ScriptEngineMajorVersion;k.prototype.append=function(a,b,d){if(k.FAST){this.value+=a;if(b!=null){this.value+=b;if(d!=null)this.value+=d}}else this.value.push.apply(this.value,arguments)};k.prototype.clear=function(){this.value=k.FAST?"":[]};k.prototype.toString=function(){return k.FAST?this.value:this.value.join("")};q.prototype.toString=function(){var a;
+var b=this.value;try{var d=new k;w(d,b);a=d.toString()}catch(c){a="["+c+"]"}return a};var I={rowspan:"rowSpan",colspan:"colSpan",cellpadding:"cellPadding",cellspacing:"cellSpacing",tabindex:"tabIndex",accesskey:"accessKey",hidefocus:"hideFocus",usemap:"useMap",maxlength:"maxLength",readonly:"readOnly",contenteditable:"contentEditable"},p={enctype:"encoding",onscroll:"DOMMouseScroll"},J={onblur:true,onchange:true,onclick:true,ondblclick:true,onerror:true,onfocus:true,onkeydown:true,onkeypress:true,
+onkeyup:true,onload:true,onmousedown:true,onmouseenter:true,onmouseleave:true,onmousemove:true,onmouseout:true,onmouseover:true,onmouseup:true,onresize:true,onscroll:true,onselect:true,onsubmit:true,onunload:true};q.prototype.toDOM=q.prototype.toDOM=function(){try{return B(o(this.value[0]),this.value)}catch(a){return l.createTextNode("["+a+"]")}}})(window);

File src/js/bind.js

 	/* bind.js --------------------*/
-	
-	/**
-	 * @private
-	 * @constant
-	 * @type {string}
-	 */
-	var BIND_EXTERN = "bind";
 
 	/**
 	 * @private
 		var each = node[1] && node[1][EACH];
 
 		// execute code block
-		if (getType(each) === FUN) {
+		if (isFunction(each)) {
 			each = each(model, index, count);
 		}
 	
 	
 			switch (cmd) {
 				case IF:
-					if (getType(test) === FUN) {
+					if (isFunction(test)) {
 						test = test(model, index, count);
 					}
 	
 			// Closure Compiler type cast
 			c = /** @type {number} */ (bind(args[COUNT], model, index, count));
 
-		return bind(duel(v).value, m, i, c);
+		return (v && isFunction(v.getView)) ?
+			bind(v.getView(), m, i, c) : null;
 	}
-	
+
 	/**
 	 * Binds the node to model
 	 * 
 	
 		return result;
 	};
-	
-	/**
-	 * Binds and wraps the result
-	 * 
-	 * @public
-	 * @this {View}
-	 * @param {*} model The data item being bound
-	 */
-	View.prototype[BIND_EXTERN] = View.prototype.bind = function(model) {
-		var result = bind(this.value, model, 0, 1);
-		return new Result(result);
-	};
 
-

File src/js/dom.js

 	 * @param {function(Event)} handler The event handler
 	 */
 	function addHandler(elem, name, handler) {
-		if (typeof handler === "string") {
+		if (isString(handler)) {
 			/*jslint evil:true */
 			handler = new Function("event", handler);
 			/*jslint evil:false */
 		}
 	
-		if (typeof handler === "function") {
+		if (isFunction(handler)) {
 			elem[name] = handler;
 		}
 	}
 				delete elem[key];
 			} catch (ex) {
 				// sometimes IE doesn't like deleting from DOM
-				elem[key] = undefn;
+				elem[key] = undefined;
 			}
 
-			if (typeof method !== "function") {
+			if (!isFunction(method)) {
 				try {
 					/*jslint evil:true */
 					method = new Function(""+method);
 				}
 			}
 		}
+
 		return method;
 	}
 
 	}
 
 	/**
-	 * Renders an error as a text node
-	 * 
-	 * @private
-	 * @param {Error} ex The exception
-	 * @return {Node}
-	 */
-	function onError(ex) {
-		return document.createTextNode("["+ex+"]");
-	}
-
-	/**
 	 * Applies node to DOM
 	 * 
 	 * @private
 	}
 
 	/**
+	 * Renders an error as a text node
+	 * 
+	 * @private
+	 * @param {Error} ex The exception
+	 * @return {Node}
+	 */
+	function onErrorDOM(ex) {
+		return document.createTextNode(onError(ex));
+	}
+
+	/**
 	 * Returns result as DOM objects
 	 * 
 	 * @public
 		try {
 			return patchDOM(createElement(this.value[0]), this.value);
 		} catch (ex) {
-			try {
-				// handle error with complete context
-				var err = (typeof duel.onerror === "function") ? duel.onerror : onError;
-				return err(ex, this.value);
-			} catch (ex2) {
-				return onError(ex2);
-			}
+			// handle error with context
+			return onErrorDOM(ex);
 		}
 	};
 

File src/js/factory.js

 	var RAW_EXTERN = "raw";
 
 	/**
+	 * Renders an error as text
+	 * 
+	 * @private
+	 * @param {Error} ex The exception
+	 * @return {string}
+	 */
+	function onError(ex) {
+		return "["+ex+"]";
+	}
+
+	/**
+	 * Wraps a view definition with binding method
+	 * 
+	 * @private
+	 * @param {Array|Object|string|number} view The template definition
+	 * @return {function(*)}
+	 */
+	function factory(view) {
+		if (getType(view) !== ARY) {
+			// ensure is rooted element
+			view = ["", view];
+		}
+
+		/**
+		 * Binds and wraps the result
+		 * 
+		 * @public
+		 * @param {*} model The data item being bound
+		 * @return {Result}
+		 */
+		var self = function(model) {
+			try {
+				// Closure Compiler type cast
+				var result = bind(/** @type {Array} */(view), model, 0, 1);
+				return new Result(result);
+			} catch (ex) {
+				// handle error with context
+				return onError(ex);
+			}
+		};
+
+		/**
+		 * Gets the internal view definition
+		 * 
+		 * @private
+		 * @return {Array}
+		 */
+		self.getView = function() {
+			// Closure Compiler type cast
+			return /** @type {Array} */(view);
+		};
+
+		return self;
+	}
+
+	/**
 	 * @public
 	 * @param {Array|Object|string|number|function(*,number,number):Array|Object|string} view The view template
-	 * @return {View}
+	 * @return {function(*)}
 	 */
 	var duel = window[DUEL_EXTERN] = function(view) {
-		return (view instanceof View) ? view : new View(view);
+		return (isFunction(view) && isFunction(view.getView)) ? view : factory(view);
 	};
 
 	/**
 	duel[RAW_EXTERN] = duel.raw = function(/*string*/ value) {
 		return new Markup(value);
 	};
+

File src/js/intro.js

  * Licensed under the MIT License (http://duelengine.org/license.txt)
  */
 
-var duel = (
-
 /**
  * @param {Window} window Window reference
- * @param {*=} undefn undefined constant
  */
-function(window, undefn) {
+(function(window) {
 
 	"use strict";
 

File src/js/outro.js

-
-	return duel;
-
 })(window);

File src/js/render.js

 	 * @return {Array|Object|string|number}
 	 */
 	function htmlEncode(val) {
-		if (typeof val !== "string") {
+		if (!isString(val)) {
 			return val;
 		}
 	
 	 * @return {Array|Object|string|number}
 	 */
 	function attrEncode(val) {
-		if (typeof val !== "string") {
+		if (!isString(val)) {
 			return val;
 		}
 	
 	 * @return {string}
 	 */
 	 function render(view) {
-		var buffer = new Buffer();
-		renderElem(buffer, view);
-		return buffer.toString();
+		try {
+			var buffer = new Buffer();
+			renderElem(buffer, view);
+			return buffer.toString();
+		} catch (ex) {
+			// handle error with context
+			return onError(ex);
+		}
 	}
 
 	/**
 		return render(this.value);
 	};
 
-	/**
-	 * Returns result as HTML text
-	 * 
-	 * @public
-	 * @override
-	 * @this {Result}
-	 * @return {string}
-	 */
-	View.prototype.toString = function() {
-		return render(this.value);
-	};
-

File src/js/types.js

 	}
 
 	/**
+	 * Determines if the value is a string
+	 * 
+	 * @private
+	 * @param {*} val the object being tested
+	 * @return {boolean}
+	 */
+	function isString(val) {
+		return (typeof val === "string");
+	}
+
+	/**
+	 * Determines if the value is a function
+	 * 
+	 * @private
+	 * @param {*} val the object being tested
+	 * @return {boolean}
+	 */
+	function isFunction(val) {
+		return (typeof val === "function");
+	}
+
+	/**
 	 * Wraps a binding result with rendering methods
 	 * 
 	 * @private
 		this.value = /** @type {Array} */(view);
 	}
 
-	/**
-	 * Wraps a template definition with binding methods
-	 * 
-	 * @private
-	 * @this {View}
-	 * @param {Array|Object|string|number} view The template definition
-	 * @constructor
-	 */
-	function View(view) {
-		if (getType(view) !== ARY) {
-			// ensure is rooted element
-			view = ["", view];
-		}
-
-		/**
-		 * @type {Array}
-		 * @const
-		 * @protected
-		 */
-		// Closure Compiler type cast
-		this.value = /** @type {Array} */(view);
-	}
-

File test/index.html

-<!DOCTYPE html>
-<html>
-<head>
-	<title>duel.js tests</title>
-	<link href="../lib/qunit/qunit.css" rel="stylesheet" type="text/css" />
-
-	<script src="../lib/qunit/qunit.js" type="text/javascript"></script>
-	<script src="../build/duel.min.js" type="text/javascript"></script>
-	<script src="js/utils.js" type="text/javascript"></script>
-	<script src="js/bind.js" type="text/javascript"></script>
-	<script src="js/render.js" type="text/javascript"></script>
-	<script src="js/dom.js" type="text/javascript"></script>
-</head>
-<body>
-	<h1 id="qunit-header">duel.js tests</h1>  
-	<h2 id="qunit-banner"></h2>  
-	<h2 id="qunit-userAgent"></h2>  
-	<ol id="qunit-tests"></ol> 
-</body>
-</html>

File test/js/bind.js

-module("View.bind()");
+module("duel(model)");
 
 test("static view", function() {
 
 			]
 		];
 
-	var actual = duel(expected).bind().value;
+	var actual = duel(expected)().value;
 
 	same(actual, expected, "");
 });
 			]
 		]);
 
-	var actual = view.bind(model).value;
+	var actual = view(model).value;
 
 	var expected = 
 		["div", { "class" : "download" },
 		]);
 
 	var model1 = { name: "Example" };
-	var actual1 = view.bind(model1).value;
+	var actual1 = view(model1).value;
 	var expected1 =
 		["",
 		 	["p", "True: Example === Example"],
 	same(actual1, expected1, "Binding with simple if statements.");
 
 	var model2 = { name: "Sample" };
-	var actual2 = view.bind(model2).value;
+	var actual2 = view(model2).value;
 	var expected2 =
 		["",
 		 	["p", "False: Example !== Sample"],
 	 	]);
 
 	var model1 = { name: "Three", children: [0,2,4] };
-	var actual1 = view.bind(model1).value;
+	var actual1 = view(model1).value;
 	var expected1 = ["p", "Has 3 items."];
 
 	same(actual1, expected1, "Binding with choose block.");
 
 	var model2 = { name: "One", children: [42] };
-	var actual2 = view.bind(model2).value;
+	var actual2 = view(model2).value;
 	var expected2 = ["p", "Has only one item."];
 
 	same(actual2, expected2, "Binding with choose block.");
 
 	var model3 = { name: "Zero", children: [] };
-	var actual3 = view.bind(model3).value;
+	var actual3 = view(model3).value;
 	var expected3 = ["p", "Has no items."];
 
 	same(actual3, expected3, "");
 			]
 		]);
 
-	var actual = view.bind(model).value;
+	var actual = view(model).value;
 
 	var expected =
 		["div", { "class" : "list", "style" : "color:blue" },
 		 	]
 	 	]);
 
-	var actual = view.bind(model).value;
+	var actual = view(model).value;
 
 	var expected =
 		["",
 			]
 		]);
 
-	var actual = view.bind(model).value;
+	var actual = view(model).value;
 
 	var expected = 
 		["div", { "class" : "test" },
 					])
 			};
 
-	var actual = Foo.listView.bind(model).value;
+	var actual = Foo.listView(model).value;
 
 	var expected = 
 		["div",

File test/js/dom.js

 			]
 		]);
 
-	var actual = view.bind().toDOM();
+	var actual = view().toDOM();
 
 	var temp, expected = document.createElement("div");
 	expected.setAttribute("class", "download");
 		 	["p", "Inner child two." ]
 		]);
 
-	var actual = view.bind().toDOM();
+	var actual = view().toDOM();
 
 	var expected = document.createDocumentFragment();
 
 			]
 		]);
 
-	var actual = view.bind().toDOM();
+	var actual = view().toDOM();
 
 	var expected = document.createElement("div");
 
 			]
 		]);
 
-	var actual = view.bind().toDOM();
+	var actual = view().toDOM();
 
 	var expected = document.createElement("div");
 	expected.className = "test";

File test/js/render.js

 			]
 		]);
 
-	var actual = view.bind().toString();
+	var actual = view().toString();
 
 	var expected =
 		'<div class="download"><h2>Filename: Foo.js</h2>'+
 	same(actual, expected, "");
 });
 
+test("native toString", function() {
+
+	var view = duel(
+		["div", { "class" : "download" },
+			["h2",
+			 	"Filename: Foo.js"
+			],
+			["p",
+			 	"URL: ",
+			 	["a", { "href" : "http://example.com/foo.js", "target" : "_blank", "title" : "Lorem ipsum dolor sit amet" },
+			 		"http://example.com/foo.js"
+		 		],
+			 	" (5.87KB)"
+		 	],
+			["p",
+			 	"Description: Lorem ipsum dolor sit amet"
+			]
+		]);
+
+	var actual = ""+view();
+
+	var expected =
+		'<div class="download"><h2>Filename: Foo.js</h2>'+
+		'<p>URL: <a href="http://example.com/foo.js" target="_blank" title="Lorem ipsum dolor sit amet">http://example.com/foo.js</a> (5.87KB)</p>'+
+		'<p>Description: Lorem ipsum dolor sit amet</p>'+
+		'</div>';
+
+	same(actual, expected, "");
+});
+
+test("innerHTML toString", function() {
+
+	var view = duel(
+		["div", { "class" : "download" },
+			["h2",
+			 	"Filename: Foo.js"
+			],
+			["p",
+			 	"URL: ",
+			 	["a", { "href" : "http://example.com/foo.js", "target" : "_blank", "title" : "Lorem ipsum dolor sit amet" },
+			 		"http://example.com/foo.js"
+		 		],
+			 	" (5.87KB)"
+		 	],
+			["p",
+			 	"Description: Lorem ipsum dolor sit amet"
+			]
+		]);
+
+	var actual = document.createElement("div");
+	actual.innerHTML = view();
+
+	var expected = document.createElement("div");
+	expected.innerHTML =
+		'<div class="download"><h2>Filename: Foo.js</h2>'+
+		'<p>URL: <a href="http://example.com/foo.js" target="_blank" title="Lorem ipsum dolor sit amet">http://example.com/foo.js</a> (5.87KB)</p>'+
+		'<p>Description: Lorem ipsum dolor sit amet</p>'+
+		'</div>';
+
+	same(actual.innerHTML, expected.innerHTML, "");
+});
+
 test("docFrag root", function() {
 
 	var view = duel(
 		 	["p", "Inner child two." ]
 		]);
 
-	var actual = view.bind().toString();
+	var actual = view().toString();
 	var expected = '<p>Inner child one.</p><p>Inner child two.</p>';
 
 	same(actual, expected, "");
 			]
 		]);
 
-	var actual = view.bind().toString();
+	var actual = view().toString();
 	var expected = '<div><p>Inner child one.</p><p>Inner child two.</p></div>';
 
 	same(actual, expected, "");
 		 	'&hello"foo<bar><&>'
 		]);
 
-	var actual = view.bind().toString();
+	var actual = view().toString();
 
 	var expected =  '<p>&amp;hello"foo&lt;bar&gt;&lt;&amp;&gt;</p>';
 	
 		 	"Encoded attributes"
 		]);
 
-	var actual = view.bind().toString();
+	var actual = view().toString();
 
 	var expected =  '<p title="&amp;hello&quot;foo&lt;bar&gt;&lt;&amp;&gt;">Encoded attributes</p>';
 	
 			]
 		]);
 
-	var actual = view.bind().toString();
+	var actual = view().toString();
 
 	var expected = 
 		'<div class="test">'+

File test/unit.html

+<!DOCTYPE html>
+<html>
+<head>
+	<title>duel.js tests</title>
+	<link href="../lib/qunit/qunit.css" rel="stylesheet" type="text/css" />
+
+	<script src="../lib/qunit/qunit.js" type="text/javascript"></script>
+	<script src="../build/duel.min.js" type="text/javascript"></script>
+	<script src="js/utils.js" type="text/javascript"></script>
+	<script src="js/bind.js" type="text/javascript"></script>
+	<script src="js/render.js" type="text/javascript"></script>
+	<script src="js/dom.js" type="text/javascript"></script>
+</head>
+<body>
+	<h1 id="qunit-header">duel.js tests</h1>  
+	<h2 id="qunit-banner"></h2>  
+	<h2 id="qunit-userAgent"></h2>  
+	<ol id="qunit-tests"></ol> 
+</body>
+</html>