本文从简的演示利用Composite Pattern来动态创建form,它支持保存和还原上次输入的数据。
第一步,定义接口:
var Composite = new Interface("Composite", ["add", "remove", "getChild"]);
var FormItem = new Interface("FormItem", ["save", "restore", "getElement"]);
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;
}
}
};
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);
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