// vim:et:sw=4:ts=4
// useful template functions, 2006.
// by Kevin Ko <kevin.s.ko@gmail.com>
// $Id: template.js,v 1.4 2006/04/22 17:13:19 kevin Exp $
//
// Licensed under the Creative Commons Attribution 2.5 License
//   http://creativecommons.org/licenses/by/2.5/
// (open use, as long as credit is given to the author)
//
function TextBoxFactory(tmplName)
{
    if (tmplName == null)
        return;
    this.tf = new TemplateFactory(tmplName, function() { return new TextBox(); });
    this.baseInit();
    this.tf.regClassInit("text",
            function(parElem, elem) {
                parElem.revealShow.push(elem);
            });
}

TextBoxFactory.prototype = {
    baseInit: function() {
        this.tf.regClassInit("hide", 
            function(parElem, elem) {
                // provide access within handler
                elem.parentBox = parElem;
                if (elem.getAttribute("href")) {
                    elem.href = "#" + parElem.elem.id;
                }
                Lib.prototype.addEvent(elem, "click", parElem.closeHandler, true);
                parElem.revealShow.push(elem);
            });
        this.tf.regClassInit("show", 
            function(parElem, elem) {
                elem.parentBox = parElem;
                if (elem.getAttribute("href")) {
                    elem.href = "#" + parElem.elem.id;
                }
                Lib.prototype.addEvent(elem, "click", parElem.openHandler, true);
                parElem.revealHide.push(elem);
            });
    },
    createShow: function(t, defShow) {
        if (defShow) {
            t.reveal();
        } else {
            t.shade();
        }
        return t;
    },
    create: function(id, defShow) {
        return this.createShow(this.tf.create(id), defShow);
    },
    createFromBase: function(baseId, defShow) {
        return this.createShow(this.tf.createFromBase(baseId), defShow);
    },
    createFrom: function(id, defShow, title, text) {
        var t = this.create(id, defShow);
        t.title.innerHTML = title;
        t.text.innerHTML = text;
        return t;
    }
}

function TemplateVar()
{
    this.value = null;
    this.elems = new Array();
}

TemplateVar.prototype = {
    setF: function(setF, value) {
        this.value = value;
        for (var i = 0; i < this.elems.length; i++) {
            setF(this.elems[i], value);
        }
    },
    set: function(value) {
        this.value = value;
        for (var i = 0; i < this.elems.length; i++) {
            this.elems[i].innerHTML = value;
        }
    },
    get: function() {
        return this.value;
    }
}

function TextBox() 
{
    // maintain elements to reveal/hide when toggled
    this.revealShow = new Array();
    this.revealHide = new Array();
}

TextBox.prototype = {
    // true - make visible everything in expandShow
    applyVisibility: function(isShowRevealed) {
        var show, hide; 
        if (isShowRevealed) {
            show = "block";
            hide = "none";
        } else {
            show = "none";
            hide = "block";
        }
        for (var i = 0; i < this.revealShow.length; i++) {
            var e = this.revealShow[i];
            e.style.display = show;
        }
        for (var i = 0; i < this.revealHide.length; i++) {
            var e = this.revealHide[i];
            e.style.display = hide;
        }
    },
    reveal: function() {
        this.applyVisibility(true);
    },
    shade: function() {
        this.applyVisibility(false);
    },
    closeHandler: function(evt) {
        evt.target.parentBox.shade();
    },
    openHandler: function(evt) {
        evt.target.parentBox.reveal();
    }
};

/**
 * Supports HTML templates.
 * varTmpls is an array of {string name, dom elem, int index} structures.
 * classInits is an array of {string class, function initF} structures. 
 * varElems is an array of DOM elements.
 *
 * @constructor
 * @param {String} baseName id of template element
 * @param {String} consF function returning the base object for the
 *                       template; this is called by {@link TemplateFactory#create}
 */
function TemplateFactory(baseName, consF)
{
    if (baseName == null)
        return;
    this.elem = document.getElementById(baseName);
    this.elem.parentNode.removeChild(this.elem);
    this.varTmpls = null;
    this.varElems = null;
    this.classInits = new Array();
    this.consF = consF;
    this.initVars();
}

TemplateFactory.prototype = {
    /** 
     * Searches for variables (of DOM class "var") in the template and
     * initializes varElems and varTmpls of {@link TemplateFactory}.
     *
     * @private
     */
    initVars: function() {
        this.varElems = Lib.prototype.getElementsByClassName(this.elem, "var");
        this.varTmpls = new Array();
        var varNameRe = new RegExp(/[a-zA-Z][a-zA-Z0-9]*/);
        for (var i = 0; i < this.varElems.length; i++) {
            var e = this.varElems[i];
            var m = e.innerHTML.match(varNameRe);
            if (m != null) {
                this.varTmpls.push({name: m[0], elem: e, index: i});
            }
        }
    },
    /**
     * on create, initFunc(baseObj, elem) will be called on each class element.
     * where initFunc has the signature:
     * baseObj  the Template object that contains the target element
     * elem     the target element
     * returns  none
     *
     * @param {String} className class that initializer associates with
     * @param {function} initFunc initializer for className 
     */
    regClassInit: function(className, initFunc) {
        this.classInits.push({className: className, initF: initFunc});
    },
    /**
     * Creates a base template object. 
     *
     * @private
     * @param {String} id the identifer for the new template
     * @return the new object
     */
    createObj: function(id) {
        var obj = this.consF();
        obj.elem = this.elem.cloneNode(true);
        obj.elem.id = id;
        return obj;
    },
    /**
     * Creates attributes for each template variable.  
     * The default value is taken from the template.
     * A TemplateVar is created to maintain variable values and template locations.
     *
     * @private
     * @param obj the new object 
     * @return the object with variable attributes
     */
    createObjVars: function(obj) {
        // since this is from a deep copy, ve.length must match
        // this.varTmpls.length
        var ve = Lib.prototype.getElementsByClassName(obj.elem, "var");
        // assign object vars to corresponding DOM containers
        for (var i = 0; i < this.varTmpls.length; i++) {
            var vt = this.varTmpls[i];
            if (!obj[vt.name]) {
                obj[vt.name] = new TemplateVar();
            }
            obj[vt.name].elems.push(ve[vt.index]);
        }
        return obj;
    },
    /**
     * Call initializers for each class within a template object.
     *
     * @private
     * @param obj the template object
     * @return object post class initialization
     */
    initClasses: function(obj) {
        // initialize DOM class elements
        for (var i = 0; i < this.classInits.length; i++) {
            var ci = this.classInits[i];
            var elems = 
                Lib.prototype.getElementsByClassName(obj.elem, ci.className);
            for (var j = 0; j < elems.length; j++) {
                var elem = elems[j];
                if (ci.initF) {
                    ci.initF(obj, elem);
                }
            }
        }
        return obj;
    },
    /**
     * Create a template object whose variables only hold default values.
     * All class initialization functions are called.
     *
     * @param {String} id DOM identifier for new object's node
     * @return the created object
     */
    create: function(id) {
        var obj = this.createObj(id);
        obj = this.createObjVars(obj);
        return this.initClasses(obj);
    },
    /**
     * Create a template object.  Variable names are taken from DOM elements
     * having the id <baseId>_<varName>.  The resulting object has the
     * identifier <baseId>.  Elements used for initializing variables are
     * removed.
     *
     * This initializes variables first and then classes.  As a
     * consequence, classes may be introduced within variables.
     *
     * @param {String} baseId identifier for variable extraction and final object
     * @return the initialized object
     * @type See consF param to {@link TemplateFactory} constructor.
     */
    createFromBase: function(baseId) {
        var t = this.createObj(baseId);
        t = this.createObjVars(t);
        // assign variables
        for (var i = 0; i < this.varTmpls.length; i++) {
            var vt = this.varTmpls[i];
            var elem = document.getElementById(baseId + "_" + vt.name);
            if (elem) {
                elem.parentNode.removeChild(elem);
                t[vt.name].set(elem.innerHTML);
            }
        }
        return this.initClasses(t);
    }
};

