﻿/**
* jQuery Plugin SuperFlyDOM v0.9g
*
* Create DOM elements on the fly and automatically append or prepend them to another DOM object.
* There are also template functions (tplAppend and tplPrepend) that can take a JSON-formatted
* complex HTML structure, apply a dataset, and therefore add siblings way faster
* 
* This plugin is built off of FlyDom 3.0.8, by dohpaz
* [http://dohpaz.mine.nu/jquery/jquery.flydom.html], who was inspired by "Oslow"
* [http://mg.to/2006/02/27/easy-dom-creation-for-jquery-and-prototype#comment-176],
* and since I could not get dohpaz code to work with my template (and since dohpaz
* could not get Oslow's code to work), and neither Michael Geary's code nor Sean's
* [http://www.pinkblack.org/itblog/?page_id=22] code were jQuery style and chainable,
* and I wanted a ton of features anyway, while retaining a small code base, I decided
* to rip apart, clean up (JSLint),and add features to their plugins. My hope is that
* this version will be easier to understand, more forgiving and flexible, and maintain
* with future versions of the fantastic framework which is jQuery.
*
* Note: For event attaching using the liveQuery plugin is highly recommended.
*
* Copyright (c) 2007 Charles Phillips [charles at doublerebel dot com]
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* @version	 $Id: jquery.superflydom-0.9g.js 9 2007-08-27 00:02:34 polyrhythmic $
*
* @author	  Charles Phillips [charles at doublerebel dot com]
* @copyright   (C) 2007. All rights reserved.
*
* @license	 http://www.opensource.org/licenses/mit-license.php
* @license	 http://www.opensource.org/licenses/gpl-license.php
*
* @package	 jQuery Plugins
* @subpackage  SuperFlyDOM
*
* @todo		 (dohpaz): Cache basic elements that are created, and if an already existing basic element is
*			 asked to be created an additional time, use a copy of the cached element to build from.
*			 (Charles): If dohpaz accomplishes this I will be happy to merge it with my code fork.
* 
*/

/**
* Create DOM elements on the fly and automatically append them to the current DOM obejct
*
* @uses	jQuery
*
* @param   string  element - The name of the DOM element to create (i.e., img, table, a, etc)
* @param   object  attrs   - An optional object of attributes to apply to the element
* @param   string  text    - An optional string for text node to prepend to element
* @param   array   content - An optional array of content (or element children) to append to element
*
* @return  jQuery  element - The jQuery object representing the new element
*
* @since   FlyDom 1.0
*/

(function($) {
    var el;
    if ($.elHash === undefined) { //If there is no jQuery global hash yet defined,
        var elArray, elHash = {}; //Define hash table of HTML 4.01 DOM Elements, excluding deprecated elements.
        elArray = "a|abbr|acronym|address|area|b|base|bdo|big|blockquote|body|br|button|caption|cite|code|col|colgroup|dd|del|dfn|div|dl|dt|em|fieldset|form|frame|frameset|h1-h6|head|hr|html|i|iframe|img|input|ins|kbd|label|legend|li|link|map|meta|noframes|noscript|object|ol|optgroup|option|p|param|pre|q|samp|script|select|small|span|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|ul|var".split("|"); //packed hash
        for (var i = 0; i < elArray.length; i++) { elHash[elArray[i]] = true; } //Process hash
        $.elHash = elHash; //Set jQuery global hash
    }
    if ($.browser.msie) { //From Dean Edwards IE7 fixes [http://dean.edwards.name] I can't believe I have to do this, damn you IE
        Array.prototype.unshift = function() {
            var a = Array.prototype.concat.call(Array.prototype.slice.apply(arguments, [0]), this), i = a.length;
            while (i--) { this[i] = a[i]; }
            return this.length;
        }
    }
    $.fn.extend({
        createAppend: function() {
            if (arguments.length === 0) { return this; } //If nothing to process, move along!
            var ie, pEl, elH = $.elHash, arg1 = arguments[0], arg2 = arguments[1], a = 1;
            pEl = this[0] || this; //Parent element may be either jQuery Object or DOM element
            if (typeof arg1 === "number") { arg1 = String(arg1); } //Pure numbers cannot be made into textNodes, nor can you run RegExp's or .constructor against them.  I learned this the hard way.
            if (typeof arg1 === "string") { //We should have a string by now, or the input JSON was improperly formatted.
                if (arg1 in elH) { //If first argument is valid HTML Element
                    ie = $.browser.msie;
                    el = (ie && arg1 === 'input') ? '<input type="' + arg2.type + '" />' : arg1; //Fix input for ie. REQUIRES attribute type.
                    el = document.createElement(el);
                    if (ie && pEl.nodeName.toLowerCase() === "table" && el.nodeName.toLowerCase() === "tr") { //Fix table/tbody for ie.
                        pEl = pEl.parentNode.getElementsByTagName('tbody')[0] || pEl.appendChild(document.createElement('tbody')); // Create a new tbody
                    }
                    el = pEl.appendChild(el); // Add the element directly to the parentElement
                    if (arg2.constructor === Object) { //if element's sibling is Attributes Object, process with jQuery
                        for (attr in arg2) { $.attr(el, attr, arg2[attr]); }
                        a++;
                    }
                    if (arguments.length > a) { //If element has more objects in arguments[] than we have processed
                        if (arguments[a].constructor === Array) { //and the next object is an array, it is element's children
                            el = $.fn.createAppend.apply($(el), arguments[a]); //recursively apply
                            a++;
                        }
                    }
                } else if (arg1.match(/(<\S[^><]*>)|(&.+;)/g) !== null &&
						 arg1.tagName.toUpperCase() !== 'TEXTAREA') { //Check to see if TextNode is actually mixed HTML, but not textarea
                    pEl.innerHTML += arg1; //Append HTML
                    el = pEl; //We have no information about what could be in that HTML, so return parent
                } else { el = pEl.appendChild(document.createTextNode(arg1)); } //Otherwise just append simple TextNode
            } else { } //There should be no else.  Todo: Add error catching with throw/catch on string instead of if - Instead of hashing?
            if (arguments.length > a) { //If element has unprocessed siblings
                el = $.fn.createAppend.apply($(pEl), Array.prototype.slice.call(arguments, a)); //Process siblings (sliced arguments array) and append to parent element
            }
            return $(el); //We like chaining
        },

        createPrepend: function() {
            var al = arguments.length;
            if (al === 0) { return this; } //If nothing to process, move along!
            var elH = $.elHash, ie, pEl, hCN = this[0].hasChildNodes(), arg_l, arg = [];
            arg_l = arg.push(arguments[al - 1]); //Start stacking our queue
            pEl = this[0] || this; //Parent element may be either jQuery Object or DOM element
            if (arg[0].constructor === Array) { //If element to prepend has children, add to bottom of queue
                arg_l = arg.unshift(arguments[al - 1 - arg_l]);
            }
            if (arg[0].constructor === Object) { //if element to prepend has attributes, add to queue
                arg_l = arg.unshift(arguments[al - 1 - arg_l]);
            }
            if (typeof arg[0] === "number") { arg[0] = String(arg[0]); } //Pure numbers cannot be made into textNodes, nor can you run RegExp's or .constructor against them.  I learned this the hard way.
            if (typeof arg[0] === "string") { //We should have a string by now, or the input JSON was improperly formatted.
                if (arg[0] in elH) { //If first argument is valid HTML Element
                    ie = $.browser.msie;
                    el = (ie && arg[0] === 'input') ? '<input type="' + arg[1].type + '" />' : arg[0]; //Fix input for ie. REQUIRES attribute type.
                    arg.shift();
                    el = document.createElement(el);
                    if (ie && pEl.nodeName.toLowerCase() === "table" && el.nodeName.toLowerCase() === "tr") { //Fix table/tbody for ie.
                        pEl = pEl.parentNode.getElementsByTagName('tbody')[0] || pEl.appendChild(document.createElement('tbody')); // Create a new tbody
                    }
                    if (hCN) { el = pEl.insertBefore(el, pEl.firstChild); } //If parent already has child nodes, add new element before them
                    else { el = pEl.appendChild(el); } // Otherwise add the element directly to the parentElement
                    if (arg[0].constructor === Object) {  //if element's sibling is Attributes Object, process with jQuery
                        for (attr in arg[0]) { $.attr(el, attr, arg[0][attr]); }
                        arg.shift();
                    }
                    if (arg[0] !== undefined && arg[0].constructor === Array) { //If there are still elements in the queue, and it's an array
                        el = $.fn.createPrepend.apply($(el), arg[0]); //Then recursively prepend the children
                    }
                } else if (arg[0].match(/(<\S[^><]*>)|(&.+;)/g) !== null &&
						 arg[0].tagName.toUpperCase() !== 'TEXTAREA') { //Check to see if TextNode is actually mixed HTML, but not textarea
                    pEl.innerHTML = arg.pop() + pEl.innerHTML; //Prepend HTML
                    el = pEl; //We have no information about what could be in that HTML, so return parent
                } else {
                    el = document.createTextNode(arg[0]); //Otherwise just create simple TextNode
                    el = (hCN) ? pEl.insertBefore(el, pEl.firstChild) : pEl.appendChild(el); //If parent already has child nodes, add new element before them
                }
            } else { } //There should be no else.  Todo: Add error catching with throw/catch on string instead of if - Instead of hashing?
            if (al > arg_l) { //If element has unprocessed siblings
                el = $.fn.createPrepend.apply($(pEl), Array.prototype.slice.call(arguments, 0, -arg_l)); //Process siblings (sliced arguments array) and prepend to parent element
            }
            return $(el);
        },

        tplAppend: function(json, tpl) {
            return $.fn.createAppend.apply(this, tpl.call(json)); // Return ourself for chaining
        },

        tplPrepend: function(json, tpl) {
            return $.fn.createPrepend.apply(this, tpl.call(json)); // Return ourself for chaining
        }
    });
})(jQuery);
