jQueryMobile-Web-开发基础知识-二-

59 阅读38分钟

jQueryMobile Web 开发基础知识(二)

原文:zh.annas-archive.org/md5/9E8057489CB5E1187C018B204539E747

译者:飞龙

协议:CC BY-NC-SA 4.0

第九章:处理事件

在本章中,我们将看看 jQuery Mobile 中事件是如何工作的。虽然开发人员显然可以访问常规事件(按钮点击等),但 jQuery Mobile 也为开发人员提供了自己的事件来使用。

在本章中,我们将:

  • 讨论触摸、滑动、滚动和其他物理事件

  • 讨论页面事件

处理物理事件

在本章的第一部分,我们将专注于“物理”事件,或者与使用设备时的触摸和其他操作相关的事件。

提示

对于那些一直在使用常规浏览器测试 jQuery Mobile 的人,请注意,以下一些示例在桌面浏览器上可能无法正常工作。如果愿意,可以下载并安装各种手机模拟器。例如,Android 有一个支持创建虚拟移动设备的 SDK。苹果也有一种模拟 iOS 设备的方法。设置和安装这些模拟器超出了本章的范围,但这当然是一种选择。当然,您也可以使用真实的硬件设备。

物理事件包括以下内容:

  • taptaphold: tap 表示其听起来就像 — 网页上的快速物理触摸。 taphold 是一个较长时间的触摸。许多应用程序将使用两种不同的操作 — 一个用于 tap,另一个用于 taphold

  • swipe, swipeleftswiperight: 这些表示滑动,或者对大多数设备的手指移动。 swipe 事件是通用事件,而 swipeleftswiperight 表示特定方向的滑动。不支持向上或向下的滑动事件。

  • scrollstartscrollstop: 分别处理页面滚动的开始和结束。

  • orientationchange: 当设备方向改变时触发。

  • vclick、vmousedown、vmouseup、vmousemove、vmousecancelvmouseover: 所有这些都是“虚拟”事件,旨在屏蔽对触摸或鼠标点击事件的检查。由于这些主要只是点击和触摸事件的别名,因此不会进行演示。

现在我们已经列出了基本的物理事件,让我们开始看一些示例。 清单 9-1 演示了 taptaphold 事件的一个简单示例:

Listing 9-1: test1.html
<!DOCTYPE html>
<html>
<head>
<title>Tap Tests</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="first">
<div data-role="header">
<h1>Tap Tests</h1>
</div>
<div data-role="content">
<p>
Tap anywhere on the page...
</p>
<p id="status"></p>
</div>
</div>
<script>
$("body").bind("tap", function(e) {
$("#status").text("You just did a tap event!");
});
$("body").bind("taphold", function(e) {
$("#status").text("You just did a tap hold event!");
});
</script>
</body>
</html>

该模板相当简单。页面上有一些解释性文本,要求用户点击它。其下是一个空段落。请注意,文档末尾有两个绑定。一个监听 tap,另一个监听 taphold。用户可以执行任一操作,并显示不同的状态消息。尽管相当简单,但这给了您一个很好的想法,即根据用户按住手指的时间长短做出不同的响应。(taphold 事件触发的时间大约为一秒):

处理物理事件

现在让我们来看 清单 9-2,一个关于滑动事件的示例:

Listing 9-2: test2.html
<!DOCTYPE html>
<html>
<head>
<title>Swipe Tests</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/ jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="first">
<div data-role="header">
<h1>Swipe Tests</h1>
</div>
<div data-role="content">
<p>
Swipe anywhere on the page...
</p>
<p id="status"></p>
</div>
</div>
<script>
$("body").bind("swipe", function(e) {
$("#status").append("You just did a swipe event!<br/>");
});
$("body").bind("swipeleft", function(e) {
$("#status").append("You just did a swipe left event!<br/>");
});
$("body").bind("swiperight", function(e) {
$("#status").append("You just did a swipe right event!<br/>");
});
</script>
</body>
</html>

这个例子与前一个例子非常相似,只是现在我们的事件处理程序监听swipe, swipeleftswiperight。一个重要的区别是我们附加到状态 div 而不是简单地设置它。为什么呢?swiperightswipeleft事件自动是一个滑动事件。如果我们简单地设置段落中的文本,一个将覆盖另一个。下面的截图显示了设备在几次滑动后的外观:

使用物理事件

更复杂的例子呢?考虑以下代码片段,9-3 清单

Listing 9-3: test3.html
<!DOCTYPE html>
<html>
<head>
<title>Swipe Tests</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="first">
<div data-role="header">
<h1>First</h1>
</div>
<div data-role="content">
<p>
Swipe to navigate
</p>
</div>
</div>
<div data-role="page" id="second">
<div data-role="header">
<h1>Second</h1>
</div>
<div data-role="content">
<p>
Swipe to the right...
</p>
</div>
</div>
<script>
$("body").bind("swipeleft swiperight", function(e) {
var page = $.mobile.activePage[0];
var dir = e.type;
if(page.id == "first" && dir == "swipeleft") $.mobile.changePage("#second");
if(page.id == "second" && dir == "swiperight") $.mobile.changePage("#first");
});
</script>
</body>
</html>

在这个例子中,我们有一个包含两个单独页面的文件,一个页面的 id 为first,另一个页面的 id 为second。注意我们没有链接。那么我们如何导航呢?用滑动!我们的事件处理程序现在同时监听swipeleftswiperight。我们首先使用$.mobile.activePage获取活动页面,如第八章 jQuery Mobile 中的 JavaScript 配置和实用工具中所述,关于方法和实用工具。末尾的[0]表示该值实际上是一个 jQuery 选择器。使用[0]会获取实际的 DOM 项。事件类型将是swipeleftswiperight。一旦我们知道了这一点,我们就可以根据用户当前所在的页面和他们滑动的方向积极地移动用户。

现在让我们来看一下滚动。你可以检测滚动何时开始以及何时结束。9-4 清单是另一个这样操作的简单示例:

Listing 9-4: test4.html
<!DOCTYPE html>
<html>
<head>
<title>Scroll Tests</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="first">
<div data-role="header">
<h1>Scroll Tests</h1>
</div>
<div data-role="content">
<p>
Scroll please....<br/>
<br/>
<br/>
(Many <br/> tags removed to save space!)
<br/>
<br/>
</p>
<p id="status"></p>
</div>
</div>
<script>
$("body").bind("scrollstart", function(e) {
$("#status").append("Start<br/>");
});
$("body").bind("scrollstop", function(e) {
$("#status").append("Done!<br/>");
});
</script>
</body>
</html>

这个模板与test1.html,即点击测试器非常相似,只是现在我们监听了scrollstartscrollstop。还要注意<br/>标签的列表。在真实的源文件中,这些标签有很多。这将确保在测试时页面确实是可滚动的。当滚动开始和结束时,我们只是将其附加到另一个状态div。(请注意,当前将 DOM 操作列为在监听scrollstart时存在错误。前面的例子在 iOS 上可能无法工作,但在 Android 上工作正常。)

现在让我们来看一下方向。虽然前面的例子大部分可以在你的桌面上测试,但你肯定需要一个真实的移动设备来测试下一个例子:

Listing 9-5: test5.html
<!DOCTYPE html>
<html>
<head>
<title>Orientation Tests</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="first">
<div data-role="header">
<h1>Orientation Tests</h1>
</div>
<div data-role="content">
<p>
Tilt this sideways!
</p>
<p id="status"></p>
</div>
</div>
<script>
$(window).bind("orientationchange", function(e,type) {
$("#status").html("Orientation changed to "+e.orientation);
});
</script>
</body>
</html>

前一个代码清单的关键部分是最后的 JavaScript,特别是用于更改方向的事件侦听器。这实际上不是 jQuery Mobile 支持的事件,而是浏览器本身支持的事件。一旦事件侦听器被附加,你可以根据设备的方向进行任何操作。以下截图是演示:

使用物理事件

处理页面事件

现在我们已经讨论了物理类型事件,是时候将注意力转向页面事件了。请记住,jQuery Mobile 有自己的页面概念。为了在 jQuery Mobile 中给开发人员更多控制页面工作的能力,支持了许多页面事件。并非所有事件都一定在日常开发中有用。一般来说,页面事件可以分为以下几类:

  • load:这些是与页面加载相关的事件。它们是pagebeforeload,pageloadpageloadfailed。pagebeforeload在请求页面之前触发。您的代码可以根据逻辑批准或拒绝此请求。如果加载页面,则会触发pageload。相反,pageloadfailed将在任何未完成的加载上触发。

  • change:这些事件与从一个页面更改到另一个页面有关。它们是:pagebeforechange,pagechangepagechangefailed。与以前一样,pagebeforechange函数充当编程方式拒绝事件的一种方式。如果完成,将触发pagechangefailed事件。pagebeforechangepagebeforeload事件之前触发。pagechange将在显示页面后触发。

  • transition:与从一个页面转换到另一个页面相关的事件。它们是:pagebeforeshow,pageshow,pagebeforehide,pagehidepagebeforeshowpagebeforehide在其相关事件之前运行,但与pagebeforeloadpagebeforechange不同,它们实际上不能阻止下一个事件的发生。

  • init:正如本书中多次显示的那样,jQuery Mobile 对基本 HTML 执行多次更新,以使其优化为移动显示。这些是与初始化相关的事件。您可以监听的事件是:pagebeforecreate,pagecreatepageinit。pagebeforecreate在您的控件上的任何自动更新触发之前触发。这允许您在布局由 jQuery Mobile 更新之前通过 Javascript 操纵您的 HTML。pagecreate在页面内容存在于 DOM 中之后触发,但仍然在 jQuery Mobile 更新布局之前触发。官方文档建议这是进行任何自定义小部件处理的地方。最后,pageinit将在初始化完成后运行。

  • remove:此类别有一个事件pageremove。在 jQuery Mobile 从 DOM 中删除非活动页面之前触发此事件。您可以监听此事件以防止框架删除页面。

  • layout:最后一个类别与布局相关,有一个事件updatelayout。这通常是由其他布局更改触发的一种方式,用于通知页面需要更新自身。

这还真是不少啊!看待这些事件的一个简单方法就是简单地听取它们的全部。在列表 9-6中,我们有一个这样的简单示例:

Listing 9-6: test_page.html
<!DOCTYPE html>
<html>
<head>
<title>Page Event Tests</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="first">
<div data-role="header">
<h1>Page Event Tests</h1>
</div>
<div data-role="content">
<p>
<a href="#page2" data-role="button">Go to Page 2</a>
<a href="test_pagea.html" data-role="button"> Go to Page 3</a>
<a href="test_pageb.html" data-role="button"> Go to Page 4</a>
<a href="test_pageDOESNTEXIST.html" data-role="button"> Go to Page Failed</a>
</p>
</div>
</div>
<div data-role="page" id="page2">
<div data-role="header">
<h1>Page Event Tests</h1>
</div>
<div data-role="content">
<p>
<a href="#first" data-role="button">Go to Page 1</a>
<a href="test_pagea.html" data-role="button"> Go to Page 3</a>
<a href="test_pageb.html" data-role="button"> Go to Page 4</a>
</p>
</div>
</div>
<script>
$(document).bind("pagebeforeload pageload pageloadfailed pagebeforechange pagechange pagechangefailed pagebeforeshow pagebeforehide pageshow pagehide pagebeforecreate pagecreate pageinit pageremove updatelayout", function(e) {
console.log(e.type);
});
</script>
</body>
</html>

这个模板是一个四页、三文件的简单应用程序的一部分,它有按钮链接到其他每一页。其他页面可以在你下载的 ZIP 文件中找到。为了测试这个应用程序,你应该使用支持控制台的桌面浏览器。任何版本的 Chrome,最近的 Firefox 浏览器(或带有 Firebug 的 Firefox)和最新的 Internet Explorer。浏览器控制台的完整说明无法在本章中适用,但你可以把它看作是一个隐藏的调试日志,用于记录事件和其他消息。在这种情况下,我们已经告诉 jQuery 监听我们所有的 jQuery Mobile 页面事件。然后我们将特定的事件类型记录到控制台。点击了一些东西之后,以下屏幕截图显示了在 Chrome 浏览器中控制台日志的样子:

处理页面事件

在 Chrome 中打开控制台很简单。点击浏览器右上角的扳手图标。选择工具然后选择JavaScript 控制台。在测试这些文件之前打开控制台,你可以实时监控页面事件的发生情况。

$(document).ready怎么样?

如果你是一个 jQuery 用户,你可能会好奇$(document).ready在 jQuery Mobile 站点中是如何发挥作用的。几乎所有的 jQuery 应用程序都使用$(document).ready进行初始化和其他重要的设置操作。然而,在 jQuery Mobile 应用程序中,这样做效果不佳。由于使用 Ajax 加载页面,$(document).ready只对第一个页面有效。因此,在过去使用$(document).ready的情况下,应该使用pageInit事件。

创建一个真实的例子

那么真实的例子呢?我们的下一组代码将演示如何创建一个简单但动态的 jQuery Mobile 网站。内容将通过 Ajax 加载。通常这将是动态数据,但出于我们的目的,我们将使用简单的静态 JSON 数据文件。JSON,代表 JavaScript 对象表示法,是一种将复杂数据表示为简单字符串的方法。列表 9-7是应用的首页:

Listing 9-7: test_dyn.html
<!DOCTYPE html>
<html>
<head>
<title>Test Dynamic</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/ jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="homepage">
<div data-role="header">
<h1>Dynamic Pages</h1>
</div>
<div data-role="content">
<ul id="peopleList" data-role="listview" data-inset="true"></ul>
</div>
</div>
<script>
$("#homepage").bind("pagebeforecreate", function(e) {
//load in our people
$.get("people.json", {}, function(res,code) {
var s = "";
for (var i = 0; i < res.length; i++) {
s+="<li><a href='test_people.html ?id="+res[i].id+"'>"+res[i].name+"</a></li>";
}
$("#peopleList").html(s).listview("refresh");
}, "json");
});
$("#personpage").live("pagebeforeshow", function(e) {
var thisPage = $(this);
var thisUrl = thisPage.data("url");
var thisId = thisUrl.split("=")[1];
$.get("person"+thisId+".json", {}, function(res, code) {
$("h1",thisPage).text(res.name);
s = "<p>"+res.name +" is a "+res.gender+" and likes "+res.hobbies+"</p>";
$("#contentArea", thisPage).html(s);
}, "json");
});
</script>
</body>
</html>

这个 jQuery Mobile 页面的第一印象是,实际内容几乎不存在。至少在 jQuery Mobile 页面的内容块中是这样的。有一个listview但实际内容却不存在。那么内容从哪里来呢?在页面底部,你可以看到两个事件监听器。现在让我们只关注第一个。

这里的代码绑定到了 jQuery Mobile 为页面触发的pagebeforecreate事件。我们已经告诉 jQuery Mobile 在创建页面之前运行此事件。这个事件将运行一次且仅运行一次。在这个事件中,我们使用 jQuery 的get功能对文件people.json进行了一个 Ajax 请求。该文件只是一个以 JSON 格式表示的名字数组。

[{"id":1,"name":"Raymond Camden"},{"id":2,"name":"Todd Sharp"},{"id":3,"name":"Scott Stroz"},{"id":4,"name":"Dave Ferguson"},{"id":5,"name":"Adam Lehman"}]

每个名称都有一个 ID 和实际的名称值。当通过 jQuery 加载时,这将转换为一组实际的简单对象。回顾事件处理程序,您会发现我们只需循环遍历此数组并创建表示一组li标签的字符串。请注意,每个都有一个指向test_people.html的链接,以及一个动态名称。还请注意链接本身是动态的。它们包括从 JSON 字符串中检索到的每个人的 ID 值:

创建一个真实的例子

早些时候提到过,但请注意调用listview("refresh")

$("#peopleList").html(s).listview("refresh");

没有listview("refresh")部分,我们添加到列表视图的项目将无法正确设置样式。

让我们快速看看下一个test_people.html

Listing 9-8: test_people.html
<!DOCTYPE html>
<html>
<head>
<title>Test Dynamic</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery- 1.7.1.min.js"></script>
<script src="img/ jquery.mobile.min.js"></script>
</head>
<body>
<div data-role="page" id="personpage">
<div data-role="header">
<h1></h1>
</div>
<div data-role="content" id="contentArea">
</div>
</div>
</body>
</html>

与我们的上一页一样,这一页几乎没有内容。请注意,标题和内容区域都是空白的。但是,如果您记得test_dyn.html中的第二个事件处理程序,我们支持在这里加载内容。这次我们使用了pagebeforeshow事件。为什么?我们希望在每次显示页面之前运行此代码。我们需要知道要加载的特定人员是谁。如果您记得,人员的 ID 是通过 URL 传递的。我们可以通过页面对象上存在的数据属性url获取它。这返回完整的 URL,但我们只关心它的末尾,即我们的 ID。因此,我们拆分字符串并抓取最后一个值。一旦我们有了,我们就可以为每个人加载特定的 JSON 文件。此文件名的形式为personX.json,其中X是 1 到 5 的数字。以下代码行是一个示例:

{"name":"Raymond Camden","gender":"male","hobbies":"Star Wars"}

显然,真实的人物对象会有更多的数据。一旦我们获取了这个字符串,我们就可以解析它并将结果布局在页面上:

创建一个真实的例子

总结

在本章中,我们研究了 jQuery Mobile 应用程序可以监听和响应的事件。这些事件包括物理类型(滚动、方向、触摸)和基于页面的事件。

在下一章中,我们将看看 jQuery Mobile 站点如何主题化 - 包括开箱即用的主题和自定义主题。

第十章:进一步了解 Notekeeper 移动应用程序

在这一章中,我们将开始将迄今为止学到的关于列表、表单、页面和内容格式化的所有内容组合成一个可用的“移动应用程序”;即 Notekeeper 应用程序。

在本章中,我们将:

  • 使用表单接受用户输入

  • 使用 HTML5 localStorage 功能在本地存储用户输入的数据

  • 演示如何动态地向页面添加、编辑和删除项目

什么是移动应用程序?

在编写我们的第一个移动应用程序之前,也许我们应该定义一下什么是移动应用程序。维基百科说,移动应用程序是为小型低功耗手持设备开发的软件,如个人数字助理、企业数字助理或移动电话。虽然 jQuery Mobile 应用程序是用 HTML、CSS 和 JavaScript 编写的,但这并不妨碍它们成为复杂的软件。它们肯定是针对移动设备开发的。

一些评论家可能会指出,除非“安装”,否则它实际上不能成为软件。正如您将在本书的后面看到的,与开源库 PhoneGap 配合使用时,jQuery Mobile 应用程序实际上可以安装在各种设备上(包括 iOS、Android 和 Windows Mobile)。这意味着您将能够兼得。您可能会问自己,使用 jQuery Mobile 编写的代码是否可以被视为软件,正如您将在本章中了解到的那样,答案是肯定的。

设计您的第一个移动应用程序

任何软件的目标都是满足需求。Gmail 通过让用户摆脱单一计算机并让他们可以从任何 Web 浏览器检查电子邮件来满足需求。Photoshop 通过允许用户以前所未有的方式操纵照片来满足需求。我们的 Notekeeper 应用程序通过允许我们记录简单的笔记以供以后参考来满足需求。我知道,与之相比有点令人失望,但我们必须从某个地方开始对吧?

在构建软件时,最好花时间事先撰写项目的规格说明:它将做什么,它将是什么样子,以及它应该具有什么。记住,如果你不知道你在构建什么,你怎么会知道它是否完成了?

列出要求

我们已经知道我们的应用想要做什么,记笔记。问题在于有很多种方式可以构建一个笔记应用,因此必须勾勒出我们想要的功能。不多不少,但目前足够。对开发人员来说,一个事实是我们的应用永远不会“完成”,它们只是暂时“完成”。对于 Notekeeper,我们决定我们想要用我们的应用程序做以下三件事:

  • 添加笔记

  • 显示笔记列表

  • 查看笔记/删除笔记

在决定我们的应用程序需要完成哪些任务之后,我们需要决定它将如何完成这些任务。最简单的方法就是简单地将这些事情写成一个列表。通过将每个部分细分为更小的部分,我们使它更容易理解,并且看到我们需要做些什么才能让它工作。这就像得到去你最喜欢的餐厅的指南一样;这里拐个弯,那里转个圈,你转眼间就坐在餐桌前了。让我们看看我们希望 Notekeeper 做什么,以及下面的部分和部件:

  • 添加一个注释(表单)

    • 一个表单容器。所有用户输入的小部件都被包装成一个表单。

    • 一个标题,注释的名称。这也将用于显示现有的注释。

    • 注释本身。注释的内容或主体。

    • 保存按钮。这个按钮会触发实际的保存操作。

  • 显示注释列表的能力(列表视图)

    • 包含注释标题的行项。此行应该是指向包含注释主体的页面的链接。

    • 一个部分标题行可能很好。

  • 查看注释的能力,并删除注释(标签,段落,按钮)

    • 标题的标签

    • 包含注释内容的段落

    • 一个标有删除的按钮

    • 返回按钮

制作线框图

现在我们已经列出了我们的应用程序的功能,那么我们如何勾画出每一部分,以便我们知道我们想要的是什么样子?如果你的艺术功底不好,或者你连一个竖直线都画不出来,不要担心。如果你有尺子,可以使用尺子,或者考虑使用微软 Excel 或 PowerPoint。你只需要能够画一些框和一些文本标签。

设计添加注释线框图

现在,添加注释部分怎么样?我们决定它需要一个标题,一个注释框和一个提交按钮。表单是一个不可见的容器,所以我们不需要画出来:

设计添加注释线框图

显示注释框架

列表视图是移动开发的一个重要部分。这是将类似项目简单地分组在一起的最简单方法,另外它还提供了许多额外的功能,比如滚动和内置图片链接。我们将使用列表视图来显示我们的注释列表:

显示注释框架

查看注释/删除按钮框架

最后,一旦我们添加了一个注释,我们需要能够删除证据,我是说清除旧注释以为新注释腾出空间。请注意,我们还勾画了一个返回按钮。一旦你开始看到事情摆放出来,你会发现你忘记了一些非常重要的事情(比如能够返回到上一页):

查看注释/删除按钮框架

编写 HTML

现在我们的线框图已经完成,我们对它们感到满意,是时候将铅笔画变成 1 和 0 了。由于我们的应用程序相对简单,HTML 中的任何内容都不应该难倒你。毕竟,你已经过了书的一半了,而且你应该能够做到这些事情。

你所提出的 HTML 应该看起来与下面的代码片段非常相似。让我们一起来检查一下:

Listing 10-1: notekeeper.html
<!DOCTYPE html>
<html>
<head>
<title>Notekeeper</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<script src="img/jquery-1.6.4.js"></script>
<script src="img/ jquery.mobile.min.js"></script>
<script src="img/application.js"></script>
</head>
<body>
<div data-role="page">
<div data-role="header">
<h1>Notekeeper</h1>
</div>
<div data-role="content">
<form>
<div>
<input id="title" type="text" placeholder="Add a note" />
</div>
<div>
<textarea id="note" placeholder="The content of your note"></textarea>
</div>
<div class="ui-grid-a">
<div class="ui-block-a">
<input id="btnNoThanks" type="submit" value="No Thanks" />
</div>
<div class="ui-block-b">
<input id="btnAddNote" type="button" value="Add Note" />
</div>
</div>
</form>
<ul id="notesList" data-role="listview" data-inset="true">
<li data-role="list-divider">Your Notes</li>
<li id="noNotes">You have no notes</li>
</ul>
</div>
<div data-role="footer" class="footer-docs">
<h5>Intro to jQuery Mobile</h5>
</div>
</div>
</body>
</html>

我们的笔记管理应用程序将使用单个 HTML 文件(notekeeper.html)和单个 JavaScript 文件(application.js)。直到这一点,您编写的代码都不需要 JavaScript,但是一旦您开始编写更复杂的应用程序,JavaScript 就会成为必需品。在您的网络浏览器中预览 列表 10-1 中的 HTML,您应该会看到类似以下截图的内容:

编写 HTML

请注意,我们在同一个页面上显示添加笔记表单和查看笔记。在移动应用程序开发中,尽可能压缩东西是个好主意。不要将这个作为硬性规则,但由于我们的应用程序很简单,把这两部分放在一起是可以接受的决定,只要它们清晰地标记出来。你可以看到,这个页面满足了我们为添加笔记和显示现有笔记设定的所有要求。它有一个标题输入字段,一个笔记输入字段,一个保存按钮,并且整个东西都包裹在一个表单容器中。它还有一个列表视图,用于显示我们添加笔记后的笔记。这里看不到的是一个删除按钮,但一旦我们添加了第一个笔记并查看详细页面,它就会显示出来。

使用 JavaScript 添加功能

正如本书所提到的,您不需要编写任何 JavaScript 就能从 jQuery Mobile 中获得物有所值。但是随着您在 jQuery Mobile 中的经验不断增加,您将开始看到 JavaScript 可以为您的项目增加多少附加值。在我们查看代码之前,让我们谈谈它将如何结构化。如果您有任何网络设计或开发经验,您可能已经看到过 JavaScript。毕竟,它从 1995 年就开始存在了。问题是,JavaScript 有很多种不同的方法来做同样的事情,而不是所有方法都是好的。

这个应用程序中的 JavaScript 代码将使用所谓的设计模式。这只是一个花哨的术语,用来指定代码的某种结构。使用现有设计模式的主要原因有三个:

  • 它帮助我们的代码保持组织和整洁。

  • 它防止我们编写的变量和函数被我们可能添加的任何其他代码意外覆盖或更改。也许是一个 jQuery 插件,或者是从第三方网站加载的代码。

  • 它将帮助未来的开发人员更快地适应你的代码。你在开发下一个 Facebook 的时候有考虑到未来的开发人员吗?

在我们深入了解完整代码之前,让我们先来看一个非常简单的实现这个设计模式的示例:

Listing 10-2: kittyDressUp.js
$(document).ready(function(){
// define the application name
var kittyDressUp = {};
(function(app){
// set a few variables which can be used within the app
var appName = 'Kitty Dress Up';
var version = '1.0';
app.init = function(){
// init is the typical name that developers give for the
// code that runs when an application first loads
// use whatever word you prefer
var colors = app.colors();
}
app.colors = function(){
var colors = ['red','blue','yellow','purple'];
return colors;
}
app.init();
})(kittyDressUp);
});

如果您熟悉 JavaScript 或 jQuery,您可能会看到一些您熟悉的元素。对于那些不熟悉 jQuery 或 JavaScript 的读者,我们将逐行审查这个示例。KittyDressUp.js 以 jQuery 的最好朋友开头。包含在花括号内的任何代码都会等到文档或 HTML 页面完全加载后再执行。这意味着您,开发人员,可以确保您的代码在页面上需要的一切都加载完成后才运行:

$(document).ready({
// I'm ready captain!
});

简单来说,下一行创建了一个名为 kittyDressUp 的变量,并将其赋值为空对象的值。但是,在我们的代码中,这个新对象将包含我们的整个应用程序:

// define the application name
var kittyDressUp = {};

下面的声明是 Kitty Dress Up 应用程序的核心。它创建了一个接受单个参数的函数,然后立即调用自身,并传入我们在前一行中创建的空对象。这个概念称为自执行函数,它是使外部代码无法干扰我们的应用程序的方法。

(function(app){
// define the app functionality
})(kittyDressUp);

接下来的两行设置了一些只能从我们应用程序的上下文或范围中访问的变量:

// set a few variables which can be used within the app
var appName = 'Kitty Dress Up';
var version = '1.0';

最后,最后几行设置了两个在应用程序中可用的函数。您可以看到每个函数都被分配了一个在更大应用程序范围内的名称。app 变量是函数所在的地方,. 后面的单词是函数名称。请注意,在 init 函数内部,我们正在调用同一应用程序内的另一个函数,app.colors()。我们也可以引用我们在顶部定义的任何变量。

app.init = function(){
// init is the typical name that developers give for the
// code that runs when an application first loads
// use whatever word you prefer
var colors = app.colors();
}
app.colors = function(){
var colors = ['red','blue','yellow','purple'];
return colors;
}
app.init();

请记住,app 是传递给自执行函数的参数名称,其值为空对象。作为整体,这几行代码创建了一个名为 kittyDressUp 的对象,其中包含两个变量(appNameversion),以及两个函数(initcolors)。这个示例以及 Notekeeper 的代码都是简单的示例,但它们说明了您可以如何将代码包装成离散包以用于更大应用程序的各个部分。事实上,在 kittyDressUp.js 运行之后,您甚至可以将 kittyDressUp 传递到另一组代码中以供使用。

哎呀... 大家休息五分钟,你们赚了它。

存储 Notekeeper 数据

现在我们从五分钟的休息中回来了,是时候卷起袖子开始为我们的应用程序添加功能了。虽然我们已经讨论了我们希望 Notekeeper 的行为方式,但我们还没有讨论到存储笔记数据的核心问题。有几种可能性,都有利弊。让我们列出它们:

  • 数据库(MySQL、SQL Server、PostgreSQL): 虽然数据库是理想的解决方案,但它对我们的应用程序来说有点复杂,它需要互联网连接,并且您需要一个服务器端组件(ColdFusion、PHP、.NET)作为中间人将笔记保存到数据库中。

  • 文本文件: 文本文件非常棒,因为它们占用的空间很小。问题在于作为 Web 应用程序,Notekeeper 无法将文件保存到用户的设备上。

  • localStorage: localStorage 相对较新,但它迅速成为一个很好的选择。它以键/值对的形式存储信息在用户的设备上。它有大小限制,但对于纯文本来说相当大,大多数现代浏览器都支持它,并且可以在离线模式下使用。

使用 localStorage

为了本章的目的,我们将选择localStorage作为我们的首选方法。让我们快速看一下其行为,这样当你看到它时,你就会熟悉它。如前所述,localStorage的工作原理是存储键/值对中的数据。将值保存到localStorage有两种方式,无论你选择哪一种,都很容易:

localStorage.setItem('keyname','this is the value I am saving');

localStorage['keyname'] = 'this is the value I am saving';

选择哪个版本完全取决于个人偏好,但因为输入较少,我们将使用第二种方法,方括号。我们将遇到的一个问题是,localStorage无法存储如数组或对象之类的复杂数据。它只能存储字符串。这是一个问题,因为我们将把所有数据存储在一个变量中,以便始终知道其位置。别担心,我们可以欺骗localStorage,使用一个名为stringify()的内置函数将我们的复杂对象转换为其自身的字符串表示。

以下代码片段显示了它是如何工作的:

// create our notes object
var notes = {
'note number one': 'this is the contents of note number one', 'make conference call': 'call Evan today'
}
// convert it to a string, then store it.
localStorage['Notekeeper'] = JSON.stringify(Notekeeper);

检索值与设置值一样简单,并且也提供两个选项。通常需要定义一个变量来接收localStorage变量的内容。

var family = localStorage.getItem('my family');

var family = localStorage['my family'];

如果您正在检索复杂的值,则必须在使用变量内容之前执行另一步。正如我们刚才提到的,要存储复杂的值,您必须首先使用stringify()函数,它有一个称为parse()的相对应函数。parse()函数接受包含该复杂对象的字符串,并将其转换回纯粹的 JavaScript。它的用法如下:

var myFamily = ['andy', 'jaime', 'noelle', 'evan', 'mason'];
localStorage['family'] = JSON.stringify(myFamily);
var getFamily =JSON.parse(localStorage['family']);

最后,如果你想完全删除该密钥,那么你可以在单行代码中完成,有两种选择:

localStorage.removeItem('my family');

delete localStorage[my family'];

值得注意的是,如果您尝试检索在localStorage中不存在的密钥,JavaScript 不会引发错误。它只会返回“未定义”,这是 JavaScript 表示“抱歉,什么也没有”的方式。以下代码片段是一个示例:

var missing = localStorage['yertl the turtle'];
console.log(missing);
// returns undefined

有效使用样板文件

在我们开始构建 JavaScript 文件之前,还有一件事情。在我们的应用程序中,我们只会有一个 JavaScript 文件,它将包含整个代码库。这对于像我们这样的小型应用程序来说是可以的,但对于更大的应用程序来说不是一个好主意。最好将项目分解为不同的部分,然后将每个部分放入它们自己的文件中。这样做可以使开发团队更容易地协同工作(例如,Noelle 负责登录流程,而 Mason 则负责供应商列表)。它还使每个文件变得更小且更容易理解,因为它只涉及整体的一部分。当您希望应用程序的所有部分具有相似的结构和设计时,最好的方法是从一个模板开始每个部分。我们将为我们应用程序的唯一文件使用一个模板(你可以在以下代码片段中看到,Listing 10-3)。你可能会注意到它看起来非常类似于 kittyDressUp 示例,你是对的:

Listing 10-3: application.js
$(function(){
// define the application
var Notekeeper = {};
(function(app){
// variable definitions go here
app.init = function(){
// stuff in here runs first
}
app.init();
})(Notekeeper);
});

构建添加注释功能

最后,我们可以开始构建了!由于要显示不存在的笔记列表很困难,更不用说删除笔记了,我们将首先编写 添加注释 功能。用户要能够添加注释,他们必须输入标题、注释内容,然后点击提交按钮。所以我们从那里开始。

添加绑定

我们将在 app.init() 函数定义下创建一个新的、空的函数块。它应该看起来类似于以下代码行:

app.bindings = function(){
}

绑定函数将包含在我们的应用程序中当用户执行某些操作时需要触发的任何代码片段,例如点击提交按钮或删除按钮。我们将这些代码片段组合在一起以便组织。在 bindings() 函数内部,我们将添加以下行。这将在用户单击 添加注释 表单的提交按钮时触发:

// set up binding for form
$('#btnAddNote').bind('click', function(e){
e.preventDefault();
// save the note
app.addNote(
$('#title').val(),
$('#note').val()
);
});

jQuery 的 val() 函数是一个简写方法,用于获取任何表单输入字段的当前值。

关于这个新添加的一些说明:

  • 当使用 jQuery 时,总会有更多的方法来完成某件事情,在大多数情况下,你只需选择自己喜欢的方法即可(它们通常具有相同的性能)。你可能更熟悉 $('#btnAddNote').click(),那也完全可以。

  • 请注意,click 函数接受一个参数:e,它是事件对象(在本例中是点击事件)。我们调用 e.preventDefault() 来阻止在此元素上发生标准点击事件,但仍允许其余代码继续运行。你可能已经看到其他开发人员使用 return false,但 jQuery 最佳实践建议使用 e.preventDefault()

  • 在点击绑定中,我们调用 addNote 函数,并将用户输入的标题和注释传递给它。空白不重要,仅仅是为了更容易看到我们在做什么。

即使我们已经将绑定添加到我们的代码中,如果你现在运行应用程序,当你点击添加笔记按钮时什么也不会发生。原因是还没有任何东西调用bindings()函数。在init()函数内添加以下行,然后你就可以准备好了:

app.init = function(){
app.bindings();
}

收集和存储数据

接下来,在app.bindings下面添加另一个新的空函数块:

app.addNote = function(title, note){
}

现在,因为我们将所有的笔记都存储在localStorage的一个键中,我们首先需要检查是否已经存在任何笔记。从localStorage中检索 Notekeeper 键,将其保存到一个变量中,然后进行比较。如果我们要求的键的值是一个空字符串或undefined,我们将需要创建一个空对象。如果有一个值,那么我们将取出该值并使用parse()函数将其转换为 JavaScript:

var notes = localStorage['Notekeeper'];
if (notes == undefined || notes == '') {
var notesObj = {};
} else {
var notesObj = JSON.parse(notes)
}

注意我们期望将两个变量传递给addNote()函数,titlenote。接下来,我们用破折号替换标题中的任何空格,这样某些浏览器更容易理解文本字符串。然后我们将键值对放入我们新创建的笔记对象中:

notesObj[title.replace(/ /g,'-')] = note;

JavaScript 的replace方法使字符串操作非常简单。它作用于一个字符串,接受一个搜索项和一个替换项。搜索项可以是一个简单的字符串,也可以是一个复杂的正则表达式。

下一步是将我们的notesObj变量stringify()并放入localStorage中。然后我们清除两个输入字段的值,以便用户更轻松地输入另一个笔记。在构建软件时,一般在添加或删除内容后将界面恢复到原始状态是一个不错的举措:

localStorage['Notekeeper'] = JSON.stringify(notesObj);
// clear the two form fields
$note.val('');
$title.val('');
//update the listview
app.displayNotes();

所有这些变量定义对你来说应该很熟悉,也许有一个例外,我们应该指出。许多 jQuery 开发人员喜欢为包含 jQuery 对象的变量使用传统命名。

具体来说,它们在变量名前面加上了$符号,就像在 jQuery 中一样。这让他们或者未来的开发者知道变量中包含的是什么。让我们继续在我们的应用程序顶部添加这些定义。在读取// 变量定义放在这里后面的一行,添加以下行。它们分别指的是标题输入字段和笔记文本区域字段:

var $title = $('#title');
var $note = $('#note');

作为这个函数的最后一步,我们调用app.displayNotes()来更新笔记列表。由于该函数尚不存在,接下来我们来创建它。

构建显示笔记功能

在编写上一节时,你可能已经测试了添加笔记功能。这意味着你至少已经在localStorage中保存了一个笔记,用于测试显示笔记功能。到现在为止,你已经熟悉了我们在任何新节的第一步。继续添加你的空白displayNotes()函数来保存我们的代码:

app.displayNotes = function(){
}

接下来,我们需要从localStorage中检索所有的笔记:

// get notes
var notes = localStorage['Notekeeper'];
// convert notes from string to object
return JSON.parse(notes);

你可能会注意到我们的许多函数都有一个模式,几乎所有这些函数都以从 localStorage 中检索笔记开始。虽然只需要两行代码来执行此任务,但我们不需要在每次需要获取笔记时重复这两行代码。所以我们将编写一个包含这两行代码的快速辅助函数。它看起来类似于以下代码片段:

app.getNotes = function(){
// get notes
var notes = localStorage['Notekeeper'];
// convert notes from string to object
return JSON.parse(notes);
}

有了我们的新辅助函数,我们可以像下面的代码片段中所示,在 displayNotes() 函数中使用它:

app.displayNotes = function(){
// get notes
var notesObj = app.getNotes();
}

现在我们有了包含我们笔记数据的 notesObj 变量,我们需要循环遍历该数据包并输出内容:

// create an empty string to contain html
var html = '';
// loop over notes
for (n in notesObj) {
html += li.replace(/ID/g,n.replace(/-/g,' ')).replace(/LINK/g,n);
}
$ul.html(notesHdr + html).listview('refresh');

对于 for 循环内的一行具有多个替换语句可能看起来有些奇怪,但是 JavaScript 的性质允许方法链式调用。链式调用指的是返回其操作结果的整个结果的方法。添加额外的方法调用只是简单地重复该过程。

这个代码块中可能有一些新概念,所以让我们仔细看看。名为 html 的变量并不特别,但我们如何使用它可能是特别的。当我们遍历现有的笔记时,我们将新信息存储到 html 变量中,以及其他任何内容。我们通过使用 += 运算符来实现这一点,该运算符允许我们同时赋值和追加。

第二件你可能注意到的事情是赋值右边的 li。它从哪里来?那是一个尚未创建的单个列表项的模板。让我们在谈论它之前就做这件事。在你的 app.js 文件顶部,在读取 // 变量定义在此 之后的一行之后,添加以下两行代码:

var $ul = $('#notesList');
var li = '<li><a href="#pgNotesDetail?title=LINK">ID</a></li>';

你应该已经熟悉了在变量前加$来表示一个 jQuery 对象的约定。这就是我们在 $ul 变量中所做的事情。第二个变量,li 有些不同。它包含了一个单独的列表项的 HTML,用于显示一个笔记标题。最好的做法是尽可能避免在 JavaScript 中混合使用 HTML 或 CSS。我们现在将其声明为一个模板,以防将来决定在多个地方使用它。

另一个可能感兴趣的部分是我们如何使用 li 变量。在调用字符串替换函数时,我们正在查找单词 LINK 的所有出现,并用笔记的标题替换它。因为 JavaScript 是大小写敏感的语言,所以我们可以安全地假设我们不会遇到该单词的自然出现。

动态添加笔记到我们的列表视图

在我们的笔记显示在页面上之前,还有最后一件事情要安排。您可能已经注意到,唯一调用displayNotes()函数的地方出现在addNote()函数内部。这是一个很好的地方,但它不能是唯一的地方。我们需要在页面首次加载时运行某些内容。这个地方最好是在init()函数中,并且这就是我们要放置它的地方。

不过,有一个问题,我们不能只加载我们的笔记然后运行,如果没有笔记会发生什么?我们需要向用户显示一个友好的消息,以便他们不会认为出了什么问题。让我们创建一个名为app.checkForStorage()的新函数来处理所有这些:

app.checkForStorage = function(){
// are there existing notes?
if (localStorage['Notekeeper']) {
// yes there are. pass them off to be displayed
app.displayNotes();
} else {
// nope, just show the placeholder
$ul.html(notesHdr + noNotes).listview('refresh');
}
}

到现在为止,所有这些对你来说应该都很熟悉:检查localStorage是否有笔记,并在找到它们时调用displayNotes()函数。不过,第二部分有一些新内容。当我们为$uljQuery 对象设置 html 时,我们调用了两个新变量。一个是列表视图的标题,另一个是如果我们没有任何笔记时的情况。让我们现在添加这两个变量定义。在// 变量定义在此处下面,添加以下两行:

var notesHdr = '<li data-role="list-divider">Your Notes</li>';
var noNotes = '<li id="noNotes">You have no notes</li>';

行的最后一部分通常可能会被忽视,但我们不会让它被忽视。这真的很重要。jQuery Mobile 为开发人员提供了选择。一种选择是使用静态 HTML 代码,在页面加载时已经存在;jQuery Mobile 还提供了在运行时添加 HTML 代码的选项。这确实给开发人员带来了很大的灵活性,但同时也提出了一个独特的挑战。按设计,jQuery Mobile 在页面加载之前将 HTML 转换为时尚的按钮。这意味着在此之后添加的任何 HTML 将以没有任何样式的方式呈现给用户。

然而,jQuery Mobile 也提供了一种方法来解决这个问题,即内置刷新每个转换的元素的功能。大多数元素都有一个与元素名称对应的内置函数;在我们的情况下,它是listview()函数。实际上,这种方法提供了向页面添加一个全新列表视图的能力。在我们的情况下,我们只关心刷新我们已经拥有的列表视图,因此我们只需添加refresh关键字,jQuery Mobile 就会将你的纯文本列表视图转换。试着省略最后一部分,看看 jQuery Mobile 能为你节省多少工作量。也许你应该将 jQuery Mobile 团队加入你的圣诞卡列表?

最后,我们必须实际调用我们的最新函数。在init()函数中添加以下行。然后重新加载页面,看看你的笔记如何加载。

app.checkForStorage();

查看笔记

此时,我们应该能够创建一个新的笔记,并且该笔记会立即显示在我们的列表视图中。事实上,列表视图中的行已经是链接,它们只是不起作用,让我们立即更改它。

使用 Live 函数

将以下行添加到bindings()函数中:

$('#notesList a').live('click',function(e){
e.preventDefault();
var href = $(this)[0].href.match(/\?.*$/)[0];
var title = href.replace(/^\?title=/,'');
app.loadNote(title);
});

这个新的绑定有一些新概念,所以让我们来解析一下。首先,我们不使用 bind 函数,而是使用 jQuery 的 live函数。区别在于 bind 仅适用于现有的页面元素,而 live 是主动的。它既适用于现有元素,也适用于应用绑定后创建的元素。

绑定的第二行和第三行可能看起来有点混乱,但它们只做一件事。它们从被点击的链接的 href 属性中检索 URL。我们在本章前面定义的 li 模板包含每个列表项的以下 URL:

#pgNotesDetail?title=LINK

displayNote() 函数运行后,URL 看起来像这样(将鼠标悬停在每个列表项上,以查看其在浏览器窗口底部的链接):

#pgNotesDetail?title=the-title-of-the-note

最后,我们告诉我们的代码运行一个名为 app.loadNote() 的新函数。

动态创建一个新页面

如果你还没有为我们的新 loadNote() 函数创建一个新的空函数块,现在就去做吧。记住,我们要传入要查看的笔记的标题,所以确保在 loadNote() 函数中添加这个作为参数:

app.loadNote = function(title){
}

然后将以下两行放在函数的顶部:

// get notes
var notes = app.getNotes();
// lookup specific note
var note = notes[title];

第一行检索我们的笔记对象,而第二行提取用户请求的具体笔记。下一个变量定义打破了我们之前在本章提到的关于混合 HTML 和 JavaScript 的规则,但每个规则都有例外。我们在这里定义它,而不是在我们的 JS 文件的标题,因为它只在这里需要。这仍然可以保持文档的组织性。

var page = '<div data-role="page" data-url="details" data-add-back- btn="true">\
<div data-role="header">\
<h1>Notekeeper</h1>\
<a id="btnDelete" href ="" data-href="http://ID data-role="button" class="ui-btn-right">Delete</a>\
</div>\
<div data-role="content"><h3>TITLE</h3><p>NOTE</p></div>\
</div>';

page 变量现在包含了显示"笔记详情"页面所需的所有 HTML。你还记得我们的应用只有一个 HTML 文件吗?我们实际上正在使用先前的 HTML 代码从头开始创建整个页面。其中也有一些值得指出的细节:

  • 默认情况下 jQuery Mobile 不为页面提供返回按钮。然而,你可以在每个页面上使用 data-add-back-btn="true" 属性来启用返回按钮,该属性需要添加在带有 data-role="page" 属性的任何 div 标签上。

  • data-url 属性是 jQuery Mobile 使用的标识符,以便可以跟踪生成的多个页面。

现在我们在一个变量中包含了整个页面,我们可以对它做什么?我们可以将它转换为 jQuery 对象。通过用 $() 将任何独立的 HTML 块包装起来,我们就可以将其转换为一流的 jQuery 对象:

var newPage = $(page);

然后我们可以取出新创建页面的 HTML,并用我们选择的笔记的值替换部分内容。

//append it to the page container
newPage.html(function(index,old){
return old
.replace(/ID/g,title)
.replace(/TITLE/g,title
.replace(/-/g,' '))
.replace(/NOTE/g,note)
}).appendTo($.mobile.pageContainer);

从版本 1.4 开始,jQuery 提供了在某些函数内部使用回调的选项。这些函数包括.html().text().css()等几个。该函数期望两个参数,第二个参数包含当前匹配元素中包含的完整 HTML。这意味着我们可以对newPage变量内包含的 HTML 进行微调,而不必完全更改它。太棒了,不是吗?

接下来,我们将整个newPage变量追加到当前页面的末尾,这里通过$.mobile.pageContainer常量引用。最后,因为我们取消了绑定中的默认点击操作,所以我们必须告诉链接执行一个操作,即将用户转到这个新创建的页面。jQuery Mobile 提供了内置的方法来实现这一点:

$.mobile.changePage(newPage);

现在是大揭示的时刻。如果你在浏览器中加载notekeeper.html,你应该能够在一个浏览器窗口内添加、显示和最终查看笔记。jQuery Mobile 是不是很棒?

动态创建新页面

删除笔记

回顾我们应用程序的需求,我们做得相当不错。我们编写了设置文档结构的 HTML 代码,允许我们添加笔记、显示笔记和查看笔记。剩下的只是删除一个笔记,它始于我们在bindings()函数中设置的最后一个绑定。现在就让我们添加它:

$('#btnDelete').live('click',function(e){
e.preventDefault();
var key = $(this).data('href');
app.deleteNote(key);
});

在这个绑定中,可能有一个对你来说是新的项目,那就是 jQuery 的.data()函数的使用。HTML 5 允许你通过使用以data-为前缀的属性直接在任何 HTML 元素上存储任意数据,而这种能力是 jQuery Mobile 功能的核心。任何你看到data-role="something"的地方,你都在看 HTML 5 数据的作用。此外,jQuery 允许你通过使用.data()函数并传入你想查看的项目的键来检索任何data-值。在上面的情况中,我们将笔记的标题存储到了查看页面中的删除按钮上的data-href属性中。因为我们正在添加的绑定是一个分配给删除按钮的点击处理程序,所以我们可以通过调用$(this).data('href')来检索笔记的标题。太棒了!

这将是我们在本章中添加的最后一个函数。你难过吗?这确实是一个令人难忘的时刻,但是在你成为一名成功的 jQuery Mobile 开发人员之后,我们可以怀着美好的回忆回顾这一刻。再次,我们从一个接受单个参数,即我们要删除的笔记的标题的空函数开始。

app.deleteNote = function(key){
}

随后是我们用于检索笔记的辅助函数的函数定义:

// get the notes from localStorage
var notesObj = app.getNotes();

然后我们删除笔记。你已经在我们审阅localStorage时看到了它的作用,所以应该对你来说很熟悉:

// delete selected note
delete notesObj[key];
// write it back to localStorage
localStorage['Notekeeper'] = JSON.stringify(notesObj);

删除备注紧随其后的是将剩余备注重新写入localStoragedeleteNote()函数中的最后两行将我们带回到应用程序的主页面,即备注列表。它们还会触发原始的checkForStorage()函数。

// return to the list of notes
$.mobile.changePage('notekeeper.html');
// restart the storage check
app.checkForStorage();

最后一行可能对你来说有些奇怪,但请记住,我们事先不知道是否还有任何备注。运行存储检查允许我们显示占位文本,以防没有备注。养成这种习惯很好,因为它有助于减少我们的应用程序出现错误的可能性。

摘要

在本章中,我们使用 jQuery Mobile 构建了一个活生生的移动应用程序。停下来给自己一个赞。我们通过列出应用程序的要求、构建线框图和编写 HTML 的过程来完成了这一过程。我们学习了关于 HTML 5 的localStorage,使用模板进行文本替换,以及 jQuery Mobile 的一些更酷的功能,包括动态添加和刷新页面上的元素。

在下一章中,你将学习如何为 jQuery Mobile 设置全局配置选项,如何在 jQuery Mobile 中使用其他 API 来处理表单和内容块。

第十一章:增强 jQuery Mobile

在本章中,我们将学习如何增强 jQuery Mobile,如何通过创建主题和图标来改善应用程序的外观和功能,使您的移动应用程序真正脱颖而出。

在本章中,我们将:

  • 了解 jQuery Mobile 的构建模块

  • 使用 ThemeRoller 创建我们自己的 jQuery Mobile 主题

  • 为我们的应用设计并实现自定义图标

有什么可能?

当许多开发人员第一次使用 jQuery Mobile 时,他们的反应是对其易于实现丰富、引人入胜的移动网站感到敬畏。它轻松将普通 HTML 转换为美观、可用的按钮和列表视图。表单元素非常容易处理。jQuery Mobile 团队甚至随包提供了五种设计良好、吸引人的主题和 18 个常用图标。他们甚至建立了一个工具,供我们使用来构建自己的主题;ThemeRoller

在使用 jQuery Mobile 一段时间后,开发人员可能会问"我还可以用这个做什么别的吗?" 就像 60 年代和 70 年代的肌肉车一样。它们已经很棒了,但调整者和发烧友还想做更多。如果你有这种心态,那么本章就是为你准备的。

关于 jQuery Mobile 的美妙之处在于,因为它全部是普通的 CSS 和 HTML,我们几乎可以用很少的工作做任何我们想做的事情。在本章中,我们将使用 ThemeRoller 为 jQuery Mobile 从头开始创建自己的主题。我们将设计按钮并编写必要的 CSS 代码来实现低分辨率和高分辨率版本。我们还将探讨如何扩展 jQuery Mobile 中已有的样式和类,并制作出不同和独特的东西。那么,让我们开始吧?

jQuery Mobile 的视觉构建模块

正如你已经看到的,jQuery Mobile 非常用户友好且外观令人愉悦。它充分利用了圆角、微妙的渐变、投影来突出元素与周围环境的区别,以及其他技巧,这些技巧多年来一直是平面设计师在印刷品中使用的。但在网络上,这些效果只能通过使用图片或复杂且支持不佳的插件和小程序来实现。

随着 Web 2.0 和 CSS 3 的出现,所有这些选项都已提供给我们,即普通的网页开发人员。只需记住,权力越大,责任越大。jQuery Mobile 基于渐进增强的原则运作。这个繁琐的短语只是意味着您应该为理解这些增强的浏览器开发,并为理解它们的浏览器提供增强。

幸运的是,这些样式上的附加几乎纯粹是装饰性的。如果浏览器不理解border-radius声明,那么它将简单地显示方形边角。渐变和阴影也是如此。虽然 jQuery Mobile 默认为您的应用程序添加这些效果,但了解如何自己添加它们也是值得的。

圆角

圆角可以是最优雅和吸引人的效果之一,也是最简单的效果之一。开发人员需要了解此效果和其他效果的一些注意事项。虽然 W3C 推荐了border-radius的规范,但事实证明,每个主要浏览器制造商对其支持的方式略有不同。最终结果是相同的,但路径不同。让我们来看一下最基本的border-radius声明,以下屏幕截图是其结果:

#rounded {
border-radius: 10px;
}

圆角

您还可以选择仅使某些角变圆,以及调整值,使角不是完美的四分之一圆。让我们看几个更多的示例。以下代码片段和屏幕截图演示了一个示例,以获得两个圆角:

#topLeftBottomRight {
border-radius: 15px 0 15px 0;
}

圆角

以下代码片段和屏幕截图演示了一个示例,以获得一个圆角:

#bottomLeft {
border-top-left-radius: 100px 40px;
}

圆角

遗憾的是,目前情况并不像这么简单。因为每个浏览器供应商都对此效果有自己独特的渲染,像谷歌或 Mozilla 这样的软件开发者已经开始创建自己的版本,通常称为厂商前缀。为了使先前的样式声明具有最广泛的覆盖范围,您需要添加以下代码行:

#rounded {
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
#topLeftBottomRight {
-webkit-border-top-left-radius: 15px;
-webkit-border-bottom-right-radius: 15px;
-moz-border-radius-topleft: 15px;
-moz-border-radius-bottomright: 15px;
border-top-left-radius: 15px;
border-bottom-right-radius: 15px;
/* mozilla and webkit prefixes require you to define each corner individually when setting different values */
}
#bottomLeft {
-webkit-border-top-left-radius: 100px 40px;
-moz-border-radius-topleft: 100px 40px;
border-top-left-radius: 100px 40px;
}

应用投影阴影

CSS 中的投影阴影有两种形式:文本阴影(应用于文本)和框阴影(应用于其他所有内容)。与border-radius一样,如果您查看 W3C 规范,投影阴影也相对简单。

使用 text-shadow

让我们先看一下text-shadow

p {
text-shadow: 2px 2px 2px #000000;
/* horizontal, vertical, blur, color */
}

使用 text-shadow

该属性还通过在逗号分隔的列表中添加附加声明来支持多个阴影,如以下代码片段和输出所示:

p {
text-shadow: 0px 0 px 4px white,
0 px -5px 4px #ffff33,
2px -10px 6px #ffdd33,
-2px -15px 11px #ff8800,
2px -25px 18px #ff2200
}

使用 text-shadow

border-radius属性不同,text-shadow属性不需要厂商前缀。这并不意味着所有浏览器都支持它,这只是意味着支持此属性的浏览器会按预期显示,而不支持此属性的浏览器则会看不到任何内容。

使用 box-shadow

Box-shadow 遵循与 text-shadow 非常相似的模型,只是增加了一个关键词inset,允许内部阴影。让我们看一些示例。第一个示例显示了标准外部阴影:

#A {
-moz-box-shadow: -5px -5px #888888;
-webkit-box-shadow: -5px -5px #888888;
box-shadow: -5px -5px #888888; /* horizontal, vertical, color */
}
#B {
-moz-box-shadow: -5px -5px 5px #888888;
-webkit-box-shadow: -5px -5px 5px #888888;
box-shadow: -5px -5px 5px #888888;
/* horizontal, vertical, blur, color */
}
#C {
-moz-box-shadow: 0 0 5px 5px #888888;
-webkit-box-shadow: 0 0 5px 5px #888888;
box-shadow: 0 0 5px 5px #888888;
/* horizontal, vertical, blur, spread, color */
}

使用 box-shadow

现在,在以下示例中,看看这些内部阴影。很酷,对吧?

#D {
-moz-box-shadow: inset -5px -5px #888888;
-webkit-box-shadow: inset -5px -5px #888888;
box-shadow: inset -5px -5px #888;}
#E {
-moz-box-shadow: inset -5px -5px 5px #888888;
-webkit-box-shadow: inset -5px -5px 5px #888888;
box-shadow: inset 0px 0px 10px 20px #888888;
}
#F {
-moz-box-shadow: inset -5px -5px 0 5px #888888;
-webkit-box-shadow: inset -5px -5px 0 5px #888888;
box-shadow: inset 0 0 5px 5px #888888;
}

使用 box-shadow

值得一提的是,阴影和文本阴影都可以使用不常用的 rgbrgba 声明来设置它们的颜色。这使得开发者可以使用更熟悉的 RGB 值的约定来设置颜色。rgba 声明还允许设置颜色的不透明度从 01。修改的代码如下所示:

#opacity {
box-shadow: inset 0 0 5px 5px rgb(0,0,0); /* black */
}
#transparent {
box-shadow: inset 0 0 5px 5px rgba(0,0,0,.5);
/* black with 50% transparency */
}

CSS 渐变

CSS 渐变是向你的网站添加美感和冲击力的绝佳方式。选项包括线性渐变(从右到左,从上到下等等),以及径向渐变(从中心向外)。默认情况下,渐变由起始颜色和结束颜色组成。CSS 渐变也可以使用颜色停止来添加额外的色调。

然而,老版本浏览器对 CSS 渐变的支持并不完美,特别是在 Internet Explorer 中。好消息是,有办法解决 IE 的问题,可以让开发者可靠地在开发中使用渐变。坏消息是,支持该功能的代码非常复杂。让我们来看一下最简单的渐变声明:

div {
width: 500px;
height: 100px;
background: linear-gradient(left, #ffffff 0%,#000000 100%);
}

渐变声明可能相当复杂,所以让我们用一个信息图来分解它:

CSS 渐变

现在关键来了......在撰写本文时,没有浏览器支持使用实际属性的 W3C 规范。让我们来看一下支持多个浏览器的代码,你会更加喜欢 jQuery Mobile:

div {
width: 500px;
height: 100px;
border: 1px solid #000000;
/* Old browsers */
background: #ffffff;
/* FF3.6+ */
background: -moz-linear-gradient(left, #ffffff 0%, #000000 100%);
/* Chrome10+,Safari5.1+ */
background: -webkit-linear-gradient(left, #ffffff 0%,#000000 100%);
/* Opera 11.10+ */
background: -o-linear-gradient(left, #ffffff 0%,#000000 100%);
/* IE10+ */
background: -ms-linear-gradient(left, #ffffff 0%,#000000 100%);
/* W3C spec*/
background: linear-gradient(left, #ffffff 0%,#000000 100%);
/* IE6-9 */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#000000',GradientType=1 );
}

CSS 渐变

你可以通过添加额外的逗号分隔声明来将多种颜色添加到你的渐变中。例如,以下代码:

div {
width: 500px;
height: 100px;
border: 1px solid #000000;
/* Old browsers */
background: #ffffff;
/* FF3.6+ */
background: -moz-linear-gradient(left, #ffffff 0%, #000000 35%, #a8a8a8 100%);
/* Chrome10+,Safari5.1+ */
background: -webkit-linear-gradient(left, #ffffff 0%,#000000 35%,#a8a8a8 100%);
/* Opera 11.10+ */
background: -o-linear-gradient(left, #ffffff 0%,#000000 35%,#a8a8a8 100%);
/* IE10+ */
background: -ms-linear-gradient(left, #ffffff 0%,#000000 35%,#a8a8a8 100%);
/* W3C */
background: linear-gradient(left, #ffffff 0%,#000000 35%,#a8a8a8 100%);
/* IE6-9 */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#a8a8a8',GradientType=1 );
}

结果显示在以下渐变中:

CSS 渐变

正如你在阅读最近几页后可能猜到的那样,jQuery Mobile 为你做了很多繁重的工作。它不仅添加了漂亮的渐变页面背景,还必须跟踪可能阻止甜美的阴影出现的所有浏览器怪癖。当我们进入下一节时,你可能会对它处理主题和色板的方式更为印象深刻。

jQuery Mobile 主题的基础知识

在 jQuery Mobile 中进行主题设置对开发者来说是直接简单易用的,但是在幕后却相当复杂。幸运的是,很少有时候你需要知道为你所做的一切。然而,花点时间了解它的工作原理也是值得的。

jQuery Mobile 的开箱即用版本包含了一个由五种颜色色板组成的主题集,每个与 A-E 中的一个字母相关联。该主题包含了一系列基本的 CSS 类,可以随意应用于几乎任何元素,并且它们包含了宽度、高度、边框半径、阴影的全局设置。各个色板包含了有关颜色、字体等方面的具体信息。

可以将额外的样本添加到来自 F-Z 的五个原始样本中,或者可以随意替换或覆盖原始样本。这个系统允许共有 26 个不同的样本,从而可以产生数百万种主题颜色、样式和图案的可能组合。您可以通过添加一个data-theme属性和所需主题的字母来将 jQuery Mobile 主题应用于所选元素:

jQuery Mobile 主题的基础知识

开发人员通常会选择使用data-theme属性方法来应用样式,但也可以直接将 CSS 类名附加到页面元素以获得更精细的控制。有几个主要前缀允许这种灵活性。

条(.ui-bar-?)

bar 前缀通常应用于标题、页脚和其他重要区域:

条(.ui-bar-?)

内容块(.ui-body-?)

内容块通常应用于预期出现段落文本的区域。它的颜色有助于确保文本颜色与其放置在其上的文本颜色之间具有最大的可读性:

内容块(.ui-body-?)

按钮和列表视图(.ui-btn-?)

按钮和列表视图是 jQuery Mobile 库中最重要的两个元素,您可以放心地知道团队花了很多时间来完善它们。.ui-btn前缀还包括用于上升、下降、悬停和活动状态的样式:

按钮和列表视图(.ui-btn-?)

混搭样本

jQuery Mobile 中主题的一个好处是,除非另有说明,否则子元素会从其父元素继承。这意味着,如果您在页眉或页脚栏中放置一个没有自己data-theme属性的按钮,该按钮将使用与其父元素相同的主题。酷,对吧?

混搭样本

在一个元素中使用一个样本并在另一个元素的子元素中使用另一个样本也是完全可以接受甚至是鼓励的。这可以帮助元素更加突出,或者与应用程序的不同部分匹配,或者开发人员选择的任何其他原因。这是可能的,而且更重要的是,它很容易。只需将按钮(或其他元素)放置在页眉栏内,并为其分配自己的data-theme属性:

混搭样本

全站活动状态

jQuery Mobile 还为所有元素应用了一个全局 活动 状态。此活动状态用于按钮、表单元素、导航等任何需要指示当前选择的地方。更改此颜色值的唯一方法是通过 CSS 设置(或覆盖)它。活动状态的 CSS 类名是.ui-btn-active

全站活动状态

默认图标

jQuery Mobile 集中包含了 18 个图标,涵盖了开发人员广泛的需求。图标集是白色的透明图标,jQuery Mobile 在半透明的黑色圆圈上覆盖以提供与所有样品的对比度。要添加图标,请使用所需图标的名称指定 data-icon 属性:

默认图标

jQuery Mobile 还提供了使用 data-iconpos="[top, right, bottom, left]" 属性在按钮的顶部、右侧、底部或左侧放置图标的功能,其中左侧是默认位置。开发人员还可以通过指定 data-iconpos="notext" 来仅显示图标而不显示文本:

默认图标

部署自定义图标也是可能的,将在本章后面进行讨论。

创建和使用自定义主题

我们已经讨论过 jQuery Mobile 中主题设置的强大功能。它使得用简单而优雅的样式开发丰富的移动网站变得轻而易举。更强大的是,您可以创建自己的样品库,以使您的应用程序或网站真正独特。可以通过以下两种方式之一来处理自己的主题开发:

  1. 下载并打开现有的 jQuery Mobile CSS 文件,并按自己的意愿进行编辑。

  2. 将您的网络浏览器指向 jQuery Mobile 的 ThemeRoller:jquerymobile.com/themeroller/

我们将专注于第二种选择,因为说实话,为什么要费劲地浏览所有的 CSS 呢?您可以在 10 分钟内使用指点、点击和拖放的方式创建一个充满样品的新主题。让我们了解一下 ThemeRoller 是什么。

什么是 ThemeRoller?

ThemeRoller for jQuery Mobile 是为 jQuery UI 项目编写的一个基于 Web 的应用程序的扩展。它允许用户使用拖放颜色管理在几分钟内快速组装一个充满样品的主题。它具有交互式预览功能,因此您可以立即看到您的更改如何影响您的主题。它还具有内置的检查器工具,可帮助您深入了解细节(如果您需要)。它还集成了 Adobe® Kuler®,一个颜色管理工具。您可以在完成后下载您的主题,可以通过自定义 URL 与他人共享,也可以重新导入过去的主题进行最后的微调。它是一个强大的工具,是 jQuery Mobile 的完美补充。

五个默认样品的特点之一是,jQuery Mobile 团队花了相当多的时间来改善可读性和可用性。这些样品的对比度从最高(A)到最低(E)不等。在单个主题中,对比度最高的区域是页面上最突出的区域。这包括页眉(和列表视图的标题)和按钮。在创建自己的主题时,牢记这一点是个好主意。我们总是希望专注于应用程序的可用性,对吗?如果由于颜色选择不当而无法阅读,那么漂亮的应用有什么用呢?

使用 ThemeRoller

当你加载 ThemeRoller 时,第一件事就是看到一个看起来很漂亮的启动屏幕,然后是一个有用的入门屏幕:

使用 ThemeRoller

入门屏幕上有一些有用的提示,所以在点击开始按钮之前一定要看一眼:

使用 ThemeRoller

当所有的启动屏幕都结束后,你将会看到主要界面:

使用 ThemeRoller

ThemeRoller 分为四个主要区域:预览、颜色、检查员和工具。每个区域都包含了我们需要审查的重要功能。我们将从预览部分开始。

预览

除非你正在加载现有主题,否则预览区域将呈现三个完整、相同且交互式的 jQuery 移动页面,上面装满了各种小部件:

预览

将鼠标移到上面,你会发现每个页面都是功能性的。每个页面的页眉包含了一个字母,指示了哪个色板控制了它的外观。

颜色

在页面顶部,你会看到一系列颜色芯片,以及两个滑块控件和一个切换按钮。右边更远处,你会看到另外十个颜色芯片,应该是空白的。这些专门用于最近使用的颜色,直到你选择了颜色为止:

颜色

在颜色芯片下面有两个标有亮度饱和度的滑块。亮度滑块调整了一系列色板的明亮和暗色调,而饱和度使颜色更加鲜艳或柔和。综合在一起,用户应该能够近似于他们选择的任何颜色。要使用 Kuler®的颜色,点击标有Adobe Kuler 色板的文本链接。

每个颜色芯片都可以拖放到预览区域内的任何元素上。这使得色板集的开发非常容易。请注意,许多 jQuery Mobile 样式重叠,比如页顶的标题栏与列表视图的标题接收到相同的样式。根据需要调整颜色,然后将每个色片拖放到页面上的元素上。请记住,每个单独的页面都是自己的色板,所以在选择混合颜色时要小心。

检查员

界面最左侧是检查员面板,分为两部分。顶部包含了一系列按钮,允许开发者下载他们的主题,导入现有主题,并分享他们的主题链接。还有一个帮助链接给那些没有购买这本书的人:

检查员

底部区域包含一系列标有全局, A, B, C和**+**的标签。每个标签都包含一个手风琴面板,其中包含了单个色板的所有值,除了全局标签,它适用于所有色板。

选择全局选项卡,然后点击活动状态,手风琴面板将展开,显示整个主题的活动状态设置。选项包括文本颜色、文本阴影、背景和边框。在全局更改值会导致每个当前(和将来的)色板都反映新的设置。

可以通过两种方式向主题添加额外的色板。点击检查器顶部的**+选项卡会在你的主题中的最后位置添加一个新的色板。你也可以通过点击预览区域底部的添加色板按钮来添加一个新的色板。通过选择要删除的色板的选项卡,然后单击该色板名称右侧的删除**链接来删除色板。请注意,从堆栈顶部删除色板会导致其余色板被重命名。

工具

页面顶部有一系列按钮。这些按钮允许你执行各种任务,我们马上就会介绍,但首先,仔细看看这些按钮本身:

工具

你会注意到以下按钮:一个切换按钮,允许你在当前 1.1 版本和 1.0.1 版本之间切换,撤销/重做,以及检查器的切换按钮。将此切换打开可以检查预览区域中的任何小部件。将鼠标悬停在小部件上会用蓝框突出显示该元素。单击该元素将导致检查器区域的手风琴菜单展开,显示特定于该元素的设置。

还有四个额外的按钮,允许你下载你的主题,导入或升级先前创建的主题,与他人分享你的主题,以及一个帮助按钮。

创建 Notekeeper 的主题

现在我们熟悉了 ThemeRoller 的界面,那么我们何不继续创建我们的第一个主题呢?与其在抽象中构建一个主题,不如创建一个我们实际将在之前构建的 Notekeeper 应用程序中使用的主题。让我们简单地开始,通过修改 jQuery Mobile 随附的现有主题之一。团队很友好地让用户导入默认主题作为新主题的起点,所以我们首先要去那里。点击窗口左上角的导入按钮,然后你会得到一个框,允许你粘贴现有主题的内容:

为 Notekeeper 创建主题

在右上角点击链接,适当命名为导入默认主题来导入默认主题。在文本区域填充 CSS 后,点击导入。预览区域将重新加载并显示从 AE 的色板。

我们将集中精力改变白色色板 D,因为它最接近我们的最终目标。由于我们更愿意使用色板 A 作为名称,让我们删除其他色板,以便只剩下 D。请记住,当你删除色板 A 时,ThemeRoller 会将其他色板重命名。这意味着当你删除色板 A 时,色板 B 变成 A,色板 C 变成 D,依此类推。

继续进行,直到原来是D的样本现在位于A位置。最后,删除样本 B(原来是样本 E),这样我们就只剩下样本 A:

为 Notekeeper 创建主题

这个样本看起来不错,但有点单调。让我们通过将页眉改为漂亮的绿色来注入一点色彩。确定任何元素的哪些值应该更改的最简单方法是使用检查器。在顶部切换检查器到On,然后点击主题 A 的页眉的任何地方。如果左侧选择了 A 选项卡,并且页眉/页脚栏面板展开,你就会知道你做对了:

为 Notekeeper 创建主题

你可以通过几种方式之一改变颜色。你可以直接将一个颜色芯片从顶部拖到背景上。你也可以将一个颜色芯片拖到输入字段上。最后,你可以手动输入值。注意,当你点击包含颜色值的字段时,你会看到一个时髦的颜色选择器。继续,并将此面板中的输入字段中的值更改为上一张截图中显示的值。

看起来不错,但现在主题活动状态的蓝色与我们的绿色不搭配。使用检查器工具,在 On/Off 切换栏的On部分单击一次。这将导致全局选项卡内的活动状态面板展开。我们将把蓝色改成一个漂亮的暖灰色。全局面板现在应该看起来类似于以下截图:

为 Notekeeper 创建主题

我们新主题唯一的不足之处是段落顶部的蓝色文本链接。回到我们可靠的检查器,让我们直接点击链接,这将展开内容主体面板,位于A选项卡内。现在,对于那些已经熟悉 CSS 的人来说,你知道你不能简单地改变链接颜色而不改变悬停状态,visited:hover 和活动状态。问题在于没有选项可以进行这些更改,但是 ThemeRoller 为你提供了解决方案。点击链接颜色输入字段右侧的**+**以显示其他选项,然后根据以下截图更改颜色:

为 Notekeeper 创建主题

就是这样。随时在探索检查器区域时进行其他主题的额外更改。无论你喜欢什么,都可以更改,现在只是位和字节而已。但请记住,目前没有撤销选项。如果你真的喜欢某些东西,请考虑写下值,以免丢失它们,或者将其导出为它是什么。说到…

导出你的主题

在我们实际导出主题之前,必须注意一件事。还记得带有“有用”信息的闪屏页面吗?事实证明,有一条不是建议,而是要求的。

我们建议用至少 3 个样本(A-C)来构建主题

为了使我们的主题正确应用到我们的 Notekeeper 应用程序中,我们需要将我们的单个色板(字母 A)复制到色板 BC 中。幸运的是,这是一件很容易的事情。在检查器顶部选择 A 选项卡,然后点击两次 + 选项卡。你应该会看到三个相同的色板,现在我们完成了。

现在我们已经完成了我们的主题,我们将导出它以在我们的 Notekeeper 应用中使用。这是一个简单的过程,从页面中间顶部的下载主题按钮开始。你将看到一个框,允许你为主题命名,一些关于如何应用主题的信息,以及一个标记为下载 Zip的按钮。在将我们的主题命名为 Notekeeper 后,点击下载 Zip按钮,你将在下载文件夹中收到一个美味的小东西。

解压缩 ZIP 文件的内容,你将看到以下的目录结构:

  • index.html

  • themes/

    • Notekeeper.css(你的主题的未压缩版本)

    • Notekeeper.min.css(压缩版本。在生产中使用此版本)

    • images/

      • ajax-loader.gif

      • icons-18-black.png

      • icons-18-white.png

      • icons-36-black.png

      • icons-36-white.png

树顶部的 HTML 文件包含了如何实现你的主题的信息,以及一些小部件来确认主题是否有效。示例文件中的所有链接都是相对的,因此你应该能够将其拖放到任何浏览器窗口中并查看结果。

关于主题的下载和实施的一些注意事项:

  1. jQuery 团队之所以向你提供此 ZIP 文件中的按钮图标是有原因的。主题要求这些图像与 CSS 文件相关联。这意味着,除非你已经在使用默认主题,否则在将你的主题上传到网站时,你还需要包含图像文件夹,否则图标将不会显示出来。

  2. 牢记你的主题的未压缩版本。虽然由于体积原因你不希望在生产中使用它,但是如果你希望在 ThemeRoller 中编辑它,你将需要它。截止到撰写本文时,ThemeRoller 无法导入被压缩的 CSS 文件。

创建和使用自定义图标

我们已经看到了使用 ThemeRoller 向 jQuery Mobile 添加自己的主题是多么简单。现在我们将通过创建一个自定义图标为我们的 Notekeeper 应用增添一些趣味。本节中的说明将专门针对 Photoshop,但任何能够导出透明 PNG 文件的图形应用程序都应该是可以接受的。

CSS 精灵

在我们创建和使用图标之前,我们应该先了解 jQuery Mobile 如何使用图标并应用它们。在你刚刚创建的主题中有几个图像文件(themes/images)。打开 icons-18-black.pngicons-36-black.png,在你选择的图形编辑器中将它们放大到 400% 或更多,你应该会看到与以下图像非常相似的东西:

CSS 精灵

当打开这些文件时,你可能会注意到每个图像都包含所有图标。这是因为 jQuery Mobile 利用了一种称为 CSS 雪碧图 的技术,它本身利用了 CSS 允许开发人员通过指定其容器内的位置来 裁剪 背景图像的事实,并隐藏通常显示在该容器外部的背景的任何其他部分。它的主要优点包括以下几点:

  1. 减少浏览器发出的请求数量。请求越少,通常意味着页面加载速度会更快。

  2. 图片位置居中。所有图标都可以在一个位置找到。

以下截图是该技术的简单说明:

CSS 雪碧图

浏览器始终从图像的左上角引用图像。在 CSS 语言中,即 0,0。要实现此效果,您将背景图像设置在一个容器上,然后简单地调整 XY 坐标,直到图像的位置与您的设计匹配。然后设置容器的溢出以裁剪或隐藏图像的其余部分。请记住,您正在 移动 图像到左侧,因此对于 X 位置,您将使用负数。使用前面的示例作为参考,以下代码片段用于实现此效果:

<html>
<head>
<title></title>
<style>
div {
background: url("icons-36-black.png");
background-position: -929px 4px;
background-repeat: no-repeat;
border: 1px solid #000000;
height: 44px;
overflow: hidden;
width: 44px;
}
</style>
</head>
<body>
<div></div>
</body>
</html>

设计你的第一个图标

我们只会创建一个单一图标,所以我们不需要图标周围的所有空白空间。让我们先决定我们想要描绘什么。我们的应用叫做 Notekeeper,它创建笔记。也许一个描绘纸张的图标会起作用?这样做的额外好处是在小尺寸下相对容易表示。在你选择的图像编辑器中创建一个新文档,尺寸为 36x36 像素,分辨率为 72 dpi。将其命名为 notekeeper-icon-black-36.png

设计你的第一个图标

尽管文档的尺寸是 36x36 像素,但图标的有效区域只有 22x22 像素。这是为了与 jQuery Mobile 团队提供的图标保持一致,以确保我们的图标看起来不奇怪。为了更容易地保持在线条内,使用矩形选择工具在 22px 处绘制一个正方形,然后将其位置设置在文档的顶部边缘和左侧边缘各 7px 处。

接下来,沿着每条边绘制指南线,使得你的文档看起来类似以下截图:

设计你的第一个图标

在绘制图标时,你需要考虑所描绘事物的尺寸和属性。你不可能表现出所有细节,但你需要传达事物的精神。一张纸比它宽高比更高,并且上面有线条。让我们从这两点开始,看看我们能得出什么。此套图标中的其他图标都有较粗的感觉,以便它们能在背景中显眼。让我们填充一个实心形状,然后删除页面的线条,以便图标具有相同的粗糙感。我们将用黑色绘制线条,以便它们在书中更好地打印出来,但我们的图标需要是白色的。确保你相应调整你的设计:

设计你的第一个图标

这个图标似乎符合我们所有的标准。它比宽高比更高,并且像纸一样有线条。它还有一个活泼的小翻页,给它一些态度。这不就是每个人在他们的纸图标上寻找的东西吗?确保图标的线条是白色的,然后保存它。jQuery Mobile 图标已保存为透明的 PNG-8 文件。这类似于 GIF 格式,但不是必需的。如果你愿意,可以使用透明 GIF 或透明 PNG-24。

当我们创建第一个图标时,我们创建了高分辨率版本。 为了简洁起见,我们将快速浏览创建低分辨率图标的步骤:

  1. 创建一个新的图像文档,尺寸为 18x18 像素。将其命名为notekeeper-icon-18

  2. 这个图标的活动区域将是 12x12 像素。绘制一个 12px 的正方形选择区域,然后将其位置设置为距离顶部 3px,距离左侧 3px。

  3. 绘制你的辅助线,然后草图出图标,使用以前的版本作为参考。在这么小的空间里画图标确实很难,不是吗?

  4. 你的最终结果应该类似于以下截图:设计你的第一个图标

将两个图像与你的 Notekeeper 主题一起保存并关闭 Photoshop。

高分辨率和低分辨率

分辨率是可以显示在给定区域内的点数或像素数。来自网络世界的你们通常将所有东西都以 72dpi 进行测量,因为大多数显示器都显示这个分辨率。如果你有很多移动设备的经验,你可能知道每个设备的分辨率可能与其旁边的设备不同。这样做的问题在于,分辨率更高的设备在屏幕上只是显示更多的像素。这意味着在高分辨率屏幕上显示的图像将比在低分辨率屏幕上显示的同一图像要小。

jQuery Mobile 通过为高分辨率和低分辨率设备提供两个版本的每个图标以及两套代码来解决此问题。在下一节中,我们将为我们的 Notekeeper 应用程序应用自定义主题和自定义图标。

更新 Notekeeper 应用程序

是时候将所有这些松散的端点联系在一起了。我们有一个使用 ThemeRoller 构建的自定义主题,我们有我们漂亮的自定义图标,现在是时候将所有的片段组合在一起了。您需要以下内容来完成:

  1. 你在 Notekeeper 章节末尾完成的代码。

  2. 您在本章前面创建的自定义主题。

  3. 您的自定义图标;白色;分别为 18px 和 36px 尺寸。

添加我们的自定义主题

让我们从最简单的部分开始。添加我们的自定义主题非常简单。打开notekeeper.html(在您的浏览器中,并在您选择的文本编辑器中)。查找<head>标签并添加以下行:

<title>Notekeeper</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/ latest/jquery.mobile.min.css" />
<link rel="stylesheet" href ="themes/Notekeeper.min.css" />
<link rel="stylesheet" href ="styles.css" />
<script src="img/jquery-1.6.4.js"></script>
<script src="img/jquery.mobile.min.js"></script>
<script src="img/application.js"></script>

第一行新加入了我们创建的新主题。第二行目前指向一个缺失的文件(因为我们还没有创建它)。即使像 jQuery Mobile 这样拥有丰富主题系统的系统,我们仍然会为各种事物编写一些自定义 CSS。styles.css是我们将放置各种样式的地方,特别是我们自定义图标的定义。

顺便说一句,您可以重新加载浏览器窗口,看看我们的新主题是如何运行的。是不是很漂亮?当我们的自定义图标出现时,它会看起来更加漂亮。

添加我们的自定义图标

接着,在你的 Notekeeper 应用代码的根目录下创建styles.css,然后打开它。我们将首先添加我们的 18px 图标的声明。它是低分辨率的,将在您的桌面浏览器中看到。高分辨率图标目前只在 iPhone 4 和 iPhone 4S 上显示。

要添加我们的自定义图标,我们遵循 jQuery Mobile 设定的模式。它使用.ui-icon前缀为按钮和其他元素应用图标。这意味着为了使我们的图标在框架中起作用,我们必须将我们的 CSS 类命名为以下内容:

.ui-icon-notekeeper-note {
background-image: url("themes/images/notekeeper-icon-white-18.png");
}

然后,将图标添加到我们的“添加笔记”按钮中就像添加一个data-icon属性一样简单,如下所示的代码行所示:

<div class="ui-block-b">
<input id="btnAddNote" type="button" value="Add Note" data- icon="notekeeper-note" />
</div>

请记住,字符串notekeeper-note可以是任何东西,只要它与您之前创建的 CSS 类的后半部分匹配即可。最后让我们为我们的应用程序添加剩下的一部分,即高分辨率图标。

jQuery Mobile 的一个显著特点是它对媒体查询的支持。媒体查询本质上允许您查询给定设备的各种信息,基于其媒体类型:屏幕、打印、电视、手持设备等。对这个查询的回答允许开发人员对 CSS 代码进行分支,并为桌面浏览器(屏幕)显示页面的一种方式,为电视(电视)显示页面的另一种方式。对于我们的图标,我们想要询问任何视图设备,其类型为屏幕,是否支持一个名为-webkit-min-device-pixel-ratio的属性,以及该属性的值是否为2。在低分辨率图标的声明之后,将以下行添加到styles.css中:

@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.ui-icon-notekeeper-note {
background-image: url("themes/images/notekeeper-icon-white-36.png");
background-size: 18px 18px;
}
}

除了媒体查询代码之外,这个唯一与众不同的是background-size属性。它允许开发人员指定给定背景应按指定大小(18x18 像素)缩放,而不是其原始大小 36x36 像素。由于 iPhone 4 和 4S 上的分辨率恰好是低分辨率设备的两倍,这意味着我们将两倍的像素打包到与较小图标相同的空间中。最终结果是图标看起来更加清晰和锐利。如果您拥有其中一款设备,请将您的代码上传到服务器并查看它。您的耐心将会得到回报。

总结

在本章中,我们学习了对于 jQuery Mobile 体验至关重要的高级 CSS 技术,以及 jQuery Mobile 如何利用它们为最终用户提供丰富的界面。我们深入探讨了 jQuery Mobile 主题化的基础知识以及它的工作原理。我们使用 ThemeRoller 工具构建了一个自定义主题,用我们自己的双手创建了一个自定义图标,并学习了如何将所有这些东西联系在一起并在我们的应用程序中实现它们。

在下一章中,您将学习如何运用过去 11 章学到的原则,并创建一个可以在 iOS 和 Android 平台上运行的本机应用程序(以及其他几个平台),使用 Phonegap 开源库。

第十二章:创建原生应用程序

在本章中,我们将看看如何将基于 jQuery Mobile 的 Web 应用程序转化为移动设备的原生应用程序。我们将讨论 PhoneGap 框架以及它如何允许您利用设备的硬件。

在本章中,我们将:

  • 讨论 PhoneGap 项目及其功能

  • 演示如何使用 PhoneGap 的构建服务来创建原生应用程序

HTML 作为原生应用

对大多数人来说,在诸如 Android 或 iOS 之类的平台上创建原生应用程序需要学习全新的编程语言。虽然学习新语言并扩展技能的范围总是很好,但如果您可以利用现有的 HTML 技能并在移动设备上本地使用它们,那岂不是很酷?

幸运的是,正好有这样一个平台。PhoneGap (www.phonegap.com)是一个开源项目,允许您使用 HTML 页面创建原生应用程序。这段代码完全免费,可用于开发 iOS(iPhone 和 iPad)、Android(手机和平板电脑)、Blackberry、WebOS、Windows Phone 7、Symbian 和 Bada 的应用程序。PhoneGap 通过在原生环境中创建一个项目并指向一个 HTML 文件来工作。一旦设置好,您可以利用现有的 HTML、CSS 和 JavaScript 技能来创建应用程序的用户界面和功能。

更好的是,PhoneGap 还为您的 JavaScript 代码提供了额外的 API。这些 API 允许:

  • 加速器:允许您的代码检测设备上的基本运动

  • 摄像头:允许您的代码与相机配合使用

  • Compass:让您访问设备上的指南针

  • 联系人:提供基本的搜索和联系人创建支持

  • 文件:读/写访问设备存储

  • 地理定位:提供一种检测设备位置的方式

  • 媒体:允许基本的视频/音频捕获支持

  • 网络:确定设备的网络连接设置

  • 通知:创建通知的简单方式(通过弹出窗口、声音或振动)

  • 存储:访问一个简单的 SQL 数据库

通过使用这些 API,您可以将普通的 HTML 网站转化为功能强大的类原生应用程序,用户可以下载并安装到他们的设备上。

在我们继续之前,让我们简要了解一下PhoneGap。PhoneGap 是 Apache 目前处于孵化状态的开源项目。它已更名为Cordova。你可能会听到人们用这两个名字来指代它。在写这本书的时候,大多数人仍然把这个项目称为 PhoneGap,这也是我们将使用的术语。重要的是要记住,PhoneGap 是免费且开源的!

在我们继续之前,让我们快速讨论一下 PhoneGap 应用程序与原生应用程序的比较。在大多数情况下,原生应用程序的性能比使用 PhoneGap 创建的应用程序要快。PhoneGap 并不意味着取代原生开发。但通过允许您使用现有技能并一次部署到多个平台,其好处可能远远超过对性能的任何关注。

使用 PhoneGap

创建一个 PhoneGap 项目有两种主要方法。人们使用 PhoneGap 的主要方式是首先使用他们正在为之构建的平台的开发工具。所以,对于一个安卓项目,这涉及使用具有正确插件的 Eclipse 编辑器,而在 iOS 上则涉及使用 XCode。入门指南 (www.phonegap.com/start) 提供了如何为您选择的设备平台设置环境的详细信息:

使用 PhoneGap

对于每个平台的设置细节对于本书来说太多了(而且只是重复了 PhoneGap 网站上的内容),所以我们将专注于创建原生应用的另一种选项,即 PhoneGap Build 服务。PhoneGap Build (build.phonegap.com) 是一个在线服务,简化并自动化了创建原生应用的过程。它允许您简单地上传代码(或使用公共源代码控制存储库)以生成原生二进制文件。更好的是,您可以使用 PhoneGap Build 为所有受支持的平台生成二进制文件。这意味着您可以编写您的代码,并从该网站生成 iPhone、安卓、黑莓和其他版本的代码:

使用 PhoneGap

PhoneGap Build 服务并不免费。定价计划和其他详情可以在该网站上找到,但幸运的是有一个免费的开发者计划。这就是我们将在本章中使用的服务。让我们开始创建一个账户。(在接下来的屏幕截图和示例中,请确保将细节更改为您自己的特定内容。)

首先点击 创建账户 按钮并填写相关细节:

使用 PhoneGap

注册后,您将返回到 PhoneGap Build 的首页,您不会看到任何类型的确认消息。这有点不幸,但如果您检查您的电子邮件,您应该会看到他们发来的一封验证注册的消息。点击那个链接,您将被带到一个页面,询问您要创建您的第一个 PhoneGap Build 项目:

使用 PhoneGap

请注意,构建服务支持从新的 Github 存储库、现有的 Git 或 Subversion 存储库或通过上传的 ZIP 或 HTML 文件中的项目种子。此时,让我们离开网站,再回到代码。我们想要从一个非常简单的代码集开始。稍后在本章中我们会做一些更有趣的事情,但现在,我们的目标只是上传一些 HTML 并看看接下来会发生什么。在你从 GitHub 下载的代码中,打开c12文件夹,看看app1文件夹。其中包含了第四章 Working with Lists中一个列表示例的副本。它使用 jQuery Mobile 创建了一个简单的包括缩略图片的四人列表。这并不是太令人兴奋,但对我们目前的目的来说已经足够了。你会注意到已经有一个app1.zip文件。

如果你回到网站并选择上传存档,然后可以浏览到你从计算机上解压文件的位置并选择那个 ZIP 文件。确保还为应用程序输入一个名称。我选择了FirstBuildApp。点击创建后,你会被带到包含您所有应用程序的页面,如果您是一个新的构建用户,那里将只包含刚刚创建的一个应用程序。

使用 PhoneGap

点击应用程序标题,然后您可以选择下载各种版本的应用程序。信不信由你——你已经能够在大多数平台上下载版本。但使用 iOS 需要你提供额外的细节:

使用 PhoneGap

如果你看不到下载链接而是看到一个排队通知,请给构建服务一两分钟来赶上。如果你简单地重新加载页面,最终你就会看到链接显示出来。

真正使用应用程序取决于你选择的平台。对于安卓,你需要确保已启用允许安装非市场应用程序设置。该设置的确切措辞和位置将取决于您的设备。这个短语可以在我的 HTC Inspire 设备的应用设置中找到。您可以通过在 PhoneGap Build 网站上编辑设置来对应用程序签名。一旦你做过了,你就可以将你的应用程序提交到安卓市场。但由于安卓允许您测试时使用未签名的应用程序,您可以跳过此步骤。如果您下载 APK(表示您的应用程序的实际文件),您可以以几种方式将其放在设备上。安卓 SDK 包括从命令行安装应用程序的工具。最简单的方法是使用你的电子邮件。如果你将文件发给自己,并在设备上检查你的电子邮件,你应该能够在那里安装它。以下屏幕截图显示了我的手机上运行的应用程序:

使用 PhoneGap

添加 PhoneGap 功能

我们刚刚演示了如何使用 PhoneGap Build 服务将 HTML(当然还包括 JavaScript、CSS 和图像)转换为真正的本机应用程序,以适应多个平台。然而,在本章的前面部分提到过,PhoneGap 提供的不仅仅是简单的包装器来将 HTML 转换为本机应用程序。PhoneGap JavaScript API 提供了对许多酷炫的设备中心服务的访问,这些服务可以极大地增强您的应用程序的功能。对于我们的第二个示例,我们将看一下其中一个功能——联系人 API(有关详细信息,请参阅联系人 API 文档,可在docs.phonegap.com/en/1.4.1/phonegap_contacts_contacts.md.html#Contacts)上找到)。

应用程序在Listing 12-1中是一个简单的联系人搜索工具。让我们看看代码,然后解释一下其中的内容:

Listing 12-1: index.html
<!DOCTYPE html>
<html>
<head>
<title>Contact Search</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href ="jquery.mobile.min.css" />
<script src="img/jquery.js"></script>
<script src="img/jquery.mobile.min.js"></script>
<script src="img/phonegap-1.4.1.js"></script>
<script>
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady(){
$("#searchButton").bind("touchend", function() {
var search = $.trim($("#search").val());
if(search == "") return;
var opt = new ContactFindOptions();
opt.filter = search;
opt.multiple = true;
navigator.contacts.find(["displayName","emails"], foundContacts, errorContacts, opt);
});
foundContacts = function(matches){
//create results in our list
var s = "";
for (var i = 0; i < matches.length; i++) {
s += "<li>"+matches[i].displayName+"</li>";
}
$("#results").html(s);
$("#results").listview("refresh");
}
errorContacts = function(err){
navigator.notification.alert("Sorry, we had a problem and gave up.", function() {});
}
}
</script>
</head>
<body>
<div data-role="page">
<div data-role="header">
<h1>Contact Search</h1>
</div>
<div data-role="content">
<input type="text" id="search" value="adam" />
<button id="searchButton">Search</button>
<ul id="results" data-role="listview" data-inset="true"></ul>
</div>
</div>
</div>
</body>
</html>

让我们首先看看应用程序的布局部分,它位于文件的下半部分。您可以看到我们的 jQuery Mobile 页面结构,其中包括一个输入字段、一个按钮和一个空列表。这里的想法是用户将输入要搜索的名称,点击按钮,结果将显示在列表中。以下屏幕截图展示了输出:

添加 PhoneGap 功能

现在看一下 JavaScript 代码。我们所做的第一个更改是包含 PhoneGap JavaScript 库:

<script src="img/phonegap-1.4.1.js"></script>

此 JavaScript 库可从您从 PhoneGap 下载的 ZIP 文件中获得。即使我们不打算在本地构建我们的应用程序(当然您也可以),我们也需要在发送到 Build 服务的 ZIP 文件中包含 JavaScript 文件。这里有一个棘手的部分。截至 PhoneGap v 1.4.1,每个平台的 JavaScript 文件都是唯一的。这意味着 PhoneGap 支持的每个操作系统都有一个不同的 JavaScript 文件。Build 服务足够智能,可以用适当平台的正确文件替换您的文件引用。如果您使用本书的 Github 存储库中的代码,则是 Android 版本。如果您想将此代码用于 iOS,请务必在本地替换 JavaScript 文件。

下一个有趣的细节是以下代码行:

document.addEventListener("deviceready", onDeviceReady, false);

deviceready事件是由 PhoneGap 触发的特殊事件。它基本上意味着您的代码现在可以使用高级功能,例如 Contacts API。

在事件处理程序onDeviceReady中,我们有一些事情要做。值得注意的第一个函数是搜索按钮的事件处理程序。前几行代码只是获取、修整和验证值。

在确保实际有内容可以搜索后,您可以看到对 Contacts API 的第一个实际使用,如下面的代码片段所示:

var opt = new ContactFindOptions();
opt.filter = search;
opt.multiple = true;
navigator.contacts.find(["displayName","emails"], foundContacts, errorContacts, opt);

联系人 API 具有搜索方法。其第一个参数是要搜索和返回的字段数组。在我们的案例中,我们表示我们要针对联系人的姓名和电子邮件值进行搜索。第二个和第三个参数是成功和错误回调。最后一个选项是搜索的选项集。你可以在调用之前看到它被创建。过滤器键仅是搜索词条。默认情况下,联系人搜索返回一个结果,所以我们特别要求多个结果。

现在让我们来看一下成功处理程序:

foundContacts = function(matches){
//create results in our list
var s = "";
for (var i = 0; i < matches.length; i++) {
s += "<li>"+matches[i].displayName+"</li>";
}
$("#results").html(s);
$("#results").listview("refresh");
}

联系人搜索的结果将是一个结果数组。记住你只会得到你要求的内容,所以我们的结果对象包含 displayNameemails 属性。目前,我们的代码只是获取 displayName 并将其添加到列表中。根据我们从之前的章节学到的知识,我们还知道每当修改列表时,我们需要刷新 jQuery Mobile listview。以下屏幕截图显示了一个示例搜索:

添加 PhoneGap 功能

摘要

在本章中,我们研究了 PhoneGap 开源项目以及它如何允许你使用 HTML、JavaScript 和 CSS 创建多种不同设备的原生应用程序。我们使用 Build 服务并用它来上传我们的代码和下载编译后的原生应用程序。虽然 jQuery Mobile 不是 PhoneGap 的必需品,但两者组合在一起非常强大。

在下一章中,我们将使用这个团队创建我们的最终应用程序,一个功能齐全的 RSS 阅读器。

第十三章:成为专家 - 构建一个 RSS 阅读器应用程序

现在您已经了解了 jQuery Mobile 及其功能,是时候构建我们最终的完整应用程序了 —— 一个 RSS 阅读器。

在这一章中,我们将:

  • 讨论 RSS 阅读器应用程序及其功能

  • 创建应用程序

  • 讨论可以添加到应用程序的内容

RSS 阅读器 —— 应用程序

在深入代码之前,可能有必要快速展示应用程序的最终工作形式,以便您可以看到各个部分及其如何一起工作。RSS 阅读器应用程序就是这样一个应用程序,它旨在获取 RSS 源(例如来自 CNN、ESPN 和其他网站的源),将它们解析为可读数据,并提供一种查看文章的方式。该应用程序将允许您添加和删除源,提供名称和 URL,并提供一种查看源当前条目的方法。

应用程序始于一组基本说明。只有在您运行应用程序而没有任何已知源时才会显示这些说明:

RSS 阅读器 — 应用程序

单击 添加源 按钮会带您进入一个简单的表单,允许输入名称和 URL。(不幸的是,URL 必须手动输入。幸运的是,现代移动设备支持复制和粘贴。我强烈建议使用这个!):

RSS 阅读器 — 应用程序

添加源后,您将返回到主页。以下截图显示添加了一些源后的视图:

RSS 阅读器 — 应用程序

要开始阅读条目,用户只需选择其中一个源。然后,它将获取该源并显示当前的条目:

RSS 阅读器 — 应用程序

应用程序的最后部分是入口视图本身。有些博客不会通过 RSS 提供“完整”的入口副本,显然您可能希望在博客本身发表评论。因此,在底部我们提供了一种简单的方法来访问真正的网站,如下图所示:

RSS 阅读器 — 应用程序

现在您已经看到了应用程序,让我们来构建它。我们将再次使用 PhoneGap Build 来创建最终结果,但这个应用程序实际上也可以在常规网站上运行。(我们将稍后讨论为什么。)

创建 RSS 阅读器应用程序

我们的应用程序从第一个页面 index.html 开始。此页面将加载 jQuery 和 jQuery Mobile。它的核心任务是列出您当前的源,但它必须在用户没有任何源时识别出来,并提供一些文本鼓励他们添加他们的第一个源:

Listing 13-1: index.html
<!DOCTYPE html>
<html>
<head>
<title>RSS Reader App</title>
<meta name="viewport" content="width=device-width, initial- scale=1">
<link rel="stylesheet" href ="jquery.mobile/jquery.mobile- 1.1.0.min.css" />
<script src="img/jquery-1.6.4.min.js"></script>
<script src="img/jquery.mobile-1.1.0.min.js"></script>
<script src="img/main.js"></script>
</head>
<body>
<div data-role="page" id="intropage">
<div data-role="header">
<h1>RSS Reader Application</h1>
</div>
<div data-role="content" id="introContent">
<p id="introContentNoFeeds" style="display:none">
Welcome to the RSS Reader Application. You do not currently have any RSS Feeds. Please use the "Add Feed" button below to begin.
</p>
<ul id="feedList" data-role="listview" data-inset="true" data- split-icon="delete"></ul>
<a href ="addfeed.html" data-role="button" data-theme="b">Add Feed</a>
</div>
<div data-role="footer">
<h4>Created with jQuery Mobile</h4>
</div>
</div>
<script>
$("#intropage").bind("pagecreate", function(e) {
init();
});
</script>
</body>
</html>

如代码清单前所述,我们需要首先加载 jQuery 和 jQuery Mobile 模板。您可以在前面的代码清单的开头看到这一点。页面的大部分是您在上一章中看到的模板 HTML,所以让我们指出一些具体的内容。

首先注意下导语段落。注意 CSS 来隐藏文本吗?这里的假设是 — 大多数情况下 — 用户不会需要这段文字,因为他们会有订阅源。我们的代码将在必要时处理显示它。

在该段落之后是一个空列表,将显示我们的 feeds。在下面是用于添加新 feeds 的按钮。

最后,我们在最后放了一小段脚本。这创建了一个 jQuery Mobile 页面事件 pagecreate 的事件监听器,我们将它与启动我们的应用程序任务相关联。

我们所有的代码(我们的自定义代码)都将存储在 main.js 中。这个文件有点大,所以我们只显示与每个部分相关的部分。在阅读本章时,请记住这一点。整个文件可以在书中的示例代码中找到:

Listing 13-2: Portion of main.js
function init() {
//handle getting and displaying the intro or feeds
$("#intropage").live("pageshow",function(e) {
displayFeeds();
});

我们从 main.js 中的 init 函数开始。记住这个函数在首页的 pagecreate 上运行。它在页面显示之前运行。这使得它成为一个很好的地方去注册一个函数,用于页面显示时。我们已经将大部分逻辑提取到自己的函数中,所以接下来让我们来看看它。

displayFeeds 函数

displayFeeds 处理检索我们的 feeds 并显示它们。逻辑很简单。如果没有 feeds,我们想要显示导语文本:

Listing 13-3: displayFeeds from main.js
function displayFeeds() {
var feeds = getFeeds();
if(feeds.length == 0) {
//in case we had one form before...
$("#feedList").html("");
$("#introContentNoFeeds").show();
} else {
$("#introContentNoFeeds").hide();
var s = "";
for(var i=0; i<feeds.length; i++) {
s+= "<li><a href ='http://feed.html?id="+i+"' data- feed='"+i+"'>"+feeds[i].name+"</a> <a href ='http:// class='deleteFeed' data-feedid='"+i+"'>Delete</a></li>";
}
$("#feedList").html(s);
$("#feedList").listview("refresh");
}
}

注意我们还清空了列表。可能用户有 feeds 并删除了它们。通过将列表重置为空字符串,我们确保我们不留下任何东西。如果有 feeds,我们动态创建列表,确保在最后调用 listview("refresh") API,请求 jQuery Mobile 对列表进行美化。

存储我们的 feeds

那 feeds 是从哪里来的?我们如何存储它们?虽然我们正在使用 PhoneGap 并且可以使用嵌入式 SQLite 数据库实现,但我们可以使用更简单的东西 localStoragelocalStorage 是一个 HTML5 功能,允许你在客户端存储键值对。虽然你不能存储复杂的数据,但你可以在存储之前使用 JSON 序列化来编码复杂的数据。这使得数据的存储非常简单。但请记住 localStorage 包含文件存储。当数据发生变化时,您的应用程序需要从文件中读取。尽管我们谈论的是一个简单的 feed 列表,但这些数据应该相对较小:

Listing 13-3: getFeeds, addFeed, and removeFeed
function getFeeds() {
if(localStorage["feeds"]) {
return JSON.parse(localStorage["feeds"]);
} else return [];
}
function addFeed(name,url) {
var feeds = getFeeds();
feeds.push({name:name,url:url});
localStorage["feeds"] = JSON.stringify(feeds);
}
function removeFeed(id) {
var feeds = getFeeds();
feeds.splice(id, 1);
localStorage["feeds"] = JSON.stringify(feeds);
displayFeeds();
}

前三个函数代表了我们存储系统的整个封装。getFeeds 简单地检查 localStorage 的值,如果存在,则处理将 JSON 数据转换为原生 JavaScript 对象。addFeed 接受一个 feed 名称和 URL,创建一个简单的对象,并存储 JSON 版本。最后,removeFeed 函数简单地处理找到数组中的正确项,删除它,并将其存储回 localStorage

添加一个 RSS feed

目前一切顺利。现在让我们看看添加 feed 所需的逻辑。如果你记得,我们用来添加 feed 的链接指向addfeed.html。让我们来看看它:

Listing 13-4: addfeed.html
<!DOCTYPE html>
<html>
<head>
<title>Add Feed</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div data-role="page" id="addfeedpage" data-add-back-btn="true">
<div data-role="header">
<h1>Add Feed</h1>
</div>
<div data-role="content">
<form id="addFeedForm">
<div data-role="fieldcontain">
<label for="feedname">Feed Name:</label>
<input type="text" id="feedname" value="" />
</div>
<div data-role="fieldcontain">
<label for="feedurl">Feed URL:</label>
<input type="text" id="feedurl" value="" />
</div>
<input type="submit" value="Add Feed" data-theme="b">
</div>
<div data-role="footer">
<h4>Created with jQuery Mobile</h4>
</div>
</div>
</body>
</html>

除了表单外,这个页面没有太多内容。请注意,我们的表单没有 action。我们在这里不使用服务器。相反,我们的代码将处理表单提交并执行某些操作。还要注意,我们没有按照之前建议的做法——将 jQuery 和 jQuery Mobile 包含在顶部。在桌面应用程序中,这些包含是必需的,因为用户可能会将页面添加到应用程序的主页之外的书签中。由于该代码的最终目标是 PhoneGap 应用程序,我们不必担心这一点。这使得我们的 HTML 文件稍微小了一点。现在让我们返回到main.js,看看处理这一逻辑的代码。

以下代码是main.jsinit方法的片段。它处理表单上的按钮点击:

Listing 13-5: Add Feed event registration logic
//Listen for the addFeedPage so we can support adding feeds
$("#addfeedpage").live("pageshow", function(e) {
$("#addFeedForm").submit(function(e) {
handleAddFeed();
return false;
});
});

现在我们可以看看handleAddFeed了。我已经将这段代码抽象出来,只是为了简化事情:

Listing 13-6: handleAddFeed
function handleAddFeed() {
var feedname = $.trim($("#feedname").val());
var feedurl = $.trim($("#feedurl").val());
//basic error handling
var errors = "";
if(feedname == "") errors += "Feed name is required.\n";
if(feedurl == "") errors += "Feed url is required.\n";
if(errors != "") {
//Create a PhoneGap notification for the error
navigator.notification.alert(errors, function() {});
} else {
addFeed(feedname, feedurl);
$.mobile.changePage("index.html");
}
}

在大部分情况下,这里的逻辑应该很容易理解。我们获取 feed 名称和 URL 值,确保它们不为空,并可选地提醒任何错误。如果没有发生错误,那么我们运行之前描述的addFeed方法。请注意,我们使用changePageAPI 返回用户到主页。

我在这里特别指出一段代码,处理显示错误的那一行:

navigator.notification.alert(errors, function() {});

这一行来自于 PhoneGap API。它为您的设备创建了一个针对移动设备的特定警报通知。你可以把它想象成一个更高级的 JavaScript alert() 调用。第二个参数是警报窗口解除时的回调函数。因为我们在那种情况下不需要执行任何操作,所以我们提供了一个什么都不做的空回调。

查看 feed

当用户点击查看 feed 时会发生什么?这可能是应用程序中最复杂的部分。我们从 HTML 模板开始,这相当简单,因为大部分工作将在 JavaScript 中完成:

Listing 13-7: feed.html
<!DOCTYPE html>
<html>
<head>
<title>Feed</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div data-role="page" id="feedpage" data-add-back-btn="true">
<div data-role="header">
<h1></h1>
</div>
<div data-role="content" id="feedcontents">
</div>
<div data-role="footer">
<h4>Created with jQuery Mobile</h4>
</div>
</div>
</body>
</html>

这个页面基本上充当一个外壳。请注意,它根本没有真正的内容,只是空的 HTML 元素等待填充。让我们返回到main.js,看看这是如何工作的:

Listing 13-8: Feed display handler (part 1)
//Listen for the Feed Page so we can displaying entries
$("#feedpage").live("pageshow", function(e) {
//get the feed id based on query string
var query = $(this).data("url").split("=")[1];
//remove ?id=
query = query.replace("?id=","");
//assume it's a valid ID, since this is a mobile app folks won't be messing with the urls, but keep
//in mind normally this would be a concern
var feeds = getFeeds();
var thisFeed = feeds[query];
$("h1",this).text(thisFeed.name);
if(!feedCache[thisFeed.url]) {
$("#feedcontents").html("<p>Fetching data...</p>");
//now use Google Feeds API
$.get("https://ajax.googleapis.com/ajax/services/feed/ load?v=1.0&num=10&q="+encodeURI(thisFeed.url)+"&callback=?", {}, function(res,code) {
//see if the response was good...
if(res.responseStatus == 200) {
feedCache[thisFeed.url] = res.responseData.feed.entries;
displayFeed( thisFeed.url);
} else {
var error = "<p>Sorry, but this feed could not be loaded:</p><p>"+res.responseDetails+"</p>";
$("#feedcontents").html(error);
}
},"json");
} else {
displayFeed(thisFeed.url);
}
});

这段代码片段处理了对feed.html上的pageshow事件的监听。这意味着每次查看该文件时都会运行该代码,这正是我们想要的,因为它用于每个不同的 feed。这是如何工作的?记得我们的 feeds 列表包括了 feed 本身的标识符:

for(var i=0; i<feeds.length; i++) {
s+= "<li><a href='http://feed.html?id="+i+"' data- feed='"+i+"'>"+feeds[i].name+"</a> <a href='http:// class='deleteFeed' data-feedid='"+i+"'>Delete</a></li>";
}

jQuery Mobile 通过数据("url")API 为我们提供了对 URL 的访问。由于这会返回整个 URL,而我们只关心问号后的内容,因此我们可以使用一些字符串函数来清理它。最终结果是一个数值查询,我们可以使用它来从我们的 feed 查询中提取数据。在常规的桌面应用程序中,用户很容易搞乱 URL 参数。因此,我们在这里进行一些检查,以确保请求的值确实存在。由于这是一个移动设备上的单用户应用程序,因此不需要担心这个问题。

在我们尝试获取 feed 之前,我们利用了一个简单的缓存系统。在 main.js 中的第一行创建了一个空对象:

//used for caching
var feedCache= {};

此对象将存储我们的 feeds 结果,以便我们不必不断重新获取它们。这就是为什么有下面这行代码:

if(!feedCache[thisFeed.url]) {

在我们执行任何额外的网络调用之前运行。那么我们如何实际获取 feed 呢?Google 有一个很酷的服务叫做 Feed API(developers.google.com/feed/)。它允许我们使用 Google 来处理获取 RSS feed 的 XML 并将其转换为 JSON。JavaScript 可以处理 XML,但 JSON 更容易,因为它变成了常规的、简单的 JavaScript 对象。我们有一些错误处理,但如果一切顺利,我们只需缓存结果。最后一部分是对 displayFeed: 的调用:

Listing 13-9: displayFeed
function displayFeed(url) {
var entries = feedCache[url];
var s = "<ul data-role='listview' data-inset='true' id='entrylist'>";
for(var i=0; i<entries.length; i++) {
var entry = entries[i];
s += "<li><a href ='entry.html?entry="+i+"&url="+encodeURI(url)+"'>"+ entry.title+"</a></li>";
}
s += "</ul>";
$("#feedcontents").html(s);
$("#entrylist").listview();
}

前面的代码块只是迭代了结果 feed。当 Google 解析 feed 中的 XML 时,它转换为我们可以循环的对象数组。虽然 feed 中有许多我们可能感兴趣的属性,但我们只关心标题。注意我们如何构建我们的链接。我们传递数值索引和 URL(我们将在下一部分中使用)。然后,这被呈现为一个简单的 jQuery Mobile listview。

创建条目视图

准备好了最后一部分了吗?让我们来看看个别条目的显示。与之前一样,我们将从模板开始:

Listing 13-10: entry.html
<!DOCTYPE html>
<html>
<head>
<title>Entry</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div data-role="page" id="entrypage" data-add-back-btn="true">
<div data-role="header">
<h1></h1>
</div>
<div data-role="content">
<div id="entrycontents"></div>
<a href ="" id="entrylink" data-role="button">Visit Entry</a>
</div>
<div data-role="footer">
<h4>Created with jQuery Mobile</h4>
</div>
</div>
</body>
</html>

与之前的 feed.html 类似,entry.html 是一个空壳。请注意,标题、内容和链接都是空的。所有这些都将被真实的代码替换。让我们返回到 main.js 并查看处理此页面的代码:

Listing 13-11: Entry page event handler
$("#entrypage").live("pageshow", function(e) {
//get the entry id and url based on query string
var query = $(this).data("url").split("?")[1];
//remove ?
query = query.replace("?","");
//split by &
var parts = query.split("&");
var entryid = parts[0].split("=")[1];
var url = parts[1].split("=")[1];
var entry = feedCache[url][entryid];
$("h1",this).text(entry.title);
$("#entrycontents",this).html(entry.content);
$("#entrylink",this).attr("href",entry.link);
});

那么这里发生了什么?记得我们传递了一个索引值(点击了哪个条目,第一个,第二个?)和 feed 的 URL。我们从 URL 中解析出这些值。一旦我们知道了 feed 的 URL,我们就可以使用我们的缓存来获取特定的条目。一旦我们有了这个,更新标题、内容和链接就是一件简单的事情了。就是这样!

更进一步

现在,您可以从此应用程序中获取代码,并将其上传到 PhoneGap Build 服务,以便在您自己的设备上尝试。但是我们还能做些什么?以下是考虑的一些事项:

  • PhoneGap 提供了一个连接 API(docs.phonegap.com/en/1.4.1/phonegap_connection_connection.md.html),返回设备连接状态的信息。你可以添加对此的支持,以防止用户在设备离线时尝试阅读订阅。

  • 虽然我们将用户的订阅存储在localStorage中,但从阅读 RSS 条目缓存的数据是临时存储的。你也可以存储这些数据,并在用户离线时使用它。

  • PhoneGap 有一个出色的插件 API,并且已经有很多插件可用(github.com/phonegap/phonegap-plugins)。其中一个插件可以更轻松地发送短信。你可以添加一个选项,通过短信向朋友发送条目标题和链接。我们提到过 PhoneGap 还让你可以使用你的联系人,详细信息请参见联系人 API:docs.phonegap.com/en/1.4.1/phonegap_contacts_contacts.md.html

希望你能明白。这只是 jQuery Mobile 和 PhoneGap 强大功能的一个例子。

摘要

在本章中,我们利用了上一章学到的 PhoneGap 知识,创建了一个完整但相当简单的移动应用程序,利用了 jQuery Mobile 来进行设计和交互。