本文从简的演示利用Composite Pattern来动态创建form,它支持保存和还原上次输入的数据。

    第一步,定义接口:

var Composite = new Interface("Composite", ["add", "remove", "getChild"]);
var FormItem = new Interface("FormItem", ["save", "restore", "getElement"]);

你懂的,这里是基于single responsibility rule将接口分为2个。


    然后,分别来实现2个接口了: 

var CompositeForm = function (id, method, action) {
    this.formComponents = [];

    this.element = document.createElement("form");
    this.element.id = id;
    this.element.method = method || "post";
    this.element.action = action || "#";
};
CompositeForm.prototype = {
    add: function (child) {
        Interface.ensureImplements(child, [Composite, FormItem]);
        this.formComponents.push(child);
        this.element.appendChild(child.getElement());
    },
    remove: function (child) {
        for (var i = 0; i < this.formComponents.length; i++) {
            if (this.formComponents[i] == child) {
                this.formComponents.splice(i, 1);
                this.element.removeChild(child.getElement());
                break;
            }
        }
    },
    getChild: function (i) {
        if (i >= this.formComponents.length) {
            throw new Error("i is out of range.");
        }
        return this.fromComponents[i];
    },
    save: function () {
        for (index in formComponents) {
            this.formComponents[index].save();
        }
    },
    restore: function () {
        for (index in formComponents) {
            this.formComponents[index].resotre();
        }
    },
    getElement: function () {
        return this.element;
    }
};
CompositeForm.prototype.constructor = CompositeForm;

// composite fieldset
var CompositeFieldset = function (id, legendText) {
    this.components = {};

    this.element = document.createElement("fieldset");
    this.element.id = id;
    if (legendText) {
        this.legend = document.createElement("legend");
        this.legend.appendChild(document.createTextNode(legendText));
        this.element.appendChild(this.legend);
    }
};
CompositeFieldset.prototype = {
    add: function (child) {
        Interface.ensureImplements(child, [Composite, FormItem]);
        this.components[child.getElement().id] = child;
        this.element.appendChild(child.getElement());
    },
    remove: function (child) {
        delete this.components[child.getElement().id];
        this.element.removeChild(child.getElement());
    },
    getChild: function (id) {
        return this.components[id];
    },
    save: function () {
        for (index in components) {
            if (components.hasOwnProperty(index)) {
                this.fieldset[index].save();
            }
        }
    },
    restore: function () {
        for (index in components) {
            if (components.hasOwnProperty(index)) {
                this.fieldset[index].resotre();
            }
        }
    },
    getElement: function () {
        return this.element;
    }
};
CompositeFieldset.prototype.constructor = CompositeFieldset;


// field abstract class
var Field = function (id) {
    this.element = document.createElement('div');
    this.element.id = id;
    this.element.className = 'input-field';
};
Field.prototype = {
    add: function (child) {
    },
    remove: function (i) {
    },
    getChild: function (i) {
    },
    save: function () {
        setCookie(this.id, this.getValue());
    },
    resotre: function () {
        var value = getCookie(this.id);
        setValue(value);
    },
    getElement: function () {
        return this.element;
    },
    getValue: function () {
        throw new Error("Unimplemented on the class Field.");
    },
    setValue: function () {
        throw new Error("Unimplemented on the class Field.");
    }
};
Field.prototype.constructor = Field;

// input field class
var InputField = function (id, label) {
    this.superClass.constructor.call(this, id);

    this.input = document.createElement('input');
    this.input.id = id + "_input";
    this.label = document.createElement('label');
    this.label.id = id + "_label";
    var labelTextNode = document.createTextNode(label);
    this.label.appendChild(labelTextNode);

    this.element.appendChild(this.label);
    this.element.appendChild(this.input);
};
extend(InputField, Field);
InputField.prototype.getValue = function () {
    return this.input.value;
};
InputField.prototype.getValue = function (value) {
    this.input.value = value;
};

// textarea field class
var TextAreaField = function (id, label) {
    this.superClass.constructor.call(this, id);

    this.textArea = document.createElement('textarea');
    this.textArea.id = id + "_textarea";
    this.label = document.createElement('label');
    this.label.id = id + "_label";
    var labelTextNode = document.createTextNode(label);
    this.label.appendChild(labelTextNode);

    this.element.appendChild(this.label);
    this.element.appendChild(this.textArea);
};
extend(TextAreaField, Field);
TextAreaField.prototype.getValue = function () {
    return this.textArea.value;
};
TextAreaField.prototype.getValue = function (value) {
    this.textArea.value = value;
};

// select field class
var SelectField = function (id, label, options) {
    this.superClass.constructor.call(this, id);

    this.select = document.createElement('select');
    this.select.id = id + "_select";
    for(var i = 0;i<options.length;i++){
        var option = document.createElement("option");
        option.text = options[i].text;
        option.value = options[i].value;
        try{
            this.select.add(option, this.select.options[null]);
        }
        catch(e){
            this.select.add(option, null);
        }
    }
    this.label = document.createElement('label');
    this.label.id = id + "_label";
    var labelTextNode = document.createTextNode(label);
    this.label.appendChild(labelTextNode);

    this.element.appendChild(this.label);
    this.element.appendChild(this.select);
};
extend(SelectField, Field);
SelectField.prototype.getValue = function () {
    return this.select.options[this.select.selectedIndex].value;
};
SelectField.prototype.getValue = function (value) {
    for (index in this.select.options) {
        if (this.select.options[index].value == value) {
            this.select.selectedIndex = index;
            break;
        }
    }
};

 

测试代码如下: 

    var contactForm = new CompositeForm("contactForm", "post", "contact.php"); 
    var nameFieldset = new CompositeFieldset('nameFieldset', "Name");
    nameFieldset.add(new InputField("firstName", "First Name"));
    nameFieldset.add(new InputField("lastName", "Last Name"));
    contactForm.add(nameFieldset);

    var addressFieldset = new CompositeFieldset('addressFieldset', "Address");
    addressFieldset.add(new InputField("address", "Address"));
    addressFieldset.add(new SelectField("state", "State", [{"text": "Alabama", "value": "Alabama"}, {"text": "Seattle", "value": "Seattle"}]));
    addressFieldset.add(new InputField('zip', 'Zip'));    
    contactForm.add(addressFieldset);

    contactForm.add(new TextAreaField('comments', 'Comments'));

    body.appendChild(contactForm.getElement());
    addEvent(window, "load", contactForm.restore);
    addEvent(window, "unload", contactForm.save);

这里不再详细解释了,注意Field类是abstract的即可。 download