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

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

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

时间: 2024-11-06 13:01:22

表单设计器系列之代码的组织的相关文章

表单设计器系列之初窥

总体来说,市面上的表单设计器还是有很多,先进点的比如 ace那套,陈旧点的如封装一个编辑的.就在我做表单设计器的时候公司又新来一个娃儿,弄了套bootstarp做的模板,也有表单设计.而且一天不停的把  托拉拽放在嘴边.那么托拉拽真的好么? 断断续续,差不多写了一个半月应该完成了80%+的工作,代码量在1W行左右. firstBlood,让我们先来看看我做的表单设计器的样子吧: 是的,我是往excel那个方向做的,正如我之前做的流程设计器一样模仿viso做的,这样的好处有一下几点: 用户比较习惯

表单设计器系列之模板行的设置

嗯,下午我正在做一个BT版本,准备放出来让大家玩玩.做的时候 还是发现有些地方不太严谨,需要做一下设置. firstBlood,添加顺序 一般来说,我们添加一个表单是先添加中文描述,再添加输入.在我的表单设计器里面,每当添加一个 输入类型对象后 程序会自动去匹配其中文描述,匹配优先级如下: 优先匹配 当前输入对象所在td 紧挨着的左侧td,如果该左侧td是一个纯文本对象,那么就匹配它. 如果左侧td是一个输入类型对象,那么就会匹配 当前输入对象所在td垂直方向  上方的td 如果 1 和  2都

表单设计器系列之一些细节操作

下午好像么有什么事情,接着写,这篇文章跟大家讲讲表单设计器里面一些细节的操作和值得注意的地方, firstBlood,表格的宽.高调整. 默认情况下,我们会给表格的每个td设置一个默认的宽度和高度,如图所示: 我们可以看到 A B C D每列的宽度和高度都是一样的,那么同城情况下 我们期望 输入的地方变宽点.同样,操作很简单,比如我们想要B和D列变宽点,只需拖动B和D右边的调整按钮 向右边拖动即可,操作细节如下: 鼠标hover按钮 ---> 按下鼠标左键 ---> 向右平移 --->

表单设计器系列之消失的javascript

在做表单设计器之初,我也考虑过表单会涉及到的和JS交互,我们留了一个textarea,准备让开发人员把 js代码写在那里.然后人算不如天算,当那天需求说 他们不写代码的时候,一切都变了. firstBlood,radio 不同值 切换 不同的显示 我相信,这个是表单中最常见的一种动态效果,不多说直接上图,不然你们又要来 n p s j b了. 选中我们将要操作的 raido,鼠标点击后 配置面板自动出来: 点击 红色方框 标示的那个 链条按钮,会弹出下面这样一个界面: 在这个配置面板中, rai

基于Extjs的web表单设计器 第五节——数据库设计

这里列出表单设计器系列的内容,6.7.8节的内容应该在春节后才有时间出了.因为这周末就请假回老家了,准备我的结婚大事.在此提前祝大家春节快乐! 基于Extjs的web表单设计器 基于Extjs的web表单设计器 第一节 基于Extjs的web表单设计器 第二节——表单控件设计 基于Extjs的web表单设计器 第三节——控件拖放 基于Extjs的web表单设计器 第四节——控件拖放 基于Extjs的web表单设计器 第五节——数据库设计 基于Extjs的web表单设计器 第六节——界面框架设计

基于Extjs的web表单设计器 第六节——界面框架设计

基于Extjs的web表单设计器 基于Extjs的web表单设计器 第一节 基于Extjs的web表单设计器 第二节——表单控件设计 基于Extjs的web表单设计器 第三节——控件拖放 基于Extjs的web表单设计器 第四节——控件拖放 基于Extjs的web表单设计器 第五节——数据库设计 基于Extjs的web表单设计器 第六节——界面框架设计 基于Extjs的web表单设计器 第七节——取数公式设计 基于Extjs的web表单设计器 第八节——表单引擎设计 这一节我给大家介绍一下表单设

基于Extjs的web表单设计器 第二节——表单控件设计

这一节介绍表单设计器的常用控件的设计. 在前面两章节的附图中我已经给出了表单控件的两大分类:区域控件.常用控件.这里对每个分类以及分类所包含的控件的作用进行一一的介绍,因为它们很重要,是表单设计器的基本元素,更是核心组成部门. 一.区域控件,它主要包含三个类型的控件:卡片区域.表格区域.混合区域.这三个控件是我们的其他控件的容器或者叫包装器,相当于VS里面的各种Panel.它们很重要,每种区域控件的作用都不一样,能够包含的控件类型也不大一样,它们三个区域相互配合使用,可以为我们的表单提供强大的支

.net web 开发平台- 表单设计器 一(web版)

如今为了适应需求的不断变化,动态表单设计器应运而生.它主要是为了满足界面的不断变化和提高开发速度.比如:一些页面客户可能也无法确定页面的终于布局,控件的位置,在哪种情况下显示或不显示等可能须要随时改动.为了应对这些需求而不去多次改动源码进行公布,就能够在项目中使用动态表单设计器.如今分享一下我做的动态表单设计器的设计思路,共同学习. 想做一个表单设计器,首先要确定是做c/s的还是b/s.我考虑到以后的发展方向是c/s向b/s转化,所以就选择了b/s的方向,并且做b/s比做c/s要简单非常多.在做

基于Extjs的web表单设计器 第三节——控件拖放

看过之前设计器截图的朋友应该有印象,可能会发觉我们的设计器UI设计布局其实类似Visual studio 的设计界面,采用的是左.中.右三个区域布局.左侧为控件区域.中间为表单的画布设区域.右侧为属性区域.这样的UI设计肯定就得支持控件的拖拽设计,用户只要拖放一个控件到我们的画布上,那么画布就应该立即能够看到我们拖放的控件在画布中的位置.大小.以及一些控件自带的默认信息.不用说这样的设计对于用户来说不论在操作体验上还是设计的感官上都更加直接和方便,因为我在设计阶段就可以知道我设计后的表单在系统运