写点什么

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:3412776

评论

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

模块2作业G20210698020270

哆啦A萌

模块二作业 微信朋友圈高性能复杂度分析

君子意如何

「架构师训练营第 1 期」

领域驱动设计中的分层模型

escray

学习 极客时间 7月日更 如何落地业务建模

架构实战营 -- 模块二

小牧ah

架构实战营

架构实战营模块 2 作业

zlz

微信朋友圈复杂度分析

buoge

架构模块2 作业

柱林

Java单例模式一文通

喵叔

7月日更

模块二作业

秀聪

架构实战营

分析微信朋友圈的高性能复杂度

feitian

模块二作业

king

微信朋友圈的高性能复杂度分析

tjudream

架构 高性能 朋友圈

架构训练营模块二作业

河马先生

架构实战营

架构实战营 - 模块 2 - 作业

Vincent

架构实战营

架构实战营第一期--模块二作业

clay

架构实战营

架构训练营-模块二

小卷儿

推荐一个软件--IObit Uninstaller

IT蜗壳-Tango

7月日更

在线XML转CSV工具

入门小站

工具

【架构实战营】模块二作业

Abner S.

架构实战营 #架构实战营

趣说开源|为什么要参与到开源社区中?

SphereEx

架构实战营 模块二 作业

三叔叔_拖延症晚期

架构实战营

架构实战营模块二 作业

酷飞不会飞

设计消息队列存储消息数据的 MySQL 表格——架构实战营作业八

开拓纪

架构师实战营 作业八

作业2-微信朋友圈高性能分析

Nullrable

架构实战课

大数据训练营一期0711作业

朱磊

微信朋友圈的高性能复杂度

伏波

架构实战营

作业二朋友圈高性能架构设计

王小森

模块一作业-架构训练营

零度

「架构师训练营第 1 期」

Linux之df命令

入门小站

Linux

架构实战营 - 模块二

绝影

架构训练营

模块二作业

晨晨

Durandal快速入门_JavaScript_Rob Eisenberg_InfoQ精选文章