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

当初之所以转前端,就是因为写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个路径: 

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

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

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

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

secondBlood,form.js

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

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

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


  3. function Storage(){
    	var self = this;
    	self.XXX = '';
    	self.xxxx = function(){}	
    }

    在Storage里面也提供了很多 set/get方法 用来设置或者返回一些数据

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

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

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

thirdBlood拨开迷雾见青天

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

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

4个文件夹:

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

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

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

  4. 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和其对应的作用关系如下:

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

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

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

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

  5. 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

总体来说,整个开发过程中用到的内容都没有太大的难度,我们需要做的就是把每个细节做好, 谢谢大家的阅读。

你可能感兴趣的:(js,表单设计器,代码组织)