50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

Durandal 快速入门

  • 2014-03-05
  • 本文字数:6201 字

    阅读完需:约 20 分钟

Durandal 是一个轻量级的 JavaScript 框架,其目标是单页面应用(SPAs)的开发变得简单而优雅。它支持 MVC、MVP 和 MVVM 等模式,因此不论你采用哪种类型的前端架构,Durandal 都能胜任。

Durandal 以 RequireJS 为基础,加上一个轻量级的惯例层,带来了令人惊叹的生产效率,并且帮助你维持稳健的编码实践。配上开箱即用的富界面组件、模态对话框、事件 / 消息、组件、过渡效果、导航等等,使你可以轻松开发出任何你能想象的应用。

尽管 Durandal 才发布大约一年时间,但其社区正以飞快的速度成长。

因此,我们推出了一个 Kickstarter 来帮助我们在 2014 年完成一些神奇的事情,希望你能关注。但现在,我将向你展示如何开发一个简单的 Durandal 程序。

要开始使用 Durandal,可以有多种方式,这取决于你的平台。因为 Durandal 是一个纯 JavaScript 库,独立于任何服务端平台,我们尝试用多种方式来打包,以满足各类 Web 开发人员。在本教程中,我们将直接使用 HTML Starter Kit。你可以在官方网站上直接下载

下载完HTML Starter Kit 后,解压缩,你就可以直接在Firefox 各版本中打开_index.html_ 页面,运行其示例程序了。或者你也可以将其部署到Web 服务器中,浏览其index 页面。

Starter Kit 演示了一个基本的导航架构,包括导航、页面历史、数据绑定、模态对话框等等。当然,我们不只是看看而已,我们要从头开始写一个小程序。首先打开 app 文件夹,删除里面的所有内容,然后删除 _index.html_。这样我们就有了一个空项目,并且预配置了所有必须的 scripts 和 css。

_*__ 注:_IE, Chrome__ 和 __Safari__ 可能无法从文件系统中直接打开这类文件。如果你仍希望使用这些浏览器,可以将其部署到你喜欢的 _Web_ 服务器中。

Index.html
我们开始编写 _index.html_ 文件,内容如下:

复制代码
<html>
<head>
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.css" />
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.css" />
<link rel="stylesheet" href="lib/durandal/css/durandal.css" />
<link rel="stylesheet" href="css/starterkit.css" />
</head>
<body>
<div id="applicationHost"></div>
<script src="lib/require/require.js" data-main="app/main"></script>
</body>
</html>

我们看到,文件中只有一些 css 样式文件,一个 id 为 applicationHost 的简单的 div, ,一个 script 标签。我们加上了 Bootstrap FontAwesome ,使界面看起来美观一些,但它们并不是 Durandal 所必须的。我们要关注的关键代码是 script 标签。

Durandal 采用 RequireJS 作为其核心构件之一,鼓励模块化的编程方式。在 Durandal 应用中,所有的 JS 代码都写在模块中。上文 _index.html_ 中的 script 标签就是用于加载 RequireJS 来完成框架的模块策略。当模块加载器完成初始化后,它通过 _data-main_ 属性的值来启动应用。就像 C 语言中的 _main_ 函数一样,_data-main_ 属性指向的是主模块,是整个应用的入口。让我们进行下一步,创建这个模块。首先创建一个名为 _main.js_ 的文件,把它放到 _app_ 文件夹下。其代码如下:

复制代码
main.js
requirejs.config({
paths: {
'text': '../lib/require/text',
'durandal':'../lib/durandal/js',
'plugins' : '../lib/durandal/js/plugins',
'transitions' : '../lib/durandal/js/transitions',
'knockout': '../lib/knockout/knockout-2.3.0',
'jquery': '../lib/jquery/jquery-1.9.1'
}
});
define(function (require) {
var system = require('durandal/system'),
app = require('durandal/app');
system.debug(true);
app.title = 'Durandal Starter Kit';
app.configurePlugins({
router:true,
dialog: true
});
app.start().then(function() {
app.setRoot('shell');
});
});

这是一个非常标准的 Durandal 应用的样板配置。我们详细地看一下代码。

文件的最上面是 _requirejs.config_ 代码。这部分代码用于配置模块系统,使其能够找到核心库。配置的都是一些相对路径,因此当我们引用“jQuery”时,模块系统就知道到哪里去加载它。我们可以看到,路径的配置包括 requirejs 的 text 插件(用于加载视图)、Durandal 的核心模块、Knockout 和 jQuery。 Durandal 采用 RequireJS 来构建模块, Knockout 用于数据绑定,而 jQuery 用于浏览器层的抽象。

完成 RequireJS 的配置后,定义应用的主模块。Durandal 应用的所有代码都写在模块中,并且按照以下格式:

复制代码
define(function (require) {
var someModule = require('some/module');
...other modules required here...
return someValue;
});

我们用 _define_ 函数来创建一个模块。它的参数是另一个函数,这个函数是模块的工厂。也就是说,这个函数的返回值将是模块的一个实例。当函数返回一个对象时,你可以创建单例对象模块。或者返回一个函数,你可以创建一个类构造器模块。另外,你也可以声明该模块依赖于其他模块,此时你需要使用 _require_ 函数来引入其他模块。

在上面的主模块代码中,你可以看到我们引入或者说 _require_ 了两个 Durandal 模块:system_ 和 _app。然后通过 _system_ 模块开启框架的调试功能,通过 _app_ 模块设置应用的标题,加载两个可选的插件。最后,我们调用 _start_ 方法启动应用。该方法是一个异步的动作,当它结束时,调用一个特殊的方法:setRoot

当 _setRoot_ 方法被调用时,才会真正去展示页面。你可以将 _root_ 视为应用的主窗口、主布局或者应用的 shell。该方法的参数指向一个包含 shell 的行为的模块。现在唯一的问题是,嗯,它还不存在。好吧,我们开始创建它。

Shell

Durandal 的大部组件都同时包含行为(behavior)和展现(presentation)。你的应用的 shell 毫无疑问也遵循这个规则。我们将通过两个文件来展示这两个不同的概念:一个是用于行为的 JavaScript 文件,另一个是用于展现的 HTML 文件。在 _app_ 文件夹下创建:shell.js_ 和 _shell.html_。_ 两个文件的代码如下所示,我们来看看它们是如何工作的。

复制代码
shell.js
define(function (require) {
var app = require('durandal/app'),
ko = require('knockout');
return {
name: ko.observable(),
sayHello: function() {
app.showMessage('Hello ' + this.name() + '! Nice to meet you.',
'Greetings');
}
};
});
shell.html
<section>
<h2>Hello! What is your name?</h2>
<form class="form-inline">
<fieldset>
<label>Name</label>
<input type="text" data-bind="value: name, valueUpdate:
'afterkeydown'"/>
<button type="submit" class="btn" data-bind="click: sayHello, enable:
name">Click Me</button>
</fieldset>
</form>
</section>

首先看看 _shell.js_,正如我们之前讲到的,它定义了一个模块。同时它还依赖两个别的模块:app_ 和 _knockout。从代码可以看出,模块返回了一个对象。这个对象有一个 _name_ 属性和一个 _sayHello_ 方法。属性 _name_ 看起来有点特别,它是我们使用 Knockout 创建的一个可被观察的 (observable) 属性。可被观察的(observable)属性支持数据到 html 的双向绑定,变更通知和一些其它特性。注意看 _sayHello_ 中调用了 _app_ 模块的方法来显示一个包含 _name_ 属性值的消息对话框。

要完全理解这些代码,你应该将它与 HTML 代码放到一起来看。注意观察 HTML 中的 _data-bind_ 属性。该属性将 HTML 与模块中的属性和方法联系起来。例如 input 标签通过 _data-bind_ 属性将它的值与 _name_ 属性连接起来的。它同时还指定,当 key down 事件发生时,属性 _name_ 的值将被更新(如果不指定事件,则默认光标离开时会进行更新)。同样地,按钮的 _click_ 事件绑定了 _sayHello_ 方法,而且仅当 _name_ 属性的值为真时,按钮才是有效状态。看起来是不是很酷?现在就把应用运行起来,你可以用 Firefox 打开 _index.html_,或者如果你使用其他浏览器的话,可以部署到 Web 服务器下然后浏览 _index.html_。当页面打开时,会经历以下这些步骤:

  1. 加载 RequireJS。
  2. RequireJS 将加载 _main.js_,然后配置框架。
  3. _main.js_ 调用 _setRoot_ 展示整个应用。
  4. 加载 _shell.js_ 和 _shell.html_,绑定数据,然后注入到页面的 _applicationHost_ div 中。

当页面加载完成后,在输入框中试着输入一些内容,观察按钮的有效性是如何变化的,然后点击按钮看看发生了什么。

导航

上面的例子看起来不错,但大多数应用都不只有一页。所以,我们开始下一步,将它扩展成具有页面导航的应用。首先,把 _shell.js_ 改名为 _home.js_,shell.html_ 改名为 _home.html。然后创建两个新文件 _shell.js_ 和 _home.js_ 用于导航。以下是两个新文件的代码:

复制代码
shell.js
define(function (require) {
var router = require('plugins/router');
return {
router: router,
activate: function () {
router.map([
{ route: '', title:'Home', moduleId: 'home', nav: true }
]).buildNavigationModel();
return router.activate();
}
};
});
shell.html
<div>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<ul class="nav" data-bind="foreach: router.navigationModel">
<li data-bind="css: { active: isActive }">
<a data-bind="attr: { href: hash }, html: title"></a>
</li>
</ul>
<div class="loader pull-right" data-bind="css: { active:
router.isNavigating }">
<i class="icon-spinner icon-2x icon-spin"></i>
</div>
</div>
</div>
<div class="container-fluid page-host" data-bind="router: {
transition:'entrance' }"></div>
</div>

可以看到,shell.js_ 仍然是模块的标准定义方式。首先 require 了一个 router 插件,利用它创建对象。我们配置了一个 route 指向 _home_ 模块。router 配置中指定了每个 route 匹配的模式、显示的标题、对应加载的 moduleId,以及 _route_ 匹配时,是否应该将此 route 显示到导航栏中(nav:true)。配置完成后,调用 _buildNavigationModel。该方法利用 route 信息,构建一个特殊的集合,这个集合通过数据绑定,可以用来显示页面顶部的导航栏。最后,我们激活这个 router。所有这些都发生在 _activate_ 方法中。每次 Durandal 向屏幕展现一个组件时,它都会查找可选的 _activate_ 回调方法。如果找到,就会在数据绑定前调用此方法。因此这种方式允许你执行自定义的激活代码,例如上面的例子在 _activate_ 方法中配置应用程序的 router。

如果把模块和视图的代码放到一起,这次你应该很容易理解它们是如何工作的了。首先绑定 _navigationModel_ 属性,生成一个导航栏 navbar。每一个可导航的 route 被转换成一个 li 标签,其中包含一个链接。链接的标题对应 _title_,地址对应 _hash_。同时,在 navbar 中还包含了一个 spinner 动画图标,并将其与 _router.isNavigating_ 属性进行绑定。当应用从一个页面导航到另一页面时,我们就能看到一个 spinner 动画图标。

在 html 文件的最下方有一个特殊的 _router_ 绑定,也就是连接模块中的 router。它扮演占位符的角色,用于显示当前页面。属性 _transition_ 表示当页面发生变化时,Durandal 要使用“entrance”过渡动画。

现在将它运行起来。它看起来跟之前差不多,除了在顶端多了个导航栏。因为现在仍然只有一个页面,因此没有什么令人印象深刻的东西。我们开始创建第二个页面,以便能够在页面间前进或后退。

第二页:雷尼尔峰

我们创建的第二个页面将调用 Flickr,获取一组雷尼尔峰的照片并进行展示。首先修改 shell 的 router 配置,增加另一个 route,这样它就有了两个 route:

复制代码
{ route:'', title:'Home', moduleId:'home', nav:true },
{ route:'rainier', title:'Mount Rainier', moduleId:'rainier', nav:true }

第一个是我们之前的 home route,第二个是新的 route 用于即将创建的新页面。这时候,希望你已经知道应该怎么做了。我们为新页面创建一个模块(module)和一个视图(view)。

复制代码
rainier.js
define(function (require) {
var http = require('plugins/http'),
ko = require('knockout');
var url = 'http://api.flickr.com/services/feeds/photos_public.gne';
var qs = {
tags: 'mount ranier',
tagmode: 'any',
format: 'json'
};
return {
images: ko.observableArray([]),
activate: function () {
var that = this;
if (this.images().length > 0) {
return;
}
return http.jsonp(url, qs, 'jsoncallback').then(function(response) {
that.images(response.items);
});
}
};
});
rainier.html
<section>
<h2>Mount Rainier</h2>
<div class="row-fluid">
<ul class="thumbnails" data-bind="foreach: images">
<li>
<img style="width: 260px; height: 180px;" data-bind="attr:
{ src: media.m }"/>
</li>
</ul>
</div>
</section>

我想你已经能够看出来它是怎么工作的了。新模块在 activate 回调方法中使用 http 插件调用 Flickr 的 api 去获取图片。然后将数据保存到 _images_ 属性中。_Images_ 是一个可被观察的(observable)数组,页面绑定这个数组并展示图片。

将应用运行起来,可以看到在导航栏中出现了两项,你能在两者间进行前进和后退操作。你也可以使用浏览器的返回按钮。Durandal 能做的事情远不止这些。也许你该下载 Starter Kit 并仔细研究,就会发现一些更有趣的东西。又或者你应该将官方的示例程序下载下来,看看各种功能是如何工作的,例如发布 / 订阅消息、自定义模态对话框、组件、高级的视图组合等等。如果你想看一些更酷的东西,可以看看 Kickstarter 资助的系列培训中我们将构建的应用。

Durandal 通过模块化的方法,使得构建富客户端,动态的 JavaScript 应用更加简单。因此理所当然地,我们的社区发展得非常快。我们的计划才刚刚开始,希望你也能加入我们的奇妙旅程。请浏览一下我们的 Kickstarter 。2014 年我们已经有了一些很酷的目标,同时,也有一些非常棒的奖励给你。Web 正在发生改变,光明的未来指日可待,希望我们在那里相逢。

关于作者

Rob Eisenberg是一个 JavaScript 专家和.NET 架构师,工作在佛罗里达州的塔拉哈西,同时他还是 Blue Spire 咨询公司的总裁。Rob 在 9 岁时开始计算机编程,那时他彻底地爱上了家里的新 Commodore 64。他痴迷于编程,从 Commodore Basic 语言到 Q Basic 和 QuickBasic 开始,很快转到 C, C++, C#和 JavaScript。Rob 定期在 devlicio.us 上发表技术文章,并经常在区域性的专业活动中和相关公司做演讲和报告,主要涉及 Web 和.NET 技术,敏捷软件开发实践和 UI 工程。他是 Sam 出版的《Teach Yourself WPF in 24 Hours》一书的联合作者,也是 Durandal 和 Caliburn.Micro 框架的架构师和首席开发人员。

原文英文链接: Durandal: Quick Start


感谢马国耀对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-03-05 10:3417845

评论

发布
暂无评论
发现更多内容

一文带你吃透Spring Cloud相关微服务组件及Spring Cloud Config框架

Java 程序员 后端

【源码分析设计模式 10】SpringMVC中的建造者模式,mybatis技术原理pdf

Java 程序员 后端

【计算机网络】局域网原理与技术,一次哔哩哔哩面试经历

Java 程序员 后端

一元稀疏多项式计算器 【 数据结构课设作业 】 带界面

Java 程序员 后端

一个非常强大和友好的nginx基于lua-nginx-module(openresty)

Java 程序员 后端

一篇神文让你“一夜封神“Mycat 中间件 (最详细讲解),linux操作系统实用教程文东戈课后答案

Java 程序员 后端

一文参透:缓存一致性策略以及雪崩、穿透等问题,java系统架构设计详解

Java 程序员 后端

一文彻底弄懂如何选择抽象类还是接口,java序列化和反序列化面试

Java 程序员 后端

一文看透Java高并发:Synchronized锁的性质、原理及其缺陷

Java 程序员 后端

【设计模式】原型模式,java基础入门第二版第四章课后答案

Java 程序员 后端

一个专科生和云计算的故事,java注解处理器工作原理及过程

Java 程序员 后端

一招教你搞定微信小程序-登录+支付(后台Java,windows内核编程全套视频教程

Java 程序员 后端

【程序猿历程】2020年总结,java高级课程视频

Java 程序员 后端

一篇文章带你深入了解MySQL 索引相关,linux视频教程下载

Java 程序员 后端

一篇文章带你快速理解JVM运行时数据区 、程序计数器详解 (手画详图

Java 程序员 后端

一篇文章!彻底弄透Java处理GMT-UTC日期时间,java百度天气接口api

Java 程序员 后端

【金九银十冲刺】Java岗面试题核心每日知识点,kafka原理图

Java 程序员 后端

【阿里Java岗的魔鬼三面】狠心刷完这6份pdf,Java开发经验谈

Java 程序员 后端

一文读懂 spring MVC 请求处理流程,java程序设计教程第三版

Java 程序员 后端

【设计模式】代理模式,java面试官常问的问题

Java 程序员 后端

【线程】,东软集团Java笔试题

Java 程序员 后端

一个项目了解 SpringBoot 集成 MyBatis(1),面试必备知识点

Java 程序员 后端

一夜之间火爆GitHub的好文!!阿里资深架构师整理分享,疯狂膜拜

Java 程序员 后端

一文带你理解Spring Cloud高并发微服务架构核心理念的五脏六腑

Java 程序员 后端

【新】虚拟机深层系列,java底层实现原理

Java 程序员 后端

【深度思考】JDK8中日期类型该如何使用,java面试题百度网盘

Java 程序员 后端

【牛客】从青铜到王者01,java基础入门第二版第二章答案

Java 程序员 后端

一口气说出 Redis 16 个常见使用场景,rxjava原理

Java 程序员 后端

【自我感悟&&致学弟学妹】大三上的感悟,linux学习教程

Java 程序员 后端

【设计模式】适配器模式,手动实现一个简单的AOP框架

Java 程序员 后端

一个即将从《蚂蚁金服》离职的Java工程师个人经历与总结

Java 程序员 后端

Durandal快速入门_JavaScript_Rob Eisenberg_InfoQ精选文章