/*
Copyright (c) 2006 Author
Author: Anil Sharma for the book "Real-World AJAX: Secrets of the Masters"

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

function Form(name) {
   // name of the component
   this.name = name;
   // the associated form element
   this.element = null;
   // collection of sub-elements contained inside
   // the form element
   this.collection = null;
   // list of components (fields)
   this.components = new Array();
   // list of value change listeners
   this.valueChangedListeners = new Array();
   // current data set displayed by this form
   this.data = null;
   
   // original objects table
   this.originalObjs = new Object();
   // recorded changes by object
   this.changesByObj = new Object();
}

// sets the form element
// builds a collection of sub-elements
Form.prototype.setHtmlElement = function(e) {
    this.element = e;
    this.collection = new ElementCollection(e);
}

// sets a field component
// retreieves the field element using the name of the
// component (component name is same as the field id)
Form.prototype.setFieldComp = function(comp) {
    if (this.element != null) {
        var e = this.collection.getElementById(comp.name);
        if (e != null) {
        comp.setHtmlElement(e);
        }
    }
    // form listens for any change to its field
    comp.addValueChangedListener(this);
    // add the component to the components' list
    this.components.push(comp);
}

// sets the dataset of the form
// iterates over the component list and
// sets data for each field
Form.prototype.setData = function(data) {
    this.data = data;
    this.copyOriginalData(data);
    for (var i=0; i<this.components.length; i++) {
        this.components[i].setData(data);
    }
}

// invoked when the value of one of the field of the
// form changes
// when any field of the form changes, the form
// invokes valueChanged method of its listener
Form.prototype.valueChanged = function(data, name, value) {
    this.recordChanges(data, name, value);
    for (var i=0; i<this.valueChangedListeners.length; i++) {
        this.valueChangedListeners[i].valueChanged(data, name, value);
    }
}

// add a value change listener
// when any field of the form changes, the form
// invokes valueChanged method of its listener
Form.prototype.addValueChangedListener = function(l) {
    if (l.valueChanged == null) {
        alert("The given listener should implement valueChanged(data, name, newValue) method");
        return;
    }
    this.valueChangedListeners.push(l);
}

// makes a copy of the given data
// saves only if the given data has __id attribute
Form.prototype.copyOriginalData = function(data) {
    // data has id and not added to the originalObjs yet
    if (data.__id && this.originalObjs[data.__id] == null) {
        var newData = new Object();
        // copy attributes
        for (property in data) {
			newData[property] = data[property];
        }
        // insert into the orginal objects table
        this.originalObjs[data.__id] = newData;
    }
}

// recrods changes in the given object
Form.prototype.recordChanges = function(data, name, value) {
    // retrieve original object
    var origObj = this.originalObjs[data.__id];
    if (origObj != null) {
        // is value different
        if (value != origObj[name]) {
        // look for the object where the changes are recorded
        var obj = this.changesByObj[data.__id];
        if (obj == null){
            // create an object for recording changes
            obj = new Object();
            // set its id to the object for which it is
            // recording the changes
            obj.__id = data.__id;
            obj.__className = data.__className;
            // insert into changedByObj table
            this.changesByObj[obj.__id] = obj;
        }
        obj[name] = value;
        } else {
        // cleanup any change recorded for this attribute
        var obj = this.changesByObj[data.__id];
        // if changes were recroded, remove them
        if (obj != null && obj[name]){
            // delete the changes
            delete obj[name];
        }
        }
    }
}

Form.prototype.clearChanges = function() {
    // recreate the tables
    this.originalObjs = new Object();
    this.changesByObj = new Object();
    if (this.data != null) {
        // copy  the current object
        this.copyOriginalData(this.data);
    }
}

// returns the changes list as XML
Form.prototype.getChangeList = function() {
    var changes = "";
    // for each changed object
    for (id in this.changesByObj) {
        var obj = this.changesByObj[id];
        var s = "";
        // for each changed attribute (except __id, _className)
        for (property in obj) {
			if (property != "__id" && property != "__className") {
				s = s + "<" + property + ">" + obj[property] + "</" + property + ">";
			}
        }
        // if there were changes
        if (s != "") {
			var t = obj.__className;
			if (t == null) {
				t = "Change";
			}
			changes = "<" + t + " id=\"" + obj.__id + "\">" + s + "</" + t + ">";
        }
    }
    // if changes are present, wrap them in changelist element
    if (changes != "") {
        return "<ChangeList>" + changes + "</ChangeList>";
    }
    return changes;
}

// renders Form's element on the client side
Form.prototype.render = function() {
    // table is the Form level element.
    var table = document.createElement("table");
    table.border = "0";
    table.cellspacing = "0";
    table.cellpadding = "0";
    table.width = "100%";
    //table.style="margin-left";
    table.id = this.name;
    var tbody = document.createElement("tbody");
    table.appendChild(tbody);
    // For each field component create a row
    for (var i=0; i<this.components.length; i++) {
        var tr = document.createElement("tr");
        tbody.appendChild(tr);
        var comp = this.components[i];
        // if the component is not a TextAreaComp
        // add label using the display name of the comp.
        if (comp.type != "TextAreaComp") {
          // create cell for the label
          var td = document.createElement("td");
          td.style.width = "100px";
          td.className = "formLabelCell";
          td.appendChild(document.createTextNode(comp.displayName));
          tr.appendChild(td);
        }
        // create cell for the field
        td = document.createElement("td");
        td.className = "formFieldCell";
        // render the component by calling its 'render'
        var elem = comp.render();
        if (comp.type == "TextAreaComp") {
          elem.style.width="100%";
          td.colSpan = "2";
        } else {
            elem.style.width="200px";
        }
        elem.className = "FormField";
        // add the component's element to the cell
        td.appendChild(elem);
        // add the field cell to the row
        tr.appendChild(td);
    }
    this.element = table;
}

