前端轻量级MVC框架CanJS详解


Posted in Javascript onSeptember 26, 2014

选择正确的库

创建一个JS APP没有好的工具是很有难度的,jQuery只是操作DOM的库,没有提供任何创建APP的基础,这就是为什么我们要一个类似CanJS的专门的库。

CanJS 是一个轻量级的MVC库,提供你创建一个JS APP所需的工具。

CanJS 是一个轻量级的MVC库,提供你创建一个JS APP所需的工具。 它提供有MVC (Model-View-Control) 模式的基本框架,模板动态绑定, route的支持且 内存安全。同时支持 jQuery, Zepto, Mootools, YUI, Dojo,有丰富的扩展和插件。

第一部分你将学到:
创建Control控制层 和 View 视图层(UI模板) 来显示联系人
用Model模型层来表示数据
使用 fixtures 插件模拟ajax返回数据
你肯定激动了!我们开始码代码吧。
建立好你的文件夹和HTML
你先给你的APP创建一个文件夹,目录下再建立4个子文件夹:css, js,views 和 img。如下:
contacts_manager
css
js
views
img

保存以下的代码为 index.html:

<!doctype html>

<html lang="en">

  <head>

    <meta charset="utf-8">

    <title>CanJS Contacts Manager</title>

    <link rel="stylesheet" href="css/bootstrap.min.css">

    <link rel="stylesheet" href="css/contacts.css">

  </head>

  <body>

    <div class="container">

      <div class="row">

        <div class="span12">

          <h1>Contacts Manager</h1>

        </div>

      </div>

      <div class="row">

        <div class="span3">

          <div class="well">

            <nav id="filter"></nav>

          </div>

        </div>

        <div class="span9">

          <div id="create"></div>

          <div id="contacts"></div>

        </div>

      </div>

    </div>

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js"></script>

    <script src="js/can.jquery.min.js"></script>

    <script src="js/can.fixture.js"></script>

    <script src="js/contacts.js"></script>

  </body>

</html>

在页面的底部你加载所需的JS(包括你的APP:contacts.js)。
教程中用到的CSS和图片文件可以下载。

用View来打造你的UI

View是用来渲染你APP的UI模板。CanJS 支持多种模板引擎,本文用EJS ,CanJS包含有而且支持动态绑定。
EJS 模板的标签与HTML很像,支持包含JS代码,三种常用标签如下:
<% CODE %> 执行JS
<%= CODE %> 执行JS,并将非转义的结果写入当前位置的HTML
<%== CODE %>  执行JS,并将转义的结果写入当前位置的HTML(用于子模板).
模板可以从文件或者script标签中加载得到,本教程从 EJS 文件加载。

显示联系人

要创建联系人,你得先建立一个EJS 模板,保存以下代码为contactsList.ejs 进你的views 文件夹:

<ul class="clearfix">

  <% list(contacts, function(contact){ %>

    <li class="contact span8" <%= (el)-> el.data('contact', contact) %>>

      <%== can.view.render('views/contactView.ejs', {

        contact: contact, categories: categories

      }) %>

    </li>

  <% }) %>

</ul>

contactLists.ejs 会渲染一个联系人列表,我们分析一下此模板:

<% list(contacts, function(contact){ %>

list()方法里的回调方法如果配合配置有观察者的list使用时,一旦list的数据发生改变就运用动态绑定重复调用。

<li class="contact span8" <%= (el)-> el.data('contact', contact) %>>

以上代码通过元素的回调方法生成 一个有联系人数据的<li>。 箭头后的方法执行后将el对象的数据设置给对应的元素。

<%== can.view.render('views/contactView.ejs', {

  contact: contact, categories: categories

}) %>

以上代码将子模板contactView.ejs 渲染成一个联系人。 can.view.render() 以模板和数据为参数返回HTML。

渲染单个联系人

子模板是一个将view组织成可管理块的好办法。 同时也使你的模板简单和易于重用。教程后面将会用到此模板来创建联系人,将下面的代码保存为contactView.ejs 进 views 文件夹:

<a href="javascript://" class="remove"><i class="icon-remove"></i></a>

<form>

<div class="row">

  <div class="span2">

    <img src="img/contact.png" width="100" height="100">

  </div>

  <div class="span3">

    <input type="text" name="name" placeholder="Add Name"

      <%= contact.attr('name') ? "value='" + contact.name + "'" : "class='empty'" %>>

    <select name="category">

      <% $.each(categories, function(i, category){ %>

        <option value="<%= category.data %>" <%= contact.category === category.data ? "selected" : "" %>>

          <%= category.name %>

        </option>

      <% }) %>

    </select>

  </div>

  <div class="span3">

    <label>Address</label>

    <input type="text" name="address"

      <%= contact.attr('address') ? "value='" + contact.address + "'" : "class='empty'" %>>

    <label>Phone</label>

    <input type="text" name="phone"

      <%= contact.attr('phone') ? "value='" + contact.phone + "'" : "class='empty'" %>>

    <label>Email</label>

    <input type="text" name="email"

      <%= contact.attr('email') ? "value='" + contact.email + "'" : "class='empty'" %>>

  </div>

</div>

</form>

联系人的属性都放入了 <input> 标签里,这就可以编辑更新用户的资料。

活化你的View(好文艺。。)

EJS 处理模板过程中如果有用到attr() ,它周围的代码将会交由事件处理器管理,监听对应属性的变化,当属性发生变化,APP中关联的UI将会被更新。这功能利益于模板动态绑定机制,EJS的动态绑定是有选择性的,只有使用了attr()时才会为对应的属性开启。
我们通过 contactView.ejs 中一个<input>标签来了解它的用法:

<input type="text" name="name" placeholder="Add Name"

  <%= contact.attr('name') ? "value='" + contact.name + "'" : "class='empty'" %>>

特殊标记里的代码将转变成事件绑定到此联系人的name属性上。当name属性发生变化,事件将被触发同时HTML结构会被更新。

使用can.Control来处理业务逻辑

can.Control 创建了一个可组织,内在无泄漏,全权控制器,能用来创建widget或者处理业务逻辑。你通过所需要数据为一个DOM元素创建一个Control实例,可以在你的Control中定义方法绑定事件。
当 Control 所关联的元素从DOM被删除时,Contol会自去销毁自己,同时清除所绑定的方法。
要创建一个 Control,通过传入你定义的包含有函数的对象给 can.Control() 来实现继承。接下来事件也给传进去了。
每个Contol实例都有几个重要的值和方法规范:
this ?  Control 实例的引用
this.element ? 实例中你所创建的DOM 元素
this.options ? 创建实例所需要的参数对象
init() ? 当实例创建成功时被调用

管理联系人

将以下代码片段添加到contacts.js 文件来创建管理联系人的Control:

Contacts = can.Control({

  init: function(){

    this.element.html(can.view('views/contactsList.ejs', {

      contacts: this.options.contacts,

      categories: this.options.categories

    }));

  }

})

当 Contacts 的实例被创建时, init() 会做两件事:
使用can.view() 来渲染联系人。 can.view() 接收两个参数:包含有模板和数据的文件或者stript标签;将返回一个documentFragment (一个管理DOM元素的轻量容器)。
使用jQuery.html()将can.view() 的documentFragment 插入Control的元素

使用Model来表现数据

Model 是APP数据的抽象层。本APP用到两个Model:一个对应联系人,一个对应类别。将以下代码添加到contacts.js:

Contact = can.Model({

  findAll: 'GET /contacts',

  create  : "POST /contacts",

  update  : "PUT /contacts/{id}",

  destroy : "DELETE /contacts/{id}"

},{});

 

Category = can.Model({

  findAll: 'GET /categories'

},{});

一个model 有5个方法可能定义来CRUD数据, 分别是findAll, findOne, create, update 和 destroy。你可重写这几个方法,不过最好的办法是使用 REST 服务(Representational State Transfer表述性状态转移)。正如上面的代码,你放心的忽略APP中不会用到的静态方法了。

这里要重点指出的是,model实例其实是源自 CanJS 的‘observables'。can.Observe 提供对象的观察者模式can.Observe.List 提供数组的观察模式。这意味着你可以通过attr()来get和set数据,同时监听数据的变动。
findAll() 方法返回一个 Model.list,就是当元素被添加或者移除时 can.Observe.List 所触发的事件。

使用Fixture来模仿Rest

Fixture拦截 AJAX 请求并通过文件或者方法来模拟应答。这对测试,或者后端还没有就绪时是非常有用的。Fixture就是APP的model模拟REST所需要的。
首先,你要准备一些数据给fixture,添加以下代码到:

var CONTACTS = [

  {

    id: 1,

    name: 'William',

    address: '1 CanJS Way',

    email: 'william@husker.com',

    phone: '0123456789',

    category: 'co-workers'

  },

  {

    id: 2,

    name: 'Laura',

    address: '1 CanJS Way',

    email: 'laura@starbuck.com',

    phone: '0123456789',

    category: 'friends'

  },

  {

    id: 3,

    name: 'Lee',

    address: '1 CanJS Way',

    email: 'lee@apollo.com',

    phone: '0123456789',

    category: 'family'

  }

];

 

var CATEGORIES = [

  {

    id: 1,

    name: 'Family',

    data: 'family'

  },

  {

    id: 2,

    name: 'Friends',

    data: 'friends'

  },

  {

    id: 3,

    name: 'Co-workers',

    data: 'co-workers'

  }

];

有了数据,要将其连接到fixture来模拟REST 。can.fixture()接收两个参数。 我们要拦截的URL和我们应答用的文件和方法。通常你要拦截的URL都是动态且遵循一个模式的。在需要在URL里添加以{}括起的通配符即可。

添加以下代码到contacts.js:

can.fixture('GET /contacts', function(){

return [CONTACTS];

});

 

var id= 4;

can.fixture("POST /contacts", function(){

return {id: (id++)}

});

 

can.fixture("PUT /contacts/{id}", function(){

return {};

});

 

can.fixture("DELETE /contacts/{id}", function(){

return {};

});

 

can.fixture('GET /categories', function(){

return [CATEGORIES];

});

前4个 fixture模拟 Contact model的GET, POST, PUT 和 DELETE 应答,第5个模拟 Category model的GET应答。

启动APP

你的APP有管理数据的Model,渲染联系人的 View,将这一切组织起来的的Control。现在要做的就是启动APP了。 Now you need to kickstart the application!
将以下代码添加到contacts.js :

$(document).ready(function(){

  $.when(Category.findAll(), Contact.findAll()).then(

    function(categoryResponse, contactResponse){

      var categories = categoryResponse[0],

        contacts = contactResponse[0];

 

      new Contacts('#contacts', {

        contacts: contacts,

        categories: categories

      });

  });

});

我们来分析一下这段代码:

$(document).ready(function(){

使用 jQuery.ready 方法监听DOM的ready。
$.when(Category.findAll(), Contact.findAll()).then(

  function(categoryResponse, contactResponse){

调用两个Model的 findAll() 方法来获取全部联系人的类型,由于findAll() 有延时, $.when()则确保两个请求同时完成后才执行回调方法。
var categories = categoryResponse[0],

  contacts = contactResponse[0];

从两个 findAll() 方法中获取对应Model实例的数据集。 是应答所返回的数组的第一个元素。

new Contacts('#contacts', {

  contacts: contacts,

  categories: categories

});

为 #contacts 元素创建Contact 的Control 。联系人和类型数据集传进Control。
用浏览器打开你的APP,你将看到如下的联系人列表:

前端轻量级MVC框架CanJS详解

总结

这是第教程系列的第一篇,你已经了解了CanJS的核心:
Models 你的APP数据的抽象层
Views 将数据转换成HTML的模板
Controls 组织关联一切

Javascript 相关文章推荐
jquery触发a标签跳转事件示例代码
Jul 21 Javascript
使用GruntJS链接与压缩多个JavaScript文件过程详解
Aug 02 Javascript
node.js中的http.response.setHeader方法使用说明
Dec 14 Javascript
jquery中map函数遍历数组用法实例
May 18 Javascript
原生JS和jQuery版实现文件上传功能
Apr 18 Javascript
socket.io学习教程之基本应用(二)
Apr 29 Javascript
原生js jquery ajax请求以及jsonp的调用方法
Aug 04 jQuery
spirngmvc js传递复杂json参数到controller的实例
Mar 29 Javascript
JS事件流与事件处理程序实例分析
Aug 16 Javascript
JS实现打砖块游戏
Feb 14 Javascript
openlayers实现图标拖动获取坐标
Sep 25 Javascript
jquery实现异步文件上传ajaxfileupload.js
Oct 23 jQuery
alert出数组中的随即值代码
Sep 25 #Javascript
jquery得到iframe src属性值的方法
Sep 25 #Javascript
jquery获得同源iframe内body下标签的值的方法
Sep 25 #Javascript
jquery 实现两Select 标签项互调示例代码
Sep 25 #Javascript
$(&quot;&quot;).click与onclick的区别示例介绍
Sep 25 #Javascript
Jquery设置attr的disabled属性控制某行显示或者隐藏
Sep 25 #Javascript
javascritp添加url参数将参数加入到url中
Sep 25 #Javascript
You might like
PHP下利用header()函数设置浏览器缓存的代码
2010/09/01 PHP
php判断变量类型常用方法
2012/04/24 PHP
解密ThinkPHP3.1.2版本之独立分组功能应用
2014/06/19 PHP
Symfony2在Nginx下的配置方法图文教程
2016/02/04 PHP
jQuery Ajax文件上传(php)
2009/06/16 Javascript
改变javascript函数内部this指针指向的三种方法
2010/04/23 Javascript
JavaScript执行顺序详细介绍
2013/12/04 Javascript
Javascript数组操作函数总结
2015/02/05 Javascript
JavaScript实现的简单拖拽效果
2015/06/01 Javascript
详解Javacript和AngularJS中的Promises
2016/02/09 Javascript
IScroll5 中文API参数说明和调用方法
2016/05/21 Javascript
JS中如何实现Laravel的route函数详解
2017/02/12 Javascript
Node.js与Sails redis组件的使用教程
2017/02/14 Javascript
详解使用vuex进行菜单管理
2017/12/21 Javascript
Vuex的基本概念、项目搭建以及入坑点
2018/11/04 Javascript
解决Layui数据表格的宽高问题
2019/09/28 Javascript
Vue父组件向子组件传值以及data和props的区别详解
2020/03/02 Javascript
基于ajax及jQuery实现局部刷新过程解析
2020/09/12 jQuery
[46:20]DOTA2-DPC中国联赛 正赛 PSG.LGD vs LBZS BO3 第二场 1月22日
2021/03/11 DOTA
python 简单备份文件脚本v1.0的实例
2017/11/06 Python
linecache模块加载和缓存文件内容详解
2018/01/11 Python
python 字符串和整数的转换方法
2018/06/25 Python
Python应用领域和就业形势分析总结
2019/05/14 Python
Django model 中设置联合约束和联合索引的方法
2019/08/06 Python
经验丰富程序员才知道的8种高级Python技巧
2020/07/27 Python
python tkinter实现连连看游戏
2020/11/16 Python
matplotlib之属性组合包(cycler)的使用
2021/02/24 Python
韩国著名的在线综合购物网站:Akmall
2016/08/07 全球购物
可以在一个PHP文件里面include另外一个PHP文件两次吗
2015/05/22 面试题
DBA数据库管理员JAVA程序员架构师必看
2016/02/07 面试题
大学新生军训自我鉴定
2014/03/18 职场文书
电子商务助理求职自荐信
2014/04/10 职场文书
企业员工爱岗敬业演讲稿
2014/08/26 职场文书
大学生简历自我评价2015
2015/03/03 职场文书
Python基础之教你怎么在M1系统上使用pandas
2021/05/08 Python
SqlServer数据库远程连接案例教程
2021/07/15 SQL Server