/***
_.js client-side xhtml generation library (c) 2009-2011 by Richard Edwards.

license: use however you wish.

For a recent AJAX project, I needed a highly efficient cross-browser solution for client-side content generation. 
Finding none to my satisfaction, I wrote my own. This library is a reusable by-product of that work.

HTML is a bulky notation, with every open tag essentially being duplicated in its close tag. Moreover, repeated 
constructs are usually just sent over-the-wire again. Yet, many web developers have discovered that stuffing 
content into the innerHTML of an existing element is often the fastest way to create new content on-the-fly.

To deal with the need for fast HTML generation, I created a single comprehensive function that can implement 
all possible tags and tag attributes. Furthermore, I wanted this versatility with a minimal footprint on 
JavaScript's global namespace. And I wanted it to be easy to rework existing HTML code into equivalent clientlets.

My new _.js client-side XHTML generation library defines a single function that easily generates arbitrary HTML. 
In a nod to Prototype's ubiquitous $ function, I named my function _. The _ function accepts one or more 
parameters, the first of which is the required tag name. Pairs of optional parameters that follow define 
attribute-value pairs. An optional final parameter is the content to be wrapped. For convenience, attribute-value 
pairs can be specified as JavaScript objects. As an alternative, the tag, attribute-value pairs and content can 
all be passed as elements of a single array parameter. With these parameters in HTML source order, clientlets are 
easy to create from raw HTML. 

But when an array is passed as an optional parameter, it indicates that multiple tags are to be built. The array 
elements stripe across all pending tag constructs. Any missing attributes, values or content are simply echoed 
from prior pending constructs. This parallel tag construction capability is what makes _ rock. Reusable variables 
can hold collections of tag attributes, whole tags, or even arrays of related tags.

And it's lightning fast!

EXAMPLE:

 _('table',{border:0,cellpadding:0,cellspacing:0},
  _('tr',[
   _('td',{colspan:7},ctr={align:'center'},[
    _('select',{name:'month',title:'month'},
     _('option','value',[0,1,2,3,4,5,6,7,8,9,10,11],'JanFebMarAprMayJunJulAugSepOctNovDec'.match(/.../g))._())].concat(
    _('input','type',['hidden','text'],'name',hint=['date','day','year'],'title',hint,'size',[10,2,4],'value',[(d=new Date())._('mm/dd/yyyy'),d._('d'),d._('yyyy')]))._())._(),
   _('td',ctr,sq={style:'width:25px;height:25px'},
    _('input',btn={type:'button'},{name:'dow'},sq,'value','SMTWTFS'.split('')))._(),x=
   _('td',{align:'right'},sq,[x=
    _('input',btn,{name:'btn'},sq),x,x,x,x,x,x])._(),x,x,x,x,x])._())


I can be reached via email at ricardo[dot]eduardos[at]gmail[dot]com.

CLIENTLET EDITOR:  http://ca_redwards.webs.com/_.html

To evaluate (or design new) javascript "clientlet" expressions, just use my clientlet editor. Try it!  Copy the example above and paste it to
replace the contents of the lefthand textarea.  Press the generate "html" button to evaluate it.  Mouseover each textarea to appreciate the
difference in size between the clientlet and the html it generates.  In the center textarea, edit (or paste in new) html code as desired.
Press the render "live" button to see how it looks in the div on the right.  Modern web browsers allow the live content to be edited (retype
cell contents, button labels, etc).  Finally, press the infer "code" button to walk the DOM of the live div to generate an equivalent clientlet.

Though it would certainly optimize its results, the infer "code" function does not (presently) attempt to construct array or object parameters.

 ***/

/* implement missing array functions for IE 4-5 */
if (!Array.prototype.push)
	Array.prototype.push = function(item) {
		this[this.length] = item;
		return this
	};
if (!Array.prototype.pop)
	Array.prototype.pop = function() {
		var item = this[this.length - 1];
		this.length--;
		return item;
	};
if (!Array.prototype.concat)
	Array.prototype.concat = function(arr) {
		var result = [];
		for ( var i = 0; i < this.length; i++)
			result.push(this[i]);
		for ( var i = 0; i < arr.length; i++)
			result.push(arr[i]);
		return result;
	};

	/*** convenience functions ***/
	/**
	 * (string/array).write into current page
	 */

Array.prototype.write = String.prototype.write = function() {
	document.write(this);
	return this
};
/**
 * (string/array).status update status line
 */
Array.prototype.status = String.prototype.status = function() {
	window.status = this;
	return this
};
/**
 * (string/array).title retitle document
 */
Array.prototype.title = String.prototype.title = function() {
	document.title = this;
	return this
};
/**
 * (string/array).alert pop an alert dialog
 */
Array.prototype.alert = String.prototype.alert = function() {
	alert(this);
	return this
};
/**
 * concatenate array elements together (tight)
 */
Array.prototype._ = function(delim) {
	if (!delim) {
		return this.join('')
	} else
		return this.join(delim)
}; // string-ize array by concatenation.
/**
 * tautology, used when a single element array operation yields a string
 */
String.prototype._ = function() {
	return this
};
/**
 * rewrites string/array as a string suitable for inclusion in a web page
 */
Array.prototype.source = String.prototype.source = function() {
	return this.toString().replace(/\</g, '&lt;').replace(/\>/g, '&gt;')
			.replace(/"/g, '&quot;')
};
/**
 * zero-padded to 2-digits.
 */
Number.prototype._ = function() {
	return (this < 10) ? ('0' + this) : this
};
/**
 * formats a date using a formatting string thus... yyyy: 4-digit year mmmm:
 * verbose month of year mmm: abbreviated month of year mm: 2-digit month m: 1-
 * or 2-digit month dddd: verbose day of week ddd: abbreviated day of week dd:
 * 2-digit day of month d: 1- or 2-digit day of month
 * 
 * @param {Object}
 *            s
 */
Date.prototype._ = function(s) {
    return s
        .replace(/\byyyy\b/g, this.getFullYear())
        .replace(
            /\bmmmm\b/g,
            'January February March April May June July August September October November December'
                .split(' ')[this.getMonth()]).replace(
            /\bmmm\b/g,
            'JanFebMarAprMayJunJulAugSepOctNovDec'.match(/.../g)[this
                .getMonth()]).replace(/\bmm\b/g,
            (this.getMonth() + 1)._()).replace(/\bm\b/g,
            this.getMonth() + 1).replace(
            /\bdddd\b/g,
            'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'
                .split(' ')[this.getDay()]).replace(/\bddd\b/g,
            'SunMonTueWedThuFriSat'.match(/.../g)[this.getDay()])
        .replace(/\bdd\b/g, (this.getDate())._()).replace(/\bd\b/g,
            this.getDate())
};
/**
 * adds any number of days, months and years onto an existing date (presumed
 * zero, if missing). Negative values acceptable.
 * 
 * @param {Object}
 *            days
 * @param {Object}
 *            months
 * @param {Object}
 *            years
 */
Date.prototype.add = function(days, months, years) {
	return new Date(this.getFullYear() + (!years ? 0 : years), this.getMonth()
			+ (!months ? 0 : months), this.getDate() + (!days ? 0 : days))
};

/**
 * The _ function accepts one or more parameters, the first of which is the
 * required tag name. Pairs of optional parameters that follow define
 * attribute-value pairs. An optional final parameter is the content to be
 * wrapped. For convenience, attribute-value pairs can be specified as
 * JavaScript objects. As an alternative, the tag, attribute-value pairs and
 * content can all be passed as elements of a single array parameter. With these
 * parameters in HTML source order, clientlets are easy to create from raw HTML.
 * But when an array is passed as an optional parameter, it indicates that
 * multiple tags are to be built in parallel. The array elements stripe across
 * all pending tag constructs. Any missing attributes, values or content are
 * simply echoed from prior pending constructs. This parallel tag construction
 * capability is what makes _ rock.
 * 
 * @param {Object}
 *            tag
 */
function _(tag) {

	if (_.arguments.length < 1) {
		return alert(_)
	} // tag is required.
	/**
	 * True typeof function
	 * 
	 * @param {Object}
	 *            obj
	 * @param {Object}
	 *            proto
	 */
	function isTypeOf(obj, proto) {
		return obj && obj.constructor === proto
	}
	/**
	 * is param descended from Object?
	 * 
	 * @param {Object}
	 *            obj
	 */
	function isObject(obj) {
		return isTypeOf(obj, Object)
	}
	/**
	 * is param descended from Array?
	 * 
	 * @param {Object}
	 *            obj
	 */
	function isArray(obj) {
		return isTypeOf(obj, Array)
	}

	if (isObject(tag)) { // object array-ized
		var arr = [];
		for (a in tag) {
			arr.push(a);
			arr.push(tag[a]);
		}
		;
		return arr;
	}
	/**
	 * Tag[,Attr,Value][,Content] function takes an array parameter... 1st
	 * element is tag, followed by pairs of elements (implying attribute=value
	 * pairs], with final element presumed to be content for enclosure
	 * 
	 * @param {Object} array
	 */
    function tavc(array){
    	/**
    	 * Tag function takes a name parameter to return a simple tag (reused for end tags).
    	 * @param name
    	 * @return
    	 */
        function t(name){
            return ['<', '>'].join(name);
        }
        /**
         * Attribute="Value" function accepts an attribute name and an unquoted value.
         * @param attribute
         * @param value
         * @return
         */
        function av(attribute, value){
        	/**
        	 * "Quoted" function accepts a value (which should NOT contain any double quote characters).
        	 * @param value
        	 * @return
        	 */
            function q(value){
                return ['"', '"'].join(value);
            }
            return [attribute, q(value)].join('=');
        }
        
        var x = [array[0]];  //tag is required (tag should not be empty nor contain spaces).
        for (var i = 2; i < array.length; i += 2) {
            x.push(av(array[i - 1], array[i])); // optional attribute-value pairs (starting AFTER the tag element). 
        }
        if (array.length % 2 > 0) {
            x.push('/');
            return t(x.join(' ')); // without any dangling element at the end of the array, this construct is presumed to be an "empty" element.
        }
        else {
            return [t(x.join(' ')), array[array.length - 1], t(['/', array[0]].join(''))].join(''); // a dangling element is the content to be wrapped.
        }
    }

    if (isArray(tag)) { // tag[,attr,value]...[,content]
		return tavc(tag);
	}

	var result = [ [ tag ] ]; // array of arrays into feed tavc (supports multiple parallel tag constructions with possibly differing attributes/values). 

	/**
	 * echoes last element onto existing array
	 * 
	 * @param {Object}
	 *            arr
	 */
	function echo(arr) { // echoes last element
		arr.push(arr[arr.length-1]);
		return arr;
	}

	/**
	 * processes a parameter in ongoing tag construction(s).
	 * 
	 * @param {Object}
	 *            item
	 */
	function todo(item) { // processes an optional parameter
		if (isObject(item)) {
			for ( var i = 0; i < result.length; i++) {
				if ((result[i].length % 2) == 0)
					result[i] = echo(result[i]); // every attribute needs a
				// value! (ie. selected,
				// readonly, disabled)
				result[i] = result[i].concat(_(item)); // adds array-ized
				// object onto all
				// results.
			}
		} else {
			if (!isArray(item))
				item = [ item ]; // array-ize, if needed.
			while (result.length > item.length)
				item = echo(item); // echo item, if needed.
			while (result.length < item.length)
				result = echo(result); // echo results, if needed.
			for ( var i = 0; i < result.length; i++) {
				result[i] = result[i].concat( [ item[i] ]); // add this item
				// onto all results.
			}
		}
		;
	}

	for ( var i = 1; i < _.arguments.length; i++) {
		todo(_.arguments[i]); //processes each _ argument as either a single value, or an array of values (possibly as an object bearing attribute-value pairs).  
	}

	for ( var i = 0; i < result.length; i++) {
		result[i] = _(result[i]); // creates array of tavc'd results
	}
	if (result.length == 1) {
		result = result[0]; // pops string out of frivilous array.
	}
	return result; // returns string (or array of strings).

};
/**
 * writes an object as a JSON string
 */
_.toJSON = function(object) { 
	function q(value){
        return ['"','"'].join(value);
    }
	switch(object.constructor) {
		case Object:{
			var results = [];
			for (var property in object) {
				var value = _.toJSON(object[property]);
				results.push([q(property),value].join(':'));
			}
			return ['{','}'].join(results );
		};
		break;
		case Array:{
			var results = [];
			for ( var i = 0; i < object.length; i++) {
				var value = _.toJSON(object[i]);
				results.push(value);
			}
			return ['[',']'].join(results);
		};
		break;
		case Date: {
			return ['new Date(',')'].join(q(object));
		};
		break;
		case String:{return q(object);};
		break;
		default: 
			//	Boolean, RegExp, Math, Number, Function, DOM, ...
			return object;
	}
}

