﻿
// DomMgr : To dynamically create and update HTML layouts using json
    
DomMgr = createClass();
DomMgr.prototype.init = function(args) {
    /*	        
        parent			: Parent div-id or element
        layout			: Layout for parent ( style, attributes, handlers etc)
		beforeCreate	: Handler function for create
		beforeUpdate	: Handler function for update
    */
	if(!isDefined(args))
		args = {};

	/*
		"defaults" will contain the default value for each parameter in "args"
		If an expected parameter is not passed (during constructor call) the default value
		is used for that parameter
	*/
	defaults = { parent: "", layout: "Layout not defined", beforeCreate: null, beforeUpdate: null, type: "DomMgr" };
	for(var pr in defaults)
		this[pr] = isDefined(args[pr]) ?args[pr] :defaults[pr];
	
	/*
		An index in DomMgr, is a variable that is used as enumerator in foreach loops.
		For eg. a "fare" index can be used to display a list of fares in a table. 
		The "fare" index will correspond to the current row-index being created or updated.
		"indices" will contain the list of such indices used in this manager.
	*/
	this.indices = {};

	/*
		Some key dom-elements can be hashed/cached using this list for global access in DomMgr. 
	*/
	this.elements = {};

	/* 
		A document fragment is used to create fewer document reflows, and as a result
		it can improve performance for big changes.
	*/
	this.docFragment = document.createDocumentFragment();

	// Create innerLayout for container (initial function call that initiates the recursion)
	this.create = function(parent) {
		if(this.beforeCreate)
			this.beforeCreate();

		/* Get reference for the parent-div
		   If parent is not found, create a new div element, 
		   and assign it to the parent */
		this.parent = $(parent || this.parent);
		if(!this.parent)
			this.parent = DOM.create_Element("div");
		
		// Empty document fragment
		this.docFragment.innerHTML = "";

		// Initiate the recursion to create the layouts
		this.indices = {};
		this.createLayout(this.layout, this.docFragment);
		this.indices = {};
		
		// Load the document-fragment contents into the container
		this.parent.appendChild(this.docFragment);
	};

	// Update innerLayout for container (initial function call that initiates the recursion)
	this.update = function(useFragment, parent) {
		if(this.beforeUpdate)
			this.beforeUpdate();

		// Get reference for the parent-div
		this.parent = $(parent || this.parent);

		// 'useFragment' is used to specify whether the document-fragment is to be used or not
		useFragment = useFragment || false;

		this.indices = {};
		if(useFragment) {
			var el = this.parent.firstChild;
			while(el) {
				this.docFragment.appendChild(el);
				el = el.nextSibling;
			}
			this.updateLayout(this, this.docFragment);
			this.parent.appendChild(this.docFragment);
		}
		else {
			this.updateLayout(this, this.parent);
		}
		this.indices = {};
	};

	// Create innerLayout for container (recursive function)
	this.createLayout = function(layout, container) {
		// If type of "layout" is a function, call it with 'element' and 'manager-instance' as arguments
		if(isFunction(layout)) {
			layout(container, this);
		}
		// If type of "layout" is a string, append it to element's innerHTML
		else if(isString(layout)) {
			container.innerHTML += layout;
		}
		// If type of "layout" is an array, call createLayout for each array element
		else if(isArray(layout)) {
			for (var i=0; i<layout.length; i++) {
				this.createLayout(layout[i], container);
			}
		}
		// If type of "layout" is DomMgr, call its create function 
		else if(/DomMgr/i.test(layout.type)) {
			layout.create(container);
		}
		// If type of "layout" is Layout, create the layout 
		else {
			var tagName = this.getValue(layout.tagName);
			var el;
			/*
				Create element using document.createElement()
				Incase of an input element we use innerHTML to create the element
			*/
			if(/input/i.test(tagName)) {
				if(!isDefined(layout.attributes)) layout.attributes = {};
				if(!isDefined(layout.attributes.type)) layout.attributes.type = "text";
				container.innerHTML += "<input type='" + this.getValue(layout.attributes.type)
										+ "' name='" + this.getValue(layout.attributes.name) + "' />";
				el = container.childNodes[container.childNodes.length-1];
			}
			else
				el = DOM.create_Element(tagName, container);

			// Set style, attributes and event-handlers for this element
			this.setElement(el, layout);
			
			// Register the element to "elements" if a name or id is specified
			if(isDefined(layout.name)) {
				if(!isArray(this.elements[this.getValue(layout.name)]))
					this.elements[this.getValue(layout.name)] = new Array();
				this.elements[this.getValue(layout.name)].push(el);
			}
			else if(isDefined(layout.id)) {
				this.elements[this.getValue(layout.id)] = el;
			}
			
			/*
				Run a for loop if "foreach" is specified
				use "startIndex" as starting point and "stopIndex" as end point
				"indexKey" will be used as the identifying key in "indices"
			*/
			if(isDefined(layout.foreach)) {
				var foreach = layout.foreach;
				var start = this.getValue(foreach.startIndex);
				var end = this.getValue(foreach.stopIndex);
				var indexKey = this.getValue(foreach.indexKey);

				for(var i=start; i<=end; i++) {
					this.indices[indexKey] = i;
					this.createLayout(layout.layout, el);
				}
			}
			// or, step to next level in recursion
			else if(isDefined(layout.layout))
				this.createLayout(layout.layout, el);
		}
	};

	// Update innerLayout for container (recursive function)
	this.updateLayout = function(layout, el) {
		// If type of "layout" is array; call updateLayout for each element
		if(isArray(layout)) {
			for (var i=0; i<layout.length; i++) {
				this.updateLayout(layout[i], el);
				el = el.nextSibling;
			}
		}
		// If type of "layout" is Layout, update the layout 
		else {
			if(isDefined(layout.updateFunc))
				layout.updateFunc(el, this);

			if(isDefined(layout.foreach)) {
				var foreach = layout.foreach;
				var start = this.getValue(foreach.startIndex);
				var end = this.getValue(foreach.stopIndex);
				var indexKey = this.getValue(foreach.indexKey);

				for(var i=start, k=0; i<=end; i++) {
					this.indices[indexKey] = i;
					this.updateLayout(layout.layout, el.childNodes[k]);
					if(isArray(layout.layout))
						k += layout.layout.length;
					else
						k++;
				}
			}
			else if(isDefined(layout.layout)) {
				// If type of "layout" is DomMgr, call its update function 
		        if(/DomMgr/i.test(layout.layout.type))
			        layout.update(el);
			    else
			    	this.updateLayout(layout.layout, el.firstChild);
			}
		}
	};

	// Get value if string or function
	this.getValue = function(val) {
		return getValue(val, this);
	};

	// Set element attributes, style, assign handlers
	this.setElement = function(el, layout) {
		// Set element attributes
		for(var attr in layout.attributes)
			el.setAttribute(attr, this.getValue(layout.attributes[attr]));
		
		for(var pr in layout.style)
		    el.style[pr] = this.getValue(layout.style[pr]);
		
		if(isDefined(layout.cssClass))
		    DOM.addClass(el, this.getValue(layout.cssClass));
		
		// Set event handlers (mgr variable is used to create a function closure)
		var mgr = this;
		for(var ev in layout.handlers) {
			DOM.addEvent(el, ev, function(e) {
				return layout.handlers[e.type]( e, mgr);
			});
		}
	};
};