当你开始为 Firefox OS 开发时,你可能会对提供的工具感到失望。没有标准的 UI 工具包,也没有所有应用都构建在其上的 JavaScript 框架。这并非本质上是一个糟糕的情况,因为 Firefox OS 本质上是 Web;因此,它让你在使用的工具链方面拥有完全的自由。这使我们能够在 Firefox OS 上使用任何新兴的技术。缺点是你可能会错过在 Android 或 iOS 上习惯的一些东西,比如内置模板、视图过渡和 UI 组件。
在 Telenor Digital,我们决定构建一个现成的应用框架来解决这些缺点,该框架构建在 AngularJS(Google 的 MVW 框架)之上。该模板是我们为 Firefox OS 构建的内部应用程序迭代的结果,并解决了以下问题
- 构建在 AngularJS 之上,提供数据绑定、模板、路由和代码结构
- 内置一组 UI 组件
和 Firefox OS 风格的过渡效果 - 能够将应用发布为移动网页(托管应用),
或作为 Firefox OS 应用商店的打包应用 - 优先考虑离线功能。每个构建在模板之上的应用都能离线工作,
即使托管在您自己的 Web 服务器上。 - 一个构建系统,可以使用一条命令创建发布版本,
进行代码压缩和模板缓存以获得最佳性能
让我们看看演示应用是什么样子的。这是一个标准的 CRUD 应用,展示了列表-详情模式:http://janjongboom.com/ffos-list-detail/。您可以点击项目进入详情视图,可以编辑项目或添加新项目。“+”按钮是安装按钮(仅在 Firefox 中可见),允许您将应用添加到手机(Android/FxOS)。
获取代码
要开始构建,请执行以下操作
git clone git@github.com:comoyo/ffos-list-detail.git
npm install
./node_modules/bower/bin/bower install
- 现在,您可以在任何浏览器中打开
www/index.html
,或使用应用管理器并将
www
文件夹添加为打包应用。
结构
应用位于 www/ 文件夹中,并由以下子文件夹组成
- components/,第三方库,通过 bower 加载
- css/,样式表。将应用使用的所有样式列在
css/main.css.
中。为了获得最佳性能,它们将合并成一个大型样式表。 - img/,包含应用的三种格式的图标。
- js/,我们的代码
- controllers/,将数据绑定到 UI 的代码
- lib/,不在 bower 中的外部库
- services/,数据提供程序或未绑定到 UI 的代码
- app.js,应用的起点,包含全局配置(如路由)
- main.js,基于 RequireJS 的引导文件。
列出我们使用的所有 JavaScript 文件。当您创建新的 JS 文件时,将其添加到此处。
- views/,视图模板
- index.html,我们加载应用的引导文件。您可能永远不会接触它。
- manifest.appcache,AppCache 文件。
您需要在此处列出应用需要的所有图像和其他资源(CSS/JS 除外),
以启用托管应用的离线功能。 - manifest.webapp,Firefox OS 应用清单 文件。
在开发过程中,您无需设置任何构建链,您可以随意编辑 www 中的文件并刷新 index.html。这就是 Web 的强大之处:-) 当然,如果您在应用管理器中进行开发,请按“更新”以刷新应用。
现在让我们向此应用添加一些新功能,以便我们可以了解在实践中如何开发新功能。
添加一个新按钮
假设我们要添加一个显示应用构建者的版权信息屏幕。首先,我们需要在某个地方添加一个按钮。在本例中,我们将其放在应用的主屏幕上。视图代码位于www/views/list.html
中
您看到的组件来自 Firefox OS 构建块,这些块与用于构建 Firefox OS 本身相同的块。让我们在屏幕底部(</ul>
和</section>
下方)添加一个新按钮
Credits
这里重要的是ng-tap
属性。当我们点击此项目时,我们将转到/credits
URL,并使用动画popup
。有四种内置动画:forward
、backward
、popup
和popdown
;但是您可以使用简单的 CSS 创建自己的动画。
现在,当我们查看它时,它看起来还不像一个按钮,因为我们还没有说明需要按钮构建块。转到css/main.css
并添加以下行以使其外观美观
@import url("../components/building-blocks/style/buttons.css");
所有这些都始终记录在构建块网站的页面上。
连接它
但是,当我们点击按钮时,什么也不会发生(好吧,我们会重定向回列表视图),这是因为我们还没有监听/credits
URL。要解决此问题,我们需要创建一个路由处理程序(就像在任何 MV*服务器端框架中一样)。在js/app.js
中打开路由列表,并为credits
URL添加一个处理程序(在otherwise
处理程序之前)
.when('/credits', {
templateUrl: 'views/credits.html',
controller: 'CreditsCtrl'
})
在这里,我们告诉想要咨询哪个控制器(使用 JS 代码)以及哪个视图(使用 HTML)属于该控制器。让我们先创建视图。在views
文件夹中添加一个名为credits.html
的新文件。
back
Credits
This application is made by {{ name }}.
要设置此视图的样式,我们可以在css/app.css
中添加一些内容,例如,添加一些填充并使文本更大
.view.credits {
padding: 1.5rem;
font-size: 2rem;
}
现在,编写一个简单的控制器来填充{{ name }}
的内容,使用标准的 AngularJS 数据绑定。在www/js/controllers
中添加一个名为credits.js
的新文件
/* We use RequireJS AMD style modules to get a reference to the app object */
define(['app'], function(app) {
/* Tell that we're defining a controller with name
CreditsCtrl, and with dependencies on $scope, we specify this as string
to not break when minifying
*/
app.controller('CreditsCtrl', ['$scope',
/* Implementation. AngularJS does dependency injection to fill the $scope var */
function CreditsCtrl($scope) {
/* Data binding to the view */
$scope.name = 'Your name';
}
]);
});
最后,告诉 RequireJS 我们有一个需要包含在构建中的新 JS 文件,方法是编辑js/main.js
并在'js/controllers/edit.js'
上方添加一行
'js/controllers/credits.js',
现在,当我们点击应用中的按钮时,一切按预期工作。视图弹出,我们有数据,并且可以通过点击后退按钮将其关闭。同样很棒的是,当您将 URL 发送给其他人(例如http://your/url/index.html#/credits)时,他们默认会转到相同的视图。这是因为我们默认通过 URL 进行正确的状态管理。
与第三方数据源通信
该应用目前仅与静态数据通信,因此我们希望将其连接到真实的数据源。在本例中,项目列表应来自 GitHub 上 mozilla-b2g 的项目页面。他们的 API 位于:https://api.github.com/users/mozilla-b2g/repos。
AngularJS 有一个服务的概念,它将数据从控制器中抽象出来。对于此应用,我们有一个数据库服务,该服务目前返回内存中的数据。我们可以修改服务以改为与 Web 服务通信。清除www/js/services/database.js
并将其内容替换为
/*global define */
"use strict";
define(['app'], function(app) {
/* Add a new factory called database, with a dependency on http */
app.factory('database', ['http', function(http) {
var getItems = function() {
/* getItems makes a HTTP get call to github */
return http.get('https://api.github.com/users/mozilla-b2g/repos', {
// this is the cache configuration, we want to always cache requests
// because it gives better UX. Plus when there is no internet, we can
// get the data from cache and not break for the user...
idbCache: {
cacheKey: 'api.index',
// expiration time in ms. from now (this is 5 minutes)
// This is only obeyed if there is an internet connection!
expiresInMs: 5 * 60 * 1000
}
}).then(function(res) {
// Format it, sort it and map it to have the same format as our previous in mem dataset
return res.data.sort(function(a, b) {
return a.stargazers_count < b.stargazers_count;
}).map(function(item) {
return {
title: item.name,
description: item.description,
id: item.name
};
});
});
};
// Similar story but now for just one item
var getItemById = function(id) {
return http.get('https://api.github.com/repos/mozilla-b2g/device-flatfish', {
idbCache: {
cacheKey: 'api.detail.' + id,
expiresInMs: 10 * 60 * 1000
}
}).then(function(res) {
var repo = res.data;
return {
title: repo.name,
description: repo.description,
id: repo.name,
date: new Date((repo.pushed_at || "").replace(/-/g,"/").replace(/[TZ]/g," "))
};
});
};
return {
getItems: getItems,
getItemById: getItemById
};
}]);
});
但是,此 API 现在是异步的,但这对 Angular 来说并不重要。如果将数据绑定到 Promise,则 Angular 将等待 Promise 解析,直到数据绑定发生。
这里的美妙之处在于,即使没有 Internet 连接,数据仍将加载(只要它至少加载过一次),并且数据会自动缓存。控制器无需为此担心。
发布应用
这是我们快速向此应用添加一些功能的两种方法。首先,添加一个新按钮和一个新视图;其次,显示服务器数据的绑定和离线缓存。请注意,此应用模板可用于不仅仅是列表到详情的应用,您拥有 AngularJS 的全部功能!
现在,当我们想要与世界其他地区共享此应用时,我们可以选择两种方法
- 创建托管应用。这是一个位于您自己的服务器上的应用,就像任何移动网站一样。托管应用仍可以在应用商店中发布,并且可以离线工作,但由于安全限制,无法使用 Firefox OS 中的所有 API。
- 创建打包应用。这是一个 ZIP 文件,类似于 Android 上的 APK 文件,其中包含应用的所有资源,并通过应用商店分发。
这两种应用都可以使用我们的构建脚本生成。该脚本将创建一个名为dist/
的新文件夹,其中列出应用需要的所有文件。如果要将应用发布到您自己的服务器,只需复制文件夹的内容即可。如果要将应用发布为打包应用,请将内容压缩为 ZIP 并发布到应用商店。
要构建,请运行
- 打包:
node build.js
- 托管:
node build.js appcache
编码愉快!
关于 Jan Jongboom
Jan Jongboom 是 Telenor Digital 的战略工程师,负责物联网工作。
关于 Robert Nyman [荣誉编辑]
Mozilla Hacks 的技术布道师和编辑。发表演讲并撰写有关 HTML5、JavaScript 和开放 Web 的博客文章。Robert 是 HTML5 和开放 Web 的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作 - 在瑞典和纽约市。他还定期在 http://robertnyman.com 上撰写博客文章,并且喜欢旅行和结识新朋友。
8 条评论