/**
  Raul Parolari, Febr 2007: 
  module based on the MochiKit library and D.Flanagan' book on Javascript
 
 * First, a brief review of typical of Dom Primitives:
 *   parent = document.createElement(tagname);    // create parent
 *   parent.setAttribute(attr_name, attr_value);  // set attribute
 *   child = document.createTextNode(text);       // for string child
 *   parent.appendChild(child);                   // append child

 * Recapping:
 *  - parent, child are elements (objects)
 *  - text is a string, that is a turned into a child via createTextNode
 *  - attr_name & attr_vale are passed as { name: value } objects
 * We need a primitive able to receive attributes and children that are
 * either arrays of elements, a string, or a solitary element

 * make(tagname, attributes, children):
 *   create an HTML ELEMENT with specified tagname, attributes and children.
 *
 * Tagname: a string containing the tag of the Parent element to be created.
 *
 * attributes: a JavaScript OBJECT { name1: val1, name2: val2 }: its names & 
 * values of it are taken as the names and values of the attributes to set 
 * for the Parent element.
 * Can 'attributes' be omitted if null? if 'children' is an array or string,
 * yes. In which case 'children' would not be an array or string? in case it
 * is a single element; if so,  both 'attributes' and 'children' would be 
 * objects, and we can't tell which is the one present
 
 * children: 
 *  - (usually) an ARRAY of children to be added to the Parent element.
 *  - a STRING:  TextNode will be Created and Appended. 
 *  - a child Element (formatted by caller) to be appended to parent. In 
 *    this case, 'attributes' cannot be omitted even if null ({ }), as
 *    it becomes impossible to determine if 2nd param is attributes/children

 * FEATURE: this function can be called 'make(tagname, children)' skipping
 * a second null parameter. The code (see beginning) will detect this
 * by checking that the 2nd parameter is an Array or string: thus, the 
 * 'children' arg cannot be a Node element (at least when there is no
 * attributes arg). 
 
 * MY USAGE: if attributes is null, caller should always pass an empty 
 * object hash. No guessing any more! Also: user should always pass
 * ARRAY of element children (even when there is only 1!), or a String
 *        dom_make(tag, { x: y }, [ elem1, elem2 ])
 *        dom_make(tag, { x: y }, [ elem1        ])
 *        dom_make(tag, { x: y }, string)
 *        dom_make(tag, { x: y }, [ string, element, string, ..])

 * Examples: 
 *   // create a paragraph element: no attributes
 *   var paragr = make("p", ["This is a ", make("b", "bold"), " word."]);
 *
 *   // create table elem (attrs and headings; rows added later dynamically)
 *   var table  = make("table", 
 *            { border : 1, cellpadding : 5 },            // attributes 
 *            [ make("th", "Isbn"), make("th", "Book")];  // children
 */
function dom_make(tagname, attributes, children) {
    // If we are invoked with two arguments and the attributes argument is
    // an array or string, we assume that it is a 'children' argument.
    // [This allows the user to skip typing a null 'attributes' argument]
    if (    (arguments.length == 2) 
         && (attributes instanceof Array || typeof attributes == "string")
       ) {
        children = attributes;
        attributes = null;
    }

    // Create the element
    var e = document.createElement(tagname);

    // Set attributes of element
    if (attributes) {
        for(var name in attributes) {
           e.setAttribute(name, attributes[name]);
        }
    }

    // Add children to element: notice difference for string (Text!) child
    if (children != null) {
        if (children instanceof Array) {  
            for(var i = 0; i < children.length; i++) { 
                var child = children[i];
                if (typeof child == "string") {    
                    // create the text node element, then append it
                    child = document.createTextNode(child);
                }
                e.appendChild(child);  // Assume anything else is a Node
            }
        }
        else if (typeof children == "string") { // Handle single text child
            e.appendChild(document.createTextNode(children));
        }
        else e.appendChild(children);       // Handle any other single child
    }

    // Finally, return the element.
    return e;
}

/**
 * maker(tagname): return a function that calls make() for the specified tag.
 * Example: var table = maker("table"), tr = maker("tr"), td = maker("td");
 */
function dom_maker(tag) {
    return function(attrs, kids) {
        if (arguments.length == 1) { return dom_make(tag, attrs); }
        else { return dom_make(tag, attrs, kids); }
    }
}
