Commits

Stephen McKamey committed 9bbb421

improvements to correctness of binding DOM attributes vs. properties

Comments (0)

Files changed (9)

 
 	<groupId>org.duelengine</groupId>
 	<artifactId>duel-js</artifactId>
-	<version>0.8.4</version>
+	<version>0.8.5</version>
 	<packaging>pom</packaging>
 
 	<name>DUEL Client Scripts</name>

duel-js/src/main/javascript/dom.js

 	 * @type {Object.<string>}
 	 */
 	var ATTR_MAP = {
-		'rowspan': 'rowSpan',
-		'colspan': 'colSpan',
+		'accesskey': 'accessKey',
+		'bgcolor': 'bgColor',
 		'cellpadding': 'cellPadding',
 		'cellspacing': 'cellSpacing',
+		'checked': 'defaultChecked',
+		'class': 'className',
+		'colspan': 'colSpan',
+		'contenteditable': 'contentEditable',
+		'defaultchecked': 'defaultChecked',
+		'for': 'htmlFor',
+		'formnovalidate': 'formNoValidate',
+		'hidefocus': 'hideFocus',
+		'ismap': 'isMap',
+		'maxlength': 'maxLength',
+		'novalidate': 'noValidate',
+		'readonly': 'readOnly',
+		'rowspan': 'rowSpan',
+		'spellcheck': 'spellCheck',
 		'tabindex': 'tabIndex',
-		'accesskey': 'accessKey',
-		'hidefocus': 'hideFocus',
 		'usemap': 'useMap',
-		'maxlength': 'maxLength',
-		'readonly': 'readOnly',
-		'contenteditable': 'contentEditable'
+		'willvalidate': 'willValidate'
 		// can add more attributes here as needed
 	};
 
 	 */
 	var ATTR_DUP = {
 		'enctype': 'encoding',
-		'onscroll': 'DOMMouseScroll',
-		'checked': 'defaultChecked'
+		'onscroll': 'DOMMouseScroll'
 		// can add more attributes here as needed
 	};
 
 					// DOM Level 2
 					elem.addEventListener(name, handler, false);
 
-				} else if (isFunction(window.jQuery) && 'undefined' !== typeof elem[name]) {
+				} else if (isFunction(window.jQuery) && getType(elem[name]) !== NUL) {
 					// cop out and patch IE6-8 with jQuery
 					var $elem = window.jQuery(elem);
 					if (isFunction($elem.on)) {
 						$elem.bind(name, handler);	// pre-1.7
 					}
 
-				} else if (elem.attachEvent && 'undefined' !== typeof elem[name]) {
+				} else if (elem.attachEvent && getType(elem[name]) !== NUL) {
 					// IE legacy events
 					elem.attachEvent('on'+name, handler);
 
 				} else {
 					// DOM Level 0
 					var old = elem['on'+name] || elem[name];
-					elem['on'+name] = elem[name] = ('function' !== typeof old) ? handler :
+					elem['on'+name] = elem[name] = !isFunction(old) ? handler :
 						function(e) {
 							return (old.call(this, e) !== false) && (handler.call(this, e) !== false);
 						};
 	}
 
 	/**
-	 * Appends a child to an element
+	 * Appends an attribute to an element
 	 * 
 	 * @private
 	 * @param {Node} elem The element
 					}
 
 					name = ATTR_MAP[name.toLowerCase()] || name;
-					if (ATTR_BOOL[name]) {
-						elem[name] = !!value;
+					if (ATTR_BOOL[name.toLowerCase()]) {
+						value = !!value;
+					}
 
-						// also set duplicated attributes
-						if (ATTR_DUP[name]) {
-							elem[ATTR_DUP[name]] = !!value;
-						}
-
-					} else if (name === 'style') {
-						if (typeof elem.style.cssText !== 'undefined') {
+					if (name === 'style') {
+						if (getType(elem.style.cssText) !== NUL) {
 							elem.style.cssText = value;
 						} else {
 							elem.style = value;
 						}
 
-					} else if (name === 'class') {
-						elem.className = value;
-
 					} else if (name.substr(0,2) === 'on') {
 						addHandler(elem, name, value);
 
 						// also set duplicated events
-						if (ATTR_DUP[name]) {
-							addHandler(elem, ATTR_DUP[name], value);
+						name = ATTR_DUP[name];
+						if (name) {
+							addHandler(elem, name, value);
 						}
 
-					} else if (type === VAL && name.charAt(0) !== '$') {
-						elem.setAttribute(name, value);
-	
-						// also set duplicated attributes
-						if (ATTR_DUP[name]) {
-							elem.setAttribute(ATTR_DUP[name], value);
+					} else if (type !== VAL || name.charAt(0) === '$' || getType(elem[name]) !== NUL || getType(elem[ATTR_DUP[name]]) !== NUL) {
+						// direct setting of existing properties
+						elem[name] = value;
+
+						// also set duplicated properties
+						name = ATTR_DUP[name];
+						if (name) {
+							elem[name] = value;
+						}
+
+					} else if (ATTR_BOOL[name.toLowerCase()]) {
+						if (value) {
+							// boolean attributes
+							elem.setAttribute(name, name);
+
+							// also set duplicated attributes
+							name = ATTR_DUP[name];
+							if (name) {
+								elem.setAttribute(name, name);
+							}
 						}
 
 					} else {
-						// allow direct setting of complex properties
-						elem[name] = value;
+						// http://www.quirksmode.org/dom/w3c_core.html#attributes
+
+						// custom and 'data-*' attributes
+						elem.setAttribute(name, value);
 
 						// also set duplicated attributes
-						if (ATTR_DUP[name]) {
-							elem[ATTR_DUP[name]] = value;
+						name = ATTR_DUP[name];
+						if (name) {
+							elem.setAttribute(name, value);
 						}
 					}
 				}
 			try {
 				delete elem[key];
 			} catch (ex) {
-				// sometimes IE doesn't like deleting from DOM
-				elem[key] = undef;
+				try {
+					// IE7 doesn't like deleting from DOM
+					elem[key] = '';
+					elem.removeAttribute(key);
+				} catch (ex2) {}
 			}
 
 			if (!isFunction(method)) {
 					/*jslint evil:true */
 					method = new Function(''+method);
 					/*jslint evil:false */
-				} catch (ex2) {
+				} catch (ex3) {
 					// filter
 					method = null;
 				}

duel-js/src/main/javascript/render.js

 		'async': 1,
 		'autofocus': 1,
 		'checked': 1,
+		'defaultchecked': 1,
 		'defer': 1,
 		'disabled': 1,
 		'formnovalidate': 1,
 		'hidden': 1,
+		'indeterminate': 1,
+		'ismap': 1,
 		'multiple': 1,
-		'novalidate': 1
+		'novalidate': 1,
+		'readonly': 1,
+		'required': 1,
+		'spellcheck': 1,
+		'willvalidate': 1
 		// can add more attributes here as needed
 	};
 
 				for (var name in child) {
 					if (child.hasOwnProperty(name)) {
 						var val = child[name];
-						if (ATTR_BOOL[name]) {
+						if (ATTR_BOOL[name.toLowerCase()]) {
 							if (val) {
 								val = name;
 							} else {

duel-js/src/main/javascript/types.js

 	var isArray = Array.isArray || function(val) {
 		return (val instanceof Array);
 	};
-	
-	/**
-	 * Determines the type of the value
-	 * 
-	 * @private
-	 * @param {*} val the object being tested
-	 * @return {number}
-	 */
-	function getType(val) {
-		switch (typeof val) {
-			case 'object':
-				return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
-			case 'function':
-				return FUN;
-			case 'undefined':
-				return NUL;
-			default:
-				return VAL;
-		}
-	}
 
 	/**
 	 * Determines if the value is a string
 	}
 
 	/**
+	 * Determines the type of the value
+	 * 
+	 * @private
+	 * @param {*} val the object being tested
+	 * @return {number}
+	 */
+	function getType(val) {
+		switch (typeof val) {
+			case 'object':
+				return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
+			case 'function':
+				return FUN;
+			case 'undefined':
+				return NUL;
+			default:
+				return VAL;
+		}
+	}
+
+	/**
 	 * String buffer
 	 * 
 	 * @private

duel-js/src/test/javascript/bindTests.js

 test('simple expressions', function() {
 
 	var data = {
-	        name: 'Foo.js',
-	        url: 'http://example.com/foo.js',
-	        size: 5.87,
-	        datestamp: new Date(),
-	        details: 'Lorem ipsum dolor sit amet'
-	    };
+			name: 'Foo.js',
+			url: 'http://example.com/foo.js',
+			size: 5.87,
+			datestamp: new Date(),
+			details: 'Lorem ipsum dolor sit amet'
+		};
 
 	var view = duel(
 		['div', { 'class' : 'download' },
 			['h2',
-			 	'Filename: ',
+				'Filename: ',
 				function(data, index, count) { return data.name; }
 			],
 			['p',
-			 	'URL: ',
-			 	['a', { 'href' : function(data, index, count) { return data.url; }, 'target' : '_blank' },
-			 	 	function(data, index, count) { return data.url ;}
-			 	],
-			 	' (',
-			 	function(data, index, count) { return data.size ;},
-			 	'KB)'
-		 	],
-			['p',
-			 	'DateStamp: ',
-			 	function(data, index, count) { return data.datestamp; }
+				'URL: ',
+				['a', { 'href' : function(data, index, count) { return data.url; }, 'target' : '_blank' },
+					function(data, index, count) { return data.url ;}
+				],
+				' (',
+				function(data, index, count) { return data.size ;},
+				'KB)'
 			],
 			['p',
-			 	'Description: ',
-			 	function(data, index, count) { return data.details; }
+				'DateStamp: ',
+				function(data, index, count) { return data.datestamp; }
+			],
+			['p',
+				'Description: ',
+				function(data, index, count) { return data.details; }
 			]
 		]);
 
 	var expected = 
 		['div', { 'class' : 'download' },
 			['h2',
-			 	'Filename: Foo.js'
+				'Filename: Foo.js'
 			],
 			['p',
-			 	'URL: ',
-			 	['a', { 'href' : 'http://example.com/foo.js', 'target' : '_blank' },
-			 		'http://example.com/foo.js'
-		 		],
-			 	' (5.87KB)'
-		 	],
-			['p',
-			 	'DateStamp: '+data.datestamp
+				'URL: ',
+				['a', { 'href' : 'http://example.com/foo.js', 'target' : '_blank' },
+					'http://example.com/foo.js'
+				],
+				' (5.87KB)'
 			],
 			['p',
-			 	'Description: Lorem ipsum dolor sit amet'
+				'DateStamp: '+data.datestamp
+			],
+			['p',
+				'Description: Lorem ipsum dolor sit amet'
 			]
 		];
 
 
 	var view = duel(
 		['',
-		 	['$if', { 'test' : function(data, index, count) { return data.name === 'Example'; } },
-		 	 	['p', 'True: Example === ', function(data, index, count) { return data.name; } ]
-		 	],
-		 	['$if', { 'test' : function(data, index, count) { return data.name !== 'Example'; } },
-		 	 	['p', 'False: Example !== ', function(data, index, count) { return data.name; } ]
-		 	],
-		 	['$if',
-		 	 	['p', 'Both: orphaned else always executes' ]
-		 	]
+			['$if', { 'test' : function(data, index, count) { return data.name === 'Example'; } },
+				['p', 'True: Example === ', function(data, index, count) { return data.name; } ]
+			],
+			['$if', { 'test' : function(data, index, count) { return data.name !== 'Example'; } },
+				['p', 'False: Example !== ', function(data, index, count) { return data.name; } ]
+			],
+			['$if',
+				['p', 'Both: orphaned else always executes' ]
+			]
 		]);
 
 	var data1 = { name: 'Example' };
 	var actual1 = view(data1).value;
 	var expected1 =
 		['',
-		 	['p', 'True: Example === Example'],
-		 	['p', 'Both: orphaned else always executes' ]
+			['p', 'True: Example === Example'],
+			['p', 'Both: orphaned else always executes' ]
 		];
 
 	same(actual1, expected1, 'Binding with simple if statements.');
 	var actual2 = view(data2).value;
 	var expected2 =
 		['',
-		 	['p', 'False: Example !== Sample'],
-		 	['p', 'Both: orphaned else always executes' ]
+			['p', 'False: Example !== Sample'],
+			['p', 'Both: orphaned else always executes' ]
 		];
 
 	same(actual2, expected2, '');
 test('XOR block', function() {
 
 	var view = duel(
-	 	['$xor',
-		 	['$if', { 'test' : function(data, index, count) { return !data.children || !data.children.length; } },
-		 	 	['p', 'Has no items.']
-		 	],
-		 	['$if', { 'test' : function(data, index, count) { return data.children && data.children.length === 1; } },
-		 	 	['p', 'Has only one item.']
-		 	],
-		 	['$if',
-		 	 	['p', 'Has ', function(data, index, count) { return data.children.length; }, ' items.']
-		 	]
-	 	]);
+		['$xor',
+			['$if', { 'test' : function(data, index, count) { return !data.children || !data.children.length; } },
+				['p', 'Has no items.']
+			],
+			['$if', { 'test' : function(data, index, count) { return data.children && data.children.length === 1; } },
+				['p', 'Has only one item.']
+			],
+			['$if',
+				['p', 'Has ', function(data, index, count) { return data.children.length; }, ' items.']
+			]
+		]);
 
 	var data1 = { name: 'Three', children: [0,2,4] };
 	var actual1 = view(data1).value;
 test('for-each array', function() {
 
 	var data = {
-	        title: 'This is the title',
-	        items: [
-	            { name: 'One' },
-	            { name: 'Two' },
-	            { name: 'Three' },
-	            { name: 'Four' },
-	            { name: 'Five' }
-	        ]
-	    };
+			title: 'This is the title',
+			items: [
+				{ name: 'One' },
+				{ name: 'Two' },
+				{ name: 'Three' },
+				{ name: 'Four' },
+				{ name: 'Five' }
+			]
+		};
 
 	var view = duel(
 		['div', { 'class' : 'list', 'style' : 'color:blue' },
 			['h2',
-			 	function(data, index, count) { return data.title; }
+				function(data, index, count) { return data.title; }
 			],
 			['ul',
-			 	['$for', { 'each' : function(data, index, count) { return data.items; } },
+				['$for', { 'each' : function(data, index, count) { return data.items; } },
 					['li', { 'class' : 'item' },
 						['b',
-						 	function(data, index, count) { return data.name; }
+							function(data, index, count) { return data.name; }
 						],
 						': ',
 						['i',
-						 	function(data, index, count) { return index + 1; },
+							function(data, index, count) { return index + 1; },
 							' of ',
 							function(data, index, count) { return count; }
 						]
 					]
-			 	]
+				]
 			]
 		]);
 
 test('for-each primitive', function() {
 
 	var data = {
-	        title: 'This is the title',
-	        items: 'One'
-	    };
+			title: 'This is the title',
+			items: 'One'
+		};
 
 	var view = duel(
 		['div', { 'class' : 'list', 'style' : 'color:blue' },
 			['h2',
-			 	function(data, index, count) { return data.title; }
+				function(data, index, count) { return data.title; }
 			],
 			['ul',
-			 	['$for', { 'each' : function(data, index, count) { return data.items; } },
+				['$for', { 'each' : function(data, index, count) { return data.items; } },
 					['li', { 'class' : 'item' },
 						['b',
-						 	function(data, index, count) { return data; }
+							function(data, index, count) { return data; }
 						],
 						': ',
 						['i',
-						 	function(data, index, count) { return index + 1; },
+							function(data, index, count) { return index + 1; },
 							' of ',
 							function(data, index, count) { return count; }
 						]
 					]
-			 	]
+				]
 			]
 		]);
 
 
 test('for-in object', function() {
 	var data = {
-	        name: 'List of items',
-	        total: 5,
-	        items: [
-	            'One',
-	            'Two',
-	            'Three',
-	            'Four',
-	            'Five'
-	        ]
-	    };
+			name: 'List of items',
+			total: 5,
+			items: [
+				'One',
+				'Two',
+				'Three',
+				'Four',
+				'Five'
+			]
+		};
 
 	var view = duel(
 		['',
-		 	'data => ',
-		 	['dl',
+			'data => ',
+			['dl',
 				['$for', { 'in' : function(data) { return data; } },
-				 	['dt',
-					 	function(data, index) { return index; },
-					 	' of ',
-					 	function(data, index, count) { return count; },
-					 	' - ',
-					 	function(data, index, count, key) { return key; },
-					 	' : '],
+					['dt',
+						function(data, index) { return index; },
+						' of ',
+						function(data, index, count) { return count; },
+						' - ',
+						function(data, index, count, key) { return key; },
+						' : '],
 					['dd',
-					 	'(',
-					 	function(data) { return (data instanceof Array) ? 'array' : typeof data; },
-					 	') ',
-					 	function(data) { return '' + data; }
-				 	]
-			 	]
-		 	]
-	 	]);
+						'(',
+						function(data) { return (data instanceof Array) ? 'array' : typeof data; },
+						') ',
+						function(data) { return '' + data; }
+					]
+				]
+			]
+		]);
 
 	var actual = view(data).value;
 
 	var expected =
 		['',
-		 	'data => ',
-		 	['dl',
-		 	 	['dt', '0 of 3 - name : '],
-		 	 	['dd', '(string) List of items'],
-		 	 	['dt', '1 of 3 - total : '],
-		 	 	['dd', '(number) 5'],
-		 	 	['dt', '2 of 3 - items : '],
+			'data => ',
+			['dl',
+				['dt', '0 of 3 - name : '],
+				['dd', '(string) List of items'],
+				['dt', '1 of 3 - total : '],
+				['dd', '(number) 5'],
+				['dt', '2 of 3 - items : '],
 				['dd', '(array) One,Two,Three,Four,Five']
-		 	]
-	 	];
+			]
+		];
 
 	same(actual, expected, '');
 });
 
 test('for-count', function() {
 	var data = {
-	        name: 'List of items',
-	        total: 5,
-	        items: [
-	            'One',
-	            'Two',
-	            'Three',
-	            'Four',
-	            'Five'
-	        ]
-	    };
+			name: 'List of items',
+			total: 5,
+			items: [
+				'One',
+				'Two',
+				'Three',
+				'Four',
+				'Five'
+			]
+		};
 
 	var view = duel(
 		['',
-		 	'list => ',
-		 	['dl',
+			'list => ',
+			['dl',
 				['$for', {
 						'count' : function(data, index, count) { return 4; },
 						'data' : function(data, index, count) { return data.name; }
 					},
-				 	['dt',
-					 	function(data, index, count) { return index; },
-					 	' of ',
-					 	function(data, index, count) { return count; },
-					 	': '],
+					['dt',
+						function(data, index, count) { return index; },
+						' of ',
+						function(data, index, count) { return count; },
+						': '],
 					['dd',
-					 	function(data, index, count) { return '' + data; }
-				 	]
-			 	]
-		 	]
-	 	]);
+						function(data, index, count) { return '' + data; }
+					]
+				]
+			]
+		]);
 
 	var actual = view(data).value;
 
 	var expected =
 		['',
-		 	'list => ',
-		 	['dl',
-		 	 	['dt', '0 of 4: '],
-		 	 	['dd', 'List of items'],
-		 	 	['dt', '1 of 4: '],
-		 	 	['dd', 'List of items'],
-		 	 	['dt', '2 of 4: '],
-		 	 	['dd', 'List of items'],
-		 	 	['dt', '3 of 4: '],
+			'list => ',
+			['dl',
+				['dt', '0 of 4: '],
+				['dd', 'List of items'],
+				['dt', '1 of 4: '],
+				['dd', 'List of items'],
+				['dt', '2 of 4: '],
+				['dd', 'List of items'],
+				['dt', '3 of 4: '],
 				['dd', 'List of items']
-		 	]
-	 	];
+			]
+		];
 
 	same(actual, expected, '');
 });
 test('markup data', function() {
 
 	var data = {
-	        details: '<blink>Lorem ipsum dolor sit amet</blink>'
-	    };
+			details: '<blink>Lorem ipsum dolor sit amet</blink>'
+		};
 
 	var view = duel(
 		['div', { 'class' : 'test' },
 			['p',
-			 	'Description: ',
-			 	function(data, index, count) { return duel.raw(data.details); }
+				'Description: ',
+				function(data, index, count) { return duel.raw(data.details); }
 			]
 		]);
 
 	var expected = 
 		['div', { 'class' : 'test' },
 			['p',
-			 	'Description: ',
-			 	duel.raw('<blink>Lorem ipsum dolor sit amet</blink>')
+				'Description: ',
+				duel.raw('<blink>Lorem ipsum dolor sit amet</blink>')
 			]
 		];
 
 
 	var expected = 
 		['p',
-		 	'Should render "fuel" => "fuel"'
+			'Should render "fuel" => "fuel"'
 		];
 
 	same(actual, expected, '');
 test('call view', function() {
 
 	var data = {
-	        name: 'Outer list',
-	        items: ['One', 'Two', 'Three']
-	    };
+			name: 'Outer list',
+			items: ['One', 'Two', 'Three']
+		};
 
 	var Foo = {
 			itemView: duel(
 					['li',
-					 	'data: ',
-					 	function(data, index, count) { return data; },
-					 	['br'],
-					 	'index: ',
-					 	function(data, index, count) { return index; },
-					 	['br'],
-					 	'count: ',
-					 	function(data, index, count) { return count; },
+						'data: ',
+						function(data, index, count) { return data; },
+						['br'],
+						'index: ',
+						function(data, index, count) { return index; },
+						['br'],
+						'count: ',
+						function(data, index, count) { return count; },
 					]),
 			listView: duel(
 					['div',
-					 	['h2', function(data, index, count) { return data.name; } ],
+						['h2', function(data, index, count) { return data.name; } ],
 						['ul',
-						 	['$for', { 'each' : function(data, index, count) { return data.items; } },
-						 		['$call', {
-							 			'view' : function(data, index, count) { return Foo.itemView; },
-							 			'data' :  function(data, index, count) { return data; },
-							 			'index' :  function(data, index, count) { return index; },
-							 			'count' :  function(data, index, count) { return count; }
-						 			}
-						 		]
-						 	]
+							['$for', { 'each' : function(data, index, count) { return data.items; } },
+								['$call', {
+										'view' : function(data, index, count) { return Foo.itemView; },
+										'data' :  function(data, index, count) { return data; },
+										'index' :  function(data, index, count) { return index; },
+										'count' :  function(data, index, count) { return count; }
+									}
+								]
+							]
 						]
 					])
 			};
 
 	var expected = 
 		['div',
-		 	['h2', 'Outer list' ],
+			['h2', 'Outer list' ],
 			['ul',
 				['li',
 					'data: One',
 test('call wrapper view', function() {
 
 	var data = {
-	        name: 'Outer list',
-	        items: ['One', 'Two', 'Three']
-	    };
+			name: 'Outer list',
+			items: ['One', 'Two', 'Three']
+		};
 
 	var Foo = {
 			itemView: duel(
 					['li',
-					 	['$part', { 'name' : 'itemLayout' }]
+						['$part', { 'name' : 'itemLayout' }]
 					]),
 			listView: duel(
 					['div',
-					 	['h2', function(data, index, count) { return data.name; } ],
+						['h2', function(data, index, count) { return data.name; } ],
 						['ul',
-						 	['$for', { 'each' : function(data, index, count) { return data.items; } },
-						 		['$call', {
-							 			'view' : function(data, index, count) { return Foo.itemView; },
-							 			'data' :  function(data, index, count) { return data; },
-							 			'index' :  function(data, index, count) { return index; },
-							 			'count' :  function(data, index, count) { return count; }
-						 			},
-						 			['$part', { 'name' : 'itemLayout' },
-						 			 	'data: ',
-									 	function(data, index, count) { return data; },
-									 	['br'],
-									 	'index: ',
-									 	function(data, index, count) { return index; },
-									 	['br'],
-									 	'count: ',
-									 	function(data, index, count) { return count; }
-								 	]
-						 		]
-						 	]
+							['$for', { 'each' : function(data, index, count) { return data.items; } },
+								['$call', {
+										'view' : function(data, index, count) { return Foo.itemView; },
+										'data' :  function(data, index, count) { return data; },
+										'index' :  function(data, index, count) { return index; },
+										'count' :  function(data, index, count) { return count; }
+									},
+									['$part', { 'name' : 'itemLayout' },
+										'data: ',
+										function(data, index, count) { return data; },
+										['br'],
+										'index: ',
+										function(data, index, count) { return index; },
+										['br'],
+										'count: ',
+										function(data, index, count) { return count; }
+									]
+								]
+							]
 						]
 					])
 			};
 
 	var expected = 
 		['div',
-		 	['h2', 'Outer list' ],
+			['h2', 'Outer list' ],
 			['ul',
 				['li',
 					'data: One',

duel-js/src/test/javascript/domTests.js

 test('boolean attributes', function() {
 
 	var view = duel(['form',
-		['input', { 'type': 'checkbox', checked: false } ],
+		['label', { 'for': 'cbx' }, 'click me' ],
+		['input', { 'id': 'cbx', 'type': 'checkbox', checked: false } ],
 		['input', { 'type': 'checkbox', checked: '' } ],
 		['input', { 'type': 'checkbox', checked: null } ],
 		['input', { 'type': 'checkbox', checked: true } ],
 
 	var temp, expected = document.createElement('form');
 
+	temp = document.createElement('label');
+	temp.htmlFor = 'cbx';
+	temp.appendChild(document.createTextNode('click me'));
+	expected.appendChild(temp);
+
 	temp = document.createElement('input');
+	temp.id = 'cbx';
 	temp.type = 'checkbox';
 	expected.appendChild(temp);
 

duel-js/src/test/javascript/renderTests.js

 test('boolean attributes', function() {
 
 	var view = duel(['form',
-		['input', { 'type': 'checkbox', checked: false } ],
+		['label', { 'for': 'cbx' }, 'click me' ],
+		['input', { 'id': 'cbx', 'type': 'checkbox', checked: false } ],
 		['input', { 'type': 'checkbox', checked: '' } ],
 		['input', { 'type': 'checkbox', checked: null } ],
 		['input', { 'type': 'checkbox', checked: true } ],
 
 	var actual = view().toString();
 
-	var expected = '<form><input type="checkbox" /><input type="checkbox" /><input type="checkbox" /><input type="checkbox" checked="checked" /><input type="checkbox" checked="checked" /><input type="checkbox" checked="checked" /></form>';
+	var expected = '<form><label for="cbx">click me</label><input id="cbx" type="checkbox" /><input type="checkbox" /><input type="checkbox" /><input type="checkbox" checked="checked" /><input type="checkbox" checked="checked" /><input type="checkbox" checked="checked" /></form>';
 
 	same(actual, expected, '');
 });

duel-js/target/duel.js

 /*global window */
 
 /**
- * @license DUEL v0.8.4 http://duelengine.org
+ * @license DUEL v0.8.5 http://duelengine.org
  * Copyright (c)2006-2012 Stephen M. McKamey.
  * Licensed under The MIT License.
  */
 	var isArray = Array.isArray || function(val) {
 		return (val instanceof Array);
 	};
-	
-	/**
-	 * Determines the type of the value
-	 * 
-	 * @private
-	 * @param {*} val the object being tested
-	 * @return {number}
-	 */
-	function getType(val) {
-		switch (typeof val) {
-			case 'object':
-				return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
-			case 'function':
-				return FUN;
-			case 'undefined':
-				return NUL;
-			default:
-				return VAL;
-		}
-	}
 
 	/**
 	 * Determines if the value is a string
 	}
 
 	/**
+	 * Determines the type of the value
+	 * 
+	 * @private
+	 * @param {*} val the object being tested
+	 * @return {number}
+	 */
+	function getType(val) {
+		switch (typeof val) {
+			case 'object':
+				return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
+			case 'function':
+				return FUN;
+			case 'undefined':
+				return NUL;
+			default:
+				return VAL;
+		}
+	}
+
+	/**
 	 * String buffer
 	 * 
 	 * @private
 		'async': 1,
 		'autofocus': 1,
 		'checked': 1,
+		'defaultchecked': 1,
 		'defer': 1,
 		'disabled': 1,
 		'formnovalidate': 1,
 		'hidden': 1,
+		'indeterminate': 1,
+		'ismap': 1,
 		'multiple': 1,
-		'novalidate': 1
+		'novalidate': 1,
+		'readonly': 1,
+		'required': 1,
+		'spellcheck': 1,
+		'willvalidate': 1
 		// can add more attributes here as needed
 	};
 
 				for (var name in child) {
 					if (child.hasOwnProperty(name)) {
 						var val = child[name];
-						if (ATTR_BOOL[name]) {
+						if (ATTR_BOOL[name.toLowerCase()]) {
 							if (val) {
 								val = name;
 							} else {
 	 * @type {Object.<string>}
 	 */
 	var ATTR_MAP = {
-		'rowspan': 'rowSpan',
-		'colspan': 'colSpan',
+		'accesskey': 'accessKey',
+		'bgcolor': 'bgColor',
 		'cellpadding': 'cellPadding',
 		'cellspacing': 'cellSpacing',
+		'checked': 'defaultChecked',
+		'class': 'className',
+		'colspan': 'colSpan',
+		'contenteditable': 'contentEditable',
+		'defaultchecked': 'defaultChecked',
+		'for': 'htmlFor',
+		'formnovalidate': 'formNoValidate',
+		'hidefocus': 'hideFocus',
+		'ismap': 'isMap',
+		'maxlength': 'maxLength',
+		'novalidate': 'noValidate',
+		'readonly': 'readOnly',
+		'rowspan': 'rowSpan',
+		'spellcheck': 'spellCheck',
 		'tabindex': 'tabIndex',
-		'accesskey': 'accessKey',
-		'hidefocus': 'hideFocus',
 		'usemap': 'useMap',
-		'maxlength': 'maxLength',
-		'readonly': 'readOnly',
-		'contenteditable': 'contentEditable'
+		'willvalidate': 'willValidate'
 		// can add more attributes here as needed
 	};
 
 	 */
 	var ATTR_DUP = {
 		'enctype': 'encoding',
-		'onscroll': 'DOMMouseScroll',
-		'checked': 'defaultChecked'
+		'onscroll': 'DOMMouseScroll'
 		// can add more attributes here as needed
 	};
 
 					// DOM Level 2
 					elem.addEventListener(name, handler, false);
 
-				} else if (isFunction(window.jQuery) && 'undefined' !== typeof elem[name]) {
+				} else if (isFunction(window.jQuery) && getType(elem[name]) !== NUL) {
 					// cop out and patch IE6-8 with jQuery
 					var $elem = window.jQuery(elem);
 					if (isFunction($elem.on)) {
 						$elem.bind(name, handler);	// pre-1.7
 					}
 
-				} else if (elem.attachEvent && 'undefined' !== typeof elem[name]) {
+				} else if (elem.attachEvent && getType(elem[name]) !== NUL) {
 					// IE legacy events
 					elem.attachEvent('on'+name, handler);
 
 				} else {
 					// DOM Level 0
 					var old = elem['on'+name] || elem[name];
-					elem['on'+name] = elem[name] = ('function' !== typeof old) ? handler :
+					elem['on'+name] = elem[name] = !isFunction(old) ? handler :
 						function(e) {
 							return (old.call(this, e) !== false) && (handler.call(this, e) !== false);
 						};
 	}
 
 	/**
-	 * Appends a child to an element
+	 * Appends an attribute to an element
 	 * 
 	 * @private
 	 * @param {Node} elem The element
 					}
 
 					name = ATTR_MAP[name.toLowerCase()] || name;
-					if (ATTR_BOOL[name]) {
-						elem[name] = !!value;
+					if (ATTR_BOOL[name.toLowerCase()]) {
+						value = !!value;
+					}
 
-						// also set duplicated attributes
-						if (ATTR_DUP[name]) {
-							elem[ATTR_DUP[name]] = !!value;
-						}
-
-					} else if (name === 'style') {
-						if (typeof elem.style.cssText !== 'undefined') {
+					if (name === 'style') {
+						if (getType(elem.style.cssText) !== NUL) {
 							elem.style.cssText = value;
 						} else {
 							elem.style = value;
 						}
 
-					} else if (name === 'class') {
-						elem.className = value;
-
 					} else if (name.substr(0,2) === 'on') {
 						addHandler(elem, name, value);
 
 						// also set duplicated events
-						if (ATTR_DUP[name]) {
-							addHandler(elem, ATTR_DUP[name], value);
+						name = ATTR_DUP[name];
+						if (name) {
+							addHandler(elem, name, value);
 						}
 
-					} else if (type === VAL && name.charAt(0) !== '$') {
-						elem.setAttribute(name, value);
-	
-						// also set duplicated attributes
-						if (ATTR_DUP[name]) {
-							elem.setAttribute(ATTR_DUP[name], value);
+					} else if (type !== VAL || name.charAt(0) === '$' || getType(elem[name]) !== NUL || getType(elem[ATTR_DUP[name]]) !== NUL) {
+						// direct setting of existing properties
+						elem[name] = value;
+
+						// also set duplicated properties
+						name = ATTR_DUP[name];
+						if (name) {
+							elem[name] = value;
+						}
+
+					} else if (ATTR_BOOL[name.toLowerCase()]) {
+						if (value) {
+							// boolean attributes
+							elem.setAttribute(name, name);
+
+							// also set duplicated attributes
+							name = ATTR_DUP[name];
+							if (name) {
+								elem.setAttribute(name, name);
+							}
 						}
 
 					} else {
-						// allow direct setting of complex properties
-						elem[name] = value;
+						// http://www.quirksmode.org/dom/w3c_core.html#attributes
+
+						// custom and 'data-*' attributes
+						elem.setAttribute(name, value);
 
 						// also set duplicated attributes
-						if (ATTR_DUP[name]) {
-							elem[ATTR_DUP[name]] = value;
+						name = ATTR_DUP[name];
+						if (name) {
+							elem.setAttribute(name, value);
 						}
 					}
 				}
 			try {
 				delete elem[key];
 			} catch (ex) {
-				// sometimes IE doesn't like deleting from DOM
-				elem[key] = undef;
+				try {
+					// IE7 doesn't like deleting from DOM
+					elem[key] = '';
+					elem.removeAttribute(key);
+				} catch (ex2) {}
 			}
 
 			if (!isFunction(method)) {
 					/*jslint evil:true */
 					method = new Function(''+method);
 					/*jslint evil:false */
-				} catch (ex2) {
+				} catch (ex3) {
 					// filter
 					method = null;
 				}

duel-js/target/duel.min.js

 /*
- DUEL v0.8.4 http://duelengine.org
+ DUEL v0.8.5 http://duelengine.org
  Copyright (c)2006-2012 Stephen M. McKamey.
  Licensed under The MIT License.
 */
-var duel=function(m,u,w){function x(a){this.value=a}function l(a){switch(typeof a){case "object":return!a?0:y(a)?2:a instanceof x?5:a instanceof Date?4:3;case "function":return 1;case "undefined":return 0;default:return 4}}function n(a){return"function"===typeof a}function o(){this.value=o.FAST?"":[]}function q(a){y(a)||(a=["",a]);this.value=a}function r(a,b){switch(l(b)){case 2:if(""===b[0])for(var c=1,f=b.length;c<f;c++)r(a,b[c]);else a.push(b);break;case 3:if(1===a.length)a.push(b);else if(c=a[1],
-3===l(c))for(f in b)b.hasOwnProperty(f)&&(c[f]=b[f]);else a.splice(1,0,b);break;case 4:""!==b&&(b=""+b,c=a.length-1,0<c&&4===l(a[c])?a[c]+=b:a.push(b));break;case 0:break;default:a.push(b)}}function s(a,b,c,f,d,e){var g=3===l(a[1]);if(a.length===(g?3:2))return k(a[a.length-1],b,c,f,d,e);for(var h=[""],g=g?2:1,j=a.length;g<j;g++)r(h,k(a[g],b,c,f,d,e));return h}function A(a,b,c,f,d,e){for(var g=1,h=a.length;g<h;g++){var j=a[g],i=j[1].test;if(3===l(j[1])&&i&&(n(i)&&(i=i(b,c,f,d)),!i))continue;return s(j,
-b,c,f,d,e)}return null}function J(a){return"string"!==typeof a?a:a.replace(/[&<>]/g,function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";default:return a}})}function K(a){return"string"!==typeof a?a:a.replace(/[&<>"]/g,function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";default:return a}})}function B(a,b){var c=b[0]||"",f=b.length,d=1,e,g=L[c];if("!"===c.charAt(0))"!DOCTYPE"===b[0]?a.append("<!DOCTYPE ",
-b[1],">"):a.append("<\!--",b[1],"--\>");else{if(c){a.append("<",c);e=b[d];if(3===l(e)){for(var h in e)if(e.hasOwnProperty(h)){var j=e[h];if(C[h])if(j)j=h;else continue;a.append(" ",h);0!==l(j)&&a.append('="',K(j),'"')}d++}g&&a.append(" /");a.append(">")}for(;d<f;d++)e=b[d],y(e)?B(a,e):a.append(J(e));c&&!g&&a.append("</",c,">")}}function t(a){if(a){if("!"===a.charAt(0))return m.createComment("!"===a?"":a.substr(1)+" ")}else{if(m.createDocumentFragment)return m.createDocumentFragment();a=""}return"style"===
-a.toLowerCase()&&m.createStyleSheet?m.createStyleSheet():m.createElement(a)}function v(a,b){if(b){var c=(a.tagName||"").toLowerCase();if(8===a.nodeType)3===b.nodeType&&(a.nodeValue+=b.nodeValue);else if("table"===c&&a.tBodies)if(b.tagName)if((c=b.tagName.toLowerCase())&&"tbody"!==c&&"thead"!==c){var f=0<a.tBodies.length?a.tBodies[a.tBodies.length-1]:null;f||(f=t("th"===c?"thead":"tbody"),a.appendChild(f));f.appendChild(b)}else!1!==a.canHaveChildren&&a.appendChild(b);else{if(11===b.nodeType)for(;b.firstChild;)v(a,
-b.removeChild(b.firstChild))}else if("style"===c&&m.createStyleSheet)a.cssText=b;else if(!1!==a.canHaveChildren)a.appendChild(b);else if("object"===c&&b.tagName&&"param"===b.tagName.toLowerCase()){try{a.appendChild(b)}catch(d){}try{a.object&&(a.object[b.name]=b.value)}catch(e){}}}}function D(a,b,c){"on"===b.substr(0,2)&&(b=b.substr(2));switch(typeof c){case "function":if(a.addEventListener)a.addEventListener(b,c,!1);else if(n(window.jQuery)&&"undefined"!==typeof a[b])if(a=window.jQuery(a),n(a.on))a.on(b,
-c);else a.bind(b,c);else if(a.attachEvent&&"undefined"!==typeof a[b])a.attachEvent("on"+b,c);else{var f=a["on"+b]||a[b];a["on"+b]=a[b]="function"!==typeof f?c:function(a){return!1!==f.call(this,a)&&!1!==c.call(this,a)}}break;case "string":a["on"+b]=new Function("event",c)}}function E(a){return!!a&&3===a.nodeType&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function F(a,b){a&&(3===a.nodeType&&b.exec(a.nodeValue))&&(a.nodeValue=a.nodeValue.replace(b,""))}function z(a){if(a){for(;E(a.firstChild);)a.removeChild(a.firstChild);
-for(F(a.firstChild,M);E(a.lastChild);)a.removeChild(a.lastChild);F(a.lastChild,N)}}function G(a,b){var c=a[b];if(c){try{delete a[b]}catch(f){a[b]=w}if(!n(c))try{c=new Function(""+c)}catch(d){c=null}}return c}function H(a){if(a){var b=G(a,"$init");b&&b.call(a);(b=G(a,"$load"))?setTimeout(function(){b.call(a);b=a=null},0):b=a=null}}function I(a,b){for(var c=1,f=b.length;c<f;c++){var d=b[c];switch(l(d)){case 2:var e=d[0],d=I(t(e),d);if("html"===e)return z(d),H(d),d;v(a,d);break;case 4:""!==d&&v(a,m.createTextNode(""+
-d));break;case 3:if(1===a.nodeType){var e=a,g=d;if(g.name&&m.attachEvent&&!e.parentNode)try{var h=t("<"+e.tagName+' name="'+g.name+'">');e.tagName===h.tagName&&(e=h)}catch(j){}d=void 0;for(d in g)if(g.hasOwnProperty(d)){var i=g[d],k=l(i);d&&((0===k&&(i="",k=4),d=O[d.toLowerCase()]||d,C[d])?(e[d]=!!i,p[d]&&(e[p[d]]=!!i)):"style"===d?"undefined"!==typeof e.style.cssText?e.style.cssText=i:e.style=i:"class"===d?e.className=i:"on"===d.substr(0,2)?(D(e,d,i),p[d]&&D(e,p[d],i)):4===k&&"$"!==d.charAt(0)?(e.setAttribute(d,
-i),p[d]&&e.setAttribute(p[d],i)):(e[d]=i,p[d]&&(e[p[d]]=i)))}a=e}break;case 5:e=v;g=a;i=d;d=t("div");d.innerHTML=""+i;z(d);if(1===d.childNodes.length)d=d.firstChild;else{for(i=t("");d.firstChild;)i.appendChild(d.firstChild);d=i}e(g,d)}}z(a);H(a);11===a.nodeType&&1===a.childNodes.length&&(a=a.firstChild);return a}x.prototype.toString=function(){return this.value};var y=Array.isArray||function(a){return a instanceof Array};o.FAST=!(u&&9>u());o.prototype.append=function(a,b,c){o.FAST?null!==a&&(this.value+=
-a,null!==b&&b!==w&&(this.value+=b,null!==c&&c!==w&&(this.value+=c))):this.value.push.apply(this.value,arguments)};o.prototype.clear=function(){this.value=o.FAST?"":[]};o.prototype.toString=function(){return o.FAST?this.value:this.value.join("")};var k;k=function(a,b,c,f,d,e){switch(l(a)){case 1:return a(b,c,f,d);case 2:var g=a[0]||"";switch(g){case "$for":a:{var h=a[1]||{},g=[""],j;if(h.hasOwnProperty("count")){j=h.count;n(j)&&(j=j(b,c,f,d));h.hasOwnProperty("data")?(h=h.data,n(h)&&(h=h(b,c,f,d))):
-h=b;for(b=0;b<j;b++)r(g,s(a,h,b,j,null,e))}else{if(h.hasOwnProperty("in")){var i=h["in"];n(i)&&(i=i(b,c,f,d));if(3===l(i)){h=[];for(j in i)i.hasOwnProperty(j)&&h.push(j);b=0;for(j=h.length;b<j;b++)r(g,s(a,i[h[b]],b,j,h[b],e));a=g;break a}h=i}else h=h.each,n(h)&&(h=h(b,c,f,d));b=l(h);if(2===b){b=0;for(j=h.length;b<j;b++)r(g,s(a,h[b],b,j,null,e))}else 0!==b&&(g=s(a,h,0,1,null,e))}a=g}return a;case "$xor":return A(a,b,c,f,d,e);case "$if":return A(["$xor",a],b,c,f,d,e);case "$call":e=a[1]||{};if(e.view){g=
-k(e.view,b,c,f,d);h=e.hasOwnProperty("data")?k(e.data,b,c,f,d):b;j=e.hasOwnProperty("index")?k(e.index,b,c,f,d):c;i=e.hasOwnProperty("count")?k(e.count,b,c,f,d):f;b=e.hasOwnProperty("key")?k(e.key,b,c,f,d):d;c={};for(f=a.length-1;2<=f;f--)d=a[f],e=d[1]||{},e.hasOwnProperty("name")&&(c[e.name]=d);a=g&&n(g.getView)?k(g.getView(),h,j,i,b,c):null}else a=null;return a;case "$part":return g=(a[1]||{}).name||"",g=e&&e.hasOwnProperty(g)?e[g]:a,s(g,b,c,f,d)}g=[g];h=1;for(j=a.length;h<j;h++)r(g,k(a[h],b,c,
-f,d,e));return g;case 3:e={};for(g in a)a.hasOwnProperty(g)&&(e[g]=k(a[g],b,c,f,d));return e}return a};u=function(a){if(!n(a)||!n(a.getView)){var b=a;2!==l(b)&&(b=["",b]);a=function(a,f,d,e){try{var g=k(b,a,isFinite(f)?f:0,isFinite(d)?d:1,"string"===typeof e?e:null);return new q(g)}catch(h){return new q("["+h+"]")}};a.getView=function(){return b}}return a};u.raw=function(a){return new x(a)};var L={area:!0,base:!0,basefont:!0,br:!0,col:!0,frame:!0,hr:!0,img:!0,input:!0,isindex:!0,keygen:!0,link:!0,
-meta:!0,param:!0,source:!0,wbr:!0},C={async:1,autofocus:1,checked:1,defer:1,disabled:1,formnovalidate:1,hidden:1,multiple:1,novalidate:1};q.prototype.toString=function(){var a;var b=this.value;try{var c=new o;B(c,b);a=c.toString()}catch(f){a="["+f+"]"}return a};q.prototype.write=function(a){(a||m).write(""+this)};var O={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",checked:"defaultChecked"},M=/^[\r\n]+/,N=/[\r\n]+$/;q.prototype.toDOM=function(a,b){4===l(a)&&(a=m.getElementById(a));var c;try{b&&(c=a,a=null),c=I(c||t(this.value[0]),this.value)}catch(f){c=m.createTextNode("["+f+"]")}a&&a.parentNode&&a.parentNode.replaceChild(c,a);return c};q.prototype.reload=function(){var a=m;try{var b=this.toDOM();a.replaceChild(b,a.documentElement);if(a.createStyleSheet){for(var c=
-b.firstChild;c&&"HEAD"!==(c.tagName||"");)c=c.nextSibling;for(var f=c&&c.firstChild;f;){if("LINK"===(f.tagName||""))f.href=f.href;f=f.nextSibling}}}catch(d){a=a.open("text/html"),a.write(this.toString()),a.close()}};return u}(document,window.ScriptEngineMajorVersion);
+var duel=function(m,t,A){function w(a){this.value=a}function l(a){return"function"===typeof a}function k(a){switch(typeof a){case "object":return!a?0:x(a)?2:a instanceof w?5:a instanceof Date?4:3;case "function":return 1;case "undefined":return 0;default:return 4}}function o(){this.value=o.FAST?"":[]}function p(a){x(a)||(a=["",a]);this.value=a}function q(a,b){switch(k(b)){case 2:if(""===b[0])for(var c=1,f=b.length;c<f;c++)q(a,b[c]);else a.push(b);break;case 3:if(1===a.length)a.push(b);else if(c=a[1],
+3===k(c))for(f in b)b.hasOwnProperty(f)&&(c[f]=b[f]);else a.splice(1,0,b);break;case 4:""!==b&&(b=""+b,c=a.length-1,0<c&&4===k(a[c])?a[c]+=b:a.push(b));break;case 0:break;default:a.push(b)}}function r(a,b,c,f,d,e){var g=3===k(a[1]);if(a.length===(g?3:2))return n(a[a.length-1],b,c,f,d,e);for(var h=[""],g=g?2:1,j=a.length;g<j;g++)q(h,n(a[g],b,c,f,d,e));return h}function B(a,b,c,f,d,e){for(var g=1,h=a.length;g<h;g++){var j=a[g],i=j[1].test;if(3===k(j[1])&&i&&(l(i)&&(i=i(b,c,f,d)),!i))continue;return r(j,
+b,c,f,d,e)}return null}function J(a){return"string"!==typeof a?a:a.replace(/[&<>]/g,function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";default:return a}})}function K(a){return"string"!==typeof a?a:a.replace(/[&<>"]/g,function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";default:return a}})}function C(a,b){var c=b[0]||"",f=b.length,d=1,e,g=L[c];if("!"===c.charAt(0))"!DOCTYPE"===b[0]?a.append("<!DOCTYPE ",
+b[1],">"):a.append("<\!--",b[1],"--\>");else{if(c){a.append("<",c);e=b[d];if(3===k(e)){for(var h in e)if(e.hasOwnProperty(h)){var j=e[h];if(y[h.toLowerCase()])if(j)j=h;else continue;a.append(" ",h);0!==k(j)&&a.append('="',K(j),'"')}d++}g&&a.append(" /");a.append(">")}for(;d<f;d++)e=b[d],x(e)?C(a,e):a.append(J(e));c&&!g&&a.append("</",c,">")}}function s(a){if(a){if("!"===a.charAt(0))return m.createComment("!"===a?"":a.substr(1)+" ")}else{if(m.createDocumentFragment)return m.createDocumentFragment();
+a=""}return"style"===a.toLowerCase()&&m.createStyleSheet?m.createStyleSheet():m.createElement(a)}function v(a,b){if(b){var c=(a.tagName||"").toLowerCase();if(8===a.nodeType)3===b.nodeType&&(a.nodeValue+=b.nodeValue);else if("table"===c&&a.tBodies)if(b.tagName)if((c=b.tagName.toLowerCase())&&"tbody"!==c&&"thead"!==c){var f=0<a.tBodies.length?a.tBodies[a.tBodies.length-1]:null;f||(f=s("th"===c?"thead":"tbody"),a.appendChild(f));f.appendChild(b)}else!1!==a.canHaveChildren&&a.appendChild(b);else{if(11===
+b.nodeType)for(;b.firstChild;)v(a,b.removeChild(b.firstChild))}else if("style"===c&&m.createStyleSheet)a.cssText=b;else if(!1!==a.canHaveChildren)a.appendChild(b);else if("object"===c&&b.tagName&&"param"===b.tagName.toLowerCase()){try{a.appendChild(b)}catch(d){}try{a.object&&(a.object[b.name]=b.value)}catch(e){}}}}function D(a,b,c){"on"===b.substr(0,2)&&(b=b.substr(2));switch(typeof c){case "function":if(a.addEventListener)a.addEventListener(b,c,!1);else if(l(window.jQuery)&&0!==k(a[b]))if(a=window.jQuery(a),
+l(a.on))a.on(b,c);else a.bind(b,c);else if(a.attachEvent&&0!==k(a[b]))a.attachEvent("on"+b,c);else{var f=a["on"+b]||a[b];a["on"+b]=a[b]=!l(f)?c:function(a){return!1!==f.call(this,a)&&!1!==c.call(this,a)}}break;case "string":a["on"+b]=new Function("event",c)}}function E(a){return!!a&&3===a.nodeType&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function F(a,b){a&&(3===a.nodeType&&b.exec(a.nodeValue))&&(a.nodeValue=a.nodeValue.replace(b,""))}function z(a){if(a){for(;E(a.firstChild);)a.removeChild(a.firstChild);
+for(F(a.firstChild,M);E(a.lastChild);)a.removeChild(a.lastChild);F(a.lastChild,N)}}function G(a,b){var c=a[b];if(c){try{delete a[b]}catch(f){try{a[b]="",a.removeAttribute(b)}catch(d){}}if(!l(c))try{c=new Function(""+c)}catch(e){c=null}}return c}function H(a){if(a){var b=G(a,"$init");b&&b.call(a);(b=G(a,"$load"))?setTimeout(function(){b.call(a);b=a=null},0):b=a=null}}function I(a,b){for(var c=1,f=b.length;c<f;c++){var d=b[c];switch(k(d)){case 2:var e=d[0],d=I(s(e),d);if("html"===e)return z(d),H(d),
+d;v(a,d);break;case 4:""!==d&&v(a,m.createTextNode(""+d));break;case 3:if(1===a.nodeType){var e=a,g=d;if(g.name&&m.attachEvent&&!e.parentNode)try{var h=s("<"+e.tagName+' name="'+g.name+'">');e.tagName===h.tagName&&(e=h)}catch(j){}d=void 0;for(d in g)if(g.hasOwnProperty(d)){var i=g[d],l=k(i);d&&((0===l&&(i="",l=4),d=O[d.toLowerCase()]||d,y[d.toLowerCase()]&&(i=!!i),"style"===d)?0!==k(e.style.cssText)?e.style.cssText=i:e.style=i:"on"===d.substr(0,2)?(D(e,d,i),(d=u[d])&&D(e,d,i)):4!==l||"$"===d.charAt(0)||
+0!==k(e[d])||0!==k(e[u[d]])?(e[d]=i,(d=u[d])&&(e[d]=i)):y[d.toLowerCase()]?i&&(e.setAttribute(d,d),(d=u[d])&&e.setAttribute(d,d)):(e.setAttribute(d,i),(d=u[d])&&e.setAttribute(d,i)))}a=e}break;case 5:e=v;g=a;i=d;d=s("div");d.innerHTML=""+i;z(d);if(1===d.childNodes.length)d=d.firstChild;else{for(i=s("");d.firstChild;)i.appendChild(d.firstChild);d=i}e(g,d)}}z(a);H(a);11===a.nodeType&&1===a.childNodes.length&&(a=a.firstChild);return a}w.prototype.toString=function(){return this.value};var x=Array.isArray||
+function(a){return a instanceof Array};o.FAST=!(t&&9>t());o.prototype.append=function(a,b,c){o.FAST?null!==a&&(this.value+=a,null!==b&&b!==A&&(this.value+=b,null!==c&&c!==A&&(this.value+=c))):this.value.push.apply(this.value,arguments)};o.prototype.clear=function(){this.value=o.FAST?"":[]};o.prototype.toString=function(){return o.FAST?this.value:this.value.join("")};var n;n=function(a,b,c,f,d,e){switch(k(a)){case 1:return a(b,c,f,d);case 2:var g=a[0]||"";switch(g){case "$for":a:{var h=a[1]||{},g=
+[""],j;if(h.hasOwnProperty("count")){j=h.count;l(j)&&(j=j(b,c,f,d));h.hasOwnProperty("data")?(h=h.data,l(h)&&(h=h(b,c,f,d))):h=b;for(b=0;b<j;b++)q(g,r(a,h,b,j,null,e))}else{if(h.hasOwnProperty("in")){var i=h["in"];l(i)&&(i=i(b,c,f,d));if(3===k(i)){h=[];for(j in i)i.hasOwnProperty(j)&&h.push(j);b=0;for(j=h.length;b<j;b++)q(g,r(a,i[h[b]],b,j,h[b],e));a=g;break a}h=i}else h=h.each,l(h)&&(h=h(b,c,f,d));b=k(h);if(2===b){b=0;for(j=h.length;b<j;b++)q(g,r(a,h[b],b,j,null,e))}else 0!==b&&(g=r(a,h,0,1,null,
+e))}a=g}return a;case "$xor":return B(a,b,c,f,d,e);case "$if":return B(["$xor",a],b,c,f,d,e);case "$call":e=a[1]||{};if(e.view){g=n(e.view,b,c,f,d);h=e.hasOwnProperty("data")?n(e.data,b,c,f,d):b;j=e.hasOwnProperty("index")?n(e.index,b,c,f,d):c;i=e.hasOwnProperty("count")?n(e.count,b,c,f,d):f;b=e.hasOwnProperty("key")?n(e.key,b,c,f,d):d;c={};for(f=a.length-1;2<=f;f--)d=a[f],e=d[1]||{},e.hasOwnProperty("name")&&(c[e.name]=d);a=g&&l(g.getView)?n(g.getView(),h,j,i,b,c):null}else a=null;return a;case "$part":return g=
+(a[1]||{}).name||"",g=e&&e.hasOwnProperty(g)?e[g]:a,r(g,b,c,f,d)}g=[g];h=1;for(j=a.length;h<j;h++)q(g,n(a[h],b,c,f,d,e));return g;case 3:e={};for(g in a)a.hasOwnProperty(g)&&(e[g]=n(a[g],b,c,f,d));return e}return a};t=function(a){if(!l(a)||!l(a.getView)){var b=a;2!==k(b)&&(b=["",b]);a=function(a,f,d,e){try{var g=n(b,a,isFinite(f)?f:0,isFinite(d)?d:1,"string"===typeof e?e:null);return new p(g)}catch(h){return new p("["+h+"]")}};a.getView=function(){return b}}return a};t.raw=function(a){return new w(a)};
+var L={area:!0,base:!0,basefont:!0,br:!0,col:!0,frame:!0,hr:!0,img:!0,input:!0,isindex:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,wbr:!0},y={async:1,autofocus:1,checked:1,defaultchecked:1,defer:1,disabled:1,formnovalidate:1,hidden:1,indeterminate:1,ismap:1,multiple:1,novalidate:1,readonly:1,required:1,spellcheck:1,willvalidate:1};p.prototype.toString=function(){var a;var b=this.value;try{var c=new o;C(c,b);a=c.toString()}catch(f){a="["+f+"]"}return a};p.prototype.write=function(a){(a||m).write(""+
+this)};var O={accesskey:"accessKey",bgcolor:"bgColor",cellpadding:"cellPadding",cellspacing:"cellSpacing",checked:"defaultChecked","class":"className",colspan:"colSpan",contenteditable:"contentEditable",defaultchecked:"defaultChecked","for":"htmlFor",formnovalidate:"formNoValidate",hidefocus:"hideFocus",ismap:"isMap",maxlength:"maxLength",novalidate:"noValidate",readonly:"readOnly",rowspan:"rowSpan",spellcheck:"spellCheck",tabindex:"tabIndex",usemap:"useMap",willvalidate:"willValidate"},u={enctype:"encoding",
+onscroll:"DOMMouseScroll"},M=/^[\r\n]+/,N=/[\r\n]+$/;p.prototype.toDOM=function(a,b){4===k(a)&&(a=m.getElementById(a));var c;try{b&&(c=a,a=null),c=I(c||s(this.value[0]),this.value)}catch(f){c=m.createTextNode("["+f+"]")}a&&a.parentNode&&a.parentNode.replaceChild(c,a);return c};p.prototype.reload=function(){var a=m;try{var b=this.toDOM();a.replaceChild(b,a.documentElement);if(a.createStyleSheet){for(var c=b.firstChild;c&&"HEAD"!==(c.tagName||"");)c=c.nextSibling;for(var f=c&&c.firstChild;f;){if("LINK"===
+(f.tagName||""))f.href=f.href;f=f.nextSibling}}}catch(d){a=a.open("text/html"),a.write(this.toString()),a.close()}};return t}(document,window.ScriptEngineMajorVersion);
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.