1.1.1 摘要
现在,我们经常都可以看到复杂的JavaScript应用程序,由于这些应用程序变得越来越复杂,一长串的jQuery回调语句或者通过应用程序在各个状态执行不同的函数调用,这些做法都会变得无法再让人接受,这导致了JavaScript开发人员开始寻找一种组织和效率更优秀的开发方式。
实现组织和效率的其中一个最常用的架构模式,就是我们熟知的Model View Controller (MVC)模式,这种模式鼓励开发人员将其应用程序的不同部分分割为更易于管理的模块,我们不必使用一个函数直接调用数据库,通过创建了一个Model(模型或实体)来管理数据库;通过模板(Template)或视图(View)来简化显示代码; 最后,通过使用控制器(Controller)来处理我们的应用程序的请求,MVC模式尽量降低每个模块之间的耦合度,提供程序的开发效率。
我们熟知的Javascript MVC框架有:Ember.js、Backbone.js、Knockout.js、Spine.js、Batman.js 和 Angular.js等。
图1 Javascript MVC framework
通过上图,我们我们可以清楚地了解Javascript MVC框架之间的特性,复杂度和学习曲线的区别,从左到右我们了解到各个Javascript MVC框架是否支持数据绑定(Data Binding)、模板(Templating)和持久化等特性,从下到上MVC框架的复杂性递增,说实话我并没有去对比每个框架之间的优劣,如果大家有做过相关的对比或看过有关的文章也不吝赐教。
在接下来的博文中,我们将介绍Ember.js的使用。
目录
1.1.2 正文
Ember.js是一个JavaScript的MVC框架,它由Apple前雇员创建的SproutCore 2.0改名进化而来,Ember已经发布到1.0.0-RC.3。
MVC定义
在介绍Ember之前,首先让我们回顾一下MVC模式,下面我们讲通过一个例子介绍MVC模式在程序设计中的作用,例如:
1. 用户执行一个操作,比如敲击键盘或单击鼠标按钮。
2. 控制器(Controller)接收输入并触发一个消息给模型(Model)。
3. 模型根据消息修改其内容(CRUD操作)。
4. 视图(View)监视模型中的变更,并将相应地更新呈现到用户界面中。
通过上面,我们了解到MVC中各个部件之间的作用和联系,在了解 MVC 模式的工作方式后,我们可以更加明确是否需要在我们的项目中引入Javascript的MVC框架。
在构建Ember应用程序时,我们会使用到六个主要部件:应用程序(Application)、模型(Model)、视图(View)、模板(Template)、路由(Routing)和控制器(Controller)。
接下来,我们将通过实现一个具体的程序来介绍Ember的使用。
设置Ember
首先,我们需要引用一系列Javascript库,所以我们在程序中添加js文件,并且把以下js文件保存到该文件夹中:
ember-data.js: revision 12
handlebars.js: handlebars 1.2.rc.3
jquery.js: jQuery 1.9.1
app.js: 我们的应用程代码
上面,我们把一系列的js文件保存到了本地中,当然我们也可以通过使用CDN(内容分发网络)来获取相应Javascript库,接下来,让我们创建index.html页面。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="description" content="" /> <meta name="description" content="" /> <meta name="keywords" content="" /> <meta name="author" content="" /> <title></title> <link rel="stylesheet" href="" type="text/css" /> <link rel="stylesheet" href="" type="text/css" /> </head> <body> <!-- Add Javascript libs Reference --> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script src="js/libs/handlebars-1.0.0-rc.3.js"></script> <script src="js/libs/ember-1.0.0-rc.2.js"></script> <script src="js/libs/ember-data.js"></script> <script src="js/app.js"></script> </body> </html>
现在,我们已经实现了第一个Ember程序,但是它还没有具体功能,接下来,我们将给程序添加功能。
模板(Handlebars)
Ember.js使用的是Handlebars模板引擎,在我们开始使用之前,首先让我们先简单介绍一下handlebars.js;如果大家有使用过jQuery模板或其他脚本模板,那么对于掌握handlebars.js的使用就没有太大的困难了,如果确实没有使用过也不用担心,因为handlebars.js的使用也是挺简单的。
它让开发人员可以混合原始HTML和Handlebars表达式生成渲染相应的HTML;表达式以包括在{{}}中,我们可以通过两种方法把Handlebars模板加载到页面中,我们可以直接内嵌的html页面中,通过在页面中添加类型为text/x-handlebars的脚本标记内;或保存到以handlebars或hbs为后缀的文件中,然后通过Ember.js加载到页面中。
为了简单起见,我们把Handlebars脚本直接嵌入到index.html页面中。
<script type="text/x-handlebars" data-template-name="application"> <h1>Employee System</h1> {{outlet}} </script>
上面,我们定义了模板application,并且添加了Handlebars表达式{{outlet}},它的作用就类似一个占位符,告诉Ember这里的内容要动态地加载到页面当中,当我们在浏览器中打开index页面并没有显示模板中的信息。
应用程序(Application)
这是由于我们还没有定义Ember程序,每个Ember应用程序都需要一个Ember应用程序实例,接下来让我们在app.js中创建第一个Ember应用程序实例吧!
首先,我们创建一个Ember应用程序实例,具体实现如下:
// Creates an application instance.
App = Ember.Application.create();
上面,我们定义了一个名为 App 的Ember应用程序,当然我们可以把程序命名为任意的,但有一点我们要注意的是Ember要求变量的名称都以大写字母开头。
现在,我们在浏览器中打开页面,可以显示模板加载的信息了。
图2 Index页面
也许有人会问Ember怎么知道哪些模板需要加载呢?更重要的一点是,我们并没有告诉Ember要加载的模板名称,我们只是直接把模板application嵌入到页面中。
其实,这里有个“潜规则”:如果我们没有定义ApplicationView(应用程序视图),那么Ember会自动生成一个ApplicationView并且默认加载名为application的模板,假设,我们把模板重命名为application1,那么默认的ApplicationView将找不到要加载的模板。
当然,我们也可以通过定义ApplicationView来指定需要加载的模板名称,具体实现如
// Defines an application view, then loading // relative templates. App.ApplicationView = Ember.View.extend({ templateName: 'application1' });
现在,我们还有一个疑问就是表达式{{outlet}}中的内容该如何加载显示呢?
路由(Routing)
由于{{outlet}}的内容是根据路由选择后动态获取的模板内容,所以我们先介绍Ember程序的路由,它可以帮助管理应该程序的状态和用户导航所需资源的资源;当我们的应用程序启动时,路由是负责显示模板,加载数据,以及管理应用程序的状态。
现在,我们通过指定URL方式义来定义应用程序的路由,具体定义如下:
// Defines a goal routing home and // the detail information of employee routing. App.Router.map(function() { this.route("home", {path: "/"}); this.route("employee", {path: "/employee/:employee_id"}); });
上面,我们定义了两个路由分别是:应用程序的全局路由home和employee,在index页面进行加载同时访问home路由的模板,数据和应用程序状态;而employee路由将根据employee_id访问每个一个员工的基本信息。
接下来,我们定义home模板,具体实现如下:
<script type="text/x-handlebars" data-template-name="home"> <h3>Employee Information</h3> <ul> {{#each item in employeeInfo}} <li>item</li> {{each}} </ul> </script>
上面,我们定义了home模板,并且使用了each表达来迭代访问employeeInfo对象中的元素,这时我们又有一个疑问了,那就是employeeInfo对象从哪里获取呢?
前面,我们提到Controller负责从Model中获取数据,然后通过模板加载显示,那么我们可以通过显市定义Controller来获取数据,如果我们不定义的话,Ember会自动生成一个HomeController。
// Defines a custom controll. App.HomeController = Ember.Controller.extend({ employeeInfo: ['Jackson Huang', 'Ada Li', 'JK Rush'] });
上面,我们自定义了HomeController并且初始化了employeeInfo数组,现在我们刷新一下index页面。
图3 Index页面
现在,我们又有一个疑问了,假如,我们程序有很多资源要访问,那么我们是否都显式地定义Controller呢?
其实,我们还可以通过定义路由控制器实现自动选择控制器,而且Ember会自动生成相应的Controller无需我们编写任何代码,具体实现如下:
// Defines a routing handler. App.HomeRoute = Ember.Route.extend({ model: function(){ return ['Jackson Huang', 'Ada Li', 'JK Rush']; }, setupController: function(controller, model){ controller.set('content', model) } });
现在,我们定义了路由控制器App.HomeRoute并且重写了方法setupController,它接收路由处理程序匹配的控制器作为第一个参数即HomeController,接着我们给HomeController传递model参数,那么HomeController就可以获取相应的数据并且加载到模板中显示了。
上面,我们成功把数据加载到页面中,但是数据都是直接hardcode在Controller中,我们并没有定义Model来获取数据。
接下来,我们将实现从Fixtures中获取数据,这时我们需要使用ember-data.js库,具体实现如下。
// Customs a store. App.Store = DS.Store.extend({ // Notify the version of ember data api used. revision: 12, // Used FixtureAdapter. adapter: 'DS.FixtureAdapter' });
上面,我们在app.js中定义DS.Store的子类App.Store,并且申明我们程序使用Ember data api的版本是12,当api版本更新或使用的版本太旧时,ember-data.js就会返回相应的错误信息。
例如:当前的ember-data.js版本是12,如果我们在app.js中定义使用的是版本1的api,在控制台中我们就会看到以下的错误信息。
图4 ember-data.js版本信息
模型(Model)
模型是一个用来表示应用程序数据的对象,它可能是一个简单的数组或通过RESTful API动态检索的数据;ember-data.js提供加载、映射和更新应用程序模型的API。
ember-data.js为每个应用程序都提供存储空间,存储空间负责保持已加载的Model和检索还未加载的Model。
前面,我们定义了应用程序App,现在,需要给程序提供数据也就是员工信息,所以我们要创建程序的模型(实体)Employee,接下来我们将实现模型的定义。
// Defines a employee model. App.Employee = DS.Model.extend({ name: DS.attr('string'), department: DS.attr('string'), title: DS.attr('string') })
上面,我们定义了Employee模型,它继承了DS.Model并且包含三个字段分别是name,department和title。
接下来,我们通过定义App.Employee.FIXTURES,模拟从服务器端获取数据。
// Defines a JSON array. App.Employee.FIXTURES = [ { id: 1, name: 'Jackson Huang', department: 'IT', title: 'programmer' }, { id: 2, name: 'Ada Chen', department: 'purchasing', title: 'buyer' }, { id: 3, name: 'JK Rush', department: 'IT', title: 'programmer' }, { id: 4, name: 'Lucy Liu', department: 'IT', title: 'tester' }, { id: 5, name: 'Julia Liu', department: 'HR', title: 'Manager' } ];
上面,我们定义了JSON数组App.Employee.FIXTURES,它包含了一系列员工的基本信息。
接下来,我们修改home和添加employee模板,具体实现如下:
<!-- Home temp START --> <script type="text/x-handlebars" data-template-name="home"> <h3> Employee Information</h3> <ul> {{#each item in content}} <li>{{item}}</li> {{/each}} </ul> <h3> Employee</h3> <ul> {{#each employee in employees}} {{#linkTo "employee" employee}} <p> {{employee.name}} </p> {{/linkTo}} {{/each}} </ul> </script> <!-- Home temp END --> <!-- Employee temp START --> <script type="text/x-handlebars" data-template-name="employee"> <div> <h3> Name: {{name}}</h3> <p> Department: {{department}} </p> <p> Title: {{title}} </p> {{#linkTo home class='btn btn-primary'}}Back{{/linkTo}} </div> </script> <!-- Employee temp END -->
在home模板中,我们添加each表达式迭代访问employee元素,然后通过linkTo选择employee路由;然后根据路由选择在employee模板显示相应的员工信息。
图5 程序页面
现在,我们完成了Employee程序的基本功能了,提供用户查下员工的信息了。
1.1.3 总结
本文通过Demo例子介绍了Ember的使用,主要介绍了Ember的模型,控制器、模板和路由,由于Ember是Javascript MVC框架,而且作为初学者很容易困惑于它的自动生成和默认规则,所以我极力推荐大家要仔细看一遍Routing和Controller的官方文档。
我们通过介绍Ember的Handlerbars模板引擎,定义了Demo程序的页面,然后通过路由控制器定义路由行为,根据路由行为选择控制器,控制器负责数据加载和显示。但我们的例子中还没有设计的Ember视图模块,如果想进一步学习请参考官方文档或书籍。
参考
- http://www.adobe.com/cn/devnet/html5/articles/flame-on-a-beginners-guide-to-emberjs.html
- http://net.tutsplus.com/tutorials/javascript-ajax/getting-into-ember-js/?search_index=3
- http://emberjs.com/guides/
- http://xbingoz.com/emberguides/0.php
- http://andymatthews.net/read/2012/03/07/Getting-Started-With-EmberJS