任务目的
- 加强对JavaScript的掌握
- 熟悉常用表单处理逻辑
- 学习如何模块如何设计,不同模块间如何尽量解耦
任务描述
- 实现以JavaScript对象的方式定义表单及验证规则
表单配置参考示例如下:(不需要一致,仅为参考)
{
label: '名称', // 表单标签 type: 'input', // 表单类型 validator: function () {...}, // 表单验证规 rules: '必填,长度为4-16个字符', // 填写规则提示 success: '格式正确', // 验证通过提示 fail: '名称不能为空' // 验证失败提示}
- 基于该配置项,实现一套逻辑,可以自动生成表单的展现、交互、验证
- 使用你制作的表单工厂,在一个页面上创建两套样式不同的表单
整体思路
常见的控件类型一般有文本框,单选框,复选框,下拉菜单和文本域。我们可以让用户生成这5种控件,并且添加基本的配置以及自动生成验证规则等。
首先准备好HTML框架,HTML分为两部分,一部分是表单的生成配置部分,另一部分用来存放生成的表单。
第一部分:
<form id="data_create">
<fieldset id="type_box" class="input">
<legend>控件类型</legend>
<select id="widget">
<option value="input">文本框</option>
<option value="radio">单选框</option>
<option value="checkbox">复选框</option>
<option value="select">下拉菜单</option>
<option value="textarea">文本域</option>
</select>
</fieldset>
<fieldset id="basic_box" class="necessary">
<legend>控件配置</legend>
<label>名称:</label>
<input type="text" id="label_box" value="input">
<select id="necessary">
<option value="necessary">必填</option>
<option value="unnecessary">选填</option>
</select>
<p>
<label>样式:</label>
<select id="style_box">
<option value="style_one">样式一</option>
<option value="style_two">样式二</option>
</select>
</p>
</fieldset>
<fieldset id="rule_input" class="text">
<legend>检验规则</legend>
<select>
<option value="text">文本</option>
<option value="password">密码</option>
<option value="number">数字</option>
<option value="email">邮箱</option>
<option value="phone">电话</option>
</select>
</fieldset>
<fieldset id="length_control">
<legend>长度</legend>
<label>字符长度:</label>
<input id="min_length" type="number" min="0" value="4" class="numInput">
<span>——</span>
<input id="max_length" type="number" min="1" value="16" class="numInput">
</fieldset>
<fieldset id="box_item">
<legend>选项</legend>
<input id="box_item_input" placeholder="可用空格,逗号,回车来分隔选项" />
<span id="box_item_show"></span>
</fieldset>
<button id="btn_add" type="button">提交</button>
</form>
第二部分:
<div id="form_box">
<h1 id="title">表单展示区</h1>
<form id="result" class="style_one">
<button type="button" id="submit_form">提交</button>
<input type="text" class="hide">
</form>
</div>
在生成单选框,复选框,下拉菜单时,每一个选项的添加利用了之前Tag输入的功能。即实现了一个tag输入框,用户输入空格,逗号,回车时,都自动把当前输入的内容作为一个tag放在输入框下面,遇到重复输入的Tag,自动忽视。
我们把这部分功能封装为showTagModel
function ShowTag(ipt,box){
this.arr = []; //数据存放数组
this.ipt = ipt; //选项输入框
this.box = box; //显示tag的容器
this.length = 20; //option的数量
}
ShowTag.prototype = {
getData : function(){
return this.arr;
},
//显示标签
show : function(){
var text = '';
for (var index = 0; index < this.arr.length; index++) {
text += '<div data-num="' + index + '" class="item"><span>点击删除</span>' + this.arr[index] + '</div>';
}
this.box.innerHTML = text;
return this;
},
//去重和限制长度
trim : function(){
var i,j;
for(i=0;i<this.arr.length;i++){
for(j=i+1;j<this.arr.length;j++){
if(this.arr[i] == this.arr[j]){
this.arr.splice(j,1);
j--;
}
}
}
while(this.arr.length > this.length){
this.arr.shift();
}
this.show();
return this;
},
//添加数据
add : function(){
var strs = this.ipt.value.split(/[ ,、, \n\t]/);
for(var i=0;i<strs.length;i++){
var item = strs[i];
if(item != ""){
this.arr.push(item);
}
}
this.trim();
return this;
},
//点击删除数据
deleteEvent : function(e){
//点击span或者点击span内的文本
var item = e.target.className == 'item' ? e.target : e.target.parentNode.className == 'item' ? e.target.parentNode : null;
if (item == null) {
return 0;
}
//删除第n个元素,之后重新显示元素
this.arr.splice(item.getAttribute('data-num'), 1);
this.show();
}
};
//input对象
function TagIpt(tag_ipt,tag_box){
ShowTag.call(this,tag_ipt,tag_box);
}
//建立一个由ShowTag.prototype继承而来的TagIpt.prototype对象.
TagIpt.prototype = Object.create(ShowTag.prototype);
//设置构造函数为ShowTag
TagIpt.constructor = ShowTag;
TagIpt.prototype.init=function() {
//绑定事件
addHandler(this.box, 'click', this.deleteEvent.bind(this));//删除元素事件的绑定
addHandler(this.ipt, 'keyup', this.keyUp.bind(this)); //输入框输入内容事件的绑定
addHandler(this.ipt, 'keydown', this.preventDefault); //阻止输入框的默认事件
};
TagIpt.prototype.keyUp=function(e) {
if (e.keyCode == 188 || e.keyCode == 32 || e.keyCode == '13') {
this.add();
this.ipt.value = '';
}
};
TagIpt.prototype.preventDefault=function(e) {
if (e.keyCode == '13') {
e.preventDefault ? e.preventDefault() : e.returnValue = false;
}
};
由于要从页面中获取很多数据,这里用一个对象把需要的控件封装,集成再在同一个对象中,避免频繁对页面的某个元素频繁重复的获取。
这里把页面用的元素封装再dataModel中
//存放需要的id节点
var data_box = {
type_box : { //控件类型
box: $("#type_box"),
value: "className" //获取方式
},
label_box : { //控件名称
box : $("#label_box"),
value: "value"
},
necessary_box : { //控件是否必填
box : $("#basic_box"),
value : "className"
},
style_box : { //控件样式
box : $("#style_box"),
value : "value"
},
input_type_box : { //input类型
box : $("#rule_input"),
value : "className"
},
min_length_box : { //控件字符最短长度
box : $("#min_length"),
value : "value"
},
max_length_box : { //控件字符最长长度
box : $("#max_length"),
value : "value"
},
item_box : [
$("#box_item_input"),
$('#box_item_show'), //选项展示区
document.getElementsByClassName('item') //获取所有node节点
],
add_btn : $("#btn_add"), //添加控件按钮
result_box : $("#result"),//控件展示区
submit_form : $("#submit_form") //展示区的提交按钮
};
下一步就可以建立一个表单工厂,通过data_box获取相应的数据,并且根据用户输入的信息建立表单控件。在表单工厂中,首先是初始化部分,建立配置选项和类名的对应关系,为下一步获取页面配置数据做准备
//数据工厂
function Data_factory(data_box){
this.box = data_box;
this.id = 0;
}
Data_factory.prototype = {
init : function(){
this.addEvent(); //初始化给form绑定事件
},
addEvent : function(){
addHandler($("#data_create"),"change",this.changeClass.bind(this)); //下拉框内容改变时更改对应元素的类名
addHandler(this.box.style_box.box,"change",this.changeStyle.bind(this)); //样式改变时修改控件样式
},
changeClass : function(e){
var box = e.target;
if(box.type == "select-one"){
var value = box.options[box.selectedIndex].value;
box.parentNode.className = value;
if(!/necessary/.test(box.id)){
this.box.label_box.box.value = value; //改变控件的名字
}
}
},
changeStyle : function(){
var style = this.getText(this.box.style_box);
this.box.result_box.className = style;
},
然后是数据获取,在获取数据时,先获取基本的信息,比如控件类型和控件名称,然后根据类型再获取其他所需要的数据。因为不同的控件需要的数据是不同的
getText : function(data_box){
return data_box.box[data_box.value]; //根据不同的属性获取不同的值
},
getData : function(){ //获取页面数据
var data = {
type : '', //控件类型
label : '',
necessary : true,
input_type : '', //input的类型 text number password...
min_length : 0,
max_length : 1,
item : [], //存放选项的数组
id : 0, //控件id
default_text : '', //默认提示
success_text : '', //成功提示
fail_text : '', //失败提示
validator : function(){} //检验规则
};
//获取基础数据
data = this.getBaseData(data);
//根据类型完善其他数据
switch(data.type){
case 'input':
switch(data.input_type){
case 'text':
case 'password':
data = this.getLengthData(data);
break;
case 'number':
case 'email':
case 'phone':
data = this.getIptData(data);
break;
}
break;
case 'textarea':
data = this.getLengthData(data);
break;
case 'radio':
case 'checkbox':
case 'select':
data = this.getItemData(data);
break;
}
return data;
},
getBaseData : function(data){
data.type = this.getText(this.box.type_box);
data.label = this.getText(this.box.label_box);
data.necessary = this.getText(this.box.necessary_box) == "necessary";
data.input_type = this.getText(this.box.input_type_box);
data.id = "form" + this.id++;
return data;
},
//补充text password和textarea的信息
getLengthData : function(data){
data.min_length = this.getText(this.box.min_length_box);
data.max_length = this.getText(this.box.max_length_box);
data.validator = validator.length_control;
data.default_text = '长度为' + data.min_length + '——' + data.max_length + '个字符,' + (data.necessary?"必填":"选填");
data.success_text = data.label + '格式正确';
data.fail_text = [
data.label + '不能为空',
data.label + '长度不能小于' + data.min_length + '个字符',
data.label + '长度不能大于' + data.max_length + '个字符'
];
return data;
},
//补充numer emai 和phone的信息
getIptData : function(data){
data.input_type = this.getText(this.box.input_type_box);
data.validator = validator[data.input_type];
data.default_text = '请输入' + data.label + (data.necessary?"必填":"选填");
data.success_text = data.label + '格式正确';
data.fail_text = [
data.label + '不能为空',
data.label + '格式不正确'
];
return data;
},
//补充radio checkbox 和select的信息
getItemData : function(data){
var items = this.box.item_box[2] //获取所有选项节点
data.item = []; //清空数据
for(var i=0;i<items.length;i++){
data.item.push(items[i].childNodes[1].data);
}
if(data.item.length == 0){
alert('你还没有添加' + data.label + '的选项');
data = null;
}else if(data.item.length == 1){
alert('你只添加了一个选项,无法创建' + data.label);
data = null;
}else{
data.default_text = '请选择' + data.label + (data.necessary?" 必填":"选填");
data.success_text = data.label + '已选择';
data.fail_text = [data.label + '未选择'];
}
return data;
},
有了数据,就可以向页面插入控件了
//根据数据生成控件
addWidget : function(data){
switch(data.type){
case 'input':
this.addIptWidget(data);
break;
case 'radio':
this.addRadioWidget(data);
break;
case 'checkbox':
this.addCheckWidget(data);
break;
case 'select':
this.addSelectWidget(data);
break;
case 'textarea':
this.addAreaWidget(data);
break;
}
},
addIptWidget : function(data){
var div = document.createElement("div");
div.innerHTML = '<label>' + data.label + ':</label>' + '<input type="' + data.input_type + '" id="' + data.id + '"><span></span>';
this.box.result_box.insertBefore(div,this.box.submit_form);
},
addRadioWidget : function(data){
var div = document.createElement("div"),text = "";
div.className = "radio_box";
text += '<div id="' + data.id + '"><label className="widgetNameLabel" >' + data.label + ':</label>';
for (var i = 0; i < data.item.length; i++) {
var id = data.id + '' + i;
text += '<input type="radio" id="' + id + '" name="' + data.id + '"><label for="' + id + '">' + data.item[i] + '</label>';
}
text += '</div><span></span>';
div.innerHTML = text;
this.box.result_box.insertBefore(div,this.box.submit_form);
},
addCheckWidget : function(data){
var div = document.createElement("div"),text = "";
div.className = "radio_box";
text += '<div id="' + data.id + '"><label className="widgetNameLabel" >' + data.label + ':</label>';
for (var i = 0; i < data.item.length; i++) {
var id = data.id + '' + i;
text += '<input type="checkbox" id="' + id + '" name="' + data.id + '"><label for="' + id + '">' + data.item[i] + '</label>';
}
text += '</div><span></span>';
div.innerHTML = text;
this.box.result_box.insertBefore(div,this.box.submit_form);
},
addSelectWidget : function(data){
var div = document.createElement("div"),text = "";
text += '<label>' + data.label + ':</label><select id="' + data.id + '" >';
for(var i=0;i<data.item.length;i++){
text += '<option>' + data.item[i] + '</option>';
}
text += '</select><span></span>';
div.innerHTML = text;
this.box.result_box.insertBefore(div,this.box.submit_form);
},
addAreaWidget : function(data){
var div = document.createElement('div');
div.innerHTML = '<label>' + data.label + ':</label><textarea id="' + data.id + '"></textarea><span></span>';
this.box.result_box.insertBefore(div, this.box.submit_form);
}
控件工厂完成了,但是还需要给生成的表单设置相应的检验事件,用于提示用户,可以把这部分功能写在initForm中,Form用于事件的绑定,validator对象用于存放所有的检验规则。
//初始化表单验证
function Form(data){
this.data = data;
console.log(data.id);
this.ipt = document.getElementById(data.id);
console.log(this.ipt);
this.tip = this.ipt.nextElementSibling;
this.validator = data.validator;
this.init();
}
Form.prototype = {
init : function(){
addHandler(this.ipt,"focus",this.default_tip.bind(this));
addHandler(this.ipt,"blur",this.validator.bind(this));
addHandler(this.ipt,"change",this.validator.bind(this));
},
default_tip : function(){
this.tip.innerHTML = this.data.default_text;
this.tip.className = "default";
// this.ipt.className = "default";
},
true_tip : function(){
this.tip.innerHTML = this.data.success_text;
this.tip.className = "true";
// this.ipt.className = "true";
},
error_tip : function(i){
this.tip.innerHTML = this.data.fail_text[i];
this.tip.className = "error";
// this.ipt.className = "error";
}
};
var validator = {
//text password textarea
'length_control': function () {
min_length = this.data.min_length;
max_length = this.data.max_length;
var text = this.ipt.value;
if (text == '') {
if (this.data.necessary)
this.error_tip(0);
else {
this.default_tip();
return true;
}
}
else {
var total = (/[\x00-\xff]/.test(text) ? text.match(/[\x00-\xff]/g).length : 0) + (/[^\x00-\xff]/.test(text) ? text.match(/[^\x00-\xff]/g).length * 2 : 0);
if (total < min_length) {
this.error_tip(1);
}
else if (total > max_length) {
this.error_tip(2);
}
else {
this.true_tip();
return true;
}
}
return false;
},
'number': function () {
var text = this.ipt.value;
if (text == '') {
if (this.data.necessary)
this.error_tip(0);
else {
this.default_tip();
return true;
}
}
else {
if (/^\d*$/.test(text)) {
this.true_tip();
return true;
}
else {
this.error_tip(1);
}
}
return false;
},
'email': function () {
var text = this.ipt.value;
if (text == '') {
if (this.data.necessary)
this.error_tip(0);
else {
this.default_tip();
return true;
}
}
else {
if (/^[0-9a-z]+([._\\-]*[a-z0-9])*@([a-z0-9]+[a-z0-9]+.){1,63}[a-z0-9]+$/.test(text)) {
this.true_tip();
return true;
}
else {
this.error_tip(1);
}
}
return false;
},
'phone': function () {
var text = this.ipt.value;
if (text == '') {
if (this.data.necessary)
this.error_tip(0);
else {
this.default_tip();
return true;
}
}
else {
if (/^1[34578]\d{9}$/.test(text)) {
this.true_tip();
return true;
}
else {
this.error_tip(1);
}
}
return false;
},
'radio': function () {
var item = $('#' + this.data.id).getElementsByTagName('input');
for (var i = 0; i < item.length; i++) {
if (item[i].checked) {
this.true_tip();
return true;
}
}
if (this.data.necessary)
this.error_tip(0);
else {
this.default_tip();
return true;
}
return false;
},
'checkbox': function () {
var children = this.ipt.children;
for (var i in children) {
if (children[i].checked) {
this.true_tip();
return true;
}
}
if (this.data.necessary)
this.error_tip(0);
else {
this.default_tip();
return true;
}
return false;
},
'select': function () {
this.true_tip();
return true;
}
}
最后,直接调用,给页面两个按钮绑定事件。
var data_factory = new Data_factory(data_box),
tagIpt = new TagIpt(data_box.item_box[0],data_box.item_box[1]),
widgetArr = [];
data_factory.init();
tagIpt.init();
//绑定添加控件事件
addHandler(data_factory.box.add_btn,"click",function(){
var data = data_factory.getData();
if(data != null){
//向表单中添加相应的控件
data_factory.addWidget(data);
//绑定验证函数并放入数组中
widgetArr.push(new Form(data));
//在控件为radio和checkbox时直接展示默认的提示
if (data.type == 'radio' || data.type == 'checkbox') {
widgetArr[widgetArr.length - 1].default_tip();
}
}
});
//给表单提交按钮绑定检验事件
addHandler(data_box.submit_form,"click",function(){
var text = "";
for(var i=0;i<widgetArr.length;i++){
text += !widgetArr[i].validator() ? widgetArr[i].tip.textContent + '\n' : "";
}
if(text == ""){
alert("提交成功");
}else{
alert(text);
}
});