您的位置:首页 > 编程语言

表单设计器系列之代码的组织

2015-08-03 15:38 375 查看
摘要: JS是一门自由的语言,书写方便。这也会让我们在编写大应用时比较难的组织里面的代码

当初之所以转前端,就是因为写JS代码不用去部署环境,方便,自由。然而,正是因为自由,才让我们在组织代码时十分头疼。在这里,我要感谢 Kener-林峰 @Kener-林峰 的 #echarts#,正是多次拜读了 echarts的源码,才让我知道如何去组织代码,谢谢!

firstBlood,模块化开发

模块化开发的好处在这里就不再赘述了,当然现在各种模块加载框架也很多,比如我之前在系统中用的 seajs。在表单设计器里面我还是沿用了 echarts里面的 esl.js。文档结构如下图:



对应的esl配置如下:

require.config({
packages: [{
name: 'designer',
location: 'js/autotable/statement/src',
main: 'designer'
}],
paths:{
designer : 'js/autotable/statement/src',
form : 'js/autotable/statement/form',
main: 'js/autotable/statement/formMain'
}
});
require(['main'], function (main) {
main.init({
saveUrl : '',
editId : ''
});
});

抛开路径中的 ace(一个js代码插件)不看,来解读一下paths里面的3个路径:

designer 包含了表单设计所有需要用的代码块,供form调用

form是表单设计器的底层实现,包括业务数据存储,事件处理,dom生成等

通过 main 指定了 一个调用 form的程序入口,如果我们要对表单设计器做一些初始化操作(特别是生成报表之类)也是在main指定的这个js文件中。

特别的, 根据第三点,我们可以在不同的应用场景指定不同的main来实现 不同的需求,如本例所示:formMain.js只是表单设计器的一个入口,并没有做其他的初始化操作。而在tabMain.js中,除了开启表单设计程序,还在表单设计器中初始化了报表的表头以及一些固定的报表输入项。

secondBlood,form.js

form.js里面的实现 我是完全参考echarts底层的 zrender.js来设计和实现的,主要有一下几点:

定义_form对象,该对象包含了诸如 生成模板,添加表单输入对象,合并、拆分单元格的接口,供前面提到的main对象已经一些组件调用。而这些接口的最终实现全都不在该对象上,最终的实现全在下面3个构造函数里面

Storage,顾名思义,所有的数据都在该构造函数里面。需要说明的是,在zrender里面构造函数的所有方法和书写都不是通过原型链来实现的,实现方式如下:

function Storage(){
var self = this;
self.XXX = '';
self.xxxx = function(){}
}
在Storage里面也提供了很多 set/get方法 用来设置或者返回一些数据

Painter,嗯,这家伙儿是用来绘制dom的,同时所有的工具箱类也是放到他里面的,后面我们在讲工具箱类的时候再说。

handler,包含了页面上,特别是表单主题部分的事件捕获以及分配,比如 表格大小的拖动,右键弹出添加面板等等都是在这里面来监听。

总体来说form.js就实现这些功能。

thirdBlood,拨开迷雾见青天

打开src,我们可以看到一下文档结构:



4个文件夹:

componet,这里面包含了所有的工具箱方法实现,

nav,导航?没错,就是导航,在这个系列的第一篇文章中,有界面截图,界面里面顶部的导航不是在页面中写死了的,而是通过诸如formMain,tabMain中调用form的navInit来具体生成的,因为我们可能在不同的应用需求中使用不同的导航栏工具。

shape,所有的表单对象包括纯文本,Input, select这些都在shape里面

tool,包括在日常开发中,我们都需要一些特殊的方法集合来实现一些功能,tool里面js文件就提供了这些方法,如 div的 拖动等

首先,我们先来从shape讲起,先看看里面的内容:



这里面的每一个js文件都对应着我们鼠标右键时需要添加进去的表单对象。这里以input.js为例看看里面的代码内容:

define(
function(require) {
function Input() {
this.type = 'input';
}
Input.prototype =  {
create : function(params, _form){
var dom = this.createDom();
this._form = _form;
if(undefined != params.callBack) params.callBack.call(this);
return dom;
},
createDom : function() {
var wrapDiv = document.createElement('div'),
input = document.createElement('input');

input.type = "text";
this.formTag = input;
wrapDiv.appendChild(input);
return wrapDiv;
},
getConfig : function() {
return '<li><span>id</span><strong>:</strong><input type="text" name="id" /></li><li><span>name</span><strong>:</strong><input type="text"  name="name"  /></li><li><span>中文名</span><strong>:</strong><input type="text"  name="cname"  /></li><li><span>验证</span><strong>:</strong><input type="text" class="validate-input" readonly="readonly" /><a class="config-panel-btn validate-add"></a></li><li><span>默认值</span><strong>:</strong><input type="text" name="defaultVal"></input></li><li><span>只读</span><strong>:</strong><input type="checkbox" name="readonly"></input></li><li><span>描述ID</span><strong>:</strong><input type="text" name="relevanceId"></input></li><li class="btn-wrap-li"><a class="remove-btn panel-btn"><i></i>删除该控件</a></li>';
}
};
var shape = require('../shape');
shape.define('input', new Input());
return Input;
}
);

基本上,每个表单对象所对应的Js文件里面都会有 类型,配置,创建dom的方法或者属性。



细心的朋友可能已经发行了在代码的最后 引入了一个shape,那么这个shape是拿来干什么的呢,直接看代码:

define(
function(/*require*/) {
var _ = require('designer/tool/undersore');		//提供一些独立的方法
var self = {};
var _shapeLibrary = {};     //shape库
self.define = function(name, clazz) {
_shapeLibrary[name] = clazz;
//对每个图形实现进行扩展
_.extend(clazz.__proto__, {
attrInject : function(obj) {	//attr注入
for(var i in obj) {
this._form.setAttr(i, obj[i]);
}
},
setDefaultVal : function(val){
if(this.formTag) this.formTag.value = val;
},
setReadyOnly : function(flag){
if(flag){
this.formTag.setAttribute('readonly', 'readonly');
}
else {
this.formTag.removeAttribute('readonly');
}
},
dictionaryInit : function(dicId){
this._form.getDictionary(this.dom, dicId);
}
})
return self;
};
self.get = function(name) {
return _shapeLibrary[name];
};

return self;
}
);

通过define这个方法里面的这句:

_shapeLibrary[name] = clazz;

我们可以得知,其实shape就是用 _shapeLibrary 这个对象建立了一个 表单对象名称和 实例对象的一个映射关系。并在其 get方法中返回这个实例。同时,如果我们对每个表单对象做的扩展也是在shape里面实现的(当然你也可以建立一个表单对象基类,让每个表单对象去继承,看个人喜好)。

完整的添加过程如下:

1,调用 storage里面的一个add方法,将将要添加的表单对象放到数组中

2,执行painter的refresh方法,在该方法会先去 storage里面取要添加的(当然实际过程中还包括要删除的)集合,根据该集合进行遍历,最终生成的方法还是会回到 表单对象里面的 create 方法,并执行需要的初始化方法(取决于callBack)。需要注意的是,添加也可能会隐性的产生删除操作,比如一个td里面已经有表单对象了就需要删除操作。

3,根据 添加/删除操作,调整表格的宽度和高度。

PS:为什么先说这个 表单元素 的添加过程,是因为我觉得这块其实是大家应该比较感兴趣的部分。如果你有额外的需求,可照葫芦画瓢添加其他的表单对象(当然更多的 可能是你封装的组件)。

接着 我们回到 componet里面



就个人而言,我不太喜欢用jq,所以你在这里看到 ajax.js。抛开这个js,每个js和其对应的作用关系如下:

addFormBox.js 当我们鼠标右击点击表格的时候,弹出的那个添加界面,包括里面的点击事件,已经添加的调用都在该js代码里面

configPanel.js 每个表单对象的配置面板,包括配置内容的更改 初始化,以及和表单对象dom之间的联动都在其中实现,这个地方感觉用mvvm更合适

lineRowManger.js 我们对表格进行的右键 添加/删除 行的操作处理代码

modelPanel.js 模板行配置处理js代码

shadowDIv.js 右键 或者左键 选择表单块时 生成的那个半透明遮罩 层的相关代码

按照我对 tom大叔博客里面对 SPA的解释,上面列出来的 5个功能 应该按照这样的方法来组织,包括tools里面提供的dialog,undersore,以及div移动的monving 都是一个道理,只是按照功能稍微做了一个区分。

最后, 我们来看看 nav这个文件夹,内容 如下:



这里面每个JS文件都对应着 导航工具栏的每个功能,还是那句话,我们可以根据不同的需求去自由的组织导航栏的功能。还是举个例子: 看看前面所说的入口formMain.js文件

define(function (require) {
var self = {};
/**
* 入口方法
*/
self.init = function (config) {
var self = this,
tab = document.getElementById('tab-wrap');
var _form = require('form').init(tab, config, function(){
this.navInit(['name', 'code', 'relevance', 'view', 'save']);
});
}
return self;
})

有一句 navInit方法,传入的正式 导航工具栏 对象,一看是数组大家就都懂了。

文章对 Storage, Painter, Handler并没有做过多的说明,其实现的细节大家可以参阅 zrender.js

总体来说,整个开发过程中用到的内容都没有太大的难度,我们需要做的就是把每个细节做好, 谢谢大家的阅读。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息