3.MCRV设计模式
综上所述,MVC是解决具有复杂交互界面、代码规模大的应用程序面临代码组织、复用问题的有效设计模式。基于此,本文尝试基于MVC设计思想解决前端开发中的类似问题。然而,在前端页面开发中不能直接套用MVC设计模式。因为,前端开发中的Web页面包含了HTML、CSS、Javascript等多个种类的代码,相对Web后端来说,Web页面整体就是一个负责UI展现、用户本地交互、发送服务请求的大View,与经典MVC及Web后端开发MVC模式中的View皆有不同。因此,需要具体问题具体分析。首先对Web页面中的HTML、CSS、Javascript等代码进一步分析,明确它们的具体功能分类。根据业界提出的Web开发标准,Web页首先被分为了内容-结构-表现-行为几个部分。内容、结构、表现是页面的静态部分,主要负责UI的展现,用户操作指令(键盘、鼠标)的接受,因此,内容、结构、表现属于视图 (View)的范畴。行为主要就是javascript代码,负责对用户操作指令的响应。在复杂ajax应用中, javascript代码的功能一般包括:响应用户指令,执行数据验证/处理、执行客户端交互逻辑,向服务器发送ajax请求,接受并处理服务器返回的数据,根据数据改变UI(向页面结构填充内容数据、变换样式等)。基于模型-视图-控制器分离的思想,Javascript代码中的数据验证/处理、本地业务逻辑计算、向服务器发请求获取数据的功能,对应模型(Model)的功能;响应(翻译)用户操作指令,根据指令执行业务逻辑处理的功能,属于控制器(Controller)的功能;而接受处理过的数据,根据数据修改页面的内容/结构/样式的功能既不属于控制器的功范畴,也不属于模型的功能范畴,这部分代码因为根据数据对View进行呈现(render),可以命名为Renderer(渲染器或呈现器)。根据前述分析,本文提出了MCRV设计模式,如图3所示。阐述如下: M(Model):模型。完成数据验证、数据处理,执行客户端业务逻辑计算,或向服务器发起ajax请求调用服务端逻辑、接受返回的数据,将处理后的数据返回控制器。
C(Controller):控制器。控制器响应View上的事件,根据事件调度执行模型的业务逻辑,从业务逻辑获取返回数据,调度相应的渲染器(Render)来完成界面展现。在这个过程中控制器会有数据的传递:控制器调用模型中的逻辑时会传送Renderer搜集的数据(form表单各域的name/value、其他控制参数),模型执行逻辑后返回作为执行结果的数据给控制器,控制器根据数据来调用渲染器(renderer)来完成界面呈现(rendering),呈现(rendering)就是修改页面结构、内容和样式的过程。数据传递过程可以用图4表示。
R(Renderer):渲染器(呈现器)。渲染器被控制器调用,接受从控制器传递的数据,完成对界面的具体渲染。渲染器也负责控件(widget)的初始化,及建立Controller与具体事件的对应关系,事件发生时负责搜集View上的数据传送到Controller。
V(View):视图。视图是用户最终看到的整个Web界面,由结构、内容、样式(表现)等静态内容共同构成。View由Renderer进行初始化渲染和修改。
图3:MCRV开发模式
图4:数据传递过程可以看到在MCRV开发模式中,Controller处于控制中心的位置,Model完成具体的商业逻辑计算以及向后端发起ajax请求返回数据的功能。Controller与Model、Renderer之间的交互本质上数据交互的过程,它们之间存在着一个数据流,如图4所示。因此,制定Controller、Model、Renderer之间的交互接口时,数据格式定义很重要。4.基于MCRV设计模式的Demo
下面是一个用MCRV模式来开发的Demo页面,页面的功能是用表格展示和修改用户信息。javascript使用了jQuery库。页面界面如图5所示。页面代码在程序清单1中
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html>
- <head>
- <meta http-equiv="content-type" content="text/html;charset=utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=7" />
- <title>MCRV 设计模式 Demo</title>
- <style type="text/css">
- /********表现*********/
- table{width:100%;border-collapse: collapse;}
- td{border: 1px solid black;padding: 2px;}
- #container {width:800px;margin:0px auto;}
- #tbUsers{margin:20px auto;}
- #tbUsers th{background-color: navy;color:white;text-align: center;vertical-align: middle;border:1px solid navy}
- #tbUsers td{text-align: center;}
- .editCaption{width:100px;text-align: right;}
- .buttonMargin{margin:0px 20px;}
- .buttonContainer{text-align: center;vertical-align: middle;height:50px}
- </style>
- <script src="./jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
- </head>
- <body>
- <!-------结构--------->
- <div id="container">
- <table id="tbUsers">
- <thead><th>id</th><th>姓名</th><th>年龄</th><th>修改</th></thead>
- <tbody/>
- </table>
- <div style="display: none;" id="dvEditPanel">
- <form id="frmModify" name="frmModify">
- <table>
- <tr>
- <td class="editCaption"> id: </td>
- <td><span id="spID"></span></td>
- </tr>
- <tr>
- <td class="editCaption"> 姓名: </td>
- <td><input type="text" size="20" id="txtName"/></td>
- </tr>
- <tr>
- <td class="editCaption"> 年龄: </td>
- <td><input type="text" size="20" id="txtAge"/></td>
- </tr>
- <tr>
- <td colspan="2" class="buttonContainer">
- <button id="btnSubmitModify" class="buttonMargin" type="button">提交</button>
- <button id="btnCancelModify" class="buttonMargin" type="button">取消</button>
- </td>
- </tr>
- </table>
- </form>
- </div>
- </div>
- <script>
- /***************************行为********************************/
- var UserManagerMCR;
- $(function()
- {
- UserManagerMCR=new MCR(UserController,UserModel,UserRenderer);
- });
- /*
- * MCR 三元组
- */
- function MCR(Controller,Model,Renderer)
- {
- this.controller=new Controller();
- this.model=new Model();
- this.renderer=new Renderer();
- thisthis.controller.model=this.model;
- thisthis.controller.renderer=this.renderer;
- thisthis.model.controller=this.controller;
- thisthis.renderer.controller=this.controller;
- if(typeof this.model.init=="function")
- {
- this.model.init();
- }
- if(typeof this.renderer.init=="function")
- {
- this.renderer.init();
- }
- if(typeof this.controller.init=="function")
- {
- this.controller.init();
- }
- }
- /*
- * 控制器
- */
- function UserController()
- {
- this.init=function()
- {
- this.initUserList();
- }
- this.initUserList=function()
- {
- var list=this.model.getUserList();
- this.renderer.renderUserList(list);
- }
- this.beginModify=function(data)
- {
- var user=this.model.getUserByID(data.id);
- this.renderer.showModifyUI(user);
- }
- //提交修改
- this.submitModify=function(user)
- {
- var result=this.model.modifyUser(user);
- if(result.success)
- {
- var list=this.model.getUserList();
- this.renderer.renderUIWhenSubmitModifySuccess(list);
- }
- else
- {
- alert(result.msg);
- }
- }
- //取消修改
- this.cancelModify=function()
- {
- this.renderer.hideModifyUI();
- }
- }
- /*
- * 模型
- */
- function UserModel()
- {
- //模拟的数据,实际应用中经常从服务器获取
- this.init=function()
- {
- this.data = [
- {id:0,name:"John",age:22},
- {id:1,name:"Tom",age:30},
- {id:2,name:"Tony",age:25}
- ];
- }
- //获得用户数据列表
- this.getUserList=function()
- {
- //todo ,可能ajax从后端返回
- return this.data;
- }
- //获得用户数据
- this.getUserByID=function(id)
- {
- var ix;
- $.each(this.data,function(i,item){if(item["id"]==id ) { iix=i; return false;}});
- return this.data[ix];
- }
- //修改用户数据
- this.modifyUser=function(user)
- {
- var result={success:true,msg:"修改成功"};
- //todo,验证参数user
- //todo,修改用户数据
- $.each(this.data,function(i,item)
- {
- if(item["id"]==user["id"])
- {
- item["name"]=user["name"];
- item["age"]=user["age"]
- return false;
- }
- });
- return result;
- }
- }
- /*
- * 渲染器
- */
- function UserRenderer()
- {
- this.init=function()
- {
- var me=this;
- $("#btnSubmitModify").click(function()
- {
- var user={id:$("#spID").text(),name:$("#txtName").val(),age:$("#txtAge").val()};
- me.controller.submitModify(user);
- });
- $("#btnCancelModify").click(function()
- {
- me.controller.cancelModify();
- });
- $("#tbUsers .modify").live("click",function()
- {
- var id=$(this).attr("uid");
- me.controller.beginModify({"id":id});
- });
- }
- this.renderUserList=function(list)
- {
- var htm=[];
- for(var ix=0;ix<list.length;ix++)
- {
- htm.push("<tr><td>" +list[ix]["id"]+"</td>" +"<td>"+list[ix]["name"]+"<td>"+list[ix]["age"]+"</td>"
- +"<td>"+"<a class='modify' href='javascript:void(0)' uid='"+list[ix]["id"]+"'>修改</a></td>"+"</tr>");
- }
- $("#tbUsers").children("tbody").html(htm.join(""));
- }
- this.showModifyUI=function(user)
- {
- $("#dvEditPanel").show();
- $("#spID").text(user["id"]);
- $("#txtName").val(user["name"]);
- $("#txtAge").val(user["age"]);
- }
- this.hideModifyUI=function()
- {
- document.frmModify.reset();
- $("#dvEditPanel").hide();
- }
- this.renderUIWhenSubmitModifySuccess=function(list)
- {
- this.hideModifyUI();
- this.renderUserList(list);
- }
- }
- </script>
- </body>
- </html>