JavaScriptMVC之实战
下面我们通过一个真实的项目例子来实战JavaScriptMVC。
项目名称:人事系统
开发模块:部门管理
开发工具:Eclipse
技术支持:JavaScriptMVC3.2.2
首先,我们需要到www.javascriptmvc.com下载JavaScriptMVC包,然后解压到目录E:\jQuery\javascriptmvc-3.2.2
下,接着我们点击"开始->运行",输入cmd进入Doc窗口,然后在Doc窗口中cd E:\jQuery\javascriptmvc-3.2.2跳转
到该目录下。
下面我们通过JavaScriptMVC提供的命令生成项目工程:personnel
在Doc窗口输入:js jquery/generate/app personnel 命令生成工程。
整个Doc操作过程如下:
C:\Documents and Settings\lyndon.LINYAOLIANG>E:
E:\>cd E:\jQuery\javascriptmvc-3.2.2
E:\jQuery\javascriptmvc-3.2.2>js jquery/generate/app personnel
personnel/personnel.css
personnel/personnel.html
personnel/personnel.js
personnel/personnel.md
personnel/fixtures/fixtures.js
personnel/funcunit.html
personnel/models/models.js
personnel/qunit.html
personnel/scripts/build.html
personnel/scripts/build.js
personnel/scripts/clean.js
personnel/scripts/crawl.js
personnel/scripts/docs.js
personnel/test/funcunit/personnel_test.js
personnel/test/funcunit/funcunit.js
personnel/test/qunit/personnel_test.js
personnel/test/qunit/qunit.js
E:\jQuery\javascriptmvc-3.2.2>
然后在E:\jQuery\javascriptmvc-3.2.2目录下,我们看到一个文件夹personnel,这就是我们的工程。
接着我们需要创建我们需要开发的模块:office 部门管理
接着执行Doc命令:js jquery/generate/scaffold Personnel.Models.Office
生成Office模块的页面。
部门管理模块中部门模块只有2个字段:officeId(不能为空),officeName(可以为空)。
我们首先来完成页面的工作,这部分最提供结果,不提供操作过程,请直接查看工程。
接着我们需要来完成创建,修改部门时检验,在这个校验功能中,我们使用了不少的
插件:
1、jquery/model/validations 校验插件,直接引用这个插件即可。
2、jquery/tie 数据绑定插件
修改这个插件源代码,让这个插件在数据出错时,及时反映出错误。
修改点:
blur : function(el, ev, val){ ev.preventDefault(); if(!this.type && val === undefined){ val = this.element.val(); } this.inst.attr(this.attr, val, this.proxy('setPrev'), this.proxy('setBack')); }, // change : function(el, ev, val){ // console.info('change'); // ev.preventDefault(); // if(!this.type && val === undefined){ // val = this.element.val(); // } // // this.inst.attr(this.attr, val, this.proxy('setPrev'), this.proxy('setBack')); // }, setPrev : function(){ this.element.removeClass("error"); this.element.attr("title",""); }, setBack : function(errors){ this.element.addClass("error"); this.element.attr("title",errors[this.attr][0]); // this.setVal(this.lastValue); },
3、jquery/model 模型
修改这个源代码,让模型在绑定到Form元素上,模型的字段自动绑定到对应的Form下的
input,texterea,select元素上,以名称为准。
当然,我们也可以不修改这个模型,直接在Form下的子元素上加上:
<%= $(el) -> $(el).tie(model,'officeName')%>这句也是同样的效果。
修改源代码,只是为了如果有很多字段的时候,我们一句语句就可以搞定一个Form了。
这个效果是利用了EJS视图的hookup功能,hookup即在视图加载时,如果碰到有对象
存在hookup方法,则会执行这个脚本,这个为我们需要对元素做一些特殊处理很方便,
当然,我们也可以通过View中的helper也可以实现在视图加载时执行helper方法。
修改点:
hookup: function( el ) { var me = this; var shortName = this.constructor._shortName, models = $.data(el, "models") || $.data(el, "models", {}); if(el.nodeName == 'FORM'){ $(el).find("input[name], textarea[name], select[name]").each(function(){ $(this).tie(me,$(this).attr("name")); }); } $(el).addClass(shortName + " " + this.identity()); models[shortName] = this; }
完成校验的最终代码如下:
steal('jquery/model', function(){ /** * @class Personnel.Models.Office * @parent index * @inherits jQuery.Model * Wraps backend office services. */ $.Model('Personnel.Models.Office', /* @Static */ { init : function(){ //officeId不能为空,且它只允许输入[0-9a-zA-z]字符 this.validatePresenceOf('officeId',{message:'部门编号不能为空。'}); this.validateFormatOf('officeId',/[0-9a-zA-z]/, {message:'部门编号只能包含[0-9a-zA-z]字符。'}); }, attributes : { officeId : 'string' }, /** * * id:指定对象模型的Id,即在真实中我们使用对象的唯一Id, * 这个起什么作用呢? * * 在Model中,我们发现无论是修改或者是新建,都是使用save方法, * 但是Model是如何知道,我们执行create还是update呢?这个判断就是 * 靠这个Id了,如果Id存在则说明是修改,没有Id则是新建,Model默认的 * Id:'id',我们可以像下面一样指定Id * 由于这个示例是使用Fixture来执行Ajax,指定Id会影响一些操作, * 所以在这里我们没有修改Id,但是,在真实项目中,我们必须指定Id。 */ //id:'officeId', findAll: function(params,success,error){ return $.ajax({ url: "/offices.json", data:params, fixture:'-offices', success:success, error:error, dataType:'json' }); }, findOne : "/offices/{id}.json", create : "/offices.json", update : "/offices/{id}.json", destroy : "/offices/{id}.json" }, /* @Prototype */ {}); })
下面我们来实现分页的功能,在这里我们实现分页的效果是,本地分页与远程分页结合。
本地分页插件和分页组件都参考别的实现而完成的,分页是如何实现的,请查看源代码。
完成分页的控制器代码如下:
steal( 'jquery/controller', 'jquery/view/ejs', 'jquery/controller/view', 'personnel/models' ) .then( './views/init.ejs', './views/office.ejs', './views/detail.ejs', './views/editor.ejs', function($){ /** * @class Personnel.Office.List * @parent index * @inherits jQuery.Controller * Lists offices and lets you destroy them. */ $.Controller('Personnel.Office.List', /** @Static */ { defaults : {} }, /** @Prototype */ { init : function(){ this.element.html(this.view('init') ); var paging = new PagingMemory({controller:this,model:Personnel.Models.Office,tableId:'officelist'}); paging.list(10,1,5); }, '#officelist a.detail click': function( el ){ this.element.html(this.view('detail',el.closest('.office').model())); }, '#officelist a.destroy click': function( el ){ this.element.html(this.view('detail',el.closest('.office').model())); }, '#officelist a.update click': function( el ){ this.model = el.closest('.office').model(); this.model.backup(); this.element.html(this.view('editor',this.model)); }, 'p.deleteoffice button click':function(el,ev){ switch(el.attr("name")){ case 'delete': el.closest('div').model().destroy(); break; case 'cancel': this.init(); break; } return false; }, /** * 部门信息修改点击动作处理函数 */ 'p.updateoffice button click':function(el,ev){ ev.preventDefault(); switch(el.attr("name")){ case 'submit': if(this.model.isDirty()){ var errors = this.model.errors(); if(null == errors){ this.model.save(); }else{ for(var error in errors){ $("#"+error).addClass("error"); $("#"+error).attr("title",errors[error][0]); $("#"+error).focus(); return; } } }else{ this.init(); } break; case 'reset': this.model.restore(); this.model.backup(); break; } return false; }, "{Personnel.Models.Office} destroyed" : function(Office, ev, office) { office.elements(this.element).remove(); this.init(); }, "{Personnel.Models.Office} created" : function(Office, ev, office){ this.init(); }, "{Personnel.Models.Office} updated" : function(Office, ev, office){ this.model.backup(); this.init(); } }); });
最后,我们来实现一下history的功能,即我们在浏览上点击前进/后退的功能,
在这里我们需要使用到插件:jquery/dom/route
因此,我们使用命令创建了模块router,
在Doc窗口执行:js query/generate/scaffold Personnel.Models.Router。
在router模块中,我们添加了一个友好的提示,就是在当我们在修改部门信息时,
信息如果已经被修改,如果我们没有保存,则当我们跳转页面时,会弹出一个
友好提示:当前数据已经修改,是否需要保存?,点击确认则保存,并且跳转。
修改router模块的create.js文件,结果如下:
steal( 'jquery/controller', 'jquery/view/ejs', 'jquery/dom/form_params', 'jquery/controller/view') .then('./views/init.ejs', function($){ /** * @class Personnel.Router.Create * @parent index * @inherits jQuery.Controller * Creates routers */ $.Controller('Personnel.Router.Create', /** @Prototype */ { '{$.route} change' : function(el, ev, attr, how, newVal, oldVal) { switch (attr) { case 'type': this.nav_change(newVal,oldVal); break; } }, nav_change : function(val,oldVal) { var controller = $("#content").controller(); if(controller){ if(controller.model){ if(controller.model.isDirty()){ var btn = confirm("当前数据已经修改,是否需要保存?"); if(btn){ controller.model.save(); } } } /** * 因为我们都在content的Div上显示视图, * 所以在绑定一个控制器之前一定要把之前 * 的控制器销毁。 */ controller.destroy(); } switch(val){ case 'departmentlist': $('#content').personnel_office_list(); break; case 'adddepartment': $('#content').personnel_office_create(); break; } } }) });
至此我们这个实战完成,现在我们总结一下,我们给大家讲了些什么关键点:
1、如何创建工程,模块。
2、如果校验
3、如何分页
4、如果实现记录点击记录,实现前进和后退。
5、页面如何更友好。
在工程各个文件中,各个文件,我都添加了一些关键的说明,大家要阅读代码,可以看到
这些点都是很有用的。
下面附带上工程源代码。