/* ------------------------------------------------------------------
 * 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.globalValidations = new Array();

    this.buttons = new Array();
    this.controls = 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();
}

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.addGlobalValidation =
function(name, vlist, elems) {
    if (!elems) elems = new Array();
    if (!vlist.length) vlist = [vlist];
    this.globalValidations[name] = { vlist: vlist,
                                     dependent_elems: elems,
                                     valid: null
    };
    for (var id in elems) {
        id = elems[id];
        var e = document.getElementById(id);
        if (!e) continue;
        var l = this.getElementList(e);
        for (var i=0; i<l.length; i++) {
            this.controls[l[i].id].globalValidations.push(name);
        }
    }
};

OrganicForm.prototype.addButton =
function(id) {
    var button = document.getElementById(id);
    if (!button) return false;
    
    var f = this;
    button.onclick = function() { 
        // f.do_errorlist = document.getElementById(f.form_id+"_errorlist")!=null;
        return f.valid(); 
    };
    this.buttons[id] = button;
    return true;
};

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]];
        }
    }
};

OrganicForm.prototype.addControl =
function(id, validations, caption, error) {
    
    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" || type == "checkbox") {
            // if it is a text input, we can restart the timeout on every key-up request,
            // to make sure that the validation takes place after the user has stopped typing.
            if (typeof elem.onfocus != "function") 
                elem.onfocus = function() { v.errorpopup_elems[did].focus = true; v.showElementErrorPopup(did); v.startValidator(this); };
            else {
                var x = elem.onfocus;
                elem.onfocus = function() { x(); v.showElementErrorPopup(did); v.errorpopup_elems[did].focus = true; v.startValidator(this); };
            }
            
            if (typeof elem.onblur != "function") 
                elem.onblur = function() { v.stopValidator(this); v.errorpopup_elems[did].focus = false; v.hideElementErrorPopup(did); };
            else {
                var x = elem.onblur;
                elem.onblur = function() { x(); v.stopValidator(this); v.hideElementErrorPopup(did); v.errorpopup_elems[did].focus = false; };
            }
            
            if (typeof elem.onkeyup != "function") {
                elem.onkeyup = function() {
                    clearTimeout(v.controls[elem.id].timer);
                    v.controls[elem.id].timer = setTimeout( function(){v._validatorLoop(elem);}, v.validationTimeout);
                }
            } else {
                var x = elem.onkeyup;
                elem.onkeyup = function() {
                    x();
                    clearTimeout(v.controls[elem.id].timer);
                    v.controls[elem.id].timer = setTimeout( function(){v._validatorLoop(elem);}, v.validationTimeout);
                }
            }
            
        } 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,
                                  timer: 0,
                                  globalValidations: new Array(),
                                  elementValidations: validations,
                                  caption: caption,
                                  prevcontrol: this.prevcontrol
        };
        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 value = new Array();
        var l = this.complex_values[basename];
        value = new Array();
        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":
            case "radio":
                if (subelem.checked)
                    value[ext] = subelem.value;
                break;
            default:
                value[ext] = this.getElementValue(subelem);
            }
        }
        // var s=""; for (var k in value) s += k+": "+value[k]+"\n"; alert(s);
        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, noglobal) {
    if (!sync) sync = false;
    if (!noglobal) noglobal = 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 (validate_result !== null)
        this.setElementStatus(element, validate_result);

    // global validation
    if (!noglobal && validate_result === true && this.controls[element.id].globalValidations.length) {
        // run validations
        for (var i=0; i<this.controls[element.id].globalValidations.length; i++) {
            this.validateGlobal(this.controls[element.id].globalValidations[i]);
        }
    } else if (!noglobal && this.controls[element.id].globalValidations.length) {
        // invalid element result; all global validations depending on this need to be set invalid,
        // and items need to be hidden.
    }
    
    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();
        return h.validateelement(value, validations, this.formspec);                                    
    }
};

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]; // getElementsByAttribute("v_for", did);
    if (!displayelements) {
        // alert('no display elements for '+did);
        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);
};

/**
 * Runs a global validator
 *
 * The validator is only run if all dependent elements have a 'valid' status.
 */
OrganicForm.prototype.validateGlobal =
function(name, sync) {
    if (!sync) sync = false;
    //alert(caller_element_id);
    for (var i=0; i<this.globalValidations[name].dependent_elems.length; i++) {
        //if (i == caller_element_id) continue;
        var id = this.globalValidations[name].dependent_elems[i];
        if (this.controls[id].valid === false) {
            // one of the dependent controls is not valid; stop the validation.
            return true; 
        } else if (this.controls[id].valid === null) {
            // never validated; validate it. in sync (!), so we can depend on the result.
            var r = this.validateElement(this.getElementControl(id), true, true);
            // one of the dependent controls is not valid; stop the validation.
            if (r !== true)
                return true;
        }
    }

    var formvalue = this.getFormValue();
    var validate_result = true;
    var vlist = this.globalValidations[name].vlist;

    for (var i=0; i<vlist.length; i++) {
        
        if (typeof vlist[i] == "function") {
            // client-side validation: call directly
            r = vlist[i](formvalue);
        } else if (typeof vlist[i] == "object" && typeof vlist[i].callglobal == "function") {
            // server-side validation (typically an AjaxValidation); an object which has a .call() method
            r = vlist[i].callglobal(formvalue, name, this, sync);
        } else {
            alert('invalid global validation');
        }

        if (r !== true) {
            validate_result = r; break;
        }
    }
    if (r !== null)
        this.setGlobalStatus(name, validate_result);

};

OrganicForm.prototype.setGlobalStatus =
function(name, status) {
    var l = window.organicforms_renderelements[name]; // getElementsByAttribute("v_for", name);
    for (var i=0; i<l.length; i++) {
        this._formatElement(l[i], status);
    }
    
    this.globalValidations[name].valid = (status === true);
    
    this.updateGeneralErrorlist(name, 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.visibility == "hidden") {
                        w = i; break;
                    }
                }
                if (status !== true) {
                    var li = el.childNodes[j];
                    li.style.visibility = "visible";
                    li.innerHTML = status.brief;
                    el.insertBefore(li, el.childNodes[w]);
                } else {
                    el.childNodes[j].style.visibility = "hidden";
                    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.visibility == "hidden") 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;
        }
    }

    if (ok) {
        for (var n in this.globalValidations) {
            if (typeof this.globalValidations[n] != "object") continue;
            if (this.globalValidations[n].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);
    }

    // global validations
    for (var n in this.globalValidations) {
        if (typeof this.globalValidations[n] != "object") continue;
        if (this.globalValidations[n].valid !== true)
            this.validateGlobal(n, sync);
    }
    
};

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) {
        if (typeof result == "string" && VALIDATION_MESSAGES && VALIDATION_MESSAGES[result])
            result = VALIDATION_MESSAGES[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();
