限时领|《AI 百问百答》专栏课+实体书(包邮)! 了解详情
写点什么

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

评论

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

鸿蒙 next 实现应用内的暗黑模式切换

flfljh

鸿蒙

Flutter与鸿蒙三方库ohos的适配

flfljh

鸿蒙

破解知识管理难题,天润融通大模型如何提高知识库管理效率?

天润融通

鸿蒙 next 实现隐藏顶部 StatusBar

flfljh

鸿蒙

Flutter和OpenHarmony通信pigeon库的具体接入和用法

flfljh

鸿蒙

鸿蒙Next简易版本通用头部导航栏开发

flfljh

鸿蒙

Flutter与鸿蒙原生交互

flfljh

鸿蒙

深度监听对象数组变化

flfljh

鸿蒙

鸿蒙 next 实现对象数组的界面更新

flfljh

鸿蒙

Hume.ai 升级:自研情感模型集成 Claude 和 Fal;数字嗅觉公司 Osmo 用 AI 实现气味「传送」

声网

“AI”就一起来!开放原子联合龙蜥推出 OS Copilot 学习赛,show 出你的创意

OpenAnolis小助手

Linux AI 操作系统 OS Copilot

Flutter与鸿蒙原生交互二

flfljh

鸿蒙

国内首位聋人 Android 软件工程师体验通义灵码,“这真是太棒了”

阿里巴巴云原生

阿里云 云原生 通义灵码

Flutter OS外接纹理适配简介

flfljh

鸿蒙

Flutter+鸿蒙NEXT开发获取系统文件路径

flfljh

鸿蒙

并发编程之同步/异步/回调/任务 工作流程分析图解

肖哥弹架构

Java 并发编程 高并发

云桌面解决方案哪家好?青椒云云桌面有什么优势

青椒云云电脑

云桌面 云桌面解决方案

告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐

OpenAnolis小助手

c++ 操作系统 模块 技术干货

AI 场景下如何确保模型数据安全?Confidential AI 技术最佳实践解读

OpenAnolis小助手

AI 操作系统 机密计算 Confidential AI

恭喜!龙蜥赛项25支队伍获得首届中国研究生操作系统开源创新大赛一二三等奖

OpenAnolis小助手

开源 操作系统 龙蜥社区 龙蜥赛事

Flutter开发鸿蒙,终端一体化

flfljh

鸿蒙

Java学习—反射机制:解锁代码的无限可能

不在线第一只蜗牛

Java

09.外观模式设计思想

杨充

国内首位聋人 Android 软件工程师体验通义灵码,“这真是太棒了”

阿里云云效

阿里云 云原生 通义灵码

华为账号一键登录

flfljh

鸿蒙

APP 服务卡片获取 APP 内数据

flfljh

鸿蒙

对象数组,数据更新刷新ui

flfljh

鸿蒙

鸿蒙 Flutter plugin开发详细解释

flfljh

鸿蒙

鸿蒙 next 写入考试时间日程到系统日历

flfljh

鸿蒙

Durandal快速入门_JavaScript_Rob Eisenberg_InfoQ精选文章