实现一个中间部件对象,也就是可以添加叶子节点的对象,最终这个中间件部件装载在根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>

小被子
839 声望10 粉丝