实现一个中间部件对象,也就是可以添加叶子节点的对象,最终这个中间件部件装载在根form对象上.这个中间件的好处是可以按照实际业务需求进行分组.本例中划分了三种业务,分别是名字分组,地址分组和测试分组.本例中还设计了remove按照id删除元素的方法,这样会更加灵活.因为引用类型是公用的,即使前面添加了,后面再删除,最后得到的还是删除后的效果.这样可以不用漫天寻找代码,也不用非要在添加之前注释某段代码,这就是引用类型的优点.策略模式和组合模式之间是没有任何耦合的,我们完全可以在动态装载完form表单后,再根据具体字段绑定策略,至于根据实际需要拆卸某些字段的策略,那就是策略模式自己的事了.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>组合+策略模式</title>
</head>
<body>
</body>
</html>
<script type="text/javascript">
//策略类
var strategies = {
isNonEmpty: function(value, errorMsg) {
if(value === '') {
return errorMsg;
}
},
minLength: function(value, length, errorMsg) {
if(value.length < length) {
return errorMsg;
}
},
isMobile: function(value, errorMsg) {
if(!/1[3|5|8][0-9]{9}$/.test(value)) {
return errorMsg;
}
}
};
var Validator = function() {
this.cache = [];
}
Validator.prototype.add = function(dom, rules) {
var self = this;
for(var i=0; i<rules.length; i++) {
(function(rule) {
var arr = rule.strategy.split(':');
var errorMsg = rule.errorMsg;
//将函数都压入数组
self.cache.push(function() {
//拿出策略字串备用
var strategy = arr.shift();
//将dom值放在数组头一个
arr.unshift(dom.value);
//报错信息放在数组最后一个
arr.push(errorMsg);
return strategies[strategy].apply(dom, arr);
})
})(rules[i]);
}
};
Validator.prototype.start = function() {
for(var i=0; i<this.cache.length; i++) {
var msg = this.cache[i]();
if(msg) {
return msg;
}
}
}
//继承方法
function extend(newobj, obj) {
var F = function() {};
F.prototype = obj.prototype;
newobj.prototype = new F();
newobj.prototype.constructor = newobj;
}
//叶子对象
var Field = function(id) {
this.id = id;
this.divElement;
}
Field.prototype.save = function() {
sessionStorage.setItem(this.id, this.getValue());
console.log(sessionStorage.getItem(this.id));
}
//最终调用子对象的divElement,这个this指向必须是子对象。
//用call函数可以修正this指向。
//以供下面部件对象CompositeFieldSet中使用。
Field.prototype.getDivElement = function() {
//这里还要注意divElement必须有个id,在部件对象的容器中作为key值使用。
return this.divElement;
}
//子类继承使用
Field.prototype.getValue = function() {
throw new Error('you should override this method');
}
var InputField = function(label, id, type) {
Field.call(this, id);
this.input = document.createElement('input');
//策略验证类使用name寻找dom
this.input.name = id;
this.input.type = type;
this.label = document.createElement('label');
var labelTextNode = document.createTextNode(label);
this.label.appendChild(labelTextNode);
this.divElement = document.createElement('div');
this.divElement.id = id;
this.divElement.className = 'input-field';
this.divElement.appendChild(this.label);
this.divElement.appendChild(this.input);
}
extend(InputField, Field);
InputField.prototype.getValue = function() {
return this.input.value;
};
var SelectField = function(label, id, arr) {
Field.call(this, id);
this.select = document.createElement('select');
for(var i=0; i<arr.length; i++) {
var option = document.createElement('option');
option.text = arr[i].text;
option.value = arr[i].value;
this.select.options[i] = option;
}
this.label = document.createElement('label');
var labelTextNode = document.createTextNode(label);
this.label.appendChild(labelTextNode);
this.divElement = document.createElement('div');
this.divElement.id = id;
this.divElement.className = 'select-field';
this.divElement.appendChild(this.label);
this.divElement.appendChild(this.select);
}
extend(SelectField, Field);
SelectField.prototype.getValue = function() {
return this.select.options[this.select.selectedIndex].value;
}
//部件对象:可以按照业务分组表单元素
var CompositeFormSet = function(id, legendText) {
//注意这里不是数组了。
this.components = {};
//legend和fieldset元素在表单元素分组很常用
//一个fieldset包含许多legend
this.id = id;
this.element = document.createElement('fieldset');
this.element.id = id;
//dom分组
if(legendText) {
this.legend = document.createElement('legend');
this.legend.appendChild(document.createTextNode(legendText));
this.element.appendChild(this.legend);
}
}
CompositeFormSet.prototype.add = function(child) {
//容器内的元素按照id分组。
//每个child是一个叶子对象。
this.components[child.getDivElement().id] = child;
this.element.appendChild(child.getDivElement());
}
//获取这个部件对象,以供下面CompositeForm对象中使用。
CompositeFormSet.prototype.getElement = function() {
return this.element;
}
CompositeFormSet.prototype.save = function() {
console.log(this.components);
for(var id in this.components) {
//如果容器没有这个元素,直接跳过。
if(!this.components.hasOwnProperty(id)) {
continue;
}
this.components[id].save();
}
}
//拆卸某个叶子对象
//这样就不用去寻找添加元素的地方注释代码。
CompositeFormSet.prototype.remove = function() {
var child;
for(var key in this.components) {
if(this.components[key]['id'] === id) {
child = this.components[key];
delete this.components[child.getDivElement().id];
this.element.removeChild(child.getDivElement());
break;
}
}
}
var CompositeForm = function(id) {
this.formComponents = [];
this.formElement = document.createElement('form');
this.formElement.id = id;
this.formElement.onsubmit = function() {
return false;
}
}
CompositeForm.prototype.add = function(child) {
this.formComponents.push(child);
//添加部件元素
//每个child是一个部件对象。
this.formElement.appendChild(child.getElement());
}
CompositeForm.prototype.save = function() {
for(var i=0; i<this.formComponents.length; i++) {
this.formComponents[i].save();
}
}
//拆掉某个元素
CompositeForm.prototype.remove = function(id) {
var child;
for(var i=0; i<this.formComponents.length; i++) {
if(this.formComponents[i].id === id) {
child = this.formComponents[i];
this.formComponents.splice(i, 1);
this.formElement.removeChild(child.getElement);
break;
}
}
}
//获取最终form对象,以供追加到body下。
CompositeForm.prototype.getFinalElement = function() {
var submitButton = document.createElement('input');
submitButton.id = 'sub';
submitButton.type = 'submit';
this.formElement.appendChild(submitButton);
return this.formElement;
}
//开始组合
var form = new CompositeForm('comtact-form');
//造部件:名字分组
var nameFieldSet = new CompositeFormSet('name-fieldset', 'nameArea');
nameFieldSet.add(new InputField('first-name', 'firstName', 'text'));
nameFieldSet.add(new InputField('last-name', 'lastName', 'text'));
//造部件:地址分组
var addressFieldSet = new CompositeFormSet('address-fieldset', 'addressArea');
addressFieldSet.add(new SelectField('国家', 'country', [{text:'美国', value:'us'}, {text:'日本', value:'jp'}]));
addressFieldSet.add(new InputField('地址', 'detail', 'text'));
addressFieldSet.add(new InputField('手机号', 'phone', 'text'));
//造部件:测试分组
var testFieldSet = new CompositeFormSet('test-fieldset', 'testArea');
testFieldSet.add(new InputField('测试', 'test', 'text'));
form.add(nameFieldSet);
form.add(addressFieldSet);
form.add(testFieldSet);
//突然有一天,经理让你去掉测试分组,
//并且去掉地址分组中的国家元素。
//但是我并不想去找add的代码到底在哪里
//于是可以从控制台中直接找到id的相关信息,用remove删除就好
addressFieldSet.remove('country');
form.remove('test-fieldset');
var formNode = form.getFinalElement();
document.body.appendChild(formNode);
var todoFunc = function() {
var validator = new Validator();
validator.add(formNode.firstName, [
{strategy: 'isNonEmpty', errorMsg: 'firstName不能为空'},
{strategy: 'minLength:5', errorMsg: 'firstName长度不能少于5位'}
]);
validator.add(formNode.lastName, [
{strategy: 'isNonEmpty', errorMsg: 'lastName不能为空'},
{strategy: 'minLength:3', errorMsg: 'lastName长度不能少于3位'}
]);
validator.add(formNode.detail, [
{strategy: 'isNonEmpty', errorMsg: '地址不能为空'},
{strategy: 'minLength:10', errorMsg: '地址长度不能少于10位'}
]);
validator.add(formNode.phone, [
{strategy: 'isNonEmpty', errorMsg: '手机号不能为空'},
{strategy: 'isMobile', errorMsg: '手机号码格式不正确'}
]);
var errorMsg = validator.start();
return errorMsg;
}
document.getElementById('sub').onclick = function() {
form.save();
var errorMsg = todoFunc();
if(errorMsg) {
alert(errorMsg);
}
return false;
}
</script>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。