/* ------------------------------------------------------------------
 * OrganicForm: Client-side Form Library
 * ------------------------------------------------------------------
 * (c) 2005 EYEFI
 *
 * Author: Arjan Scherpenisse <arjan@eyefi.nl>
 *
 * This library contains the OrganicForm class which encapsulates the
 * client side of a form.
 * 
 * For now, it is mainly used to perform client-side validations on a
 * form. Validations come in the form of JavaScript functions and in
 * the form of AjaxValidator objects, in which the validation is done
 * at the server side, in a child of an Organic AjaxValidator class.
 *
 */

function getElementsByAttribute(attr, value, tag /*, ctxtnode */ )
{
    var nodeColl;
    if (tag!=null) {
        nodeColl = document.getElementsByTagName(tag);
    } else {
        nodeColl = new Array();
        var l = document.getElementsByTagName("SPAN");
        for (var i=0; i<l.length; i++) nodeColl.push(l[i]);
        l = document.getElementsByTagName("DIV");
        for (var i=0; i<l.length; i++) nodeColl.push(l[i]);
    }
    
    var returnColl = new Array();
    var re = new RegExp("\\b"+value+"\\b", "i");
    for(var c=0;c<nodeColl.length;c++){
        attrVal = nodeColl[c].getAttribute(attr);
        if((value!=null && attrVal && (attrVal == value || attrVal.match(re)))||attrVal&&!value) {
            // ensure the element is in the current form context
            /*var p = nodeColl[c].parentNode;
              while(p && p != ctxtnode) 
              p = p.parentNode;
              if (p) */
            returnColl.push(nodeColl[c]);
        }
    }
    return returnColl;
};

function Compare_Recursive(a, b) {
    if (typeof a != typeof b) {
        return false;
    } else if (typeof a == "object") {
        for (var i in a) {
            if (typeof b[i] == "undefined") return false;
            if (!Compare_Recursive(a[i], b[i])) return false;
        }

        for (var i in b)
            if (typeof a[i] == "undefined") return false;

        return true;
    } else {
        return a == b;
    }
}

function OrganicForm_LoadGlobal() {
    if (window.organicforms_loadglobal) return;

    // hide all v_hide elements
    var l = getElementsByAttribute("v_hide");
    for (var i=0; i<l.length; i++) {
      l[i].style.display = "none";
    }
    var l = getElementsByAttribute("v_visible");
    for (var i=0; i<l.length; i++) {
        l[i].style.visibility = "hidden";
    }
    
    window.organicforms_renderelements = new Array();
    OrganicForm_fillRenderElements();
    
    window.organicforms_loadglobal = true;
}

function OrganicForm_fillRenderElements() {
    OrganicForm_fillRenderElement("SPAN");
    OrganicForm_fillRenderElement("DIV");
}

function OrganicForm_fillRenderElement(tagname) {
    var elems = document.getElementsByTagName(tagname);
    for (var i=0; i<elems.length; i++) {
        var s;
        if (s=elems[i].getAttribute("v_for")) {
            var l = s.split(" ");
            for (var j=0; j<l.length; j++) {
                var id = l[j];
                if (!window.organicforms_renderelements[id]) window.organicforms_renderelements[id] = new Array();
                window.organicforms_renderelements[id].push(elems[i]);
            }
        }
    }
};


/**
 * Constructor 
 */
function OrganicForm(id, formspec) {
    this.form_id = id;
    this.formspec = formspec?formspec:id;
    
    this.form_node = document.getElementById(id);
    
    this.validationTimeout = 400; // validate after xxx milliseconds if the value has changed
      
    this.complex_values = new Array();

    this.buttons = new Array();
    this.controls = new Array();
    this.conditionals = new Array();
    
    this.prevcontrol = null;

    this.errorpopup_elems = new Array();
    
    this.quietErrors = true;

    var v = this;
    this.donebuildingTimer = setTimeout(function(){v.doneBuilding()}, 100);
    
    OrganicForm_LoadGlobal();
    
    return this;
};

OrganicForm.prototype.doneBuilding =
function() {
    this.original_value = this.getFormValue();
    this.runConditionals();
    if (window.firstcontrol) {
        try {
            document.getElementById(window.firstcontrol).focus();
        } catch (e) {}
        window.firstcontrol = null;
    }

    var self = this;
    if (!this.form_node.onsubmit) {
        this.form_node.onsubmit = function() {
            self.disableButtons();
        };
    } else {
        var x = this.form_node.onsubmit;
        this.form_node.onsubmit = function() {
            x();
            self.disableButtons();
        };
    }
}

OrganicForm.prototype.hasChanged =
function() {
    return !Compare_Recursive(this.original_value, this.getFormValue());
}

OrganicForm.prototype.include =
function (file) {
    var e = document.createElement("SCRIPT");
    e.setAttribute("type", "text/javascript");
    e.setAttribute("src", this.path+file);
    document.getElementsByTagName('head')[0].appendChild(e);
};

OrganicForm.prototype.addButton =
function(id) {
    var button = document.getElementById(id);
    if (!button) return false;
    
    var f = this;
    if (typeof button.onclick != "function") {
        button.onclick = function() { 
            return f.valid(); 
        };
    } else {
        var x = button.onclick;
        button.onclick = function() {
            return f.valid() ? x() : false;
        }
    }
    this.buttons[id] = button;
    return true;
};

OrganicForm.prototype.disableButtons =
function() {
    for (var id in this.buttons) {
        if (typeof this.buttons[id] == "function") continue;
        this.buttons[id].onclick = function() { return false; };
    }
}


OrganicForm.prototype.addConditional =
function(id, condition, opts) {
    if (!opts) opts = {};

    var element = document.getElementById(id);
    if (!element) { return false; }
    
    var out = new Array();
    var x = ["input", "textarea", "select"];
    for (var i=0; i<x.length; i++) {
        var lst = element.getElementsByTagName(x[i]);
        for (var j=0; j<lst.length; j++) {
            if (typeof lst[j].id == "undefined" || !lst[j].id.length) continue;
            out[out.length] = lst[j];
        }
    }
    
    this.conditionals[id] = {        
        element: element,
        condition: condition,
        controls: out,
        showhide: opts.showhide ? true : false
    };

    // for each named control in the condition, attach a ref to the conditional
    this._addConditionals(id, condition);
}
OrganicForm.prototype._addConditionals =
function(id, condition) {
    if (condition.length == 2) {
        if (condition[0] == "not") { this._addConditionals(id, condition[1]); return; }
        if (this.complex_values[condition[0]]) {
            // complex value
            var ids = this.complex_values[condition[0]];
            for (var i=0; i<ids.length; i++) {
                condition[0] = ids[i];
                this._addConditionals(id, condition);                
            }
            return;
            
        }
        if (!this.controls[condition[0]]) return;
        var c = this.controls[condition[0]].conditionals;
        for (var i=0; i<c.length; i++) if (c[i] == id) return;
        c[c.length] = id;
    } else if (condition.length > 2) {
        // and / or
        for (var i=1; i<condition.length; i++)
            this._addConditionals(id, condition[i]);
    }
}

OrganicForm.prototype.runConditionals =
function() {
    for (k in this.conditionals) {
        if (typeof this.conditionals[k] == "function") continue;        
        this.runConditional(k);
    }
}

OrganicForm.prototype.runConditional =
function(id) {
    var cond = this.conditionals[id];
    var value = this.evaluateCondition(cond.condition);

    if (cond.showhide) cond.element.style.display = value?"block":"none";
    for (var k in window.editors) {
        if (typeof window.editors[k].sizeEditor != "function") continue;
        try {
            window.editors[k].sizeEditor();
        } catch (e) {}
    }
    
    for (var i=0; i<cond.controls.length; i++) {
        if (typeof this.controls[cond.controls[i].id] == "undefined") continue;
        
        if (!value) {
            this._addFailedConditional(cond.controls[i].id, id);
        } else {
            this._delFailedConditional(cond.controls[i].id, id);
        }

        this.controls[cond.controls[i].id].valid = null;
        this.validateElement(cond.controls[i], null, true);
    }
}
OrganicForm.prototype._addFailedConditional =
function(id, conditional_id) {
    var c = this.controls[id].failed_conditionals;
    for (var i=0; i<c.length; i++) if (c[i] == conditional_id) return;
    c[c.length] = conditional_id;    
}
OrganicForm.prototype._delFailedConditional =
function(id, conditional_id) {
    var c = this.controls[id].failed_conditionals;
    for (var i=0; i<c.length; i++) if (c[i] == conditional_id) c.splice(i, 1);    
}
OrganicForm.prototype._hasFailedConditional =
function(id, conditional_id) {
    if (typeof this.controls[id] == "undefined") return false;
    var c = this.controls[id].failed_conditionals;
    for (var i=0; i<c.length; i++) if (c[i] == conditional_id) return true;
    return false;
}

OrganicForm.prototype.evaluateCondition =
function(condition) {
    if (typeof condition == "object" && condition.length == 2) {
        if (condition[0] == "not") return !this.evaluateCondition(condition[1]);
        // id, targetvalue tuple
        control = this.controls[condition[0]];
        
        if (!control) return false;
        value = this.getElementValue(control.element, control.complex);
        return this._evaluateConditionValue(value, condition[1]);
    } else if (typeof condition == "object" && condition.length > 2) {
        // [operator, item, , ...]
        var r;
        for (var i=1; i<condition.length; i++) {
            r = this.evaluateCondition(condition[i]);
            if (condition[0] == "or") {
                if (r) return r;
            } else if (condition[0] == "and") {
                if (!r) return r;
            } else {
                alert("Invalid condition: "+condition[0]);
            } 
        }
        return r;
    }
}
    
OrganicForm.prototype._evaluateConditionValue =
function(value, condition) {
    if (condition === true) {
        // simple 'required' check
        if (typeof value == "string") return value.length > 0;
        return value != null;
    } else if (typeof condition == "function") {
        return condition(value) === true;
    } else if (typeof condition == "object" && condition.length > 0) {
        // an array of conditions
        for (var i=0; i<condition.length; i++) {
            if (!this._evaluateConditionValue(value, condition[i])) return false;
        }
        return true;
    }
    
    return value == condition;
}

OrganicForm.prototype.setAjaxSubmit =
function(button_id, classname, method, returnid) {
    var button = document.getElementById(button_id);
    var f = this;
    
    button.onclick = function() {

        if (!f.valid()) {
            return false;
        }
        
        classname = classname.toLowerCase(); method = method.toLowerCase();
        
        // call ajax bla...
        var data = f.getFormValue();
        f.original_value = data;

        if (typeof window.organicforms_callbackreturn["pre_"+returnid] == 'function') {
            window.organicforms_callbackreturn["pre_"+returnid]();
        }
        eval("new "+classname+"(new AjaxSubmitCallback(method, returnid))."+method+"(data, f.form_id);");
        
        // DO NOT submit, because we have 'submitted' over ajax...
        return false;
    }
    
};

OrganicForm.prototype.delControl =
function(id) {
    var ids = new Array();
    var complex = false;
    if (typeof id == "object" && id.length) {
        ids = id;
        complex = true;
    } else
        ids.push(id);
    
    for (var i in ids) {
        if (this.controls[ids[i]]) {
            var did = this.getDisplayId(this.controls[ids[i]].element);
            this.updateGeneralErrorlist(did, true);
            delete this.controls[ids[i]];
        }
    }
};

var firstcontrol = null;

OrganicForm.prototype.addControl =
function(id, validations, caption, error) {

    // focus the first control on the page we find
    if (!window.firstcontrol) 
        window.firstcontrol = id;
    
    var ids = new Array();
    var complex = false;
    if (typeof id == "object" && id.length) {
        ids = id;
        complex = true;
    } else
        ids.push(id);
    var did;
    var v = this;

    clearTimeout(this.donebuildingTimer);
    this.donebuildingTimer = setTimeout(function(){v.doneBuilding()}, 100);

    for (var i=0; i<ids.length; i++) {
        id = ids[i];
        var elem = document.getElementById(ids[i]);
        did = complex?id.substr(0, id.lastIndexOf("_")):id;
        this.errorpopup_elems[did] = new Array();
        if (!elem) continue; // {alert('unknown element id: '+ids[i]); continue; } // no element with this id... quietly skip it

        var type = elem.type.toLowerCase();
        if (type == "text" || type == "password" || elem.tagName.toLowerCase() == "textarea") {

            var f = function() {
                v.validateElement(this);
            }
            // other input types
            if (typeof elem.onchange != "function")
                elem.onchange = f;
            else {
                var x = elem.onchange;
                elem.onchange = function() { x(); v.validateElement(this); }
            }
            var f = function() {
                v.validateElement(this);
            }
            // other input types
            if (typeof elem.onblur != "function")
                elem.onblur = f;
            else {
                var x = elem.onblur;
                elem.onblur = function() { x(); v.validateElement(this); }
            }            
            
        } else if (type == "checkbox" || type == "radio") {

            var f = function() {
                v.validateElement(this);
                return true;
            }

            elem.onselect = f;
            
            if (typeof elem.onclick != "function") 
                elem.onclick = f;
            else {
                var x = elem.onclick;
                elem.onclick = function() { x(); v.validateElement(this); return true; };
            }

            if (typeof elem.onchange != "function") 
                elem.onchange = f;
            else {
                var x = elem.onchange;
                elem.onchange = function() { x(); v.validateElement(this); };
            }
            
        } else {
            var f = function() {
                v.validateElement(this);
            }
            // other input types
            if (typeof elem.onchange != "function")
                elem.onchange = f;
            else {
                var x = elem.onchange;
                elem.onchange = function() { x(); v.validateElement(this); }
            }
            
        }
        this.controls[ids[i]] = { element: ids[i],
                                  valid: null,
                                  lastvalue: null,
                                  complex: complex,
                                  timer: 0,
                                  elementValidations: validations,
                                  caption: caption,
                                  prevcontrol: this.prevcontrol,
                                  conditionals: new Array(),
                                  failed_conditionals: new Array()
        };
        this.prevcontrol = ids[i];
    }

    if (complex) {
        this.complex_values[ids[0].substr(0, ids[0].lastIndexOf("_"))] = ids;
    }
    
    if (typeof error == "object") {
        error = this.formatErrorMessage(error, caption);
        this.setElementStatus(document.getElementById(ids[0]), error);
    }

    v = this;
    
};

OrganicForm.prototype.showElementErrorPopup =
function(eid) {
    if (this.errorpopup_elems[eid].lasterror && document.getElementById(eid+"_div") && document.getElementById(eid+"_a")) {
        var w = new PopupWindow(eid+"_div");
        this._formatElement(document.getElementById(eid+"_div"), this.errorpopup_elems[eid].lasterror);
        w.autoHide = false;
        w.offsetY = -document.getElementById(eid+"_div").clientHeight;
        w.offsetX = 12;
        w.showPopup(eid+"_a");
        this.errorpopup_elems[eid].errorpopup = w;
    }
}
OrganicForm.prototype.hideElementErrorPopup =
function(eid) {
    if (this.errorpopup_elems[eid].errorpopup) {
        this.errorpopup_elems[eid].errorpopup.hidePopup();
        this.errorpopup_elems[eid].errorpopup = null;
    }
}

OrganicForm.prototype.getElementControl =
function(id) {
    if (typeof this.controls[id].element == "string") {
        var el = document.getElementById(id);
        this.controls[id].element = el;
    }
    return this.controls[id].element;
};

OrganicForm.prototype.getElementValue =
function(element, complex) {
    var basename = element.id.substr(0, element.id.lastIndexOf("_"));
    if (!complex || !this.complex_values[basename]) {
        if (element.nodeType != 1) return false;
        var nodeName = element.nodeName.toLowerCase();
        var type = element.type.toLowerCase();
        
        if (nodeName == "input" && (type == "checkbox" || type == "radio")) {
            return element.checked?element.value:null;
            // return element.checked;
        }
        if (nodeName == "textarea" && window.editors && window.editors[element.id] && window.editors[element.id]._doc) {
            return window.editors[element.id].getHTML();
        }
        return element.value;
    } else {
        var l = this.complex_values[basename];
        var value = {};
        for (var j=0; j<l.length; j++) {
            var ext = l[j].substr(l[j].lastIndexOf("_")+1, l[j].length);
            ext = ext.replace(/#/g, "_");
            var subelem = document.getElementById(l[j]);
            if (!subelem) continue;

            switch(subelem.type.toLowerCase()) {
            case "checkbox":
                if (subelem.checked)
                    value[ext] = subelem.value;
                break;
                    
            case "radio":
                if (subelem.checked) {
                    return subelem.value;
                }
                break;
                
            default:
                value[ext] = this.getElementValue(subelem);
            }
        }
        return value;
    }
};

OrganicForm.prototype.startValidator =
function(element) {
    if (!this.controls[element.id].timer) {
        this.controls[element.id].lastvalue = this.getElementValue(element);
        var x = this;
        this.controls[element.id].timer = setTimeout( function(){x._validatorLoop(element);}, x.validationTimeout);
    }
};

OrganicForm.prototype.stopValidator =
function(element) {
    if (this.controls[element.id].timer) {
        clearTimeout(this.controls[element.id].timer);
        this.controls[element.id].timer = 0;
    }
    
    if (this.controls[element.id].valid === null || this.controls[element.id].lastvalue != this.getElementValue(element)) {
        this.validateElement(element);
        this.controls[element.id].lastvalue = this.getElementValue(element);
    }
};

OrganicForm.prototype._validatorLoop =
function(element) {
    if (this.controls[element.id].lastvalue != this.getElementValue(element)) {
        this.validateElement(element);
        this.controls[element.id].lastvalue = this.getElementValue(element);
    }
    var x = this;
    this.controls[element.id].timer = setTimeout(function(){x._validatorLoop(element);}, x.validationTimeout);
};


/**
 * Get the id of the element for looking up 'display' classes
 */
OrganicForm.prototype.getDisplayId =
function(element) {
    var basename = element.id.substr(0, element.id.lastIndexOf("_"));
    return this.complex_values[basename]?basename:element.id;
};


/**
 * Validates a single form element, or a group of elements.
 *
 * @param element: the DOM element
 * @param sync: do we want synchronous calls or not (usually not; default = false)
 */
OrganicForm.prototype.validateElement =
function(element, sync, silent) {
    if (!sync) sync = false;
    if (!silent) silent = false;

    // get the list of validations for this element type
    var vlist = this.controls[element.id].elementValidations;
    if (!vlist) alert("No element validation for element: "+element.id);
    var validate_result = true;
    var complexElements = new Array();
    
    var serversides = new Array();
    var value = this.getElementValue(element, true);

    // go trough the list of validations
    for (var i=0; i<vlist.length; i++) {
        var r;
        
        if (typeof vlist[i] == "function") {
            // client-side validation: call directly
            r = vlist[i](value);
        } else if (typeof vlist[i] == "object" && vlist[i].validatorclass) {
            // server-side validation: collect it and do it later
            serversides.push(vlist[i]);
        } else {
            alert('invalid validation');
        }

        if (r !== true) {
            validate_result = r; break;
        }
    }

    if (validate_result === true && serversides.length) {
        // run the serverside validations
        validate_result = this.validateElementServerside(element, value, serversides, sync);
    }

    if (this.controls[element.id].conditionals.length) {
        for (var i=0; i<this.controls[element.id].conditionals.length; i++) {
            this.runConditional(this.controls[element.id].conditionals[i]);
        }
    }
    
    // convert result to an object with messages, if appropriate
    validate_result = this.validationResultToMessage(element, validate_result);
    
    if ((validate_result !== null && silent === false)
        || validate_result === true) {
        this.setElementStatus(element, validate_result);
    }

    return validate_result;
};

OrganicForm.prototype.validateElementServerside =
function(element, value, validations, sync) {
    if (!sync) {
        var h = ajaxvalidationhandler(new AjaxValidatorCallback(element, this));
        h.validateelement(value, validations, this.formspec);                                    
        return null;
    } else {
        var h = ajaxvalidationhandler();
        var result =  h.validateelement(value, validations, this.formspec);                                    
        return result;
    }
};

OrganicForm.prototype.formatErrorMessage =
function(errors, caption) {
    var res = new Array();
    if (typeof caption == "object" && caption.length) {
        for (var k in errors) {
            // array of captions
            for (var i=caption.length-1; i>=0; i--) {
                res[k] = errors[k].replace(new RegExp("%"+i, "g"), caption[i]);
            }
        }
    } else {
        for (var k in errors) {
            if (!errors[k].replace) continue;
            res[k] = errors[k].replace(/%[s1]/g, caption);
        }
    }
    return res;
};

/**
 * Set the element status for an element.
 */
OrganicForm.prototype.setElementStatus =
function(element, status) {

    if (typeof status == "object") //alert(this.controls[element.id].caption);
        status = this.formatErrorMessage(status, this.controls[element.id].caption);

    // update the display elements
    var did = this.getDisplayId(element);

    var displayelements = window.organicforms_renderelements[did].slice(0);
    if (!displayelements) {
        displayelements = new Array();
    }

    // update all input elements
    var elements = this.getElementList(element);
    for (var i=0; i<elements.length; i++) {
        this.controls[elements[i].id].valid = (status === true);
        displayelements.push(elements[i]);
    }

    for (var i=0; i<displayelements.length; i++) {
        var e = displayelements[i];
        this._formatElement(e, status);
    }

    // var did = this.getDisplayId(element);
    if (!this.errorpopup_elems[did]) this.errorpopup_elems[did] = new Array();
    this.errorpopup_elems[did].lasterror = (status !== true)?status:false;
    
    if (status === true) {
        this.hideElementErrorPopup(did);
    } else if (this.errorpopup_elems[did].focus) {
        this.showElementErrorPopup(did);
    }
    
    //if (this.do_errorlist)
    this.updateGeneralErrorlist(did, status);
};


/**
 * This function operates in two modes: quiet and normal
 *
 * If mode is quiet, no errors will be added, and resolved errors will
 * become 'grayed out'. This way the page does not 'jump' while typing.
 *
 * In non-quiet mode, errors are added and removed from the
 * list. Non-quiet mode is set during submission of the form. Quiet
 * mode is the default.
 */
OrganicForm.prototype.updateGeneralErrorlist =
function(name, status) {
    var el = document.getElementById(this.form_id+"_errorlist");
    if (!el) return;

    var found = false;
    if (this.quietErrors === false) {
        // Normal mode
        for (var j=0; j<el.childNodes.length; j++) {
            if (el.childNodes[j].getAttribute("v_for") == name) {
                var elem = el.childNodes[j];
                if (status !== true) {
                    el.childNodes[j].innerHTML = status.brief;
                } else {
                    el.removeChild(el.childNodes[j]);
                }
                found = true;
            }
        }
        if (status !== true && !found) {
            // add new item
            var item = document.createElement("LI");
            item.setAttribute("v_for", name);
            item.innerHTML = status.brief;
            /*
            if (el.childNodes.length>0)
                el.insertBefore(item, el.childNodes[0]);
            else
            */
            el.appendChild(item);
            
        }
    } else {
        // Quiet mode
        for (var j=0; j<el.childNodes.length; j++) {
            if (el.childNodes[j].getAttribute("v_for") == name) {
                var w = j;
                for (var i=0; i<j; i++) {
                    if (el.childNodes[i].style.display == "none") {
                        w = i; break;
                    }
                }
                if (status !== true) {
                    var li = el.childNodes[j];
                    li.style.display = "";
                    li.innerHTML = status.brief;
                    el.insertBefore(li, el.childNodes[w]);
                } else {
                    el.childNodes[j].style.display = "none";
                    el.appendChild(el.childNodes[j]); // move to end of list
                }
                found = true;
            }
        }
    }

    var num_errors;
    for (num_errors=0; num_errors<el.childNodes.length; num_errors++) {
        if (el.childNodes[num_errors].style.display == "none") break;
    }
    var status = true;
    if (num_errors>0) status = { "ANY": true, "brief": true, "extended":true, "true":true };
    var l = getElementsByAttribute("v_for", "ALL");
    for (var i=0; i<l.length; i++) {
        this._formatElement(l[i], status);
    }
};

/**
 * Get all form values as an object tree
 *
 * This function loops trough all registered form controls and builds
 * a value-array as is implied in the name attributes of the controls.
 * E.g:
 *   form fields with names email, date[day], date[month] results in a value:
 *   { email: <value>, date: { day: <value>, month: <value>} }
 */
OrganicForm.prototype.getFormValue =
function() {
    var result = new Array();
    var done_ctls = new Array();
    
    for (var id in this.controls) {
        if (typeof this.controls[id] != "object") continue;
        var ctl = this.getElementControl(id);
        var name = ctl.name;

        var r = result;
        name = name.replace(/^(\w+)/, "[$1]");
        var oldname = "";
        while (name.indexOf("][")>=0) {
            // get prefix
            prefix = name.substr(1, name.indexOf("]")-1);
            if (!r[prefix] || typeof r[prefix] != "object") 
                r[prefix] = new Array();
            name = name.replace(/^.*?\]/, "");

            r = r[prefix];
            oldname = name;
        }
        name = name.substr(1, name.indexOf("]")-1);
        if (!name.length) continue;
        var res = this.getElementValue(ctl);
        if (res !== null) r[name] = res;
    }
    if (this.form_node && this.form_node.getAttribute("prefix")) {
        result = result[this.form_node.getAttribute("prefix")];
    }
    return result;
};

OrganicForm.prototype._formatElement =
function(e, status) {
    var a;
    if (a = e.getAttribute("v_message")) {
        if (status === true) {
            // replace node content with value of old_content attribute, but only if it is set.
            // otherwise there might not be an error yet and we want to maintain the original node contents.
            if (e.getAttribute("old_content")) {
                e.innerHTML = e.getAttribute("old_content");
            }
        } else {
            msg = status[a];
            if (!msg) msg = "";
            if (e.childNodes.length && !e.getAttribute("old_content")) {
                e.setAttribute("old_content", e.innerHTML);
            }
            e.innerHTML = msg;
        }
    }
    if (a = e.getAttribute("v_hide")) {
        if (status === true || !status[a]) {
            // hide the node
            if (e.style.display != 'none') {
                e.style.display = 'none';
            }
        } else {
            // show the node; if we have a status text
            if (status[a])
            e.style.display = e.getAttribute("v_htype")?e.getAttribute("v_htype"):"block";
        }
    }
    if (a = e.getAttribute("v_visible")) {
        if (status === true) {
            // hide the node
            if (e.style.visibility != 'hidden') {
                e.style.display = 'hidden';
            }
        } else {
            // show the node; if we have a status text
            if (status[a])
            e.style.visibility = e.getAttribute("v_htype")?e.getAttribute("v_htype"):"visible";
        }
    }
    
    if (a = e.getAttribute("v_class")) {
        if (status === true) {
            // reset old class
            var c = e.getAttribute("old_class");
            if (typeof c == "string") {
                e.className = c;
            }
        } else {
            // an error occurred
            if (e.className != a) {
                e.setAttribute("old_class", e.className?e.className:"");
                e.className = a;
            }
        }
    }
}; 

OrganicForm.prototype.getElementList =
function(element) {
    var list = new Array();
    var basename = element.id.substr(0, element.id.lastIndexOf("_"));
    if (this.complex_values[basename]) {
        var l = this.complex_values[basename];
        for (var j=0; j<l.length; j++) {
            var e = document.getElementById(l[j]);
            if (e) list.push(e);
        }
    } else {
        list.push(element);
    }
    return list;
};


OrganicForm.prototype.valid =
function() {
    this.quietErrors = false;

    this.validateAll(true); // synchronous Ajax validation

    var ok = true;
    for (var id in this.controls) {
        if (typeof this.controls[id] != "object") continue;
        if (this.controls[id].valid !== true) {
            ok = false;
            break;
        }
    }

    this.quietErrors = true;
    return ok;
};

OrganicForm.prototype.validateAll =
function(sync) {
    // validate elements
    for (var id in this.controls) {
        if (typeof this.controls[id] != "object") continue;
        if (this.controls[id].valid !== true)
            this.validateElement(this.getElementControl(id), sync);
    }
};
    
OrganicForm.prototype.validationResultToMessage =
function(element, result) {
    if (this.controls[element.id].failed_conditionals.length > 0) {
        // Achterhaal of een van de gefaalde conditionals een showhide is.
        // in dat geval hoeven de subvalidaties namelijk niet gedaan te worden!
        var showhide = false;
        var f = this.controls[element.id].failed_conditionals;
        for (var i=0; i<f.length; i++) if (this.conditionals[f[i]].showhide) {
            showhide = true; break;
        }
        
        if (typeof result == "string" && (showhide || result.match(/required/)))
            return true;
    }        
    
    
    if (typeof result == "string") {
        if (!VALIDATION_MESSAGES || !VALIDATION_MESSAGES[result]) {
            result = { brief: result+": Unknown validation message!", extended: result+": Unknown validation message!" };
        } else {
            result = VALIDATION_MESSAGES[result];
        }
    }
    return result;
};

    
function AjaxValidator(validatorclass, context) {
    this.validatorclass = validatorclass;
    this.context = context;
    return this;
};

// JPspan callback
function AjaxValidatorCallback(element, validator) {
    this.validator = validator;
    this.validateelement = function(result) {
        result = this.validationResultToMessage(element, result);
        validator.setElementStatus(element, result);
    }
    return this;
};

// JPspan callback from ajax submit
function AjaxSubmitCallback(method, returnid) {
    var callback;
    if (returnid && (typeof window.organicforms_callbackreturn[returnid] == "function"))
        callback = window.organicforms_callbackreturn[returnid];
    else {
        if (returnid) {
            callback = function(result) {
                alert("No callback function specified for AjaxSubmit on button. Return id = "+returnid+"\n\nUse:\nwindow.organicforms_callbackreturn['"+returnid+"'] = function(result) { ... }");
            }
        } else {
            // if no return id we do not alert, as we do not want to do something with the return value, probably.
            callback = function(result) {
            }
        }
    }

    var cb = function(result) { callback(result, returnid); };
    eval('this.'+method+' = cb;');
    return this;
};

window.organicforms = new Array();
window.organicforms_callbackreturn = new Array();
