首页 / 快讯大厅

二、PhoneGap 入门

2025-10-08 18:59:41快讯大厅 844

PhoneGap 入门指南(全)

原文:Beginning PhoneGap

协议:CC BY-NC-SA 4.0

零、简介

这本书是给谁的

这本书是为任何想要开始跨多个移动平台开发移动应用的人准备的。该书提供了关于 PhoneGap 的介绍和详细教程,并在以下方面为读者提供了帮助:

确定哪种 JavaScript UI 框架最适合他们

介绍 JavaScript UI 框架及其与 PhoneGap 的集成

解释插件的概念以及如何使用它来进行 OAuth 身份验证和云推送

解释如何编写自定义插件

这本书的结构

这本书首先解释了移动操作系统世界的碎片化以及它如何影响我们。它进一步讨论了如何弥合由于这种碎片化造成的差距,以及如何编写一次代码并跨移动平台部署它。

在 PhoneGap 背后的概念被弄清楚之后,这本书继续解释 PhoneGap 在 Android 上的用法,然后给出了如何在其他剩余的移动平台上做同样事情的说明。

接下来介绍了如何在 PhoneGap 上使用 JavaScript UI 框架,还讨论了在什么场景中使用哪个 JavaScript UI 框架。

最后,这本书将重点转移到插件上。它展示了几个如何用社区插件扩展 PhoneGap 框架的例子。然后解释了如何在 iOS、Android 和 BlackBerry 上构建这些插件。

下载代码

本书中提到的所有源代码都可以在[bitbucket.org/rohitghatol/apress-phonegap](https://bitbucket.org/rohitghatol/apress-phonegap)获得。这些章节本身就说明了这一点。也可在 Apress 网站[Apress.com](http://apress.com)上获得。

联系作者

可以通过作者的 LinkedIn 个人资料联系到他们:

Rohit Ghatol—[in.linkedin.com/in/rohitghatol](http://in.linkedin.com/in/rohitghatol)

Yogesh Patel—[www.linkedin.com/profile/view?id=19911394](http://www.linkedin.com/profile/view?id=19911394)

一、了解跨平台移动应用开发

这本书是关于移动应用开发的;更确切地说,是关于减轻移动应用开发的痛苦。市场上有许多智能手机平台:安卓、iPhone、黑莓、诺基亚、Windows 7 手机和 WebOS。更新的平台也在增加,比如三星的 Bada 和 Meego。

移动应用开发平台的数量之多似乎令人应接不暇。在处理移动应用开发时,这是你必须记住的第一点。

在 2000 年,我们在台式机领域看到了类似的情况。我们有微软的 Windows,苹果的 Mac,以及各种版本的 Linux 和 UNIX。那时,很难构建能在所有这些平台上运行的产品。由此产生的碎片通常通过内部解决方案来解决,方法是用 C++构建框架,抽象出特定于操作系统(OS)的模块。幸运的是,Sun 的 Java 拯救了我们,为我们提供了一个通用的构建平台。有了 Java 的“一次构建,随处运行”策略,构建桌面产品变得轻而易举。

在 2004 年到 2008 年之间,开发者社区看到了一种不同的分裂;这一次,它发生在浏览器世界。这是一场涉及非常流行的 Internet Explorer 6 与 Firefox 和 Safari 的分裂——然后,Chrome 和其他浏览器突然出现,导致了进一步的分裂。

然而,这种碎片化的本质是不同的,更温和一些:这主要是由于浏览器没有遵循万维网联盟(W3C)概述的规范。通常,这种分裂是通过写“如果浏览器是 IE,那么做这个做那个”或者“如果特性存在,那么做这个做那个”来解决的。

许多 JavaScript 库来帮忙编写跨浏览器的 web 应用。事情已经改善到这样的程度,所有的浏览器都在努力工作,越来越符合 W3C 规范。浏览器作为一个平台,现在是一个强有力的竞争者。

这本书是关于移动世界的碎片化。移动操作系统碎片化非常严重,因为在这个开发领域没有规范或标准。

2007 年,苹果和谷歌推出了他们的移动平台。2008 年,两家公司都推出了移动应用商店,允许智能手机用户下载移动应用。移动应用的时代已经开始;从此,再也没有回头。智能手机用户数量呈指数级增长。

公司开始专注于在新的智能手机平台上提供服务和内容。企业意识到他们需要将注意力转移到智能手机用户身上。不仅用户数量增加,智能手机的使用频率也增加了。

想象一下,您的开发人员日以继夜地在 iPhone、Android、BlackBerry、WebOS 和 Symbia 上发布相同的产品——现在,让我们将 Samsung Bada 添加到这个列表中!你可以看到这里的挑战。操作系统平台,从它们的开发环境开始,是如此的分散。对于 iPhone,你将需要 Mac 机,对于黑莓,你将需要 Windows。本章将更详细地讨论这些事情。

现在,对于那些刚接触移动应用开发的人来说,我们将从关注创建一个移动应用是什么样子开始。我们将回答诸如“移动应用与传统的基于网络或基于桌面的应用有何不同?”我们将研究为各种平台开发移动应用的挑战。

移动应用的类型

了解不同类型的移动应用非常重要。我将根据他们的工作把他们分为两类。

独立移动应用

移动应用(基于 web 服务)

独立移动应用是诸如闹钟、电话拨号器和离线游戏之类的应用。支持 Web 服务的移动应用包括电子邮件、日历、Twitter 客户端、在线游戏以及与 web 服务交互的应用。

这种移动应用之间的区别是本书上下文中所独有的。虽然 PhoneGap 可以用来实现独立的移动应用,但是基于 PhoneGap 的移动应用的本质通常属于“服务支持的移动应用”的范畴

了解 Web 服务

作为一名开发人员,当你看互联网上的 web 应用时,你需要考虑两种 web 开发。

可通过浏览器访问的 Web 应用(用于人机交互)

可以通过 RESTful Web 服务之类的协议访问的 web 服务(用于编程接口)

所有流行的网络应用,如谷歌、脸书、Twitter、LinkedIn、MySpace、Flickr 和 Picasa,都为它们的服务提供了 RESTful 界面。这类网站有很多在线词典。如果你访问[www.programmableweb.com](http://www.programmableweb.com),你会看到一个相当大的列表,列出了所有为编程接口提供此类服务的网络应用(见图 1–1)。

图 1–1。 可编程 Web API 目录

许多想要为多个平台开发移动应用的公司要么拥有自己的 web 服务,要么依赖于其他 web 服务。虽然 PhoneGap 可以用于独立的移动应用,但它非常适合使用 web 服务的移动应用。原因是 PhoneGap 应用主要是增加了设备功能的 web 应用。想象一下,一个 Flickr web 应用可以访问设备的摄像头或 Google Maps 应用,而后者又可以访问 GPS。另一个例子是 Foursquare,它可以访问你的 GPS,以及你手机的通讯录。

这或多或少意味着大多数基于 PhoneGap 的应用将使用 JavaScript 访问 web 服务。这使得使用 PhoneGap 的开发人员掌握使用 web 服务变得非常重要。

对于读过这本书后想要编写 PhoneGap 应用的开发人员,我建议在 ProgrammableWeb.com 上找到一些 web 服务,并为这些服务编写一个 PhoneGap 客户端作为练习。

这本书将提供一个这样的服务的例子;也就是 AlternativeTo.Net。

移动应用概述

虽然你们中的许多人至少有一些使用移动应用的经验,但你们中的许多人更熟悉非移动平台(例如,web 平台)。因此,这本书明确论述了移动应用的本质以及与之相关的挑战。如果您来自非移动背景,这将有助于您理解开发移动应用意味着什么。

移动应用功能

图 1–2。 移动应用不是 web 应用。

首先要注意的是,移动应用不是 web 应用。区别在于特性的性质和所提供的特性数量(参见图 1–3)。

移动应用的功能可能会更少。

您可以期待您的移动应用看起来与您的 web 应用非常不同。首先,智能手机的屏幕大小与桌面不同。在 web 应用中,屏幕越大,你就有越多的空间来放置菜单、工具栏和小部件。

考虑到智能手机的屏幕尺寸限制,你会看到更多仪表板类型的主屏幕。

智能手机用户需要通过不同级别的导航来找到他或她想要使用的功能。

智能手机用户和网络用户有不同的意图。智能手机用户希望在旅途中使用应用,以最少的努力获得最大的生产力,而网络用户可能会花更多的时间使用网络应用。

由于上述差异,您将看到智能手机上最具生产力(或最常用)的功能被突出显示。无论移动应用提供所有的功能,还是其中的一个子集,这些小的生产(和最频繁使用的)功能集将在移动应用上以最容易访问的方式进行组织。

图 1–3。 移动功能与网络应用功能不同。

用户互动

相对于传统的 web 应用,用户与移动应用交互的方式非常不同(参见 Figure 1–4)。

随着智能手机的触摸屏功能和更加生动的用户交互,基于加速度计和指南针,移动应用必须以不同的方式构建。

想象一个汽车游戏应用,通过向左或向右倾斜手机来操纵汽车。这是基于加速度计。想象一个地图应用,当用户改变他或她的方向时,它总是指向北方。这是基于一个指南针。

虽然与应用交互的新方式增强了用户体验,但新的移动平台上缺少物理键盘给 power keyboard 用户增加了一些额外的限制。在详细阐述移动应用需求时,需要考虑到这一点。

除此之外,智能手机有两种显示模式:布局和纵向;这些在早期的浏览器中是闻所未闻的。记录需求规格的一个重要部分是定义当设备处于纵向或横向模式时应用的外观、感觉和行为。

图 1–4。 智能手机和网络应用有不同的用户输入界面。

位置感知

位置感知是智能手机与生俱来的功能。谷歌地图、本地搜索、Foursquare 和许多其他移动应用都利用了智能手机的精密 GPS。Web 应用也使用位置感知;然而,这些应用使用相对更粗粒度的 GPS 系统(例如,国家级)(见图 1–5)。

图 1–5。 与网络应用相比,智能手机应用的位置感知能力

推送通知

应用用户喜欢收到有用事件的通知,比如收到的电子邮件和消息。智能手机是最好的通知平台,因为它几乎无时无刻不在用户身边。

除了收到电子邮件或信息等通知,任何服务都可以向智能手机用户发送通知(见图 1–6)。考虑一个组织的工作流程。用户不必总是登录 web 应用来完成涉及他或她的工作流,应用通知用户他或她需要执行某个操作来完成工作流会更有效率。这样,无论用户是否靠近笔记本电脑或台式机,他/她都可以始终高效地工作。

图 1–6。 智能手机的推送通知功能(移动通知)

跨平台移动应用开发的挑战

虽然移动应用的开发令人兴奋,但考虑到移动操作系统(OS)数量的不断增长,开发移动应用会面临许多挑战。

让我们来看看这些挑战。

OS 碎片化

碎片化增加的趋势与移动平台数量的增长相一致(参见图 1–7)。首先是黑莓和塞班智能手机,然后是强大的 iPhone 和 Android 平台。可以肯定的是,移动平台并没有就此止步。惠普随 WebOS 而来;微软推出了 Windows 7 手机;而现在,三星要出 Bada 了。

这意味着公司必须不断推出新产品,让所有移动平台都能感受到它们的存在。

图 1–7。 由于移动操作系统数量的增长而导致的碎片化

假设你想开发一个移动应用,目标是 iPhone,Android,BlackBerry 等。由于每个移动平台的操作系统不同,请考虑以下因素:

首先,你必须为每个平台设置不同的环境。

第二,你需要对每个操作系统有一点专业知识。对于移动开发者来说,学习曲线可能会很长。

不同的移动平台需要不同的编程语言。

你需要熟悉每个移动平台支持的特性;参见图 1–10。

表 1-1 描述了移动应用开发所需的设置(针对各种移动平台)。

过去,我们已经看到过类似的操作系统碎片化,从 Windows、Linux 和 Mac 的跨桌面碎片化开始,随着 Sun 推出 Java,这一问题得到了解决。在更近的过去,我们面临浏览器碎片,这是通过跨浏览器 JavaScript 框架如 jquery、YUI 和 Google Web Toolkit 解决的。

移动操作系统碎片是所有碎片中最糟糕和最多样化的。这为在所有移动平台上启动移动应用增加了相当大的技术挑战。

多个团队/产品

如果我们选择使用多个团队为每个平台构建一个移动应用,我们会面临许多问题;增加团队会增加项目交付的风险;增加产品意味着产品管理团队承担更多责任(参见图 1–8)。由于所有移动平台上的功能也是分散的,产品管理人员必须对每个平台上的产品提出具体要求。

最终,增加更多的团队,增加多个团队之间的协调,以及增加多个产品将导致管理和开发团队的额外开销。

图 1–8。为不同的移动操作系统增加多个团队带来了新的问题。

一致的用户体验

鉴于您希望您的应用在多个移动平台上保持一致,您的应用需要在所有平台上提供相似且一致的用户体验(参见 Figure 1–9)。这也与您的最终用户可能从一个平台迁移到另一个平台有关,或者他们可能存在于多个平台上。考虑一个拥有 Android 智能手机和 iPhone iPad 的用户。用户可以在家或在办公室使用 iPad,也可以在外出时使用 Android 智能手机。

这是你的应用必须提供跨移动平台的相似用户体验的众多原因之一;当然,由于设备特性和功能的分散,用户体验会因移动平台而有所不同。

图 1–9。 为跨平台的应用最终用户提供统一的用户体验

特征碎片化

设备特性和功能因平台而异(参见图 1–10)。这意味着,虽然一些机器人和 iPhones 有嵌入式指南针来显示方向,但其他智能手机没有。这可能意味着其他智能手机上的导航应用可能无法像 Android 或 iPhone 应用那样旋转地图。

总的来说,同一个应用在一些移动平台上会关闭一些功能,这是事实;应用的逻辑需要以这种方式编写。

图 1–10。 针对不同移动操作系统的特性碎片化

开发环境碎片化

开发环境是一个特别重要的片段。如果您想开发一个面向以下平台的移动应用,您至少需要两个操作系统:Windows(最好是 Windows 7)和 Mac(最好是 Leopard ):

ios

机器人

黑莓

WebOS

智能移动终端操作系统

Windows 7

此外,您将不得不使用各种 ide 和编程语言,如 Java、C++和 Objective C。此外,您将使用许多 ide,如 Xcode 和 Eclipse。

表 1-1 显示了开发环境的要求(针对各种移动平台)。

PhoneGap 的跨平台移动应用战略

PhoneGap 之所以成为可能,是因为所有移动平台之间的共性。如果没有这个通用组件,PhoneGap 就不可能存在。

浏览器组件作为通用平台

直到几年前,浏览器世界还很分散。当时,不同的浏览器在不同程度上遵循 W3C 标准。Firefox 和 Safari 浏览器在遵守标准方面走在了前列,而其他浏览器则落在了后面。

从那以后,很多事情都变了。现在,浏览器在遵守标准方面看起来更好了(在移动平台上更是如此)。这也是事实,因为大多数现代移动平台都有相同的基于 webkit 的浏览器。

此外,桌面和智能手机上的新浏览器已经开始遵循 HTML5/CSS3 等新标准。这为浏览器世界增加了更多功能,减少了移动平台之间的碎片化(参见图 1–11)。

图 1–11。 手机浏览器

我们来看表 1-2 ,表中列出了移动平台及其对应的浏览器平台。如你所见,除了 Windows 7 手机,所有的移动平台都使用基于 webkit 的浏览器。虽然 Windows 7 手机有自己的浏览器,但好消息是,这里列出的所有浏览器都已经遵守 HTML5/CSS3 标准,随着时间的推移,它们的遵守程度将继续提高。

PhoneGap 使用这些现代浏览器作为构建基于 HTML5/CSS3 的应用的平台。把所有 PhoneGap 应用想象成具有嵌入式浏览器并运行这些基于 HTML5/CSS3 的应用。

移动应用网页浏览量

所有这些移动平台都支持在应用中嵌入浏览器。这意味着移动应用的一个屏幕实际上可以是一个显示 HTML 页面的浏览器。

这些嵌入式浏览器通常被称为网络视图。这意味着您可以将应用的一个屏幕定义为 webview。

假设您的应用有一个名为“关于我们”的屏幕“关于我们”屏幕显示贵公司的信息。现在,让我们假设,例如,关于贵公司的“关于我们”的信息经常改变。您的移动应用的要求之一是显示最新的“关于我们”的信息。因此,您可以显示一个指向贵公司“关于我们”页面的 webview,而不是显示一个硬编码的“关于我们”屏幕(最好是网页的移动版本)。它将从网上加载“关于我们”页面。此外,webview 可用于加载和显示本地存储在移动设备上的 HTML 页面。我们可以进一步发展这个概念:我们可以显示与 web 服务交互的基于 Ajax 的 web 页面,而不是静态 web 页面。

本机挂钩暴露设备能力

既然我们知道浏览器可以嵌入到 web 应用中,让我们把注意力转移到通过这些嵌入式浏览器来公开设备功能上。

假设您正在开发一个基于 Flickr API 的 Flickr 应用。在这些 API 的帮助下,你可以登录 Flickr,列出图库,下载和展示你的图片。

虽然这对于 web 应用来说是一个好主意,但是当我们在手机上显示相同的应用时,请记住手机通常有一个摄像头。允许 Flickr 应用从相机中拍摄一张照片并上传到 Flickr 是非常合理的。

为了做到这一点,我们可以让嵌入式浏览器(或 webview)公开 JavaScript API,当调用该 API 时,它会让相机拍摄一张照片,并将该照片的二进制数据返回给我们(参见图 1–12)。

图 1–12。 JavaScript 到本地通信,反之亦然

从技术上讲,所有这些平台都支持在 webview 中将本机模块暴露给 JavaScript。这意味着,从程序上讲,所有这些平台都允许 JavaScript 代码调用本地 Java/C++/Objective C 代码,反之亦然。

让我们看一个例子。我们的 webview 有一个 HTML 页面,显示的是谷歌地图。我们希望根据手机的 GPS 位置将地图居中。为了做到这一点,我们需要编写一个本地组件,向设备查询 GPS 位置。

然后,我们编写代码,从 webview 中公开这个本机模块。webview 中的 JavaScript 代码调用此代码来访问 GPS 坐标。一旦代码获得了 GPS 坐标,它就相应地将地图居中。这是 PhoneGap 框架背后的主要原则。

HTML5 和 CSS3:编写应用的标准

HTML5 和 CSS3 是新兴的 web 技术。他们使网络应用更具交互性,功能更丰富。

HTML5 不仅为更强大的多媒体支持增加了新的标记;它还增加了一些特性,比如用于后台处理的 web worker、离线支持、数据库支持等等。

CSS3 是无缝、丰富的用户界面(UI)的新标准。设计师被要求在按钮或边框上制作简单的圆角或渐变的日子已经一去不复返了。有了 CSS3,事情变得更容易、更快、更好。

有了对动画的支持,CSS3 站点现在可以与基于 flash 的站点竞争了。不仅如此,只需更改 CSS 文件,门户网站就可以轻松地转换为移动网站。此外,打印预览现在可以实现不同的 CSS 文件。

众所周知,移动浏览器是 W3C 标准的早期采用者。这意味着手机是 HTML5/CSS3 应用的合适平台。

单一原产地政策不适用

对于那些使用过基于 Ajax 的应用的人来说,你知道托管在“abc.com”上的 web 应用不能对托管在“xyz.com”上的 web 服务进行 Ajax 调用。这意味着如果有人正在开发一个基于 Ajax 的应用,比如托管在 myphotobook.com 上,他或她将不能对 flickr.com 进行 Ajax 调用。

这被称为单一原产地政策——你可以在[en.wikipedia.org/wiki/Same_origin_policy](http://en.wikipedia.org/wiki/Same_origin_policy)进一步了解单一原产地政策。

对于 PhoneGap 应用来说,情况并非如此。PhoneGap 应用捆绑了所需的 HTML、JavaScript 和 CSS 文件,PhoneGap 应用没有像“abc.com”这样的域。这使得 PhoneGap 成为一个易于开发混搭的平台,可以自由地对各种其他站点进行 Ajax 调用。

想象一下,您的 PhoneGap 应用将脸书、Twitter 和 Flickr 集成到一个 mashup 中,只需要几行 JavaScript 代码。

这使得 PhoneGap 成为为在 programmableweb.com 上市的网络服务创建移动应用的理想平台。

这些限制在图 1–13 中说明:

图 1–13。 单一原产地政策

结论

PhoneGap 使用 HTML5、JavaScript 和 CSS3 来开发移动应用。这些是网络世界的标准技术。通过使用 PhoneGap,很少或没有母语背景的开发人员可以开始为所有流行的移动平台开发移动应用。

尽管 PhoneGap 提供了对移动应用标准原生功能的访问,但它的插件框架足够灵活,可以根据需要扩展和添加新功能。

PhoneGap 是一项正在发展的技术,用于开发跨移动平台应用。

二、PhoneGap 入门

PhoneGap 是一个 HTML5 应用框架,用于通过 web 技术开发本地应用。这意味着开发人员可以利用他们现有的 HTML、CSS 和 JavaScript 知识开发智能手机和平板电脑应用。有了 PhoneGap,开发人员就不必为 iPhone 学习像 Objective-C 这样的语言了。

使用 PhoneGap 开发的应用是混合应用。这些应用不完全基于 HTML/JavaScript,也不是本地的。应用的各个部分,主要是 UI、应用逻辑和与服务器的通信,都是基于 HTML/JavaScript 的。与设备(手机或平板电脑)进行通信和控制的应用的另一部分基于该平台的本地语言。PhoneGap 提供了从 JavaScript 世界到平台本地世界的桥梁,允许 JavaScript API 访问和控制设备(手机或平板电脑)。

PhoneGap 本质上为 JavaScript API 提供了对设备(手机或平板电脑)功能的访问,如摄像头、GPS、设备信息等。这些 API 将在第四章中详细介绍。

本章首先为您提供理解 PhoneGap 整体架构的适当信息。然后,我们将在 PhoneGap 示例中应用这些信息。在本章的最后,我们将使用 PhoneGap 编写一个小的 Hello World 应用。

注: PhoneGap 是一个框架;它没有为编码提供任何 ide 或特殊的开发环境。您将需要使用 Eclipse 和 Android SDK 来为 Android 开发 PhoneGap 应用;您需要使用 Xcode 为 IPhone 开发 PhoneGap 应用。

PhoneGap 架构

图 2–1。 PhoneGap 应用架构

PhoneGap 框架主要是一个 JavaScript 库,允许 HTML/JavaScript 应用访问设备功能。PhoneGap 框架也有一个本地组件,它在幕后工作,并在设备(手机或平板电脑)上完成实际工作。

请参考图 2–1 了解 PhoneGap 的整体架构。使用 PhoneGap 构建的应用主要由两部分组成:

JavaScript 业务逻辑部分,驱动 UI 及其功能。

JavaScript 部分,用于访问和控制设备(手机或平板电脑)。

考虑一个脸书应用。该应用的主要部分将是登录页面,并下载照片库。现在你想添加一个你可以拍照并上传到脸书的模块。为了做到这一点,您可以调用 PhoneGap 的相机 API 来访问手机的相机,拍摄照片,并获取图片文件。下一步是对脸书服务器的 AJAX 调用,以便上传图片。另一个可以应用的例子是使用 PhoneGap 在数据库中存储朋友列表,这样我们就可以搜索当地的朋友。

前面的描述给人的印象是,在 PhoneGap 中开发移动应用需要编写更多的业务逻辑和 UI,而很少访问设备的功能,这是正确的。这本书不仅解释了 PhoneGap APIs,也是创建基于 HTML5/CSS3 的移动应用的指南。

在 Android 上设置环境

创建 PhoneGap 应用的第一步是设置一个移动开发环境。我们将从 Android 开始,因为 Android 应用是用 Java 开发的,Java 基于 Eclipse,支持 PhoneGap 的几乎所有功能。

您需要下载并安装 Android 的以下必备软件:

JDK 1.6 以上

Eclipse 3.4 到 3.6

采用 Android 2.2 平台的 Android SDK

Eclipse 的 Android ADT 插件

Android 2.2 版的 Android AVD

Android 版 PhoneGap SDK 1.1.0

由于 Android 是用 Java 编程的,所以我们需要 JDK 1.6+和 Eclipse 3.4+。然后我们将安装 Android SDK。Android SDK 是一个通用的 SDK,不支持任何平台。一个平台就是一个操作系统版本,例如 2.2 版的 Froyo、2.3 版的津嘉·布雷德和 3.0 版的蜂巢。为了创建、构建和运行 Android 项目,需要下载这些平台。这个插件叫做 Android ADT 插件。

一旦 Eclipse、Android SDK 和 Android ADT (Eclipse 插件)都设置好了,我们就需要为 Android 创建一个模拟器环境。这被称为准备 Android AVD (Android 虚拟设备)。如果我们正在为 Android 开发一个针对 2.2 Froyo 的 PhoneGap 应用,我们需要一个相同 Android 平台的 AVD。

以下步骤将解释如何创建一个 Android 项目并将 PhoneGap 库注入到 Android 中。

PhoneGap Android 项目的必需安装

安装 Eclipse 的 3.4 版本。

安装 Android SDK。

为 Eclipse 安装 Android ADT 插件。

为模拟器创建 AVD。

安装 PhoneGap 库。

第一步:安装 Eclipse

这一步假设您已经安装了 Java SDK 1.6。安装完成后,从[www.eclipse.org/downloads/](http://www.eclipse.org/downloads/)下载 Eclipse。参见图 2–2 查看 eclipse 下载页面。我们需要一个支持 JDT (Java 开发环境)的 Eclipse IDE 版本 3.4+。您应该为 Java 开发人员安装 Eclipse IDE。

图 2–2。 月食下载页面

第二步:安装 Android SDK

设置 Android 开发环境的一些步骤是依赖于平台的。为了避免任何混淆,我们将解释如何以特定于平台的方式执行每个步骤。

从[developer.android.com/sdk/index.html](http://developer.android.com/sdk/index.html)开始下载 Android SDK(参见图 2–3)。

图 2–3。 Android SDK 下载页面

针对 Windows 的说明

使用 Android 安装程序,安装程序 r11-windows.exe 安装 Android SDK。这是推荐的 Windows 安装技术。另一种方法是下载 android-sdk r11-windows.zip 文件,并将其解压缩到一个文件夹中。我们假设 Android SDK 被提取到 c:\android_sdk。

【Linux 操作说明

下载 Archie Android-dk _ r11-Linux _ x86 . tgz 归档文件并解压到一个文件夹中。

Mac OSX 英特尔指令

下载存档 android-sdk_r11-mac_x86.zip 文件,并将其解压缩到文件夹中。

这个 Android SDK 可以支持目前已经发布的所有 Android 平台。这些平台包括 Android 1.1 平台到最近的 Android 3.0(蜂巢)平台。由于没有人需要所有的平台,Android SDK 没有预装任何平台。

对于本书,我们将只关注 SDK 平台:Android 2.2、API 8 和 revision 3。

由于没有预装平台,下一步是安装您感兴趣的平台。转到 Android SDK 位置(在我们的例子中是 c:\android_sdk),在 tools 文件夹中打开一个名为 Android 的可执行文件。如果您有带宽限制,不要下载所有平台,只下载 Android 2.2 平台(SDK 平台 Android 2.2、API 8 和修订版 3)。

这将打开在图 2–4 中看到的以下屏幕。选择可用的包选项,检查 Android 存储库,然后单击 Install。

图 2–4。 可以安装的可用平台包

既然您已经下载了平台,那么您就拥有了为目前已经发布的所有 Android 版本创建应用的必要工具。

建议您安装所有可用的包,这样您就可以有工具为已经发布的任何 Android 平台创建 Android 项目。

如果你想开发一个适用于 Froyo (Android 2.2)的移动应用,你需要有 Froyo (Android 2.2。)列在已安装的软件包中。

步骤 3:为 Eclipse 安装 Android ADT 插件

启动 Eclipse 并点击 Help->Install New Software 打开可用软件对话框。

在“使用”文本框中,输入 URL [dl-ssl.google.com/android/eclipse](https://dl-ssl.google.com/android/eclipse),如图 2–5 所示。

当您看到安装开发人员工具的选项时,单击它,选中开发人员工具复选框中的所有复选框,然后单击下一步。

图 2–5。 为 Eclipse 安装 Android ADT 插件

用之前安装的 Android SDK 的位置配置 Android ADT 插件。通过单击 Windows-> Windows 的首选项和 Eclipse-> Mac 的首选项打开 Eclipse 的首选项。如果您收到未签名内容警告对话框,您可以安全地忽略它。

In the Preferences pane, click and expand the Android option. You will see Android Preferences pane as shown in Figure 2–6. In the Android Preferences pane, put in the location of the Android SDK in the SDK Location text box, and hit Apply.

如果 Android SDK 的位置是正确的,您应该在 Target Name 下看到许多选项,包括 Android 2.2。

图 2–6。 在 Android 首选项屏幕中设置 Android SDK 的位置。

步骤 3:为 Android 2.2 平台创建 Android AVD

Open Eclipse and create a workspace for the Android PhoneGap. The next step is to create an emulator for Android. Since Android comes with many platform versions, we have to create an Android Virtual Device (AVD) for each platform that is targeted. In Figure 2–7, you will see your eclipse as depicted in the screen.

请注意,Android 模拟器运行的是 Android 虚拟设备(AVD)。

图 2–7。 带 ADT 插件的 Eclipse。

Click on the button on the toolbar to open the Android SDK and the AVD Manager. Choose the Virtual Devices option as depicted in Figure 2–8.

图 2–8。Android SDK 和 AVD 管理器

点击新建按钮创建一个新的 AVD。选择 Android 2.2 平台,也就是 Froyo。选择 128 MB 的 SD 卡大小,并选择皮肤内置为 HVGA。填写完所有内容后,单击 Create AVD。参见图 2–9 查看“AVD 屏幕”的样子。

图 2–9。 创建一个新的 Android 虚拟设备(AVD)在 Android 模拟器中运行

您将看到创建的 AVD,如图 2–10 所示。

图 2–10。 安卓 2.2 平台的 AVD(Froyo)

步骤 4:安装 PhoneGap SDK

Download the PhoneGap SDK 1.1.0 from the following link, [phonegap.googlecode.com/files/phonegap-1.1.0.zip](http://phonegap.googlecode.com/files/phonegap-1.1.0.zip). After this zip is extracted you should see a directory structure, as seen in Figure 2–11.

图 2–11。 PhoneGap SDK 1.1.0 目录结构

选择 Android 目录,你会看到 phonegap-1.1.0.jar 和 phonegap-1.1.0.js 文件(见图 2–12)。

图 2–12。PhoneGap SDK 内的 Android 文件夹。

这就完成了 Android PhoneGap 的设置。

创建新项目

本书中的第一个应用是 Hello World 应用。加载 PhoneGap 框架后,Hello World PhoneGap 移动应用会在屏幕上显示 Hello World。

第一步:创建一个 Android 项目

打开 Eclipse,点击文件->新建项目->Android 项目。这将打开一个 Android 项目对话框,如图 2–13 和图 2–14 所示。这显示在以下步骤中:

将 PhoneGap-helloworld 作为项目名称。

确保您已经选择了 Android 2.2 作为构建目标。

输入 Helloworld 作为应用名称。这是应用的可读名称。

输入org.examples.phonegap.sample作为包名。Android market 中的一个应用是由包名唯一标识的。Android market 上不能有两个具有相同包名的 Android 应用。

选中“创建活动”复选框,并输入 helloworld 作为活动名称。Android 中的活动是一个屏幕。并且活动名也是活动的类名。

在 min SDK 版本中放 7。这意味着您将允许所有 Android 2.1 设备平台(也称为 clair Android 手机)搜索和安装该应用。

图 2–13。 安卓项目创建

图 2–14。 Android 项目创建。

步骤 2:将 PhoneGap 库添加到项目中

一旦创建了 Android 项目,就该将 PhoneGap 框架注入到 Android 项目中了。正如我们之前提到的,PhoneGap 有三个主要组件:原生组件、XML 插件和 JavaScript 文件。

要在 Android 中安装原生组件,在项目中创建一个名为 lib 的目录,并将 PhoneGap jar 复制到其中。您可以将phonegap-1.1.0.jar拖放到 lib 文件夹中,也可以将其复制并粘贴到 Eclipse IDE 的 lib 文件夹中。接下来,通过右键单击 Build Path - > Add to Build Path,将 PhoneGap jar 添加到类路径中。这在图 2–15 中突出显示。

Copy the XML directory from the PhoneGap’s Android Directory into the res folder.

图 2–15。 突出显示 Android 项目中 PhoneGap jar 的位置

一旦 PhoneGap Jar 被添加到 Android 项目中,就该将 PhoneGap 的 JavaScript 文件注入到项目中了。我们将在 Android 项目的 Assets 文件夹下创建一个 www 文件夹。Assets 文件夹类似于 Android 应用的 media 文件夹。在我们的例子中,我们将把基于浏览器的应用的所有文件放在 www 文件夹中。首先,将 PhoneGap JavaScript 文件添加到 www 文件夹中,该文件夹位于 Assets 文件夹中。这在图 2–16 中突出显示。

图 2–16。 突出显示 PhoneGap JavaScript 文件在 Android 项目中的位置

第三步:修改 Android 权限

在 Android 应用中,主文件是 Android 清单文件。在这个文件中有许多特定的东西,比如包名,它们唯一地标识了市场上的应用。主文件包含一个名为 permissions 的部分。Android 使用这一部分来通知用户应用将使用手机的某些功能。假设一个应用打算使用互联网获取数据;需要获得许可才能安装应用。当用户安装应用时,Android market 会向他显示该应用将被允许使用互联网。

对于 PhoneGap,需要添加以下权限:

向 Android 清单 XML 添加以下权限:

我们还需要在清单文件中添加 supports-screen 选项,如下所示:

将android:configChanges=orignetation|keyboardHidden添加到 Android 清单中的活动。这告诉 Android 当用户翻转手机,屏幕从纵向切换到横向时,不要取消和重新创建活动,反之亦然。

通过应用以下 XML 片段,在前一个活动之后添加第二个活动:

一旦您按照前面的说明修改了 Android Manifest,就会出现一个 Android Manifest XML。将会看到如下内容:

`

package="org.examples.phonegap.helloworld" android:versionCode="1"

android:versionName="1.0">

android:normalScreens="true" android:smallScreens="true"

android:resizeable="true" android:anyDensity="true" />

android:configChanges="orientation|keyboardHidden">

android:configChanges="orientation|keyboardHidden">

`

第 4 步:修改主活动

在 Android 中,一个名为 activity 的类代表一个屏幕。为了让我们在 Android 中使用 PhoneGap,我们将把屏幕从活动更改为 DroidGap。DroidGap 是一个特殊的活动,它允许我们显示 HTML 页面。该类如 HelloWorld 类的图 2–17 所示。

注意:我们告诉 DroidGap 在 Android 资产中加载 index.html 文件。

`package org.examples.phonegap.helloworld;

import android.os.Bundle;

import com.phonegap.DroidGap;

public class HelloWorld extends DroidGap {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

super.loadUrl("file:///android_asset/www/index.html");

}

}`

图 2–17。 扩展 DroidGap 类的活动

编写 HelloWorld 应用

PhoneGap 应用是一个 HTML/JavaScript 应用。参见图 2–18。下面是 index.html。

在 HTML 页面中包含 PhoneGap JavaScript 库版本 1.1.0。

用主体的 onload 事件注册init()方法。

在init()函数中,用 DeviceReady 事件注册 JavaScript 回调函数 onDeviceReady。

在 onDeviceReady 回调函数中,将 ID 为“helloworld”的h1元素的内容更改为文本“hello World!已加载 PhoneGap 框架!”

这里列出了完整的源代码:

`

PhoneGap

jQuery HTML 操作的完整列表可以在[www.w3schools.com/jquery/jquery_ref_html.asp](http://www.w3schools.com/jquery/jquery_ref_html.asp)找到。

jQuery Ajax 调用

jQuery 为 Ajax 调用提供了许多有用的方法。

下面是一个对 URL 进行 Ajax GET 调用的例子。这是“少写多做”的经典例子。以下代码执行 Ajax GET 调用 service/employee/details.txt,并将内容放入 id 为“details”的 div 中:

$.get("service/employee/details.txt", function (result) { $("div#details").html(result); });

以下是对 URL 进行 Ajax POST 调用的示例,发布数据{name:employeeName}:

$.post("service/employee/details", { name: employeeName }, function (result) { alert("Post successful"); });

jQuery HTML 操作的完整列表可以在 [www.w3schools.com/jquery/jquery_ref_ajax.asp](http://www.w3schools.com/jquery/jquery_ref_ajax.asp)找到。

熟悉 jQueryMobile

jQueryMobile 通过提供一个通用的 UI 平台来开发跨许多流行的移动平台的移动应用,将 jQuery“少写多做”的概念推向了一个新的高度。

jQueryMobile 建立在非常流行和健壮的 jQuery 和 jQuery UI 框架之上。jQueryMobile 提供了现成的、可触摸的移动小部件,比如列表视图、带有后退按钮的标题、导航动画等等。这些小部件具有专业而精致的外观和感觉,使得开发现成的应用变得更加容易。

jQueryMobile 的主页是[jquerymobile.com/](http://jquerymobile.com/)。

而且,jQueryMobile 提供了五种开箱即用的主题供你选择。下面的示例展示了按钮在不同主题中的外观。总的来说,我们有五个主题——主题 a、主题 b、主题 c、主题 d 和主题 d——如图图 4–1 所示。

图 4–1。 jQueryMobile 主题

此外,jQueryMobile 还为表 4–1 中列出的平台提供坡度支持。

在移动应用中包含 jQueryMobile

从[jquerymobile.com/download/](http://jquerymobile.com/download/)下载 jquery.mobile-1.0rc2.zip 并解压。解压缩后,您会看到如图图 4–2 所示的文件夹结构。它包含两对 jQueryMobile JavaScript 和一个 CSS 文件。顾名思义,您可以使用。最小化生产中的 JavaScript 和 CSS 文件,因为它们是缩小的 JavaScript 和 CSS 文件。

除此之外,你还需要在手机应用中包含图片文件夹。

图 4–2。 jQueryMobile 文件夹结构

以下是 jQueryMobile 示例的 HTML 模板:

`


jQuery Mobile Demo

`

jQueryMobile 声明式用户界面

声明式 UI 构建是 jQueryMobile 最好的部分。您不需要编写复杂的 JavaScript 代码来构建 UI。UI 构建就像添加带有一些 jQueryMobile 特定属性及其值的普通 HTML 元素一样。

页面和对话框

您在上一节中看到了 HTML 模板。现在,您将向其中添加 jQueryMobile 布局和小部件。

您可以在 body 标记内的 div 元素中使用数据角色属性来声明页面。因此,在 body 标记中,您可以通过声明以下内容来声明许多页面:

同样,您可以通过将数据角色声明为“页眉”、“内容”和“页脚”来声明页面的组件以下是 jQueryMobile 中的一个页面示例:

`

`

此处显示了一个完整的页面示例。

`

jQuery Mobile Demo

Page Title

Page content goes here.

Page Footer

`

在浏览器中运行该 html 时,会显示一个如图 4–3 所示的屏幕。

图 4–3。 jQueryMobile pages

现在,您已经了解了 jQueryMobile 中页面的概念,让我们来看一个拥有多个页面或对话框的场景。

典型的应用有多个页面和对话框。jQueryMobile 最棒的一点是,您可以在同一个 HTML 页面中定义所有这些不同的页面和对话框。

下面是怎么做的。在一个 HTML 页面中定义多个 div,并赋予它们以下内容:

一个名为 data-role 的属性设置为 page。这看起来像这样:data-role="page "

一个名为“id”的属性,用于在代码中标识它们

您可以使用链接和按钮导航到这些页面和对话框。最简单的方法是执行以下操作:

将 href 定义为#+ <>

给该链接一个 data-role="button "

注意,页面和对话框的声明是相同的。事实上,没有什么叫做对话框,但是你可以在弹出窗口中加载一个页面作为对话框。

下面是一个页面链接的示例。当单击此链接时,会转换到 id 为“page2”的页面。还要注意,因为链接被赋予了 data-role="button ",所以它看起来像一个按钮。

Page Navigation

将页面作为对话框打开与导航到页面非常相似。您只需要向链接添加两个属性:

data-rel="dialog "

data-transition="pop "(这是动画效果)

Open Dialog

这里有一个完整的例子供你尝试。图 4–4 显示的是“主”页面,图 4–5 显示的是“第二页”页面,图 4–6 显示的是“对话 1”页面,相当于一个对话框:

`

jQuery Mobile Demo

Main Page

Page Nav and Dialog Example

Page Navigation

Open Dialog

Main Page Footer

Second Page

Second Page

Click back to go back to main page

Dialog Title

v

Dialog body

Click close button to go back to main page

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-04.jpg)

图 4–4。jquerymobile page navigation

图 4–5。jquerymobile page navigation

图 4–6。 jQueryMobile 对话框

工具栏和按钮

在 jQueryMobile 中,有两种类型的工具栏:

标题栏

页脚栏

一般来说,创建工具栏就像在标题栏或页脚栏中声明一些按钮一样简单。这在图 4–7 中进行了描述。

`

jQuery Mobile Demo

Cancel

Edit Contact

Save

Header Footer Toolbar Example

Remove

Add

Up

Down

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-07.jpg)

图 4–7。 jQueryMobile 工具栏和按钮

表格元素

jQueryMobile 中的表单元素是典型的 HTML 表单元素——它们只是看起来不同。这意味着您可以使用您的 HTML JavaScript 技能来呈现好看、精美的 jQueryMobile 小部件,并使用传统的事件处理技术来快速编写您的移动 web 应用。

让我们看几个表单元素的例子。在第一个例子中,我们使用了一个输入文本、一个文本区域和一个搜索框。对于所有这些,都分配了一个标签。它们被包装在一个字段集中,形成一组标签和相关的小部件。请参见 Figure 4–8 以了解表单元素在 jQueryMobile 中的外观。

`

jQuery Mobile Demo

Cancel

Edit Contact

Save

Simple Form Elements

Text Input:

Textarea:

Search Input:

Remove

Add

Up

Down

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-08.jpg)

图 4–8。 jQueryMobile 表单元素

在第二个例子中,HTML select 被包装在一个好看的开/关开关中。请注意,当您以编程方式从中获取值时,您将把它用作 HTML 选择框。您还用滑块包装了一个文本框。滑块的值放在文本框中。这相当于用户在文本框中填写一个数字,但是使用 jQueryMobile,用户可以使用滑块在给定的范围内选择一个值。参见 Figure 4–9 来看看这个 HTML 是如何呈现的。

`

jQuery Mobile Demo

Cancel

Edit Contact

Save

Simple Form Elements

On/Off Switch:

Off

On

Range Slider:

Remove

Add

Up

Down

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-09.jpg)

图 4–9。 jQueryMobile 表单元素

在下面的例子中,你可以看到将 HTML 单选按钮和复选框包装成好看的小部件是多么容易。这是通过使用数据角色“控制组”和数据类型“水平”来完成的请参见图 4–10 了解单选和多选表单元素的外观:

`

jQuery Mobile Demo

Cancel

Edit Contact

Save

Single and MultiSelect Form Elements

Choose a base:

Thin Crust

Double Cheese Burst

Class Hand Tossed

Choose Pizza toppings

Jalepeno

Olives

Cheese

Capsicum

Non Veg topping:

Pepperoni

Ham

Turkey

Payment Type:

Cash

Coupons

Credit Card

Remove

Add

Up

Down

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-10.jpg)

图 4–10。 jQueryMobile 表单元素

列表视图

到目前为止,您已经看到了用带有数据角色和 CSS 类的简单 HTML 元素来声明各种 UI 小部件是多么容易。在 jQueryMobile 中,列表视图也不例外。下面的例子将向你展示如何将一个 HTML 列表转换成一个移动可滚动列表。

`

USA

UK

Russia

`

下面是如何在 HTML 中声明列表的完整代码示例。对于动态数据,您所要做的就是在运行时将 li 元素追加到 ul 元素中。请参考 Figure 4–11 查看 jQueryMobile 中的列表视图。

`

jQuery Mobile Demo

Header

USA

UK

Russia

Footer

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-11.jpg)

图 4–11。 jQueryMobile 列表视图

您可以在 jQueryMobile 网站上了解更多相关信息。点击此链接查看演示和文档:jquerymobile.com/demos/1.0a4.1/。

jQueryMobile 事件处理

在 jQueryMobile 中,事件处理可以分为两个方面:

非 jQueryMobile 小部件生成的事件。例如文本框、文本区域、按钮、单选按钮等等。

jQueryMobile 小部件和框架生成的事件。这些事件的例子是触摸事件、方向改变事件、滚动事件和页面生命周期事件。

正常事件

普通事件应该像 jQuery 通常做的那样处理。在 jQuery 中,jQuery 选择器提供了一个通用方法 bind,它允许我们绑定到任何事件。

`

mybutton

$("#mybutton").bind("click",function(event){

alert("clicked mybutton");

});

jQuery also provides many convenience method for events like a short hand of above method is click method, which binds the callback to click event.

$("#mybutton").click(function(event){

alert("clicked mybutton");

});`

您可以在以下网站上阅读有关 jQuery 事件的更多信息:

api.jquery.com/category/events/

现在,转到 jQueryMobile 生成的事件。请注意,所有事件,无论其来源如何,都需要以上述方式处理。这同样适用于下面记录的事件。

下一节将讨论由 jQueryMobile 框架和小部件生成的各种事件。

触摸事件

手机或平板电脑上的触摸事件与点击或双击等传统鼠标事件有很大不同。以类似的方式,在传统的鼠标事件中,手势是不可能的。jQueryMobile 提供了一组用于触摸手势的新事件。这些事件在表 4–2 中描述。

以下是 jQueryMobile 如何处理触摸事件的示例。参见图 4–12 中的示例。

`

jQuery Mobile Touch Events Demo

Touch Events

Touch Events example

Tap here

Tap and hold here

Swipe in this area.

Swipe Left <-- in this area.

Swipe Right -- > in this area.

Footer

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-12.jpg)

图 4–12。 jQueryMobile 触摸事件。

方位变化事件

移动设备和平板设备都可以检测方位变化并做出反应。这非常有用,因为在纵向模式和横向模式下,移动设备或平板电脑的长宽比是不同的。jQueryMobile 允许开发人员使用这种方向更改来在两种模式下充分利用屏幕空间。为此,您需要侦听窗口元素上的“orientationchange”事件。下面的示例显示了这一点。参见图 4–13 和 4–14。

`

jQuery Mobile Touch Events Demo

\((document).ready(function(){

\)(window).bind('orientationchange', function(event){

$("#placeholder").html("Orientation changed to "+event.orientation);

});

});

Touch Events

Orientation Events example

Footer

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-13.jpg)

图 4–13。 jQueryMobile 方向改变事件

图 4–14。 jQueryMobile 定向变更事件

滚动事件

移动设备的一个重要方面是滚动和滚动时在后台做事情的能力。想想惰性加载列表,它在用户滚动列表时获取数据。

为此,您需要滚动事件(参见 Table 4–3)。jQueryMobile 提供滚动事件。请注意,当 scrollstart 事件在 iOS 上不按预期工作时,我们建议不要依赖 iOS 上的 scrollstart 事件。

页面事件

jQueryMobile 有页面的概念。jQueryMobile 中的页面被创建、显示和/或隐藏。jQueryMobile 提供了事件,这样开发人员可以在页面创建之前、创建之后以及页面显示和隐藏之前和之后进行适当的处理。所有这些事件都记录在表 4–4 中。

PhoneGap jQueryMobile 集成

既然您已经了解了 jQueryMobile 的工作原理,那么让我们来集成 PhoneGap 和 jQueryMobile 的特性来创建应用。

请注意,当您将 jQueryMobile 与 PhoneGap 一起使用时,有三个 JavaScript 框架,每个框架都有自己的引导。

PhoneGap 框架

jQuery 框架

jQueryMobile 框架

虽然所有框架都提供了自己的引导机制,但最好按照以下顺序引导这些框架:

语音间隙

框架

jQueryMobile(如果真的需要的话)。

这显示在以下示例中:

``

使用 jQueryMobile 和 PhoneGap 进行本地搜索

让我们来测试一下 jQueryMobile 和 PhoneGap。您将构建 PhoneGap geo、compass 和数据库特性的混搭,并结合使用 jQueryMobile 构建的 UI 和 Google Maps Places API。参见图 4–15。

这种混搭被称为本地搜索。这个混搭具有以下特性:

允许用户在他/她的当前位置的给定半径内搜索感兴趣的地方。

允许用户查看该地点的详细信息并访问该地点的网站。

允许用户将地点保存为收藏夹,并将其从收藏夹中删除。

允许用户浏览他保存的收藏夹。

允许用户在谷歌地图上查看所有的搜索结果。

图 4–15。使用 jQueryMobile 和 PhoneGap 的本地搜索

该屏幕中最重要的两个特征如下(参见图 4–16):

搜索按钮用于搜索用户位置 5 公里半径内的比萨饼

收藏夹按钮向用户显示所有标记为收藏夹的地方

图 4–16。使用 jQueryMobile 和 PhoneGap 的本地搜索

搜索结果可以在地图上显示为列表或标记。Figure 4–17 显示了结果如何在中显示为列表。

图 4–17。本地搜索结果

当用户单击其中一个搜索结果时,他会被带到企业详细信息页面。该页面显示了详细信息,如名称、地址和电话号码,以及企业附近。它还允许用户将业务条目添加到他/她的收藏夹中,或者从收藏夹中删除它。这显示在图 4–18 中。

图 4–18。本地搜索业务明细

当用户将企业添加到他的收藏夹时,他可以从主页导航到收藏夹页面。在这里,用户将看到保存为收藏夹的地点/企业。这些条目实际上存储在应用的内部数据库中(由 PhoneGap 提供)(参见图 4–19)。

图 4–19。本地存储的收藏夹。

同样,当用户点击其中一个条目时,他会被带到详细信息页面。如果您能观察到,我们现在可以看到“移至收藏夹”按钮,因为该企业/地点已经是收藏夹的一部分。参见图 4–20。

图 4–20。最喜欢的细节。

最后,但并非最不重要的是,整个搜索结果绘制在谷歌地图上。为此,您需要进入主页,点击搜索,然后点击地图选项卡。您将看到如图图 4–21 所示的屏幕。

图 4–21。地图上的本地搜索结果

引导 PhoneGap 和 jQuery

引导按以下顺序完成:

当调用 PhoneGap JavaScript 库时,PhoneGap 被引导来调用 onDeviceReady()函数

在 onDeviceReady 中,jQuery 被引导,以便在加载 jQuery 时调用匿名函数

安装必要的 JavaScript 库

对于这个项目,您将需要以下 JavaScript 库

jQuery: [docs.jquery.com/Downloading_jQuery#Download_jQuery](http://docs.jquery.com/downloading_jquery#download_jquery)

jQueryMobile: [jquerymobile.com/download/](http://jquerymobile.com/download/)

jQuery ui 映射:??]

语音间隙:[www.phonegap.com/download/](http://www.phonegap.com/download/)

假设您的应用 JavaScript 名为 app.js,应用 CSS 名为 app.css,您的 www 文件夹应该如图 Figure 4–22 所示。注意,images 文件夹属于 jQueryMobile 库。

图 4–22。本地搜索项目结构

本地搜索的布局

本地搜索页面是应用中的主页面。进行本地搜索需要三个输入。

PhoneGap 的地理位置

从搜索文本框中搜索关键字

jQueryMobile 滑块的搜索半径

一旦完成,它将获取搜索结果并显示在搜索结果页面中。

`

Local Search

Local Search

Search Range(kms):

Search

Favorites

About us

`

寻找当地企业

为了搜索感兴趣的地方,我们使用谷歌地图的位置 API。可以通过对 Google Maps Places 服务端点进行 restful 调用来搜索感兴趣的地方。进行这个 restful 调用需要很多参数;这些显示在表 4–5 中。

此处显示的 URL 带有每个参数的填充符:

https://maps.googleapis.com/maps/api/place/search/json?location={latitude,longi tude}&radius={radius}&types=food&name={search_keyword} &sensor=false&key={api_key}.

对于以下 URL,会出现 JSON 响应:

https://maps.googleapis.com/maps/api/place/search/json?location=- 33.8670522,151.1957362&radius=500&types=food&name=harbour&sensor=true&key=<>.

JSON 的回应如下:

{ "html_attributions": ["Listings by \u003ca href=\"http://www.yellowpages.com.au/\"\u003eYellow Pages\u003c/a\u003e"], "results": [{ "geometry": { "location": { "lat": -33.8719640, "lng": 151.1985440 } }, "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", "id": "aefbc59325ffd5f3e93d67932375d20d143289de", "name": "Toros Restaurant Darling Harbour", "reference": "CoQBdgAAAE6oRybc13OZYNH0WeuwKzTfzjYXO8nuWyGqCqSTBogR_BZxE30fgXsybOl_wIR0s_uuHLZqq- 17DTgpGHZoSehSbOG73dfIxO3rpQak2OmNuBb5Kg63rPN_afbH_PnbILiofw6WSODYOCkqhFl38qSXyujAPkQKZU 76NJypgT6mEhCg1MhyNAuyark4X8YfRg4YGhTn_MXr0gelHUHPe3JMCic-cHlu3A", "types": ["restaurant", "food", "establishment"], "vicinity": "Darling Dr, Sydney" }, …] }

注意,在前面的 JSON 中,有一个 id 和引用。您需要更好地理解这些,以便能够构建您的应用。

Id 是一个地方的唯一标识符。当您在数据库中存储地点/企业时,您将使用 id 作为主键。但是,id 不能用于获取最新信息。

Reference 是一个键,用于从 Google Places 服务器获取一个地点/企业的详细信息。但是,请注意,引用在多个搜索结果中不是唯一的。

因此,您将在数据库中存储 id(作为主键)和 reference(作为字符串),这样您就可以唯一地标识一个地点/企业(使用 id ),并随时使用 reference 从 Google Places 获取信息。

HTML 中的整体布局

下面是应用的总体布局。在应用的所有五个页面中:

搜索页面

id 为“列表”的搜索结果页面

id 为“详细信息”的详细信息页面

id 为“收藏”的收藏列表页面

id 为“Map”的地图页面

`

PhoneGap

Local Search

Local Search

Search Range(kms):

Search

Favorites

About us

Result

List

Maps

Map

List

Maps

Favorites

Business Details

Business Details

Remove to Favorite

Add to Favorite

Visit HomePage

Name

Address

Phone

Rating

`

获取并显示搜索结果

函数启动的搜索将搜索按钮事件与实际执行搜索的函数绑定在一起。

以下是搜索的事件流程:

向用户显示加载图标,让他/她知道正在进行长时间操作。这是通过调用$ .mobile.showPageLoadingMsg()来实现的;。

使用 PhoneGap 函数 navigator . geolocation . getcurrentposition(success callback,failureCallback)获取用户的当前位置。

在上述调用的 successCallback 中,使用以下参数完成了对 Google Places 的 JSON 请求:

地理定位

搜索关键字

地理位置的搜索半径

Google Places 的开发者 Api 密钥

var

jQuery 的\(。getJSON()用于对 Google Places 进行 Ajax 调用,以获取 JSON 响应。请注意,由于 PhoneGap 应用没有域名,因此您不会受到浏览器单一来源政策的限制。用\)注册 successCallback 和 failureCallback。getJSON()函数。

在上述调用的 successCallback 中,获取位置响应并将其附加到 id 为“result-list”的 ul 元素中。这个 ul 元素在 HTML 代码中被注释为 jQueryMobile 列表视图。一旦您将必要的 li 元素添加到 ul 元素中,我们将调用$(“结果列表”)。listView(“refresh”)将 ul 元素重绘为 jQueryMobile 列表。

注意,您将每个地方的 JSON 响应的引用部分作为链接的 id(一个元素)。这样做是为了当用户点击这个条目时,他会被带到详细信息页面。注意,这个链接的 href 部分实际上是“#details”

最后,您将一个 click 处理程序与 place 上的 click 事件绑定在一起,这样您就可以实际调用 Google Places 服务器,在将用户导航到详细信息页面之前获取企业/地点条目的详细信息。

最后一步是通过调用$.mobile.hidePageLoadingMsg()移除加载图标;。

`/**

* Binding Search button handler to go and fetch place results

*/

function initiateSearch(){

\(("#search").click(**function**(){

**try** {

\).mobile.showPageLoadingMsg();

navigator.geolocation.getCurrentPosition(function(position){

var radius = $("#range").val() * 1000;

mapdata = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);

var url = "https://maps.googleapis.com/maps/api/place/search/json?location=" +

position.coords.latitude + "," + position.coords.longitude + "&radius=" + radius +

"&name=" + \(("#searchbox").val() + "&sensor=false&key=AIzaSyC4vCfT_Knq1SGuNMahZqyrmZFiTuBsdlY";

\).getJSON(url, function(data){

cachedData = data;

\(("#result-list").html("");

**try** {

\)(data.results).each(function(index, entry){

var htmlData = "

entry.icon + "" class="ui-li-icon">

" + entry.name +

" vicinity:" + entry.vicinity + "

";

var liElem = $(document.createElement('li'));

$("#result-list").append(liElem.html(htmlData));

$(liElem).bind("tap", function(event){

event.stopPropagation();

fetchDetails(entry);

return true;

});

});

$("#result-

list").listview('refresh');

}

catch (err) {

console.log("Got error while putting search result on result page " + err);

}

\(.mobile.changePage("list");

\).mobile.hidePageLoadingMsg();

}).error(function(xhr, textStatus, errorThrown){

console.log("Got error while fetching search result : xhr.status=" + xhr.status);

}).complete(function(error){

$.mobile.hidePageLoadingMsg();

});

}, function(error){

console.log("Got Error fetching geolocation " + error);

});

}

catch (err) {

console.log("Got error on clicking search button " + err);

}

});

}`

显示一个地方/商业的细节

您已经看到,当用户单击显示地点搜索结果列表中的地点条目时,会调用 fetchDetails()函数。

fetchDetails()函数的流程如下:

向用户显示加载图标。这是通过调用$ .mobile.showPageLoadingMsg()来完成的。

明细位的所有字段都被重置为空白。例如$(“# name”)。html();。

创建一个详细信息位置请求的 URL(details URL ),并使用 jQuery $进行 Ajax 调用。getJSON()调用。

在$的成功回调中。getJSON(),您将获得页面的详细信息。在这里,您首先检查给定的地点是否已经被用户存储为收藏夹。基于此,向用户显示“添加到收藏夹”或“从收藏夹移除”按钮。

此页面的字段已填充。

加载图标通过调用$.mobile.hidePageLoadingMsg()来移除。

`/**

* Fetch the details of a place/business. This function is called before user navigates to details page

* @param {Object} reference

*/

function fetchDetails(entry){

currentBusinessData = null;

\(.mobile.showPageLoadingMsg();

**var** detailsUrl = "https://maps.googleapis.com/maps/api/place/details/json?reference=" +

entry.reference + "&sensor=true&key=";

\)("#name").html("");

\(("#address").html("");

\)("#phone").html("");

\(("#rating").html("");

\)("#homepage").attr("href", "");

$.getJSON(detailsUrl, function(data){

if (data.result) {

currentBusinessData = data.result;

isFav(currentBusinessData, function(isPlaceFav){

console.log(currentBusinessData.name+" is fav

"+isPlaceFav);

if (!isPlaceFav) {

\(("#add").show();

\)("#remove").hide();

}

else {

\(("#add").hide();

\)("#remove").show();

}

\(("#name").html(data.result.name);

\)("#address").html(data.result.formatted_address);

\(("#phone").html(data.result.formatted_phone_number);

\)("#rating").html(data.result.rating);

$("#homepage").attr("href", data.result.url);

});

}

}).error(function(err){

console.log("Got Error while fetching details of Business " + err);

}).complete(function(){

$.mobile.hidePageLoadingMsg();

});

}`

在收藏夹中添加和删除地点/企业

下一步是实际观察

我们如何将一个地方添加到收藏夹列表中?

我们如何从收藏夹列表中删除一个位置?

我们如何找到一个给定的地方是我们的收藏夹列表的一部分?

这里需要注意的是,您使用 PhoneGap 的数据库 API 来存储、检索和删除位置。所有这些信息都使用 PhoneGap 的数据库 API 存储在应用的数据库中。您将把收藏夹存储在名为“favorite”的表中

initiateFavButton()将“添加到收藏夹”和“从收藏夹中删除”按钮的点击处理程序绑定到实际的处理程序。“添加到收藏夹”按钮位于 id 为“添加”的 div 中,“从收藏夹中移除”位于 id 为“移除”的 div 中您可以通过隐藏或显示这些 div 来控制按钮的可见性。您还可以相应地调用 addToFavorite()和 removeFromFavorite()方法来实际执行添加和删除操作。

`/**

* Called to bind the "Add to Favorite" Button

*/

function initiateFavButton() {

$("#removefav").click(function () {

try {

if (currentBusinessData != null) {

removeFromFavorite(currentBusinessData);

\(("#add").show();

\)("#remove").hide();

}

} catch (err) {

console.log("Got Error while removing " + currentBusinessData.name + " error " + err);

}

});

\(("#addfav").click(function () {

try {

if (currentBusinessData != null) {

addToFavorite(currentBusinessData);

\)("#add").hide();

$("#remove").show();

}

} catch (err) {

console.log("Got Error while adding " + currentBusinessData.name + " error " + err);

}

});

}`

ensureTableExists()是所有其他数据库函数使用的公共函数。此函数确保您在对数据库执行任何插入、选择或删除操作之前,执行 SQL 脚本“如果不存在则创建表收藏夹(id 唯一、引用、名称、地址、电话、评级、图标、邻近)”。

`/**

* Ensure we have the table before we use it

* @param {Object} tx

*/

function ensureTableExists(tx) {

tx.executeSql('CREATE TABLE IF NOT EXISTS Favorite (id unique, reference,

name,address,phone,rating,icon,vicinity)');

}`

addToFavorite()是一个函数,它实际上为 favorites 表中的给定位置执行数据库插入。请注意,您正在数据库表“favorite”中存储 id、reference、name、icon、formatted_address、formatted_phone_number、rating 和 neighborhood。

` /**

* Add current business data to favorite

* @param {Object} data

*/

function addToFavorite(data) {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

db.transaction(function (tx) {

ensureTableExists(tx);

var id = (data.id != null) ? ('"' + data.id + '"') : ('""');

var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');

var name = (data.name != null) ? ('"' + data.name + '"') : ('""');

var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');

var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');

var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');

var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');

var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');

var insertStmt = 'INSERT INTO Favorite (id,reference,

name,address,phone,rating,icon,vicinity) VALUES (' + id + ',' + reference + ',' + name + ',' + address + ',' + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';

tx.executeSql(insertStmt);

}, function (error) {

console.log("Data insert failed " + error.code + " " + error.message);

}, function () {

console.log("Data insert successful");

});

}`

removeFromFavorite()是从“Favorite”表中删除收藏夹的函数。它只需要 id 就可以这样做。

`/**

* Remove current business data from favorite

* @param {Object} data

*/

function removeFromFavorite(data) {

try {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

db.transaction(function (tx) {

ensureTableExists(tx);

var deleteStmt = "DELETE FROM Favorite WHERE id = '" + data.id + "'";

console.log(deleteStmt);

tx.executeSql(deleteStmt);

}, function (error) {

console.log("Data Delete failed " + error.code + " " + error.message);

}, function () {

console.log("Data Delete successful");

});

} catch (err) {

console.log("Caught exception while deleting favorite " + data.name);

}

}`

isFav()是查询表“favorite”的函数,以找出给定的地点/企业是否已经存在于表中,并因此被用户标记为 favorite。

` /**

*

* @param {Object} reference

* @return true if place is favorite else false

*/

function isFav(data, callback) {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);

try {

db.transaction(function (tx) {

ensureTableExists(tx);

var sql = "SELECT * FROM Favorite where id='" + data.id + "'";

tx.executeSql(sql, [], function (tx, results) {

var result = (results != null && results.rows != null && results.rows.length > 0);

callback(result);

}, function (tx, error) {

console.log("Got error in isFav error.code =" + error.code + " error.message = " + error.message);

callback(false);

});

});

} catch (err) {

console.log("Got error in isFav " + err);

callback(false);

}

}`

载入你最喜欢的地方

到目前为止,您已经在收藏夹中添加和删除了一个位置。您还看到了如何检查一个地方是否被用户设置为他/她的最爱。现在,让我们看看检索用户所有最喜欢的地方的代码。

当用户点击主页上的“收藏夹”按钮时,就会调用这个代码。

这段代码与 isFav()非常相似,只不过在这里您将从“favorite”表中获取所有条目,获取结果集,并用“fav-list”id 填充 UL。

填充部分类似于显示搜索结果。

请注意,每次用户导航到“收藏夹位置”时,您都会从数据库表“收藏夹”中获取结果这是通过监听页面的“pagebeforeshow”事件来完成的。“pagebeforeshow”事件在向用户显示 jQueryMobile 页面之前触发。

`/**

* Called each time before user navigates to Favorites

*/

function initiateFavorites() {

$("#fav").live("pagebeforeshow", function () {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);

try {

db.transaction(function (tx) {

tx.executeSql('SELECT * FROM Favorite', [], function (tx, results) {

$("#fav-list").html("");

if (results != null && results.rows != null) {

for (var index = 0; index < results.rows.length; index++) {

var entry = results.rows.item(index)

var htmlData = "

" + entry.name + " vicinity:" + entry.vicinity + "

";

var liElem = $(document.createElement('li'));

$("#fav-list").append(liElem.html(htmlData));

$(liElem).bind("tap", function (event) {

event.stopPropagation();

fetchDetails(entry);

return true;

});

}

$("#fav-list").listview('refresh');

}

}, function (error) {

console.log("Got error fetching favorites " + error.code + " " + error.message);

});

});

} catch (err) {

console.log("Got error while reading favorites " + err);

}

});

}`

在地图上显示搜索结果

这个练习的最后一部分是在谷歌地图上显示一个地方的搜索结果。这有助于用户更好地了解一个地方的位置。在 initiateSearch()函数中,当接收到搜索结果时,将结果缓存在一个名为“cachedData”的 JavaScript 变量中。在这种情况下,您实际上将使用相同的数据在地图上绘制标记。

请注意,每次用户导航到“收藏夹位置”时,您都要重新绘制地图并从 cachedData 中绘制标记这是通过监听页面的“pagebeforeshow”事件来完成的。

` /**

* Called to initiate Map page

*/

function initiateMap() {

$("#map").live("pagebeforecreate", function () {

try {

\(('#map_canvas').gmap({

'center': mapdata,

'zoom': 12,

'callback': function (map) {

\)(cachedData.results).each(function (index, entry) {

\(('#map_canvas').gmap('addMarker', {

'position': new

google.maps.LatLng(entry.geometry.location.lat, entry.geometry.location.lng),

'animation': google.maps.Animation.DROP

}, function (map, marker) {

\)('#map_canvas').gmap('addInfoWindow', {

'position': marker.getPosition(),

'content': entry.name

}, function (iw) {

$(marker).click(function () {

iw.open(map, marker);

map.panTo(marker.getPosition());

});

});

});

});

}

});

console.log("Map initialized");

} catch (err) {

console.log("Got error while initializing map " + err);

}

});`

完整的源代码

index.html 的完整来源如下:

`

PhoneGap

Local Search

Local Search

Search Range(kms):

Search

Favorites

About us

Result

List

Maps

Map

List

Maps

Favorites

class="ui-bar-a

ui-footer ui-footer-fixed fade ui-fixed-overlay" role="contentinfo" style="top: -

1263px; ">

Business Details

Business Details

Visit

HomePage

Name

Address

Phone

Rating

`

app.js 的完整源代码如下。请注意,您需要用自己的钥匙替换。可以从[code.google.com/apis/maps/documentation/places](http://code.google.com/apis/maps/documentation/places)获取 API 密钥。

`var mapdata = null;

var cachedData = null;

var currentBusinessData = null;

/**

* Fetch the details of a place/business. This function is called before user

navigates to details page

* @param {Object} reference

*/

function fetchDetails(entry) {

currentBusinessData = null;

\(.mobile.showPageLoadingMsg();

var detailsUrl =

"https://maps.googleapis.com/maps/api/place/details/json?reference=" + entry.reference + "&sensor=true&key=";

\)("#name").html("");

\(("#address").html("");

\)("#phone").html("");

\(("#rating").html("");

\)("#homepage").attr("href", "");

$.getJSON(detailsUrl, function (data) {

if (data.result) {

currentBusinessData = data.result;

isFav(currentBusinessData, function (isPlaceFav) {

console.log(currentBusinessData.name + " is fav

" + isPlaceFav);

if (!isPlaceFav) {

\(("#add").show();

\)("#remove").hide();

} else {

\(("#add").hide();

\)("#remove").show();

}

\(("#name").html(data.result.name);

\)("#address").html(data.result.formatted_address);

\(("#phone").html(data.result.formatted_phone_number);

\)("#rating").html(data.result.rating);

$("#homepage").attr("href", data.result.url);

});

}

}).error(function (err) {

console.log("Got Error while fetching details of Business " + err);

}).complete(function () {

$.mobile.hidePageLoadingMsg();

});

}

//-------------------------------

/**

* Called to initiate Map page

*/

function initiateMap() {

$("#map").live("pagebeforecreate", function () {

try {

\(('#map_canvas').gmap({

'center': mapdata,

'zoom': 12,

'callback': function (map) {

\)(cachedData.results).each(function (index, entry) {

\(('#map_canvas').gmap('addMarker', {

'position': new

google.maps.LatLng(entry.geometry.location.lat, entry.geometry.location.lng),

'animation': google.maps.Animation.DROP

}, function (map, marker) {

\)('#map_canvas').gmap('addInfoWindow', {

'position': marker.getPosition(),

'content': entry.name

}, function (iw) {

$(marker).click(function () {

iw.open(map, marker);

map.panTo(marker.getPosition());

});

});

});

});

}

});

console.log("Map initialized");

} catch (err) {

console.log("Got error while initializing map " + err);

}

});

}

//--------------------------------------------------------------------------------

/**

* Called to bind the "Add to Favorite" Button

*/

function initiateFavButton() {

$("#removefav").click(function () {

try {

if (currentBusinessData != null) {

removeFromFavorite(currentBusinessData);

\(("#add").show();

\)("#remove").hide();

}

} catch (err) {

console.log("Got Error while removing " + currentBusinessData.name + " error

" + err);

}

});

$("#addfav").click(function () {

try {

if (currentBusinessData != null) {

addToFavorite(currentBusinessData);

\(("#add").hide();

\)("#remove").show();

}

} catch (err) {

console.log("Got Error while adding " + currentBusinessData.name + " error "

err);

}

});

}

//--------------------------------------------------------------------------------------

/**

* Called each time before user navigates to Favorites

*/

function initiateFavorites() {

$("#fav").live("pagebeforeshow", function () {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);

try {

db.transaction(function (tx) {

tx.executeSql('SELECT * FROM Favorite', [], function (tx, results) {

$("#fav-list").html("");

if (results != null && results.rows != null) {

for (var index = 0; index < results.rows.length;

index++) {

var entry = results.rows.item(index)

var htmlData = "

entry.reference + "">

" + entry.name +" vicinity:" +

entry.vicinity + "

";

var liElem = $(document.createElement('li'));

\(("#fav-

list").append(liElem.html(htmlData));

\)(liElem).bind("tap", function (event) {

event.stopPropagation();

fetchDetails(entry);

return true;

});

}

$("#fav-

list").listview('refresh');

}

}, function (error) {

console.log("Got error fetching Favorites " + error.code + " " +

error.message);

});

});

} catch (err) {

console.log("Got error while reading Favorites " + err);

}

});

}

//--------------------------------------------------------------------------------

/**

* Ensure we have the table before we use it

* @param {Object} tx

*/

function ensureTableExists(tx) {

tx.executeSql('CREATE TABLE IF NOT EXISTS Favorite (id unique,

reference, name,address,phone,rating,icon,vicinity)');

}

//-----------------------------------------------------------------------

/**

* Add current business data to Favorite

* @param {Object} data

*/

function addToFavorite(data) {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

db.transaction(function (tx) {

ensureTableExists(tx);

var id = (data.id != null) ? ('"' + data.id + '"') : ('""');

var reference = (data.reference != null) ? ('"' + data.reference + '"') :

('""');

var name = (data.name != null) ? ('"' + data.name + '"') : ('""');

var address = (data.formatted_address != null) ? ('"' + data.formatted_address +

'"') : ('""');

var phone = (data.formatted_phone_number != null) ? ('"' +

data.formatted_phone_number + '"') : ('""');

var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');

var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');

var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');

var insertStmt = 'INSERT INTO Favorite (id,reference,

name,address,phone,rating,icon,vicinity) VALUES (' + id + ',' + reference + ',' + name +

',' + address + ',' + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';

tx.executeSql(insertStmt);

}, function (error) {

console.log("Data insert failed " + error.code + " " + error.message);

}, function () {

console.log("Data insert successful");

});

}

//----------------------------------------------------------------------------------

/**

* Remove current business data from Favorite

* @param {Object} data

*/

function removeFromFavorite(data) {

try {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

db.transaction(function (tx) {

ensureTableExists(tx);

var deleteStmt = "DELETE FROM Favorite WHERE id

= '" + data.id + "'";

console.log(deleteStmt);

tx.executeSql(deleteStmt);

}, function (error) {

console.log("Data Delete failed " + error.code + " " + error.message);

}, function () {

console.log("Data Delete successful");

});

} catch (err) {

console.log("Caught exception while deleting Favorite " + data.name);

}

}

//--------------------------------------------------------------------

/**

*

* @param {Object} reference

* @return true if place is Favorite else false

*/

function isFav(data, callback) {

var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);

try {

db.transaction(function (tx) {

ensureTableExists(tx);

var sql = "SELECT * FROM Favorite where id='" + data.id + "'";

tx.executeSql(sql, [], function (tx, results) {

var result = (results != null && results.rows != null &&

results.rows.length > 0);

callback(result);

}, function (tx, error) {

console.log("Got error in isFav error.code =" + error.code + "

error.message = " + error.message);

callback(false);

});

});

} catch (err) {

console.log("Got error in isFav " + err);

callback(false);

}

}

//-------------------------------------------------------------------------------

/**

* Binding Search button handler to go and fetch place results

*/

function initiateSearch() {

\(("#search").click(function () {

try {

\).mobile.showPageLoadingMsg();

navigator.geolocation.getCurrentPosition(function (position) {

var radius = $("#range").val() * 1000;

mapdata = new

google.maps.LatLng(position.coords.latitude, position.coords.longitude);

var url =

"https://maps.googleapis.com/maps/api/place/search/json?location=" +

position.coords.latitude + "," + position.coords.longitude + "&radius=" + radius +

"&name=" + \(("#searchbox").val() + "&sensor=true&key=";

\).getJSON(url, function (data) {

cachedData = data;

$("#result-

list").html("");

try {

$(data.results).each(function (index, entry) {

var htmlData = "

entry.reference + "">

src="" + entry.icon + "" class="ui-li-icon">

" + entry.name +

" vicinity:" + entry.vicinity + "

";

var liElem = $(document.createElement('li'));

$("#result-list").append(liElem.html(htmlData));

$(liElem).bind("tap", function (event) {

event.stopPropagation();

fetchDetails(entry);

return true;

});

});

$("#result-list").listview('refresh');

} catch (err) {

console.log("Got error while putting search result on result

page " + err);

}

\(.mobile.changePage("list");

\).mobile.hidePageLoadingMsg();

}).error(function (xhr, textStatus, errorThrown) {

console.log("Got error while fetching search result : xhr.status=" +

xhr.status);

}).complete(function (error) {

$.mobile.hidePageLoadingMsg();

});

}, function (error) {

console.log("Got Error fetching geolocation " + error);

});

} catch (err) {

console.log("Got error on clicking search button " + err);

}

});

}

//--------------------------------------------------------------

function bind() {

initiateMap();

initiateFavorites();

initiateSearch();

initiateFavButton();

}

//---------------------------------------------------

function onDeviceReady() {

$(document).ready(function () {

bind();

});

}

document.addEventListener("deviceready", onDeviceReady);

//-------------------------

The complete source of the app.css is as follows

map, .map-content, #map_canvas {

width: 100%;

height: 100%;

padding: 0;

}

map_canvas {

height: min-height: 100%;

}`

jQueryMobile 的优点

jQueryMobile 是一个面向移动应用开发人员的易于使用的 JavaScript UI 框架。jQueryMobile 最好的部分是它的声明式 UI 编程。使用 HTML 标签来创建 UI,并添加“数据角色”页面来将 HTML 标签注释为页面、页眉、页脚、内容、列表和按钮,这使得快速编程 UI 布局变得非常容易。

jQueryMobile 的另一个优点是页面的概念。页面在 HTML 页面中被声明为 div。此外,jQueryMobile 内置了导航部分和历史管理部分。这消除了添加历史管理部分的麻烦。

虽然 jQueryMobile 为小部件和工具栏提供了很好的支持,但是这些的编程方面需要开发人员进行 DOM 操作。

jQueryMobile 最大的优势在于它建立在健壮的 jQuery 核心框架之上。jQueryMobile 的另一个优势是它支持 iOS、Android、BlackBerry、HP WebOS、诺基亚/Symbian、Windows Mobile、Opera Mobile/Mini、Firefox mobile 以及所有现代桌面浏览器。

jQueryMobile 为移动和平板应用提供了触摸事件。jQueryMobile 也提供了很好的主题支持。在主题之间切换就像改变 HTML 标签的属性一样简单。

jQueryMobile 的缺点

jQueryMobile 是一个非常好的轻量级框架,使用 jQuery 来操作 DOM 允许用户以一种简单的方式构建应用。然而,随着应用复杂性的增加,以及对数据模型和相应视图的需求,用 jQueryMobile 编程更多的是用 JavaScript 实现自己的 MVC 框架。

简而言之,当您的应用很复杂时,jQueryMobile 很难使用。缺乏 MVC 框架甚至模型和 JavaScript 视图,使得 jQueryMobile 中的 JavaScript UI 编程与 Sencha Touch 等其他框架相比非常痛苦。

结论

如果移动应用相当简单,jQueryMobile 是一个很好的 JavaScript 移动 UI 开发框架。随着您的移动应用 UI 的复杂性增加,用 jQueryMobile 编程将变得更加繁琐。

五、使用 PhoneGap 和 Sencha Touch

Sencha Touch 是一家名为“ExtJS”的公司的产品。“ExtJS”是 Ajax RIA 世界中一家受欢迎的公司,它提供了一个丰富的、经过打磨的 JavaScriptui 框架,名为“ExtJS”。该公司的热门产品有“ExtJS”JavaScript UI 框架,“Ext-GWT”,GWT UI 框架(Ext js 的 GWT 计数器部分),以及“Sencha Touch”移动端 JavaScript 库。

“ExtJS”公司最近更名为“Sencha”。因此,虽然名称是新的,但是“Sencha Touch”库中的内容是基于多年来使用 JavaScript 构建 UI 的经验。

如果你熟悉“ExtJS”,你会注意到“ExtJS”和“Sencha Touch”有很多相似之处,尤其是在基础类。然而,“Sencha Touch”是专为移动应用设计的。

为什么要用 Sencha Touch?

Sencha Touch 允许您为 iPhone、Android 和 BlackBerry 开发基于浏览器的应用,具有原生的外观和感觉。还有,Sencha Touch 是基于 HTML5 的。

Sencha Touch 为您提供以下优势:

触摸优化的丰富小部件集,以及支持点击、双击、滑动、点击并按住、挤压并旋转、滑动和手势的触摸事件。

新时代网络标准 HTML5 和 CSS3。

与 PhoneGap 集成。

支持 iOS、Android 和黑莓,以及这些设备的原生主题

对 Ajax、JSONP 和 Yahoo!查询语言(YQL),以及支持本地存储来支持小部件。

总之,Sencha Touch 是目前移动应用开发最好的 JavaScript ui 库之一。如果你没有从事过 ExtJS,那么学习曲线一开始可能会有点陡。

煎茶触摸的优点

Sencha Touch 的优点远远超过缺点。首先,Sencha Touch 基于 web 标准,如 HTML5 和 CSS3,而不是基于任何专有技术。Sencha Touch 的社区支持也很好。商业使用是免费的。

你可以在 Sencha Touch 中构建一个应用,它可以检测我们是在平板电脑上还是在手机上,并且你可以编写代码来使相同的应用以不同的方式工作。例如,看看厨房的水槽。当您在平板电脑和移动设备上查看它时,您将会看到该示例根据实际情况进行了自我调整。

小部件集相当丰富。用 JavaScript 构建整个小部件确保了与用户的高度交互性。你有更多的控制权。

Sencha Touch 的性能很好,并且随着每个版本的发布而不断改进。此外,随着更新版本的 iOS 和 Android 操作系统的发布,这些操作系统附带的 webkit 在性能上也有所提高。

有对国际化的支持,有像网格和传送带这样的小部件,它们是非常新时代的可视化辅助工具。

煎茶触摸的缺点

Sencha Touch 最大的缺点是它的学习曲线。使用 Sencha Touch,您很少使用任何预渲染的 HTML。一切都是通过 JavaScript 添加到 DOM 中的。对一些人来说,这可能是观念上的转变。

如果你的应用仅仅是几个带有导航的页面,视图主要是列表视图、表单和工具栏,那么 Sencha Touch 就太过了。

下载 Sencha Touch

从 Sencha 的网站-[www.sencha.com/products/touch/](http://www.sencha.com/products/touch/)下载 Sencha 触摸库。一旦你下载并解压 sdk,你会看到如图 5-1 所示的结构。

图 5-1。 森查摸目录结构

集成 Sencha 和 PhoneGap

让我们从将 Sencha Touch 与 PhoneGap 项目集成开始。本章将假设它是针对 Android 平台的。其他平台的步骤类似。

参考第二章和第三章为你的目标平台设置 PhoneGap 项目。

如图 5-2 所示,从 Sencha Touch sdk 中,您需要添加以下文件:

将 sencha-touch.js JavaScript 文件添加到 www/lib。

将 resources/css 文件夹添加到 www/lib

将所有应用代码放在一个名为 app/app.js 的文件中,这将是我们的主 JavaScript 文件。

出于本章和示例的考虑,请从 sencha-touch-1 . 1 . 0/examples/map 文件夹中复制 icon.png、phone_startup.png 和 tablet_startup.png。

图 5-2。 PhoneGap 和 Sencha Touch 项目结构

使用 Sencha Touch 构建本地搜索应用

本地搜索应用的要求类似于我们在第五章中的要求。用户通过关键字输入搜索,从他/她的当前位置搜索范围,用户获得列表视图中列出的本地位置。用户可以点击其中一个项目,并查看该地方的详细信息。在详细信息屏幕上,用户可以选择将该地点放入他/她的收藏夹列表(存储在应用数据库中以供离线访问)。用户还可以在地图视图中看到搜索结果。

最后但同样重要的是,用户可以点击收藏夹按钮(星形图标)来查看他/她的收藏夹列表。

让我们开始构建应用。请记住,Sencha Touch 相当大,本章将带您浏览该应用所需的 Sencha Touch API 的子集。

初始化煎茶触摸

第一步是确保 index.html 有 Sencha 触摸库,PhoneGap 库,和 CSS 链接。注意我们的身体是空的。这是因为,在 Sencha Touch 中,我们用 JavaScript 构建整个 ui。注意,我们包含了以下 JavaScript 和样式表。

Sencha Touch 样式表

谷歌地图 JavaScript

我们的应用 JavaScript

`

Local Search

`

现在让我们转到 app . js。PhoneGap 应用是在函数 Ext.setup()中设置的。根据经验,记住所有 Sencha Touch 函数都采用 JSON 结构作为配置。

我们将对 Ext.setup 进行同样的操作。我们将为应用提供一些图标,以及手机闪屏和平板电脑闪屏。但这些都不是我们想在本章重点讨论的部分。

这里最重要的部分是 onReadyfunction()。这就好比 jQuery 的文档就绪,PhoneGap 的设备就绪功能。我们可以在这个函数中开始绘制 Sencha Touch 的 UI。

Ext.setup({ tabletStartupScreen: 'tablet_startup.png', phoneStartupScreen: 'phone_startup.png', icon: 'icon.png', glossOnIcon: false, onReady: function () { //Sencha Touch framework has initialized here. //Create Panels and bind event handlers. } });

创建布局(应用框架)

下一步是将一个面板声明为主面板。为此,我们将创建一个新面板(new Ext。Panel())并在其配置 JSON 中,我们将声明以下内容:

布局:“卡片”-布局是卡片布局,这意味着它是一叠卡片,我们一次只展示一张卡片

full screen:true–指定此面板将占用 100%的可用宽度和高度,并自动将其自身绘制到页面上

items: [searchPanel,tabResultPanel,favourites,result detail panel]–要添加到此面板的子组件数组。由于我们使用的是“卡片”布局,它将一次显示一个子组件。我们的主面板中有四个子面板:searchPanel、tabResultPanel、favourites 和 resultDetailPanel。searchPanel 和 tabResultPanel 的声明方式与 mainPanel 相同。默认情况下,searchPanel 是可见的卡片,而其他面板隐藏在 searchPanel 后面

docket items:[]–用于声明停靠的小部件,通常用于工具栏按钮。

dockedItems 内部有一个由 JSON 表示声明的工具栏。这个工具栏有两个按钮和一个分隔它们的间隔。

对于主页按钮,我们使用图标:“主页”

对于最喜欢的按钮,我们使用图标:“星形”

对于这两个按钮,我们都声明了一个处理程序,当单击按钮时会调用这个处理程序。

`//Main Panel with CardLayout

var mainPanel = new Ext.Panel({

layout: 'card',

fullscreen: true,

items: [searchPanel, tabResultPanel, favorites, resultDetailPanel],

dockedItems: [{

xtype: 'toolbar',

title: 'Local Search',

dock: 'top',

items: [{

iconMask: true,

ui: 'round',

iconCls: 'home',

handler: function () {

}

}, { xtype: 'spacer'

}, {

iconMask: true,

ui: 'round',

iconCls: 'star',

handler: function () {}

}]

}]

});`

在没有任何子部件的情况下,图 5-3 显示了该面板的外观。

图 5-3。 带工具栏按钮的主应用面板

接下来,我们声明搜索面板。搜索面板有一个文本框,用户可以在其中输入搜索关键字,并有一个范围选择器,允许用户选择他/她的搜索范围。最后,搜索面板有一个带有搜索按钮的工具栏。声明如下:

**var searchPanel = new Ext.form.FormPanel({** ** layout: 'fit',** ** fullscreen: true,** ** scroll: 'vertical',** ** standardSubmit: false,** ** //Adding form field** ** items: [{** `** xtype: 'fieldset',**

** title: 'Local Search',**

** items: [{**

** xtype: 'textfield',**

** name: 'search',**

** label: ‘Search',**

** value: ‘Pizza',**

** userClearIcon: true,**

** autoCapitalize: false**

** }, {**

** xtype: 'sliderfield',**

** name: 'range',**

** label: 'Range (0-10 Kms)',**

** value: 5,**

** minValue: 0,**

** maxValue: 10**

** }]**

** }],**

** //Docking a toolbar at bottom**

** dockedItems: [{**

** xtype: 'toolbar',**

** dock: 'bottom',**

** items: [{**

** xtype: 'spacer'**

** }, {**

** text: 'Search',**

** iconCls: 'search',**

** title: 'Search',**

** iconMask: true,**

** ui: 'confirm',**

** handler: function () {**

** }**

** }]**

** }]**

});`

图 5-4 显示了搜索面板的显示方式。

图 5-4。 应用搜索面板

当用户进行搜索时,用户会看到两个视图。

显示搜索结果的列表视图

显示搜索结果的地图视图

这两个视图都封装在选项卡面板中。我们将选项卡面板声明如下:

`var tabResultPanel = new Ext.TabPanel({

** layout: 'fit',**

** tabBar: {**

** dock: 'bottom',**

** layout: {**

** pack: 'center'**

** }**

** },**

** items: [result, map],**

});`

没有任何子面板的选项卡面板如图 5-5 所示。请注意,我们在配置 JSON 中定义了将选项卡栏放置在两个选项卡“result”和“map”的底部。默认情况下,将选择“结果”选项卡。当我们创建“结果”和“映射”对象时,我们将为这两个选项卡定义标签和图标。

图 5-5。 搜索结果面板带标签

现在,我们将看到当用户进行搜索时如何显示搜索结果。在本章的后面部分,我们将会谈到如何使用 AJAX 调用。

出于本章的考虑,假设您有一个来自 Google place 服务器的 JSON,如下所示:

{ "status": "OK", "results": [{ "name": "Zaaffran Restaurant - BBQ and GRILL, Darling Harbour", "vicinity": "Darling Drive, Darling Harbour, Sydney", "types": ["restaurant", "food", "establishment"], "geometry": { "location": { "lat": -33.8712950, "lng": 151.1984770 } }, "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", "reference": "CpQBiwAAANM1CkdWcBxiExHinloJpp7kX2D3nyb_D0qoQ_-RuBhq9cwJKYvU8- sRJUaXF4U2kET_OH3Oh3Yz4tf5_6gBgcsFAPyRappCrJ5WksvMkXrT5lA7q9U_S0ZI0u3mrsvTtXnTDMKlBMywE_ 5Yy6lbshqPIatWZ6QkPZBNdmkifyN3vM7H2vL- 300iY6EoartWuxIQNckbM0Bs4D946thThmKOsBoUCmGgFrtYgtO0CIUc79fQi3waO0w", "id": "677679492a58049a7eae079e0890897eb953d79b" }, { "name": "Toros Restaurant Darling Harbour", "vicinity": "Murray Street, Sydney", "types": ["restaurant", "food", "establishment"], "geometry": { "location": { "lat": -33.8714080, "lng": 151.1975410 } }, "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", "reference": "CoQBdQAAALFujBuIMYXsG8Qlus2zSHeikZQNCsSbeII0-55zkhCiArbPkACXRU- CcLZbeKsXaBpoBNH5iyYJg6Nquct2LTE127X4CD1YtKpozmbjZpyCRFrJ_V5DI4IDGLCWeY_8NMxznbiqb9prR8m XJoAKv7jNz6KEMxAuGLRAXbi7G6CYEhBeR6Ur-x2ABlS3pKXsKXLvGhRWFzL3Q5TO0xe-gm_LJm9cgtzYJw", "id": "aefbc59325ffd5f3e93d67932375d20d143289de" }, { "name": "Strike Bowling Bar Darling Harbour", "vicinity": "Sydney", "types": ["restaurant", "food", "establishment"], "geometry": { "location": { "lat": -33.8662990, "lng": 151.2016580 } }, "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", "reference": "CoQBeAAAAO-prCRp9Atcj_rvavsLyv- DnxbGkw8QyRZb6Srm6QHOcww6lqFhIs2c7Ie6fMg3PZ4PhicfJL7ZWlaHaLDTqmRisoTQQUn61WTcSXAAiCOzcm0 JDBnafqrskSpFtNUgzGAOx29WGnWSP44jmjtioIsJN9ik8yjK7UxP4buAmMPVEhBXPiCfHXk1CQ6XRuQhpztsGhQ U4U6-tWjTHcLSVzjbNxoiuihbaA", "id": "0a4e24c365f4bd70080f99bb80153c5ba3faced8" } ...additional results...], "html_attributions": ["Listings by \u003ca href=\"http://www.yellowpages.com.au/\"\u003eYellow Pages\u003c/a\u003e"] }

现在我们已经看到了 Google places 结果的 JSON 结构,我们将创建面板来显示结果。在本例中,我们扩展了一个组件,并在组件中声明了一个模板(tpl)。

模板化是 Sencha Touch 的一个特性,你可以在标签中声明一个 html。在我们的例子中,我们传递上述 JSON 的结果对象。结果对象实际上是一个数组。在我们的模板代码中,请注意。这是在告诉 Sencha 模板引擎迭代结果中的所有对象。

在 html 的后面部分,您会注意到占位符,如{reference}、{icon}、{name}等。如果你们中的任何一个人使用过 java 的消息格式,你会注意到这是非常相似的。这些{}条目将被 JSON 中相应的数据替换。

{name}将被结果->条目->名称中的名称替换。

为了用数据填充这个面板,我们将调用以下 API:

//This will call the template engine and draw the AJAX's response //result. Here ‘result' is the Component object to show the results //HTML and response.results is the JSON array. result.update(response.results);

现在,让我们看看用来创建结果面板的代码。

`var result = new Ext.Component({

** title: 'Search Result',**

** iconMask: true,**

** iconCls: 'organize',**

** cls: 'timeline',**

** scroll: 'vertical',**

** tpl: ['',**

** '

',**

** '',**

** '', '{name}',**

** '{vicinity}

',**

** '',**

** '',**

** '']**

** listeners: {**

** el: {**

** tap: detailClickHandler,**

** //function which**

** //will handle tap event**

** }**

** }**

});`

注意最后的听众和 el 部分。这告诉 Sencha Touch,我们有兴趣接收关于该组件元素的事件。此外,我们告诉它,我们专门寻找点击事件。这段代码的结果是,每当用户点击结果中列出的任何地方,它都会调用 detailClickHandler 函数。

图 5-6。 搜索结果面板

Sencha Touch 的地图小工具让生活变得简单多了。否则,我们将不得不使用谷歌地图应用编程接口。我们简单地创建一个新的 Ext。映射并给它一些选项。这是制作地图最简单的方法。请注意,“地图”对象将在 AJAX 回调中使用,以在其上添加位置标记。AJAX 调用在“获取地点列表”中有描述。

**var map = new Ext.Map({** ** iconMask: true,** ** iconCls: 'maps',** ** title: 'Map',** ** // Name that appears on this tab** ** mapOptions: {** ** // Used in rendering map** ** zoom: 12** ** }** **});**

图 5-7。地图面板显示地名

接下来是面板,显示一个地方的细节。注意,一旦用户点击搜索条目,应用将从 Google places 服务器获取详细信息。该请求的 JSON 响应如下所示:

{ "status": "OK", "result": { "name": "Google Sydney", "vicinity": "Pirrama Road, Pyrmont", "types": ["establishment"], "formatted_phone_number": "(02) 9374 4000", "formatted_address": "5/48 Pirrama Road, Pyrmont NSW, Australia", "address_components": [{ "long_name": "48", "short_name": "48", "types": ["street_number"] }, { "long_name": "Pirrama Road", "short_name": "Pirrama Road", "types": ["route"] }, { "long_name": "Pyrmont", "short_name": "Pyrmont", "types": ["locality", "political"] }, { "long_name": "NSW", "short_name": "NSW", "types": ["administrative_area_level_1", "political"] }, { "long_name": "2009", "short_name": "2009", "types": ["postal_code"] }], "geometry": { "location": { "lat": -33.8669710, "lng": 151.1958750 } }, "rating": 4.5, "url": "http://maps.google.com/maps/place?cid=10281119596374313554", "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/generic_business-71.png", "reference": "CmRRAAAAUgylGnuntxKOuZy9_c5zxdFi6e491_Fv0m1hks5YkeaH7k1SP9ujAkG4GROr1XCHFnMsDhuEIgQQq2W Wyd33oGRAT8Vwr8rjTWEYEMvCZ1RxTzXSVDZ4gEFqLZcRyAw_EhBS8uZHidMMbYHuf9KHapRyGhQQ1dnf3uMghMR BlXqJE6ygh_a3ag", "id": "4f89212bf76dde31f092cfc14d7506555d85b5c7" }, "html_attributions": [] }

这个想法是以表格的方式向用户显示上述信息。为此,我们将结合使用以下方法:

将上述 JSON 显示为表的一部分的模板

一个按钮,将允许用户添加这个地方作为收藏或删除它作为收藏。

因此,我们使用一个名为 resultDetailPanel 的包装面板。这个面板有一个 vbox 布局(垂直堆叠部件)。第一个子元素是 placeDetailsPanel(见下),第二个是 button。

该按钮的文本从“添加到收藏夹”变为“从收藏夹中移除”,这取决于用户是否已经将该位置设为收藏夹。应用中为同一定义了一个名为 isFav()的函数。

**var resultDetailPanel = new Ext.Panel({** ** layout: {** ** type: 'vbox',** ** },** ** items: [** ** placeDetailsPanel,** ** {** ** xtype: 'button',** ** text: 'Add to Favorite',** ** handler: function (button, event) {** ** if (button.text == "Add to Favorite") {** ** addCurrentToFav();** ** button.setText("Remove from Favorite");** ** } else {** ** removeCurrentFromFav();** `** button.setText("Add to Favorite");**

** }**

** }**

** }],**

** dockedItems: [{**

** xtype: 'toolbar',**

** dock: 'bottom',**

** items: [{**

** ui: 'round',**

** text: 'Back',**

** handler: function () {}**

** }]**

** }]**

});`

这是以表格形式显示 JSON 结果的面板。它使用相同的模板。模板是 html 模板,它有由{ < >}表示的占位符。使用模板代码,占位符由实际值替换:

**var placeDetailsPanel = new Ext.Panel({** ** tpl: ['

',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** ** '',** `** '',**

** '

{formatted_phone_number}',**

** '',**

** '',**

** '',**

** '',**

** 'Rating',**

** '',**

** '',**

** '{rating}',**

** '',**

** '',**

** '',**

** '',**

** 'Home Page',**

** '',**

** '',**

** 'Home Page',**

** '',**

** '',**

** ''**

** ]**

});`

位置细节面板如图 5-8 中的所示。

图 5-8。 地点详情面板

现在我们已经了解了如何在收藏夹中添加或删除地点,让我们来看看列出用户收藏地点的面板。是的,这个面板与结果面板非常相似。唯一的区别是结果面板被赋予了来自 Google places 服务器的 JSON,而收藏夹面板被赋予了来自数据库的 JSON。

`var favorites = new Ext.Component({

** title: 'Favotites',**

** iconMask: true,**

** iconCls: 'organize',**

** cls: 'timeline',**

** scroll: 'vertical',**

** tpl: ['',**

** '

',**

** '',**

** '',**

** '{name}',**

** '{vicinity}

',**

** '',**

** '',**

** ''],**

** listeners: {**

** el: {**

** tap: detailClickHandler,**

** }**

** }**

});`

图 5-9。 最喜欢的地方面板

面板之间的切换

随着您探索更多的应用代码,您将需要从一个面板切换到另一个面板。这是我们在主面板上使用“卡片”布局的主要原因。

为了在卡片布局中从一个面板切换到另一个面板,我们将使用下面的代码。注意 mainPanel 的 items 中提供的小部件的第一个参数索引号。第二个论点是动画效果。

mainPanel.setActiveItem(0, "slide"); mainPanel.setActiveItem(1, {type: 'slide', direction: 'right'});

此外,主面板拥有工具栏。我们需要改变这个工具栏的标题,让用户知道他在哪里。这是使用以下代码完成的:

mainPanel.dockedItems.items[0].setTitle('Details');

获取地点列表

当用户点击搜索面板上的搜索按钮时,我们需要调用 Ajax 来获取 Google places 的结果。下面的函数展示了如何在 Sencha Touch 和 PhoneGap 中进行同样的操作。

步骤很简单。

从 PhoneGap 获取地理位置

在 getcurrentPosition 的成功调用中,通过调用 Ext.ajax.request(url,successCallback,failureCallback)来发起 Ajax 调用

在 Ext.ajax.request 的 successCallback 中,会得到 JSON 字符串。

将这个 JSON 字符串转换成 JSON 对象

通过调用 result.update(obj.results)填充结果面板;

在谷歌地图上填充市场

`var fetchFromGoogle = function () {

** var keyword = searchPanel.items.items[0].items.items[0].value;**

** var range = searchPanel.items.items[0].items.items[1].value * 1000;**

** navigator.geolocation.getCurrentPosition(**

** function (position) {**

** var lat = position.coords.latitude;**

** var lng = position.coords.longitude;**

** map.update({**

** latitude: lat,**

** longitude: lng**

** });**

** var googlePlaceUrl = 'https://maps.googleapis.com/maps/api/place/search/json?location='**

** + lat + ',' + lng + '&radius=' + range + '&types=food&name=' + keyword + '&sensor=true&key=API_Key';**

** //Note that you will need to replace the API_Key with your own key. You //can get API Key from **

** //http://code.google.com/apis/maps/documentation/places/**

** Ext.Ajax.request({**

** url: googlePlaceUrl,**

** success: function (response, opts) {**

** var obj = Ext.decode(response.responseText);**

** result.update(obj.results);**

** var data = obj.results;**

** for (var i = 0, ln = data.length; i < ln; i++) { // Loop to add points to the map**

** var place = data[i];**

** if (place.geometry && place.geometry.location) {**

** var position = new google.maps.LatLng(place.geometry.location.lat, place.geometry.location.lng);**

** addMarker(place.name, place.reference, position); // Call addMarker function with new data**

** } }**

** },**

** failure: function (response, opts) {**

** console.log('server-side failure with status code ' + response.status);**

** }**

** }, function (err) {**

** console.log('Failed to get geo location from phonegap ' + err);**

** });**

** })**

** }**`

取件地点详情

从 Google places 服务器获取地点信息甚至更容易。你需要的东西甚至更少。

通过调用 Ext.ajax.request(url,successCallback,failureCallback)启动 Ajax 调用

在 Ext.ajax.request 的 successCallback 中,会得到 JSON 字符串。

将这个 JSON 字符串转换成 JSON 对象

通过调用 placedetailspanel . update(obj . result)填充结果面板;

此外,我们将通过调用 isFav()函数来检查这个地方是否是收藏夹

如果这个地方是一个收藏,我们将这个按钮重命名为“从收藏中删除”

否则,我们将该按钮重命名为“添加到收藏夹”

`var cachedDetails = null;

/**

** * Ensure we have the table before we use it**

** * @param {Object} tx**

** /*

var ensureTableExists = function (tx) {

** tx.executeSql('CREATE TABLE IF NOT EXISTS Favourite (id unique, reference,**

** name,address,phone,rating,icon,vicinity)');**

}

/**

** * Add currentDetails to DB**

** /*

var addCurrentToFav = function () {

** addToFavorite(cachedDetails);**

}

/**** * Remove currentDetails from DB**

** /*

var removeCurrentFromFav = function () {

** removeFromFavorite(cachedDetails);**

}

/**

** * Add current business data to favourite**

** * @param {Object} data**

** /*

var addToFavorite = function (data) {

** var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);**

** db.transaction(function (tx) {**

** ensureTableExists(tx);**

** var id = (data.id != null) ? ('"' + data.id + '"') : ('""');**

** var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');**

** var name = (data.name != null) ? ('"' + data.name + '"') : ('""');**

** var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');**

** var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');**

** var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');**

** var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');**

** var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');**

** var insertStmt = 'INSERT INTO Favourite (id,reference,**

** name,address,phone,rating,icon,vicinity) VALUES**

** (' + id + ',' + reference + ',' + name + ',' + address + ','**

** + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';**

** tx.executeSql(insertStmt);**

** }, function (error) {**

** console.log("Data insert failed " + error.code + " " + error.message);**

** }, function () {**

** console.log("Data insert successful");**

** });**

}

/**

** * Remove current business data from favourite**

** * @param {Object} data**

** /*

var removeFromFavorite = function (data) {

** try {**

** var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000); db.transaction(function (tx) {**

** ensureTableExists(tx);**

** var deleteStmt = "DELETE FROM Favourite WHERE id = '" + data.id + "'";**

** console.log(deleteStmt);**

** tx.executeSql(deleteStmt);**

** }, function (error) {**

** console.log("Data Delete failed " + error.code + " " + error.message);**

** }, function () {**

** console.log("Data Delete successful");**

** });**

** } catch (err) {**

** console.log("Caught exception while deleting favourite " + data.name);**

** }**

}

/**

** ***

** * @param {Object} reference**

** * @return true if place is favourite else false**

** /*

var isFav = function (data, callback) {

** var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);**

** try {**

** db.transaction(function (tx) {**

** ensureTableExists(tx);**

** var sql = "SELECT * FROM Favourite where id='" + data.id + "'";**

** tx.executeSql(sql, [], function (tx, results) {**

** var result = (results != null && results.rows != null && results.rows.length > 0);**

** callback(result);**

** }, function (tx, error) {**

** var fetchDetails = function (reference) {**

** placeDetailsPanel.update({**

** name: "",**

** formatted_address: "",**

** formatted_phone_number: "",**

** rating: "",**

** url: ""**

** });**

** Ext.Ajax.request({**

** url: 'https://maps.googleapis.com/maps/api/place/details/json?reference='**

** + reference + '&sensor=true&key=API_Key',**

** success: function (response, opts) {**

** var obj = Ext.decode(response.responseText);**

** //global variable to store the current place cachedDetails = obj.result;**

** isFav(obj.result, function (result) {**

** if (result) {**

** resultDetailPanel.items.items[1].setText("Remove from Favorite");**

** } else {**

** resultDetailPanel.items.items[1].setText("Add to Favorite");**

** }**

** placeDetailsPanel.update(obj.result);**

** });**

** },**

** failure: function (response, opts) {**

** console.log('server-side failure with status code ' + response.status);**

** }**

** })**

** }**

** console.log("Got error in isFaverror.code =" + error.code + "**

** error.message = " + error.message);**

** callback(false);**

** });**

** });**

** } catch (err) {**

** console.log("Got error in isFav " + err);**

** callback(false);**

** }**

** }**`

从数据库中存储和检索收藏夹

该应用的最后一个重要部分是定义函数来完成以下任务:

向收藏表添加位置

从收藏夹表中移除一个位置

检查某个位置是否已经在收藏表中

从收藏夹表中获取所有条目。

为了在各种函数调用之间传递位置条目,我们在该应用中所做的是声明一个名为 cachedDetails 的变量。当我们在显示地点细节的页面中时,我们在 cachedDetails 中缓存当前地点。cachedDetails 用于将一个位置添加到收藏夹,将其从收藏夹中删除,以及检查它是否已经是用户收藏夹的一部分。

`var cachedDetails = null;

/**

** * Ensure we have the table before we use it**

** * @param {Object} tx**

** /*

var ensureTableExists = function (tx) {

** tx.executeSql('CREATE TABLE IF NOT EXISTS Favourite (id unique, reference,**

** name,address,phone,rating,icon,vicinity)');**

}

/**

** * Add currentDetails to DB**

** /*

var addCurrentToFav = function () {

** addToFavorite(cachedDetails);**

}

/**

** * Remove currentDetails from DB**

** /*

var removeCurrentFromFav = function () {

** removeFromFavorite(cachedDetails);**

}

/**

** * Add current business data to favourite**

** * @param {Object} data**

** /*

var addToFavorite = function (data) {

** var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);**

** db.transaction(function (tx) {**

** ensureTableExists(tx);**

** var id = (data.id != null) ? ('"' + data.id + '"') : ('""');**

** var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');**

** var name = (data.name != null) ? ('"' + data.name + '"') : ('""');**

** var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');**

** var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');**

** var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');**

** var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');**

** var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');**

** var insertStmt = 'INSERT INTO Favourite (id,reference,**

** name,address,phone,rating,icon,vicinity) VALUES**

** (' + id + ',' + reference + ',' + name + ',' + address + ',' + phone**

** + ',' + rating + ',' + icon + ',' + vicinity + ')'; tx.executeSql(insertStmt);**

** }, function (error) {**

** console.log("Data insert failed " + error.code + " " + error.message);**

** }, function () {**

** console.log("Data insert successful");**

** });**

}

/**

** * Remove current business data from favourite**

** * @param {Object} data**

** /*

var removeFromFavorite = function (data) {

** try {**

** var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);**

** db.transaction(function (tx) {**

** ensureTableExists(tx);**

** var deleteStmt = "DELETE FROM Favourite WHERE id = '" + data.id + "'";**

** console.log(deleteStmt);**

** tx.executeSql(deleteStmt);**

** }, function (error) {**

** console.log("Data Delete failed " + error.code + " " + error.message);**

** }, function () {**

** console.log("Data Delete successful");**

** });**

** } catch (err) {**

** console.log("Caught exception while deleting favourite " + data.name);**

** }**

}

/**

** ***

** * @param {Object} reference**

** * @return true if place is favourite else false**

** /*

var isFav = function (data, callback) {

** var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);**

** try {**

** db.transaction(function (tx) {**

** ensureTableExists(tx);**

** var sql = "SELECT * FROM Favourite where id='" + data.id + "'"; tx.executeSql(sql, [],**

** function (tx, results) {**

** var result = (results != null && results.rows != null && results.rows.length > 0);**

** callback(result);**

** },**

** function (tx, error) {**

** console.log("Got error in isFaverror.code =" + error.code + "**

** error.message = " + error.message);**

** callback(false);**

** });**

** });**

** } catch (err) {**

** console.log("Got error in isFav " + err);**

** callback(false);**

** }**

}`

这包括应用的布局部分,如何从 Google places 服务器填充数据,以及如何导航和使用数据库。

请参考下面列出的完整示例,以了解事件是如何处理的。

1。index.html

`

Sencha Touch Layout

`

2。app.js

`Ext.setup({

tabletStartupScreen: 'tablet_startup.png',

phoneStartupScreen: 'phone_startup.png',

icon: 'icon.png',

glossOnIcon: false,

onReady: function () {

var lastPanelId = 0;

var SEARCHPAGE = 0;

var TABPAGE = 1;

var FAVPAGE = 2;

var DETAILSPAGE = 3;

var cachedDetails = null;

var searchPanel = newExt.form.FormPanel({

layout: 'fit',

fullscreen: true,

scroll: 'vertical',

standardSubmit: false,

//Adding form field

items: [{

xtype: 'fieldset',

title: 'Local Search',

items: [{

xtype: 'textfield',

name: 'search',

label: 'Search',

value: 'Pizza',

useClearIcon: true,

autoCapitalize: false

}, {

xtype: 'sliderfield',

name: 'range',

label: 'Range (0-10 Kms)',

value: 5,

minValue: 0,

maxValue: 10

}]

}] //Docking a toolbar at bottom dockedItems: [{

xtype: 'toolbar',

dock: 'bottom',

items: [{

xtype: 'spacer'

}, {

text: 'Search',

iconCls: 'search',

title: 'Search',

iconMask: true,

ui: 'round',

ui: 'confirm',

handler: function () {

lastPanelId = TABPAGE;

fetchFromGoogle();

mainPanel.dockedItems.items[0].setTitle('Search Results');

mainPanel.setActiveItem(lastPanelId);

}

}]

}]

});

var detailClickHandler = function (event) {

var reference = event.getTarget(".place").id;

fetchDetails(reference);

mainPanel.dockedItems.items[0].setTitle('Details');

mainPanel.setActiveItem(DETAILSPAGE, "slide");

}

var result = new Ext.Component({

title: 'Search Result',

iconMask: true,

iconCls: 'organize',

cls: 'timeline',

scroll: 'vertical',

tpl: ['',

'

',

'',

'',

'{name}',

'

{vicinity}

', '', '', ''

],

listeners: {

el: {

tap: detailClickHandler,

delegate: '.place'

}

}

});

var favorites = new Ext.Component({ title: 'Favotites',

iconMask: true,

iconCls: 'organize',

cls: 'timeline',

scroll: 'vertical',

tpl: ['',

'

',

'',

'',

'{name}',

'{vicinity}

',

'',

'',

''],

listeners: {

el: {

tap: detailClickHandler,

delegate: '.place'

}

}

});

var map = new Ext.Map({

iconMask: true,

iconCls: 'maps',

title: 'Map',

// Name that appears on this tab

fullscreen: true,

mapOptions: { // Used in rendering map

zoom: 12

}

});

var tabResultPanel = new Ext.TabPanel({

layout: 'fit',

tabBar: {

dock: 'bottom',

layout: {

pack: 'center'

}

},

items: [result, map],

});

var placeDetailsPanel = new Ext.Panel({

//layout: 'fit',

tpl: ['

',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'',

'Business Details', '',

'Name',

'',

'{name}',

'',

'Address',

'',

'{formatted_address}',

'',

'Phone',

'',

'{formatted_phone_number}',

'',

'Rating',

'',

'{rating}',

'',

'Home Page',

'',

'Home Page',

''

]

});

var resultDetailPanel = new Ext.Panel({

layout: {

type: 'vbox',

},

items: [

placeDetailsPanel,

{

xtype: 'button',

text: 'Add to Favorite',

handler: function (button, event) { if (button.text == "Add to Favorite") {

addCurrentToFav();

button.setText("Remove from Favorite");

} else {

removeCurrentFromFav();

button.setText("Add to Favorite");

}

}

}],

dockedItems: [{

xtype: 'toolbar',

dock: 'bottom',

items: [{

ui: 'round',

text: 'Back',

handler: function () {

if (lastPanelId == 0) {

mainPanel.dockedItems.items[0].setTitle('Home Page');

} else if (lastPanelId == 1) {

mainPanel.dockedItems.items[0].setTitle('Search Results');

} else if (lastPanelId == 2) {

fetchFromDB();

mainPanel.dockedItems.items[0].setTitle('Favourites');

} else if (lastPanelId == 3) {

//Shouldn't happen

mainPanel.dockedItems.items[0].setTitle('Details');

}

mainPanel.setActiveItem(lastPanelId, {

type: 'slide',

direction: 'right'

});

}

}]

}]

});

//Main Panel with CardLayout

var mainPanel = new Ext.Panel({

layout: 'card',

fullscreen: true,

items: [searchPanel, tabResultPanel, favorites, resultDetailPanel],

dockedItems: [{

xtype: 'toolbar',

title: 'Local Search',

dock: 'top',

items: [{

iconMask: true,

ui: 'round',

iconCls: 'home',

handler: function () { lastPanelId = SEARCHPAGE;

mainPanel.dockedItems.items[0].setTitle('Home Page');

mainPanel.setActiveItem(lastPanelId, "slide");

}

}, {

xtype: 'spacer'

}, {

iconMask: true,

ui: 'round',

iconCls: 'star',

handler: function () {

fetchFromDB();

lastPanelId = FAVPAGE;

mainPanel.dockedItems.items[0].setTitle('Favourites');

mainPanel.setActiveItem(lastPanelId, "slide");

}

}]

}]

});

// These are all Google Maps APIs

var addMarker = function (name, reference, position) {

var marker = new google.maps.Marker({

map: map.map,

position: position,

clickable: true,

optimized: true,

title: name

});

google.maps.event.addListener(marker, 'click', function () {

fetchDetails(reference);

mainPanel.dockedItems.items[0].setTitle('Details');

mainPanel.setActiveItem(DETAILSPAGE, "slide");

});

};

var fetchFromGoogle = function () {

var keyword = searchPanel.items.items[0].items.items[0].value;

var range = searchPanel.items.items[0].items.items[1].value * 1000;

navigator.geolocation.getCurrentPosition(

function (position) {

var lat = position.coords.latitude;

var lng = position.coords.longitude; map.update({

latitude: lat,

longitude: lng

});

var googlePlaceUrl = 'https://maps.googleapis.com/maps/api/place/search/json?location='

+ lat + ',' + lng + '&radius=' + range + '&types=food&name=' + keyword + '&sensor=true&key=API_Key';

//Note that you will need to replace the API_Key with your own key. You

//can get API Key from //http://code.google.com/apis/maps/documentation/places/

Ext.Ajax.request({

url: googlePlaceUrl,

success: function (response, opts) {

var obj = Ext.decode(response.responseText);

result.update(obj.results);

var data = obj.results;

for (var i = 0, ln = data.length; i < ln; i++) { // Loop to add points to the map

var place = data[i];

if (place.geometry && place.geometry.location) {

var position = new google.maps.LatLng(place.geometry.location.lat, place.geometry.location.lng);

addMarker(place.name, place.reference, position); // Call addMarker function with new data

}

}

},

failure: function (response, opts) {

console.log('server-side failure with status code ' + response.status);

}

}, function (err) {

console.log('Failed to get geo location from phonegap ' + err);

});

})

}

var fetchFromDB = function () {

var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);

try {

db.transaction(function (tx) {

tx.executeSql('SELECT * FROM Favourite', [], function (tx, results) {

var arr = [];

for (var i = 0; i < results.rows.length; i++) { var data = results.rows.item(i)

arr[i] = data;

}

favorites.update(arr);

}, function (error) {

console.log("Got error fetching favourites " + error.code + " " + error.message);

});

});

} catch (err) {

console.log("Got error while reading favourites " + err);

}

}

var fetchDetails = function (reference) {

placeDetailsPanel.update({

name: "",

formatted_address: "",

formatted_phone_number: "",

rating: "",

url: ""

});

Ext.Ajax.request({

url: 'https://maps.googleapis.com/maps/api/place/details/json?reference=' + reference + '&sensor=true&key=API_Key',

success: function (response, opts) {

var obj = Ext.decode(response.responseText);

cachedDetails = obj.result;

isFav(obj.result, function (result) {

if (result) {

resultDetailPanel.items.items[1].setText("Remove from Favorite");

} else {

resultDetailPanel.items.items[1].setText("Add to Favorite");

}

placeDetailsPanel.update(obj.result);

});

},

failure: function (response, opts) {

console.log('server-side failure with status code ' + response.status);

}

})

} /**

* Ensure we have the table before we use it

* @param {Object} tx

*/

var ensureTableExists = function (tx) {

tx.executeSql('CREATE TABLE IF NOT EXISTS Favourite (id unique, reference, name,address,phone,rating,icon,vicinity)');

}

var addCurrentToFav = function () {

addToFavorite(cachedDetails);

}

var removeCurrentFromFav = function () {

removeFromFavorite(cachedDetails);

}

/**

* Add current business data to favourite

* @param {Object} data

*/

var addToFavorite = function (data) {

var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);

db.transaction(function (tx) {

ensureTableExists(tx);

var id = (data.id != null) ? ('"' + data.id + '"') : ('""');

var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');

var name = (data.name != null) ? ('"' + data.name + '"') : ('""');

var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');

var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');

var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');

var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');

var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');

var insertStmt = 'INSERT INTO Favourite (id,reference, name,address,phone,rating,icon,vicinity) VALUES (' + id

+ ',' + reference + ',' + name + ',' + address + ',' + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';

tx.executeSql(insertStmt);

}, function (error) {

console.log("Data insert failed " + error.code + " " + error.message);

}, function () {

console.log("Data insert successful");

});

} /**

* Remove current business data from favourite

* @param {Object} data

*/

var removeFromFavorite = function (data) {

try {

var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);

db.transaction(function (tx) {

ensureTableExists(tx);

var deleteStmt = "DELETE FROM Favourite WHERE id = '" + data.id + "'";

console.log(deleteStmt);

tx.executeSql(deleteStmt);

}, function (error) {

console.log("Data Delete failed " + error.code + " " + error.message);

}, function () {

console.log("Data Delete successful");

});

} catch (err) {

console.log("Caught exception while deleting favourite " + data.name);

}

}

/**

*

* @param {Object} reference

* @return true if place is favourite else false

*/

var isFav = function (data, callback) {

var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);

try {

db.transaction(function (tx) {

ensureTableExists(tx);

var sql = "SELECT * FROM Favourite where id='" + data.id + "'";

tx.executeSql(sql, [], function (tx, results) {

var result = (results != null && results.rows != null && results.rows.length > 0);

callback(result);

}, function (tx, error) {

console.log("Got error in isFaverror.code =" + error.code + " error.message = " + error.message);

callback(false);

});

});

} catch (err) {

console.log("Got error in isFav " + err);

callback(false); }

}

}

});`

结论

如果你正在构建一个相当复杂的移动应用,你应该使用 Sencha Touch。jQueryMobile 适用于较小的、不太复杂的 Ajax 应用。虽然 jQueryMobile 可以用于更复杂的应用,但是您必须自己处理 DOM,这样事情会变得更复杂。

Sencha Touch 性能不错,widget 集丰富。它的一些小部件使用数据存储来与服务器组件对话。你可以在 Sencha Touch 中使用 mvc 设计模式,甚至可以将你的应用分成几个部分。用于改进模块化代码的 js 文件。

六、在 GWT 中使用 PhoneGap

Google Web toolkit (GWT)是 Google 提供的一个框架,可以用来开发基于浏览器的应用。GWT 允许开发人员用 Java 编写代码并生成基于 JavaScript 的应用。

GWT 应用本质上是跨浏览器兼容的,它们是最小和最快的基于浏览器的应用。

本章将重点介绍如何使用 PhoneGap 为手机开发一个 GWT 应用。这些步骤基于 Daniel Kurka 开发的 GWT PhoneGap 库。你可以从[code.google.com/p/gwt-phonegap/](http://code.google.com/p/gwt-phonegap/).下载这个库

如何开发基于 GWT 的应用的知识是必不可少的。如果你是 GWT 应用的新手,你可以访问[code.google.com/webtoolkit/doc/latest/tutorial/index.html](http://code.google.com/webtoolkit/doc/latest/tutorial/index.html)来了解更多关于 GWT 的信息。

为什么使用 GWT 进行用户界面开发?

在我们开始讨论如何一起使用 GWT 和 PhoneGap 之前,让我们首先考虑一下为什么 GWT 是用户界面开发的最佳选择:

GWT 允许开发人员编写基于浏览器的应用,而不必担心跨浏览器问题、JavaScript 中的内存泄漏以及 JavaScript 语言本身。

GWT 允许开发人员用 Java 编写代码,并将用 Java 编写的用户界面和业务逻辑编译成 JavaScript。

GWT 还允许您使用诸如延迟绑定之类的概念(这就像 JavaScript 世界的运行时多态性)。这种方法允许开发人员创建一个应用,该应用可以使用不同的类为移动浏览器提供服务,并使用另一组类为桌面浏览器提供服务。

GWT 确保您可以创建最小和最快的 JavaScripts。

GWT 是一项被许多公司和大部分开发者社区广泛接受的技术。GWT 正在成为大型、复杂的基于 Ajax 的应用的事实上的选择。

除了上述优势,许多轻量级、现成的小部件对于 GWT 来说都是现成的。此外,还有专业的 GWT 库,像 EXT-GWT 和 Smart-GWT,它们使用户界面看起来非常专业和完美。想象一下,您的 Java 开发人员使用他们现有的 Java 技能,按照最佳设计实践轻松编写基于浏览器的应用。GWT 消除了编写基于浏览器的应用的痛苦,同时提供了最好的浏览器应用。

了解 GWT PhoneGap

GWT 提供了一个名为 JavaScript Native Interface (JSNI)的机制,它允许 GWT 包装现有的 JavaScript 库。这种能力允许开发人员用 Java 编写代码,而不必担心底层 JavaScript 函数是如何被调用的。

GWT PhoneGap 是 PhoneGap 库的 GWT 包装器。下一节将演示如何使用 GWT PhoneGap 编写一个 helloworld 应用。

构建 PhoneGap GWT 应用

构建 GWT PhoneGap 应用有两个主要步骤。第一步是建立一个 GWT 项目。一旦您构建了 GWT 项目,开发人员将编译 GWT 项目来创建一个 web 应用(一组 HTMLs 和 JavaScripts)。

第二步是构建一个 Android PhoneGap 应用(使用 PhoneGap 的 0.9.4 版本),并将 GWT web 应用嵌入 PhoneGap 应用中。

构建 GWT 应用

在构建 GWT 应用之前,您需要以下工具:

JDK 1.6 以上

日蚀 3.6 太阳神

Eclipse Google 插件

PhoneGap 0.9.4 库

GWT PhoneGap 0.8 版本库

用于测试的 Chrome 浏览器 12+版本

创建一个新的 web 应用项目(Google web application ),并在向导中填入如图图 6–1 所示的值。您需要选中“使用谷歌网络工具包”,取消选中“使用谷歌应用引擎”

图 6–1。 创建 GWT 项目

为项目创建一个名为“lib”的文件夹。从[code.google.com/p/gwt-phonegap/downloads/detail?name=gwt-phonegap-0.8.jar](http://code.google.com/p/gwt-phonegap/downloads/detail?name=gwt-phonegap-0.8.jar)下载 GWT-PhoneGap 库,并将其复制到应用的 lib 文件夹中。将 gwt-phonegap-0.8.jar 添加到类路径中,方法是右键单击 jar,然后单击“构建路径”——>“添加到构建路径”

现在打开 PhoneGap _ GWT _ hello world . gwt . XML 文件。在该文件中,添加以下条目:

请注意,通过将 user.agent 的 set-property 添加到 safari,GWT 将只为基于 webkit 的浏览器生成 JavaScript。Chrome 浏览器将专门用于这种情况下的测试。

您的 PhoneGap _ GWT _ hello world . gwt . XML 文件现在应该如下所示:

`

`

现在打开 PhoneGap_GWT_Helloworld.html,位于项目的 war 文件夹中,并进行以下更改:

`

Gwt PhoneGap Demo

`

如果您计划在 Android 上运行这个示例,您应该在 phonegap _ gwt _ hello world/phonegap _ gwt _ hello world . no cache . js 标记之后添加以下内容:

现在,打开 src 文件夹中的 PhoneGap_GWT_Helloworld.java,并进行以下更改:

`package com.phonegap.example.gwt.helloworld.client;

import com.google.gwt.core.client.EntryPoint;

import com.google.gwt.user.client.ui.Label;

import com.google.gwt.user.client.ui.RootPanel;

/**

* Entry point classes define onModuleLoad().

*/

public class PhoneGap_GWT_Helloworld implements EntryPoint {

/**

* This is the entry point method.

*/

public void onModuleLoad() {

RootPanel.get().add(new Label("GWT PhoneGap Demo"));

}

}`

默认创建的 GWT 项目有一个用于客户端服务器通信的 RPC 组件,这个应用不需要它。因此,您可以从项目中移除以下条目:

客户包里的 GreetingService.java 和 GreetingServiceAsync.java

共享包和服务器包

web.xml 中的任何 servlets

现在,运行 GWT 项目(运行方式-> Web 应用),您应该会看到如图图 6–2 所示的屏幕。请注意,这个示例将在浏览器中运行,以确保您的 GWT 项目设置正确。

图 6–2。 在 Chrome 浏览器中运行 GWT 项目

下一步是实际利用 PhoneGap API 将 GWT 项目编译成一个 web 应用。

PhoneGap GWT 库的一个好处是,当作为 GWT web 应用启动时,它模仿 PhoneGap 库。该库根据以下指令提供替代功能:

如果在 Android 或 iPhone 上运行,请使用 PhoneGap JavaScript。

否则,使用内部模拟类并给出虚拟值。

首先使用延迟绑定创建 PhoneGap 的对象:

PhoneGap PhoneGap = (PhoneGap)GWT.create(PhoneGap.class);

下一步是在 PhoneGap 框架中注册以下回调:

phonegavailablehandler:当一切正常并且 PhoneGap 被正确初始化时,这个回调就会发生。总之,这是一次成功的回调。

Phonegaptimeouthandler: 当 PhoneGap 没有在给定的时间限制内初始化时,会发生这个回调,可能是由于未能初始化 PhoneGap 框架。简而言之,这是一次失败回调。

最后,您必须通过调用PhoneGap.initializePhoneGap().来初始化 PhoneGap 框架。调用这个 API 将导致上面的一个回调。

主代码将被写成PhoneGapAvailableHandler回调,如下所示。使用 PhoneGap 变量是安全的,因为 PhoneGap 已经被正确初始化。在下面的代码中,您从 PhoneGap 获得设备的处理程序,然后在一个网格(2 列 5 行的表格)中打印设备信息值:

`Device device = phoneGap.getDevice();

Grid grid = new Grid(5, 2);

//Add a row mentioning Name Property of Device

grid.setWidget(0, 0, new Label("Name"));

grid.setWidget(0, 1, new Label(device.getName()));

//Add a row mentioning Platform Property of Device

grid.setWidget(1, 0, new Label("Platform"));

grid.setWidget(1, 1, new Label(device.getPlatform()));

//Add a row mentioning Version Property of Device

grid.setWidget(2, 0, new Label("Version"));

grid.setWidget(2, 1, new Label(device.getVersion()));

//Add a row mentioning Name Property of Device

grid.setWidget(3, 0, new Label("PhoneGapVersion"));

grid.setWidget(3, 1, new Label(device.getPhoneGapVersion()));

//Add a row mentioning Name Property of Device

grid.setWidget(4, 0, new Label("UUID"));

grid.setWidget(4, 1, new Label(device.getUuid()));

grid.setBorderWidth(1);

RootPanel.get().add(grid);`

以下是完整的示例:

`package com.phonegap.example.gwt.helloworld.client;

import com.google.gwt.core.client.EntryPoint;

import com.google.gwt.core.client.GWT;

import com.google.gwt.user.client.Window;

import com.google.gwt.user.client.ui.Grid;

import com.google.gwt.user.client.ui.Label;

import com.google.gwt.user.client.ui.RootPanel;

import de.kurka.phonegap.client.PhoneGap;

import de.kurka.phonegap.client.PhoneGapAvailableEvent;

import de.kurka.phonegap.client.PhoneGapAvailableHandler;

import de.kurka.phonegap.client.PhoneGapTimeoutEvent; import de.kurka.phonegap.client.PhoneGapTimeoutHandler;

import de.kurka.phonegap.client.device.Device;

/**

* Entry point classes define onModuleLoad().

*/

public class PhoneGap_GWT_Helloworld implements EntryPoint {

/**

* This is the entry point method.

*/

public void onModuleLoad() {

final PhoneGap phoneGap = GWT.create(PhoneGap.class);

phoneGap.addHandler(new PhoneGapAvailableHandler() {

public void onPhoneGapAvailable(PhoneGapAvailableEvent event) {

Device device = phoneGap.getDevice();

Grid grid = new Grid(5, 2);

//Add a row mentioning Name Property of Device

grid.setWidget(0, 0, new Label("Name"));

grid.setWidget(0, 1, new Label(device.getName()));

//Add a row mentioning Platform Property of Device

grid.setWidget(1, 0, new Label("Platform"));

grid.setWidget(1, 1, new Label(device.getPlatform()));

//Add a row mentioning Version Property of Device

grid.setWidget(2, 0, new Label("Version"));

grid.setWidget(2, 1, new Label(device.getVersion()));

//Add a row mentioning Name Property of Device

grid.setWidget(3, 0, new Label("PhoneGapVersion"));

grid.setWidget(3, 1, new Label(device.getPhoneGapVersion()));

//Add a row mentioning Name Property of Device

grid.setWidget(4, 0, new Label("UUID"));

grid.setWidget(4, 1, new Label(device.getUuid()));

grid.setBorderWidth(1);

RootPanel.get().add(grid);

}

});

phoneGap.addHandler(new PhoneGapTimeoutHandler() {

public void onPhoneGapTimeout(PhoneGapTimeoutEvent event) {

Window.alert("can not load phonegap");

}

});

phoneGap.initializePhoneGap();

}

}`

您可以使用“run as -> web application”从 Eclipse 运行这个示例,并在浏览器中查找代码。

您将看到图 6–3 中所示的表格。如上所述,当在浏览器中运行代码时,模拟值由 GWT PhoneGap 显示,而不是在 Android 或 iPhone 上。

图 6–3。 在 Chrome 浏览器中运行 GWT PhoneGap 项目

最后一步是将这个项目编译成 web 应用。右键单击项目,选择 Google 选项,然后单击名为“GWT 编译”的菜单选项您将看到如图图 6–4 所示的对话框。单击编译。

图 6–4。 GWT 编译屏幕

编译完成后,在 Eclipse 中刷新您的项目,您应该会看到如图 Figure 6–5 所示的目录结构。在 war 文件夹中,您应该会看到一个 phonegap_gwt_helloworld 文件夹,其中包含许多 HTML 和 JavaScript 文件,如下所示。

图 6–5。 GWT 战争目录结构 GWT 编译后

构建 PhoneGap Android 应用

构建 PhoneGap GWT 应用的最后一步是创建一个 Android PhoneGap 项目,如图 6–6 和图 6–7 所示,然后将 GWT 生成的 web 应用复制到 assets/www 文件夹中。

第一步是创建一个 Android 项目。

图 6–6。 Android 创建项目屏幕

图 6–7。 Android 创建项目屏幕

现在,将 PhoneGap 0.9.4 库注入 Android 项目。

从[phonegap.googlecode.com/files/phonegap-0.9.4.zip](http://phonegap.googlecode.com/files/phonegap-0.9.4.zip)下载 PhoneGap 0.9.4 库。撤消 zip 文件,您将看到如图图 6–8 所示的文件夹结构。

图 6–8。 PhoneGap 0.9.4 目录结构

在 Android 项目文件夹中创建一个 lib 文件夹,在 lib 文件夹中复制 PhoneGap.0.9.4.jar 文件,然后将其添加到 Eclipse 类路径中。(右键单击 jar 文件,转到“构建路径”,然后单击“添加到构建路径”)

下一步是在 assets 文件夹中创建一个 www 文件夹,并将 PhoneGap.0.9.4.js 文件复制到 www 文件夹中。然后,您需要将 GWT 项目中的以下文件复制到同一文件夹中:

PhoneGap_GWT_Helloworld.html

PhoneGap_Gwt_Helloworld.css

phonegap_gwt_helloworld 文件夹

您的文件夹结构现在应该类似于图 6–9 中的示例。编译项目时会生成名为“gwt”的文件夹中的文件。

图 6–9。 使用 Android 的 GWT PhoneGap 项目目录结构

现在您需要修改以下文件:

HelloWorld.java 文件

PhoneGap_GWT_Helloworld.html

此外,您需要检查 HelloWorld.java 文件是否类似于以下内容:

package com.phonegap.gwt.helloworld; import android.os.Bundle; import com.phonegap.DroidGap; public class HelloWorld extends DroidGap { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.loadUrl("file:///android_asset/www/PhoneGap_GWT_Helloworld.html"); } }

在 PhoneGap_GWT_Helloworld.html 文件夹中,您需要进行一些代码更改。

首先添加 Android 版 PhoneGap JavaScript 库 0.9.4 版本,如下:

接下来,侦听 deviceready 事件,以确定 PhoneGap 库是否可以使用:

在此将 PhoneGap.available 变量显式设置为 true。这是 Android 平台的必经步骤。

以下是 PhoneGap_gWT_Helloworld.html 的完整源代码:

`


Gwt PhoneGap Demo

`

运行此代码后,仿真器上的屏幕应显示为图 6–10 中的示例。

图 6–10。GWT PhoneGap 应用显示设备信息

与上面的代码一样,您可以编写代码来访问其他 PhoneGap APIs,也可以编写一个通过 PhoneGap 访问本地电话功能的 GWT 应用。

GWT PhoneGap 参考

下面列出了 GWT PhoneGap 项目中使用的文档和源代码的链接。丹尼尔·库尔卡是这个图书馆的作者。

主页—[code.google.com/p/gwt-phonegap/](http://code.google.com/p/gwt-phonegap/)

入门—[code.google.com/p/gwt-phonegap/wiki/GettingStarted](http://code.google.com/p/gwt-phonegap/wiki/GettingStarted)

下载 jar-[gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8.jar](http://gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8.jar)

下载 Javadocs—[gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8-javadoc.jar](http://gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8-javadoc.jar)

源代码—[code.google.com/p/gwt-phonegap/source/browse/](http://code.google.com/p/gwt-phonegap/source/browse/)

当前功能-[code.google.com/p/gwt-phonegap/wiki/Features](http://code.google.com/p/gwt-phonegap/wiki/Features)

七、PhoneGap 模拟器和远程调试

简介

我在开发 PhoneGap 应用时经历的最大痛苦是以下循环:

开发一个 eclipse/xcode 或无关的 IDE

编译二进制可执行文件并将其放在设备/仿真器上

在设备/模拟器上测试 PhoneGap 应用

调整代码并从第 1 步开始重复

显然,这一循环非常耗时且令人沮丧。如果你是一个有经验的 JavaScript 开发人员,这对你来说将是一场噩梦。

JavaScript 开发人员习惯于在以下现代浏览器上使用便捷的工具:

火狐浏览器

旅行队

微软公司出品的 web 浏览器

(注意,对于 iPhone 和 Android 开发,ie 浏览器没什么用。我们建议 iPhone 和 Android 开发使用 Firefox、Safari 或 Chrome。)

这些工具很少是开发人员的扩展。Firefox 有自己的 firebug,这是 javascript/html 调试工具系列的第一款,它不仅允许您调试页面元素(DOM 结构),还允许您调试脚本、样式表和网络。它还允许您动态地更改这些内容,并立即进行测试。Chrome 和 Safari 自带开发工具。Internet Explorer 也不例外,它有自己的扩展来做类似的事情。

显然,我们在开发 PhoneGap 时需要类似的东西。这里列出我们的两个要求。我们需要以下内容:

使用 PhoneGap 模拟器在浏览器世界中创建和测试 PhoneGap 之外的应用的能力

一旦我们将 PhoneGap 应用部署在某个仿真器或设备上,就可以对其进行调试

本章我们将讨论如何使用 PhoneGap 模拟器和远程调试工具。

Chrome PhoneGap 模拟器–使用 Ripple

Ripple 是一个多平台移动平台模拟器,来自一家名为 tinyHippos 的公司。最近,这家公司被动态研究公司(RIM)收购。Ripple 出现的主要原因是为了减少当今移动 web 开发人员面临的挑战,这些挑战是由移动操作系统世界的巨大碎片化造成的。

Ripple 是一个 Chrome 扩展,提供以下模拟:

语音间隙

Webworks(来自黑莓)

web works-平板电脑-操作系统(来自黑莓)

移动网络

陆军妇女队

歌剧

沃达丰

出于本书的目的,我们将只关注 Ripple 的 PhoneGap 仿真。

安装波纹

Ripple 唯一的先决条件是你需要 Chrome 浏览器。任何支持扩展的版本都可以,不用担心安装哪个版本的 Chrome。但是,我们建议使用最新版本。

打开 Chrome 并访问网站—[ripple.tinyhippos.com](http://ripple.tinyhippos.com)/

您将看到如图图 7–1 所示的页面;你所需要做的就是点击“获取波纹”按钮。

图 7–1。 涟漪首页

这将带您进入图 7–2 中显示的页面。现在点击“安装”

图 7–2。安装 Ripple 作为 Chrome 扩展

正如我们之前提到的,Ripple 是 Chrome 的扩展。点击“安装”进入 Chrome 网上商店。当你点击“添加到 Chrome”时,扩展/插件实际上是从 Chrome 网络商店下载的,并自动安装在你的 Chrome 浏览器中(参见 Figure 7–3)。

图 7–3。 从 Chrome 网上商店安装 Ripple

一旦插件安装完毕,你会看到一个类似于图 7–3 所示的屏幕。

图 7–4。 在 Chrome 上安装波纹

为了验证插件已经正确安装,在 Chrome 中打开一个类似[www.google.com](http://www.google.com)的站点,右键点击页面。如果插件已经正确安装,您应该会看到一个“模拟器”启用/禁用选项。这在图 7–5 中进行了描述。

图 7–5。 在 Chrome 上右击选项打开波纹模拟器

继续点击“模拟器”并选择“启用”如果你看到图 7–6 中的屏幕,你的 Ripple 插件工作正常。要退出该屏幕,只需右击并选择仿真器- >禁用。

图 7–6。 第一次发射涟漪

为 PhoneGap 有效地使用 Chrome

在我们继续在 Chrome 上使用 Ripple 之前。让我们来了解一下 PhoneGap 如何有效地使用 Chrome。PhoneGap 与移动网络应用有相似之处,但在许多方面也有所不同。让我们列出两个主要的区别。

PhoneGap 应用是基于 HTML/JavaScript 的应用,但它们从不被托管。它们被捆绑为本地移动应用的一部分,显示在应用内部的嵌入式浏览器中。这类似于在 Chrome 上从磁盘测试 HTML/JavaScript 应用。Chrome 需要调整以支持从磁盘运行 html/javascript 应用。

PhoneGap 没有任何与之相关联的域名。这就是为什么他们不遵循单一原产地政策。如果您想为 PhoneGap 应用模拟不遵守单一来源策略,该应用由文件系统或本地 web 服务器托管,您需要调整 Chrome 以支持关闭单一来源策略。

为了从本地文件系统测试 PhoneGap 应用,并使其不遵循单一来源策略,您需要使用下面几段中讨论的命令行参数启动 Chrome。

Windows

在 Windows 中,创建一个名为 chrome.cmd 的. cmd 文件,并将以下脚本复制到该文件中。现在使用 chrome.cmd 启动 chrome。

**chrome.exe --disable-web-security** -–allow-file-access-from-files

Mac 和 Linux

在 Mac 上,创建一个名为 chrome.sh 的脚本,并将以下脚本复制到其中。现在用 chrome.sh 启动 chrome。

open "/Applications/Google Chrome.app" --args --disable-web-security -–allow-file-access-from-files

更改 chrome.sh 脚本的权限,使其可执行(需要运行脚本)。

$>chmod +x chrome.sh Run the script from terminal as follows: $>./chrome.sh

利用波纹

现在我们将了解如何使用 Ripple,以及在 Ripple 中运行 PhoneGap 应用需要做哪些更改。

以下是在 Ripple 中使用 PhoneGap 应用的先决条件:

该应用需要是一个没有插件的纯欧米茄应用。

您需要从所有 HTML 文件中删除对 PhoneGap JavaScript 的任何引用。

针对 Ripple 调整您的应用

让我们举一个来自第二章的代码例子。

我们将致力于指南针应用的例子。从这个 URL-[beginingphonegap.googlecode.com/files/compass.png](http://beginingphonegap.googlecode.com/files/compass.png)获取指南针图像。该图像显示在图 7–7 中。

图 7–7。PhoneGap 应用中使用的指南针图像

现在,让我们修改 index.html 文件,如下所示。请注意,我们已经删除了对 phonegap.js 的任何引用。这是目前使用 Ripple 的先决条件。Ripple 正在与 PhoneGap 合作,以消除这种变化。希望在即将发布的版本中,我们会看到这个需求消失。

`


PhoneGap

Compass

Compass Heading

....

Degrees

`

你的应用目录看起来会像图 7–8。它包含应用的所有 HTML、JavaScript 和 CSS 文件。

图 7–8。 指南针 App 的应用目录

用特殊标志启动 Chrome

下一步是用特殊的标志启动 Chrome(就像我们在本章前面开始的那样)。

Start Chrome with --disable-web-security -–allow-file-access-from-files flags.

Chrome 启动后,进入窗口->扩展,找到“Ripple Mobile Environment Emulator”扩展,启用“允许访问文件 URL”复选框(参见图 7–9)。

图 7–9。 允许访问文件的网址为波纹扩展名

在 Chrome 中加载应用

现在在 Chrome 中加载指南针应用。在右上角,您会看到波纹图标。点击该项目,为该应用启用 Ripple。这显示在图 7–10 中。

请注意,因为我们用上述标志启动了 Chrome,这就是 Chrome 能够从本地文件系统加载 HTML 文件的原因。此外,如果 PhoneGap 应用使用 Ajax 加载数据,它也可以在 Chrome 中工作,因为我们已经禁用了单一来源策略。

图 7–10。 在 Chrome 浏览器中加载指南针 app

启用纹波

第一次启用 Ripple 时,您会看到 Figure 7–11。我们需要选择 PhoneGap 选项。

图 7–11。 为指南针 app 启用纹波

玩波纹设置

现在我们已经启用了 Ripple,我们将看到网页已经改变。以前占据整个屏幕的应用现在看起来不同了。这是因为现在 Ripple 是主要应用。Ripple 在 iframe 中加载我们自己的应用,并注入自身来模拟 PhoneGap 环境。在主页上,Ripple 提供了许多控件来改变 PhoneGap 模拟的设备的状态和属性(参见图 7–12)。

图 7–12。Chrome 浏览器中加载的指南针应用启用了 Ripple

用纹波测试应用

测试中的应用是一个 compass 应用。为了测试该应用,我们将使用右下角红色标记的地理和指南针控件(参见图 7–12)。如果我们改变方向,这意味着我们正在模拟用户移动他/她的设备的罗盘方位。

正如您在图 7–13 中看到的,当我们改变航向时,我们可以看到罗盘图像围绕中心旋转。

图 7–13。 用 Ripple 模仿 PhoneGap 的 Compass api

模拟 PhoneGap 只是涟漪的一个角度。Ripple 允许开发人员使用常规的浏览器工具来调试和更改应用的 DOM 和 CSS。在图 7–14 中,我们右击图像并点击“检查元素”然后我们可以检查被检查的 DOM 元素的 css 样式,在我们的例子中是 html img。

图 7–14。 使用 Chrome 的开发者工具查看 HTML DOM 变化

远程调试—debug.phonegap.com

虽然使用 Ripple 来模拟、测试和调试 PhoneGap 应用是可行的,并且非常有帮助,但是没有什么比得上在实际的仿真器或设备上进行调试。

在实际的仿真器或设备上进行调试的问题是,用于显示 PhoneGap 应用的 webkit webview 非常孤立,无法从外部访问。与 Chrome、Firefox 或 Safari 相比,在这些浏览器中,用户可以检查 html 元素。但是在网络视图中运行的应用(HTML/Javascript)比如 PhoneGap 应用不能被检查。

这就是远程调试发挥作用的地方。参见图 7–15 更好地理解这个概念。基本思想是将调试 JavaScript 注入到我们的 PhoneGap 应用中。这打开了一个与 debug.phonegap.com 服务器的通道。然后开发人员在浏览器中打开 debug.phonegap.com 的并检查运行在设备/模拟器上的 PhoneGap 应用。

图 7–15。 远程调试架构

设置远程调试

进行远程调试的第一步是在浏览器中打开[debug.phonegap.com](http://debug.phonegap.com)。在这里你可以提供一个你自己的向导(就像我们一样)或者使用服务器随机分配的向导。

图 7–16。?? 使用debug.phonegap.com调试你的 PhoneGap 应用

在 PhoneGap 应用中注入远程调试

下一步是复制来自[debug.phonegap.com](http://debug.phonegap.com)的 JavaScript 片段,并将其注入到 PhoneGap 应用中。这在下面用下划线表示。

`

PhoneGap

Device Info

Device Name

PhoneGap Version

Mobile Platform

Platform Version

UUID

`

调试和修改 DOM 元素

下一步是在模拟器或设备上启动 PhoneGap 应用。当这个应用启动时,运行在其中的 JavaScript 将与 debug.phonegap.com 服务器通信。然后,我们将为远程调试做好准备(参见图 7–17)。

图 7–17。 在安卓模拟器中加载设备信息应用

最后一步是在浏览器中打开[debug.phonegap.com/client/#begining-phonegap](http://debug.phonegap.com/client/#begining-phonegap)(参见图 7–18)。请记住,我们是从[debug.phonegap.com](http://debug.phonegap.com)网站获得这个 URL 的。

图 7–18。 日志消息显示一个 Android 应用连接到了debug.phonegap.com

现在移动到元素选项卡(参见图 7–19)。在这里,您将能够在 PhoneGap 应用中看到页面的 DOM 元素。

图 7–19。 在上检查 DOM

乐趣不止于此。现在您可以更改 DOM 元素(参见 Figure 7–20)。为此,双击任何 DOM 元素(在我们的例子中是 TD)并手动输入样式部分。在我们的例子中,我们将向包含 0.9.5 文本的 td 添加一个样式“style='background:red '”。现在,我们将切换到运行在模拟器中的 PhoneGap 应用,以查看即将生效的更改。

图 7–20。 在【http://debug.phonegap.com】上改变一个 DOM 属性

现在,包含“0.9.5”的 TD 元素的背景色变为红色(参见图 7–21)。这种调试帮助我们在设备/仿真器上实时调试应用。

图 7–21。 对[debug.phonegap.com](http://debug.phonegap.com)所做的更改反映在一个 Android 模拟器上

与debug.phonegap.com的问题

到目前为止,我们已经看到[debug.phonegap.com](http://debug.phonegap.com)的使用在检查真实设备或仿真器内部运行的内容时非常有用。但是,我们不想使用它,原因如下:

在开发过程中,我们不想使用外部服务器。

我们希望节省带宽并提高调试速度。

注:A Weinre(web Inspectorremote)服务器 powerdebug.phonegap.com。PhoneGap 的人也开发了 Weinre,他们已经很好地记录了它。

安装当地debug.phonegap.com

虽然 Weinre 的安装和部署超出了本书的范围,但是我将以链接的形式给出一些关于如何在本地部署 Weinre 的说明。

Weinre 的文档非常好,如果您遵循它,在本地安装和使用 Weinre 应该没有问题。

访问[phonegap.github.com/weinre/Installing.htm](http://phonegap.github.com/weinre/Installing.htm) l 获取安装文件(参见图 7–22)。

图 7–22。 关于如何安装 Weinre 的说明

访问[phonegap.github.com/weinre/Running.html](http://phonegap.github.com/weinre/Running.html)获取如何运行的说明(参见图 7–23)。

图 7–23。 关于如何运行的说明

结论

当开发 PhoneGap 应用时,使用 iPhone/Android 模拟器来测试代码是非常耗时和令人沮丧的。为了节省时间和精力并简化开发,请使用 Ripple PhoneGap 模拟器。充分利用 Chrome 的开发者工具,加速你的开发。对于远程调试,使用[debug.phonegap.com](http://debug.phonegap.com)或本地安装的 Weinre 服务器。这有助于您了解当应用在 iPhone/Android 模拟器或实际设备上运行时,HTML DOM 实际上发生了什么。

八、使用 PhoneGap 插件

PhoneGap 附带了一组 JavaScript APIs,用于访问原生电话功能,如摄像头、存储、联系人、地理定位等。,构建跨移动应用。如果您想做 PhoneGap API 中没有的事情,您可以利用 PhoneGap 插件。

在任何技术中,重用已经可用并经过测试的特性都是常见的做法。PhoneGap 有很多重要的第三方插件。例如,访问脸书的认证机制、访问用于移动推送通知的第三方服务等。

什么是 PhoneGap 插件?

PhoneGap 插件是 PhoneGap 功能的扩展。它访问手机上的一项功能。插件功能可能只能访问手机的本机功能,也可能提供访问云服务的功能。

任何 PhoneGap 插件都至少包含两个文件:

JavaScript 文件

本地语言文件

插件的 JavaScript 文件是 PhoneGap 应用和 PhoneGap 插件之间的接口。JavaScript 文件使用 JavaScript 函数来访问插件的功能。

PhoneGap 框架使用本地语言文件来与电话交互以访问本地功能。作为插件用户,我们需要将本机代码放入我们的项目结构中。在下一节中,我们将详细研究如何使用这个插件来设置项目。

脸书认证并抓取好友

让我们利用 PhoneGap 插件构建一个小应用,登录到脸书并从脸书获取朋友的信息。

我们的 PhoneGap 应用将使用脸书本地应用通过脸书-PhoneGap 插件为用户执行单点登录(SSO)。

为 Android 设置环境

首先,我们需要为 Android 建立 PhoneGap 项目。请参考第二章为 Android 设置您的项目。一个 Android 项目配置如图图 8–1 所示。

图 8–1。 Eclipse Android 项目配置

从[github.com/davejohnson/phonegap-plugin-facebook-connect/downloads](https://github.com/davejohnson/phonegap-plugin-facebook-connect/downloads)下载脸书连接插件。

脸书连接插件是一个 zip 文件。解压到你喜欢的文件夹里。文件夹结构应类似于图 8–2。

图 8–2。 脸书-连接插件文件夹结构

接下来,您需要执行以下安装步骤:

Register Facebook Plug-in

在 plugins.xml 文件中添加以下 XML 元素作为“plugins”元素的子元素,如 Figure 8–3 所示。您可能需要使用以下命令在 res 文件夹中创建一个 xml 文件夹:

图 8–3。 脸书外挂注册

Include the native part of the plug-in into the project. Copy the libs and src folder from a Facebook-connect-plug-in folder as shown in Figure 8–4 and paste it into the root of our PhoneGap application, i.e., “FaceBookPluginExample”.

图 8–4。 脸书-连接-安卓插件原生文件夹

在项目中包含插件的 JavaScript 部分

有两个 JavaScript 文件需要包含在我们来自脸书的项目中——连接插件。

/www/pg-plugin-fb-connect.js

pg-plugin-fb-connect.js文件在脸书插件的 www 文件夹下。将其复制并粘贴到我们项目的 assets/www 文件夹中。

/lib/facebook_js_sdk.js

facebook_js_sdk.js文件在脸书插件的 lib 文件夹下。将其复制并粘贴到我们项目的 assets/www 文件夹中。

一旦你完成了这三个步骤,你将会看到如图图 8–5 所示的 FaceBookPluginExample 项目结构。

图 8–5。 FaceBookPluginExample 项目结构

初始化脸书连接插件

第一步是确保 index.html 有一个脸书连接库,PhoneGap 库,并且是 CSS 链接的。请注意,我们包含了以下 JavaScript 文件。

JavaScript 语音间隙

脸书插件 JavaScript

脸书 SDK JavaScript

`

` `

`

现在,我们将在 index.html 页面中定义 JavaScript 函数,以登录到脸书并获取好友列表。以下是登录函数的代码片段:

function login() { FB.login(function(response) {…}, { perms: "email" } ); }

login()函数调用脸书 SDK 的登录函数 FB.login()。脸书的 FB.login()有两个参数。第一个是回调 JavaScript 函数,第二个是 JSON 对象,用于指定权限。我们将' function(response){…} '和' { perms: "email" } '传递到 FB.login()。脸书的 FB.login()提示用户登录。成功登录后,它调用回调 JavaScript 函数。回调函数获取“响应”对象来标识登录状态。“perms”用于指定用户权限。你可以在脸书开发者网站上找到更多关于脸书登录 API 和用户权限的细节,网址是 http://developers . Facebook . com/docs/reference/API/permissions。

接下来,我们将看到用于获取好友列表的代码片段。为此,我们将创建一个 JavaScript 函数 getFriendList()。

function getFriendList(){ FB.api('/me/friends', function(response) { if (response.error) { alert(JSON.stringify(response.error)); } else { var friends = document.getElementById('friends'); response.data.forEach(function(item) { var d = document.createElement('div'); d.innerHTML = item.name; data.appendChild(d); }); } }); }

在 getFriendList()函数中,调用了脸书 API FB.api()。第一个参数是脸书提供的图形 API 的路径。在我们的示例中,'/me/friends '用于获取登录用户的朋友列表。第二个参数是接收响应的 JavaScript 回调函数。回调方法中执行以下操作:

使用“response.error”检查响应状态是否成功

如果响应成功,那么 response 中可用的结果数据将被迭代。

为每个项目创建“div”元素,并将其附加到在 index.html 定义的“friends”div 中

接下来,我们将修改 login()函数,以便在成功登录时调用 getFriendList()。

function login() { FB.login( function(response) { if (response.session) { getFriendList(); } else { alert('not logged in'); } }, { perms: "email" } ); }

这里我们检查“response.session”值的成功响应。如果它是有效的,我们就调用 getFriendList()函数。

现在,最后一步是在 PhoneGap 的初始化事件中使用 JavaScript 函数。

document.addEventListener('deviceready', function() { try { /* Initialize the Facebook plug-in. Note that you need to replace the by your Facebook's app_id */ FB.init({ appId: "", nativeInterface:PG.FB }); document.getElementById('data').innerHTML = ""; login(); } catch (e) { alert(e); } }, false);

最后,要运行应用,您需要将脸书的 app_secret 密钥放入 AndroidManifest.xml 文件,如 Figure 8–6 所示。

图 8–6。 脸书 app _ 秘钥

您可以从脸书开发者网站[developers.facebook.com/apps](https://developers.facebook.com/apps).获取脸书应用 id 和应用密码。

在模拟器上将 FacebookPluginExample 作为 Android 应用运行。第一个屏幕将显示脸书登录页面,如图 8–7 所示。

图 8–7。 脸书登录界面

成功登录后,您将看到您的好友列表,如 Figure 8–8 所示。

图 8–8。脸书好友列表

您可以使用 jQueryMobile 或 Sencha Touch 以及脸书 PhoneGap 插件来开发一个有吸引力的脸书应用。此外,您可以通过使用脸书插件调用其他脸书图形 API 来添加更多功能。

C2DM 插件,用于向 PhoneGap 发送移动推送通知

推送通知或服务器推送是从服务器向客户端发送数据的最新方式。您是否注意到 Gmail 是如何接收和显示您收件箱中的新邮件的?你不需要刷新浏览器或者点击一些刷新按钮来发送请求和从服务器接收最新的数据。

最近,轮询是接收通知的一种流行技术。轮询技术定期向服务器发送请求,并用收到的响应刷新 UI。您可以将它视为一个后台进程,它以特定的预定义间隔发送请求,并从服务器接收更新或通知。这种方法有许多已知的缺点。轮询方法的主要缺点是识别合适的间隔来发送请求。如果间隔更短,可能会有不必要的请求和响应行程,从而导致带宽和服务器资源的损失。更长的时间间隔可能达不到轮询的目的,因为在接收通知时可能会有延迟,并且它不再符合发送通知的目的。如果没有新数据可用,这种方法会消耗移动电话的电池。

服务器推送允许服务器向客户端发送通知或更新,而无需等待请求。在推送技术中,客户端没有任何后台进程来发出定期请求。在服务器有更新的任何时候,它都可以将更新推送到所有注册的客户端。如果客户端是一个移动应用,这种技术被称为移动推送。

您的 PhoneGap 应用也可以通过 PhoneGap 插件利用移动推送技术。让我们为 Android 平台创建一个小的 PhoneGap 应用来接收来自 C2DM 服务的推送通知。

为 Android 设置环境

首先,我们需要为 Android 设置 PhoneGap 项目。参考第二章为 Android 设置您的项目。一个 Android 项目配置如图图 8–9 所示。

图 8–9。 Eclipse Android 项目配置

我们将使用 Android 云到设备消息(C2DM)框架进行推送通知。你可以在[code.google.com/android/c2dm/#intro](http://code.google.com/android/c2dm/#intro)了解更多关于 C2DM 服务的信息。

从[github.com/awysocki/C2DM-PhoneGap/downloads](http://github.com/awysocki/C2DM-PhoneGap/downloads)下载 C2DM PhoneGap 插件。C2DM-PhoneGap 插件是一个 zip 文件。解压到你喜欢的文件夹里。文件夹结构应该类似于图 8–10 中的列表。

图 8-10。C2DM-插件文件夹结构

接下来,您需要执行以下安装步骤:

Register C2DMPlug-in

在 plugins.xml 文件中添加以下 XML 元素作为“plugins”元素的子元素,如 Figure 8–11 所示。您可能需要在“res”文件夹下创建一个 xml 文件夹,并从 PhoneGap Android 示例应用中复制 plugins.xml。

图 8–11。 插件注册

Include the native part of the plug-in into the project

从 C2DM-plug-in 文件夹中复制 src 文件夹,如图 8–12 所示,并将其粘贴到我们的 PhoneGap 应用的根目录中,即“MobilePushPluginExample”。

图 8–12。C2DM-插件原生部分

Include the JavaScript part of the plug-in into the project

如 Figure 8–13 所示,从 C2DM-plug-in 文件夹中复制以下文件,并将其粘贴到我们应用的 assets 文件夹中。

C2DMPlugin.js

jquery_1.5.2 最小 js

PG_C2DM_script.js

index.html

注意,即使 index.html 不是插件的一部分,我们也在项目中使用它来节省创建和包含 js 文件的时间。

图 8–13。 JavaScript 部分插件

注意,我们没有包括 Phonegap.0.9.5.js 文件,因为我们使用的是 PhoneGap-1.1.0。我们将对 PhoneGap-1.1.0 进行必要的修改。

最后,我们需要在 AndroidManifest.xml 文件中添加 C2DM 所需的权限。清单文件应该类似于下面的列表:

`

`

完成前三个步骤后,您将看到 MobilePushPluginExample 项目结构,如图 Figure 8–14 所示。

图 8–14。 MobilePushPluginExample 项目结构

修改 PhoneGap 插件-1.1.0

在写这本书的时候,一个 C2DM 插件是基于 PhoneGap 版本 0.9.5 的。要在 PhoneGap-1.1.0 中使用它,我们必须完成一些修改。

C2DMPlugin.jsfiles.

打开 C2DMPlugin.js 并转到以下函数定义:

`PhoneGap.addConstructor(function() {

//Register the javascript plugin with PhoneGap

PhoneGap.addPlugin('C2DM', new C2DM());

//Register the native class of plugin with PhoneGap

PluginManager.addService("C2DMPlugin",

"com.plugin.C2DM.C2DMPlugin");

//alert( "added Service C2DMPlugin");

});`

删除以下行:

//Register the native class of plugin with PhoneGap PluginManager.addService("C2DMPlugin","com.plugin.C2DM.C2DMPlugin");

在 PhoneGap-1.1.0 中,插件必须在 plugin.xml 文件中注册。PhoneGap-1.1.0 中不再提供 PluginManager。我们已经在 plugin.xml 文件中注册了 C2DM-plugin。

现在,修改后的函数如下所示:

`PhoneGap.addConstructor(function() {

//Register the javascript plugin with PhoneGap

PhoneGap.addPlugin('C2DM', new C2DM());

//alert( "added Service C2DMPlugin");

});`

Move C2DMReceiver.java

我们必须将 C2DMReceiver.java 从 com.phonegap.c2dm 包中移到我们的应用项目中,即 org . examples . mobile push plugin . example,为此,将 C2DMReceiver.java 从 com.phonegap.c2dm 包中拖放到 org . examples . mobile push plugin . example 中,如 Figure 8–15 所示。

图 8-15。C2DMReceiver.java所在地

我们还必须修改 index.html 文件中的 PhoneGap 版本,因为我们是从插件文件夹中复制的。为此,在脚本标记中用 phonegap-1.1.0 替换 phonegap.0.9.5.js。

注册 C2DM 服务

前往[code.google.com/android/c2dm/signup](http://code.google.com/android/c2dm/signup)并填写表格以注册发送者。你必须提到 Android 应用的包名和发件人帐户电子邮件以及其他信息。对于我们的应用,包名应该是“org . examples . mobilepushplugin . example ”,我们应该使用 Google 帐户作为发件人帐户电子邮件。我们必须在 PhoneGap 应用中使用发件人帐户电子邮件 id 来注册接收通知的设备。

在 PhoneGap 中使用 C2DM 发送者帐户

C2DM 插件附带了现成的 PhoneGap deviceready 实现。我们必须使用我们的 C2DM 发送者帐户来注册通知设备。

打开 PG_C2DM_script.js 文件,转到 PhoneGap deviceready 事件实现。将“your_c2dm_account@gmail.com”修改为我们的 c2dm 发件人帐户,如图 8–16 所示。

图 8–16。 向 C2DM 发送方帐户注册应用

支持 C2DM 服务的 Android 模拟器

您需要使用目标为“Google API(Google Inc .)-API Level 8”的 AVD (Android 虚拟设备)来运行支持 C2DM 的 Android 应用。请参考第二章从“Android SDK 和 AVD 管理器”创建新的 AVD。

你还必须在模拟器中添加你的谷歌账户。为此,运行模拟器并打开设置,如图 8–17 所示。

图 8–17。 安卓设置选项

转到“帐户和同步”并点击添加帐户。你必须输入你的谷歌帐号和密码。注意,它不是 C2DM 发送者帐户。这是你的谷歌账户,你用它来从你的安卓手机中检索你的电子邮件和其他东西。

现在您已经准备好测试 C2DM 插件了。将 MobilePushPluginExample 作为 Android 应用运行。确保目标模拟器是 Google API 的模拟器。您将在模拟器上看到图 8–18 所示的屏幕。

图 8–18。??【模拟器上的 mobilepushpluginsampleoutput】??

为了理解屏幕上出现的输出,我们将查看 PG_C2DM_script.js 文件中的 devicereadyevent 回调函数。

这里,window.plugins.C2DM.register()调用插件的方法将设备或模拟器注册到 C2DM 服务中。注册成功后,C2DM 服务器返回注册 Id (REGID)。该 REGID 用于推送通知消息。但是等等,我们的设备不应该推送信息。它是一个通知接收器,对吗?在这里,我们必须理解在手机上运行的 PhoneGap 应用和在 Google 上托管的 C2DM 服务之间的应用服务器的角色。

我们用一个例子来了解一下 C2DM 推送的工作原理。假设 MobilePushPluginExample 安装在多部 Android 手机上。现在,每部手机都从 C2DM 服务接收 REGID。本质上,每个 REGID 都是唯一的。在发送 REGID 之前,C2DM 服务存储关于设备和网络的所有必需信息,以便在发送通知时进一步使用。C2DM 向设备发送通知。现在,中间的应用服务器负责识别更新的数据,并要求 C2DM 向实际的移动设备发送通知。为此,我们的服务器必须知道 REGIDs。

通常,支持 C2DM 的移动应用会将 REGID 发送到我们的服务器。服务器存储运行该应用的所有手机的注册 id。一旦服务器决定发送通知,它就使用 REGID 来请求 C2DM 服务这样做。

我们从 C2DM 服务收到了 REGID,这可以在图 8–18 中看到。现在,您可以使用这个 REGID 发送推送通知。您可以使用 Java servlet 或 php 创建服务器端代码来发送消息。要了解有关如何发送推送通知的更多信息,请访问 Android C2DM 网站:[code.google.com/android/c2dm/index.html#push](http://code.google.com/android/c2dm/index.html#push)。

此外,还有一个命令行工具可用于模拟服务器。转到[curl.haxx.se/download.html](http://curl.haxx.se/download.html)并下载特定于平台的 curl 工具。发送通知有两个步骤:

Get the authentication key

在控制台上运行以下命令:

D:\cURL>curl https://www.google.com/accounts/ClientLogin -d Email=-d "Passwd=" -d accountType=GOOGLE -d source=org.examples. mobilepushplugin.example -d service=ac2dm –k

你必须用你注册的谷歌账号和密码替换和。

运行以上命令后,您将获得类似于以下清单的身份验证密钥:

Auth=DQAAAMEAAABrqkqH2KYjDfCD93tndEF7n81lKgf5vczCwELPSXgW6xm_9EACDu0lsJFGud7fNBI HcRV1Q6zUmLwxFFJqosdn1nYYmGah0yu7fpT8vfjNLAVx8hs5aymz9OULg-pzKOyWWa1-6BDci1TBCoP 2q6ZwJqEjzH6rArHSlD9DhruEKBrogjfBAWyeIm2fs9THvEkilSMO2Q8utoqyfG0id9keCQad5QPV7oO vNSe6urKOV4ZWEKxG7KAlXCsjW18u_m2Az6jj7DlUoVD89MeLvX0W

Send the message to your application running on a simulator

要发送通知,我们将使用身份验证密钥,并通过以下 curl 命令注册:

S:\cURL>curl --header "Authorization: GoogleLoginauth=" "https://android.apis.google.com/c2dm/send" -d registration_id=-d “data.message=This is a test message" -d "data.msgcnt=1" -d collapse_key=0 –k

将替换为从步骤 1 接收的认证密钥,并由模拟器上的应用接收 REGID。我们正在向模拟器发送“这是测试消息”文本作为推送通知。

我们将在模拟器上看到我们的应用收到的通知,如图 8–19 所示。

图 8–19。 模拟器上的推送消息

如果您想利用 iPhone-PhoneGap 应用的推送通知服务,您可以使用 PhoneGap 插件,该插件位于:[github.com/urbanairship/ios-phonegap-plugin](https://github.com/urbanairship/ios-phonegap-plugin)。它使用城市飞艇服务来推送移动通知。

结论

PhoneGap 插件动态地扩展 PhoneGap 应用,使其包含越界特性。通过使用插件,PhoneGap 应用几乎可以使用任何本机功能。

插件是 PhoneGap 的好朋友,但社区对插件的支持还处于早期阶段。与此同时,PhoneGap 背后的组织正在使流行的插件成为官方的。然而,插件支持还远远不够完善。这方面的一个例子是尝试为 iPhone 编写一个脸书连接应用。当我们试图在 PhoneGap 1.1.0 中使用这个插件时,我们发现它不起作用。我们还发现包含这个插件非常麻烦。我们猜测,对于即将发布的 PhoneGap 来说,对插件的支持将会得到改善,插件将更容易捆绑并在 PhoneGap 应用中使用。

在本章中,我们只讨论了用于脸书连接和云推送的 Android PhoneGap 插件。出于同样的原因,我们添加了关于 iPhone 插件的指针,这些插件需要改进,才能方便有效地使用。

九、扩展 PhoneGap

到目前为止,我们已经看到 PhoneGap 有两个部分

我们从 PhoneGap 应用中调用的 JavaScript 部分

我们在 PhoneGap 项目中包含的一个本机部分,用于公开本机电话功能。

这两个部分适用于我们希望访问常见电话功能的情况,包括以下内容:

照相机

加速计

文件系统

地理位置

存储服务

然而,我们经常需要超越这些特性。

JavaScript 限制

我们已经看到 JavaScript 在过去十年中性能有所提高;它比五年前快了 100 倍。然而,即使这是真的,有时应用需要做大量的照明,在后台做事情,或者做复杂的操作。出于性能原因,这些最好在本机代码中完成。

例如,如果我们想下载一个多部分文件,它涉及到并行下载文件的不同部分,然后检查其校验和。这一部分最好用 Java 为 Android 编写,用 Objective-C 为 iPhone 编写。

如果你还记得第一章的话,我们说过 PhoneGap 是 JavaScript 世界和本地世界之间的桥梁。整个 PhoneGap 框架是基于插件架构的。这意味着 PhoneGap 提供了一种将 JavaScript 函数(以及参数、返回类型和回调)映射到本机代码的机制。

我们可以向 PhoneGap 应用添加本机代码,并使用 JavaScript 轻松公开代码。为此,我们需要两部分

完成繁重工作的本机代码

公开此本机代码的 JavaScript 代码

两者都被 PhoneGap 框架粘合在一起。

建筑

PhoneGap 架构如图 9–1 所示。正如我们所观察到的,PhoneGap 有两个部分:PhoneGap JavaScript 引擎和 PhoneGap 原生引擎。我们将本机代码作为插件添加到 PhoneGap 本机引擎,并将 JavaScript 代码作为插件添加到 PhoneGap JavaScript 引擎。

图 9–1。 PhoneGap 架构

范围

本章重点介绍如何扩展 PhoneGap 功能,以暴露更多的本机代码。

但是,请注意,即使您编写了 PhoneGap 插件,注入插件的唯一方法是将插件源代码添加到您的项目中。目前还没有办法将插件构建到包中,并将包添加到 PhoneGap 项目中。当您使用 PhoneGap build 时,这会阻止您使用自定义插件。

对于这一章,让我们保持插件的本质非常简单。我们称之为 helloworld 插件。我们向插件传递一个名称,然后我们得到一个字符串“Hello !现在的时间是”。

这样,我们主要关注插件的桥方面。

为 Android 扩展 PhoneGap

首先,我们将插件创建为 Android PhoneGap 应用的一部分,然后将插件提取出来。这是必需的,因为

该插件需要 PhoneGap jar。

我们需要测试插件。

一个插件有两个部分,PhoneGap 框架(桥)的两边各有一个。我们有一个原生部分(一个类扩展插件)和一个使用 PhoneGap 的 JavaScript 框架的 JavaScript 文件。

在我们开始之前,让我们创建一个 Android PhoneGap 项目(参见图 9-2)。第二章中有说明。

图 9–2。 新安卓项目

然后我们需要为 PhoneGap 配置基本的 Android 项目。

更改 MainScreen 类以扩展 DroidGap。

将 PhoneGap jar 添加到类路径中。

将 PhoneGap JavaScript 库添加到 assets/www 文件夹。

Android 项目看起来如图 9–3 所示。

图 9–3。 安卓项目结构

声明插件的本机部分

现在我们为插件添加一个合适的包,比如“org.examples.phonegap..plugins.simpleplugin”然后我们声明一个名为 Simple Plug-in 的类,它扩展了 PhoneGap 的 com.phonegap.api.Plugin 类,如图 Figure 9–4 所示。

图 9–4。 声明插件的原生部分

一旦你点击“完成”按钮,你将得到如下所示的代码

`package org.examples.phonegap.plugins.simpleplugin;

import org.json.JSONArray;

import com.phonegap.api.Plugin;

import com.phonegap.api.PluginResult;

/**

* @author rohit

*

*/

public class SimplePlugin extends Plugin {

/* (non-Javadoc) * @see com.phonegap.api.Plugin#execute(java.lang.String, org.json.JSONArray,

java.lang.String)

*/

@Override

public PluginResult execute(String action, JSONArray data, String callbackId) {

// TODO Auto-generated method stub

return null;

}

}`

当我们扩展com.phonegap.api.Plugin类时,我们必须实现 execute 方法。execute 方法的参数是

动作:要执行的动作。例如,对于基于文件的插件,可以是打开、关闭、读取、写入等。

数据:插件 JavaScript 端传过来的数据。这是从 PhoneGap 的 JavaScript 应用传递到本机代码的数据。例如,对于基于文件的插件,可以是文件名、数据等。

CallbackId :回调 JavaScript 函数时使用。

execute 方法的返回类型是 PluginResult。PluginResult 通常接受一个状态枚举和一个描述原因或更多信息的其他参数。

例如,新的 PluginResult(状态。OK);

状态枚举有许多值;所有这些都描述如下(名称不言自明)

没有结果

类未找到异常

非法访问异常

实例化 _ 异常

格式错误 _URL_EXCEPTION

io _ exception(io _ 异常错误)

无效 _ 操作

JSON _ exception(JSON _ 异常)

错误

下面是 hello 插件的实现,它接受一个名称并返回“Hello !时间是文本。

`package org.examples.phonegap.plugins.simpleplugin;

import java.util.Date;

import org.json.JSONArray;

import org.json.JSONException;

import com.phonegap.api.Plugin;

import com.phonegap.api.PluginResult;

import com.phonegap.api.PluginResult.Status;

/**

* @author rohit

*

*/

public class SimplePlugin extends Plugin {

public static String ACTION_HELLO="hello";

/*

* (non-Javadoc)

*

* @see com.phonegap.api.Plugin#execute(java.lang.String,

* org.json.JSONArray, java.lang.String)

*/

@Override

public PluginResult execute(String action, JSONArray data, String callbackId) {

PluginResult pluginResult = null;

if (ACTION_HELLO.equals(action)) {

String name = null;

try {

name = data.getString(0);

String result = "Hello " + name + "! The time is "

+ (new Date()).toString();

pluginResult = new PluginResult(Status.OK, result);

return pluginResult;

} catch (JSONException e) {

pluginResult = new PluginResult(Status.JSON_EXCEPTION,

"missing argument name");

}

} else {

pluginResult = new PluginResult(Status.INVALID_

ACTION,

"Allowed actions is hello");

}

return pluginResult;

}

}`

您可以在上面的代码中看到,在处理请求之前,我们显式地检查了一个操作。如果动作不是由插件处理的,我们返回Status.INVALID_ACTION。第二个检查是针对参数的。如果我们在获取第一个字符串参数时得到任何 JSON 异常,我们返回Status.INVALID_JSON。当动作和参数正确时,我们创建一个字符串“Hello < name >!时间是<时间>,用Status.OK返回。

请注意,您不必从这个方法中产生任何线程。您的整个方法可以是同步的。这将不会传递调用该代码的 JavaScript 插件调用。这是由 PhoneGap 内部处理的,这就是为什么我们在 JavaScript 中有成功和失败回调(您将在下一节看到)。

声明插件的 JavaScript 部分

这个插件的 JavaScript 部分是在一个名为 simpleplugin.js 的文件中声明的。

Plug-in Registration

在 PhoneGap 插件的 JavaScript 部分,事情是从调用在 PhoneGap 中添加插件开始的。

PhoneGap.addConstructor(**function**() { // Register the Javascript plug-in with PhoneGap PhoneGap.addPlugin('SimplePlugin', **new** SimplePlugin()); });

插件在/res/xml/plugins.xml 文件中注册。将以下 XML 元素添加为 plugins.xml 文件中“plugins”元素的子元素:

注意:这里我们做了两件事

将 JavaScript 对象注册为名为“SimplePlugin”的插件

将 PhoneGap Java 类注册为名为“SimplePlugin”的服务您可以将此视为类名“org.examples.phonegap.plugins.simpleplugin.Simple Plugin.”的别名

Create the JavaScript object SimplePlugin.

这是通过声明一个 JavaScript 函数来完成的。

var SimplePlugin = function() { }

Add a plug-in function.

在这一步中,我们将添加插件函数,我们的 JavaScript 将调用该函数。在下面的函数中,我们实际上将调用委托给本机 PhoneGap 桥,要求它实际调用“SimplePlugin”服务,即“org.examples.phonegap.plugins.simpleplugin.SimplePlugin”类。此外,我们注册了两个回调:一个是调用成功时的回调,另一个是调用失败时的回调。然后我们声明我们想要调用的动作。您可能还记得,我们的插件类中有处理“hello”服务的代码。最后,记住我们插件类的执行方法需要一个参数JSONArray;在这里,我们将其作为[name]传递。

`SimplePlugin.prototype.hello = function(name, successCallback, failureCallback) {

PhoneGap.exec(

successCallback, // Success Callback

failureCallback, // Failure Callback

‘SimplePlugin’, // Registered plug-in name

‘hello’, // Action

[name] //Argument passed in

);

};`

完整的 JavaScript 文件 simpleplugin.js 如下所示:

`/**

*

* @return Instance of SimplePlugin

* /

var SimplePlugin = function() {

}

/**

* @param name

* The name passed in

* @paramsuccessCallback

* The callback that will be called when simple plugin runs

* successfully

* @paramfailureCallback

* The callback that will be called when simple plugin

* fails

*/

SimplePlugin.prototype.hello = function(name, successCallback, failureCallback) {

PhoneGap.exec(successCallback, // Success Callback

failureCallback, // Failure Callback

'SimplePlugin', // Registered Plug-in name

'hello', // Action

[ name ]); // Argument passed in

};

/**

*

* Register the Simple Listing Javascript plugin.

*/

PhoneGap.addConstructor(function() {

// Register the Javascript plug-in with PhoneGap

PhoneGap.addPlugin('SimplePlugin', new SimplePlugin());

});`

调用插件

是时候测试我们的插件了。为此,我们需要以下内容:

HTML 文件

PhoneGap js 文件

插件 js 文件

插件 Java 文件

您的 Android 项目应该如 Figure 9–5 所示。

图 9–5。 Android PhoneGap 插件项目结构

您的 index.html 文件应该如下所示:

`

PhoneGap

script type="text/javascript" charset="utf-8" src="simpleplugin.js">

Simple Plugin Demo

Enter Name

Output:

Say Hello

`

在这里,您应该注意到插件的调用如下。我们首先传递包含名称的文本,然后注册一个成功的回调和一个失败的回调。

window.plugins.SimplePlugin.hello( text, //success callback function (result) { output.innerHTML = result; }, //failure callback function (err) { output.innerHTML = "Failed to invoke simple plugin"; } );

最后,当我们运行这个 Android 项目时,我们会看到下面的输出,如 Figure 9–6 所示。

图 9–6。Android 上的 PhoneGap 插件输出

分享 Android PhoneGap 插件

就 PhoneGap framework 1 . 1 . 0 版(写这本书时的版本)而言,没有办法打包和共享你的插件。

共享插件的唯一方式是通过

共享 Java 源文件

共享 JavaScript 源文件

自述文件,说明如何使用插件

PhoneGap 插件通常在[github.com/phonegap/phonegap-plugins](https://github.com/phonegap/phonegap-plugins).上传。如果您希望贡献您的工作,您可以与 PhoneGap 团队合作,将您的插件添加到这个存储库中。

为 iPhone 扩展 PhoneGap

PhoneGap 为 XCode 提供插件,用于创建基于 PhoneGap 的应用。在写这本书的时候,PhoneGap 从 0.9.5 版本升级到了 1.1.0 版本。iPhone-PhoneGap 插件框架有一些变化。本章重点介绍 1.1.0 插件开发。

安装 1.1.0 XCode 扩展的步骤:

下载 PhoneGap 1.1.0 zip 并解压。

转到 iOS 文件夹,安装 PhoneGapInstaller.pkg。

安装 PhoneGap 的 1.1.0 XCode 插件后,从 XCode 创建一个基于 PhoneGap 的应用,如图图 9–7 和图 9–8 所示。

图 9–7。 创建新的 iOS PhoneGap 项目

图 9–8。 创建一个新的 iOS PhoneGap 项目

按照第三章中的步骤将 www 文件夹添加到项目中。现在运行项目,并确保您能够看到基于 iPhone PhoneGap 的应用。

声明插件的本机部分

PhoneGap 1.1.0 插件的本机部分需要添加到插件文件夹中。这在图 9–9 中进行了描述。

图 9–9。 iPhone 插件原生部分

在插件文件夹中创建一个 Objective-C 类。让我们把这个类命名为 SimplePlugin。SimplePlugin 扩展了 PGPlugin。SimplePlugin.h 文件如下所示。

`#import

ifdef PHONEGAP_FRAMEWORK

import

else

import "PGPlugin.h"

endif

@interface SimplePlugin :PGPlugin {

}

/**

* Sets the idleTimerDisable property to true so that the idle timeout is disabled

*/

(void) hello:(NSMutableArray)arguments withDict:(NSMutableDictionary)options;

@end`

这里我们声明一个函数名“hello”,它有如下签名:

- (void) hello:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;

这个函数不返回任何东西。相反,它需要两个参数

争论

选择

插件或输入(在我们的例子中是名称)的任何参数都使用“参数”传递

现在让我们实现 SimplePlugin 的 hello 函数。在第一个版本中,我们将从插件返回一个字符串“hello world”。此外,我们将解释如何访问传递的参数,以及如何调用成功和失败的回调。

注意插件是从 JavaScript 调用的,如下所示:

window.plugins.SimplePlugin.hello( “Bob”, //success callback function(result){ alert(“plugin returned “+result); }, //failure callback, function(err){ alert(“got error when invoking the plugin”); } );

下面是插件方法的框架代码。

插件可以访问输入参数,在我们的例子中是“Bob”,从 arguments 对象中提取它。注意,arguments 数组中的第一个对象总是 callbackId,用于回调 JavaScript 回调函数。我们可以从索引 1 开始提取实际参数(在我们的例子中,只有“Bob”)。

NSString * name = [arguments objectAtIndex:1];

如果我们有另一个参数,我们将在索引 2 处访问它。

现在让我们关注如何调用成功或失败的 JavaScript 回调函数。这从 PluginResult 对象的声明开始。接下来声明另外两个对象,一个用于 callbackId(帮助我们调用回调函数),另一个是一个字符串,JavaScript 字符串,我们将把它嵌入到 HTML 页面中以实际调用回调。

NSString* jsString = nil; NSString* callbackId = [arguments objectAtIndex:0];

现在让我们来看一下成功和失败条件的代码流。这显示在下面的代码中。

如果一切顺利,我们创建一个状态为 PGCommandStatus_OK 的结果对象。然后我们从结果中创建 jsString 对象,传递 callbackId。最后,我们编写 JavaScript,通过调用[self writeJavascript:jsString]来实际调用成功回调。

在失败的情况下,我们创建一个状态不是 PGCommandStatus_OK 的 PluginResult 对象,并为错误/失败回调创建 jsString。最后,我们使用[self:write JavaScript:jsString]调用错误/失败回调:

`PluginResult* result=nil;

NSString* jsString=nil;

NSString* callbackId=[argumentsobjectAtIndex:0];

if(success){

result=[PluginResultresultWithStatus:PGCommandStatus_OK];

jsString=[resulttoSuccessCallbackString:callbackId];

}

else{

result=[PluginResultresultWithStatus:PGCommandStatus_ILLEGAL_ACCESS_EXCEPTION];

jsString=[resulttoErrorCallbackString:callbackId];

}

[selfwriteJavascript:jsString];`

如果我们希望在调用成功或失败回调时传递数据,我们可以通过在创建 PluginResult 对象时传递一个附加参数来实现。这里我们通过调用 PluginResult 的 resultWithStatus:messageAsString 函数来传递一个字符串。

result = [PluginResultresultWithStatus:PGCommandStatus_OK messageAsString:@”Hello World”];

完整的 SimplePlugin 如下所示。注意,这里我们没有负路径,因此,我们只为成功回调创建 jsString。

`#import "SimplePlugin.h"

@implementation SimplePlugin

(void) hello:(NSMutableArray)arguments withDict:(NSMutableDictionary)options {

PluginResult* result = nil;

NSString* jsString = nil;

NSString* callbackId = [arguments objectAtIndex:0];

NSString* name = [arguments objectAtIndex:1];

NSDate* date = [NSDate date];

NSDateFormatter* formatter = [[[NSDateFormatteralloc] init] autorelease];

//Set the required date format

[formatter setDateFormat:@"yyyy-MM-ddhh:mm:ss"];

//Get the string date

NSString* dateStr = [formatterstringFromDate:date];

NSString* returnStr = [NSStringstringWithFormat:@"Hello %@.The time is %@!",

name,dateStr];

result = [PluginResultresultWithStatus:PGCommandStatus_OK

messageAsString:returnStr];

jsString = [result toSuccessCallbackString:callbackId ];

[selfwriteJavascript:jsString];

}

@end`

只是创造了。h 和。m 文件并将文件放在插件文件夹中是不够的。我们需要向 PhoneGap 框架注册我们的 SimplePlugin。在 Supporting Files 文件夹中的 PhoneGap.plist 文件中添加一个条目就可以做到这一点。

这显示在图 9–10 中。

图 9-10。 注册 PhoneGap 插件

声明插件的 JavaScript 部分

iPhone 插件的 JavaScript 部分和你见过的 Android 不同。

这主要分两步完成

声明一个名为 SimplePlugin 的 JavaScript 类,并向其添加一个方法,在我们的示例中为“hello”。在 hello 函数中,我们将 JavaScript 参数映射到目标插件类和方法。

第二部分是为 SimplePlugin 创建一个方法 install,通过调用 phonegap . add constructor(simple plugin . install)注册 JavaScript 插件;

我们先来关注一下插件的 hello 功能。注意,我们在插件内部调用 PhoneGap.exec 函数。

继 PhoneGap.exec 的签名之后

PhoneGap.exec(<>,<>,<>,<>,<>)

注意我们如何将 hello 函数的第一个参数“name”作为参数数组的一部分传递。successCallback 和 errorCallback 作为 PhoneGap.exec 函数的第一个和第二个参数。插件类和方法名作为第三和第四个参数。

SimplePlugin.prototype.hello = function(name,successCallback, errorCallback) { PhoneGap.exec( successCallback, errorCallback, "SimplePlugin", "hello", [name]); };

JavaScript 部分的完整代码如下所示。

`if (!PhoneGap.hasResource("simpleplugin")) {

PhoneGap.addResource("simpleplugin");

/**

* @returns instance of powermanagement

*/

function SimplePlugin() {};

/**

*

* @param name Given the name, successCallBack gets the string "Hello ! The

time is ."

* @paramsuccessCallback function to be called when the wake-lock was acquired

successfully

* @paramerrorCallback function to be called when there was a problem with acquiring

the wake-lock

*/

SimplePlugin.prototype.hello = function (name, successCallback, errorCallback) {

PhoneGap.exec(successCallback, errorCallback, "SimplePlugin", "hello", [name]);

};

/**

* Register the plug-in with PhoneGap

*/

SimplePlugin.install = function () {

if (!window.plugins) window.plugins = {};

window.plugins.SimplePlugin = new SimplePlugin();

return window.plugins.SimplePlugin;

};

PhoneGap.addConstructor(SimplePlugin.install);

}`

调用插件

为了测试插件,我们将创建一个 PhoneGap 应用,并从那里调用插件。这部分和安卓的一模一样。

你需要遵循这些步骤

包括 PhoneGap 1.1.0 js 文件。

包括 simpleplugin.js 文件。

注册按钮点击调用插件。

注册成功和失败回调以显示结果。

index.html 的完整源代码如下:

`

PhoneGap

Simple Plugin Demo

Enter Name

Output:

Say Hello

`

当您运行 PhoneGap 示例时,您将看到如图 Figure 9–11 所示的应用。

图 9–11。??【PhoneGap】插件输出

共享 iPhone PhoneGap 插件

您需要共享以下文件来共享插件。

SimplePlugin.h

简单插头.m

简单插头.js

将上述文件列表添加到插件文档中。还要记录如何从 JavaScript 调用插件。

为 BlackBerry 扩展 PhoneGap

与 Android 类似,PhoneGap 针对 BlackBerry 的插件有两个部分,分别位于 PhoneGap 框架(桥)的两侧。我们有一个原生部分(一个类扩展 PhoneGap 的插件)和一个使用 PhoneGap 的 JavaScript 框架的 JavaScript 文件。

我们假设您使用的是高于 1.5 版的 BlackBerry WebWorks SDK 版本。

我们假设 BlackBerry WebWorks SDK 安装在 C:\BBWP 上,我们安装了 Java 1.6 SDK 和 Ant,并且在 path 中。我们还假设我们的开发目录是 D:\PhoneGap-Plugin,我们在 D:\ PhoneGap-Plugin \ PhoneGap-1 . 1 . 0 目录中有 PhoneGap SDK。参考第三章回忆 BlackBerry PhoneGap 开发的系统要求。

创建和测试 BlackBerry 插件的步骤如下:

创建插件 Java 文件,并将其转储到 PhoneGap SDK 的框架中。

创建 BlackBerry PhoneGap 项目来测试插件。

然后编译 BlackBerry PhoneGap 项目来编译插件 Java,您将它转储到 PhoneGap SDK 的框架文件夹中。如果出现编译错误,您需要删除您在步骤 2 中创建的项目,修复 Java 文件,并从步骤 2 开始重复。

当插件 Java 文件编译无误时,您就可以转储 JavaScript 插件文件并编写 HTML 页面来使用该插件。

声明插件的本机部分

BlackBerry 插件类与 Android 插件类非常相似。唯一不同的是 BlackBerry 插件类使用 PhoneGap 1.1.0 框架;因此,有几个不同之处。

以下是 BlackBerry 插件类的框架:

`package com.phonegap.plugins;

import com.phonegap.api.Plugin;

import com.phonegap.api.PluginResult;

import java.util.Date;

import com.phonegap.json4j.JSONArray;

public class HelloWorldPlugin extends Plugin {

private static final String ACTION_HELLO="hello";

/**

* Executes the requested action and returns a PluginResult. *

* @param action The action to execute.

* @paramcallbackIdThe callback ID to be invoked upon action completion.

* @paramargsJSONArry of arguments for the action.

* @return A PluginResult object with a status and message.

*/

public PluginResult execute(String action, JSONArray data, String callbackId) {

return null;

}

/**

* Called when the plug-in is paused.

*/

public void onPause() {

}

/**

* Called when the plug-in is resumed.

*/

public void onResume() {

}

/**

* Called when the plug-in is destroyed.

*/

public void onDestroy() {

}

}`

注意,我们不是将这个插件类转储到我们的项目区域,而是转储到 PhoneGap SDK 区域。Figure 9–12 中的屏幕截图显示了我们复制这个插件类的位置。您可能需要创建插件文件夹。

图 9–12。?? 插件的原生部分

这里的主要问题是,在继续之前,您需要在上面的目录中有一个适当的 Java 插件类(它可以编译)。我们会引导你度过这一关。

下一步是创建 BlackBerry WebWorks PhoneGap 项目。

$>D: $>cd d:\PhoneGap-plugin\phonegap-1.1.0 $>ant create -Dproject.path=D:\PhoneGap-Plugin\BB-Plugin-Test

这将向您显示图 9–13 中所示的目录。

图 9–13。 PhoneGap 黑莓项目结构

现在让我们确保我们的插件类是否编译。

$>cd D:\PhoneGap-Plugin\BB-Plugin-Test $>ant build

如果上述步骤在 HelloWorldPlugin 中显示了一些编译错误,您需要

修复那些编译错误。

删除位于 D:\ PhoneGap-Plugin \ b B- Plugin-Test 的项目。

使用 Ant create-D project . path = D:\ PhoneGap-Plugin \ b B- Plugin-Test 重新创建项目。

使用“ant build”检查编译。

现在,您已经解决了编译空白 Java 插件类的问题,让我们在其中放一些代码。

下面是插件类的完整代码(这与 Android 插件非常相似)。我们公开一个名为“hello”的动作,并期望一个名为“name”的参数假设有人用“Rohit”这个名字叫出了“hello”这个动作,我们会返回“Hello Rohit!时间是。”

`package com.phonegap.plugins;

import com.phonegap.api.Plugin;

import com.phonegap.api.PluginResult;

import java.util.Date;

import com.phonegap.json4j.JSONArray;

public class HelloWorldPlugin extends Plugin {

private static final String ACTION_HELLO="hello";

/**

* Executes the requested action and returns a PluginResult.

*

* @param action The action to execute.

* @paramcallbackIdThe callback ID to be invoked upon action completion.

* @paramargsJSONArry of arguments for the action.

* @return A PluginResult object with a status and message.

*/

public PluginResult execute(String action, JSONArray data, String callbackId) {

PluginResult pluginResult=null;

if (ACTION_HELLO.equals(action)) {

String name;

try {

name = data.getString(0);

String result = "Hello " + name

+ "! The time is "

+ (new Date()).toString();

pluginResult =

new PluginResult(PluginResult.Status.OK,

result);

returnpluginResult;

} catch (Exception e) {

pluginResult = new

PluginResult(PluginResult.Status.JSONEXCEPTION,

"missing argument name");

}

} else {

pluginResult =

new PluginResult(PluginResult.Status.INVALIDACTION,

"Allowed actions is hello");

}

return pluginResult;

}

/**

* Called when the plug-in is paused.

*/

public void onPause() {

}

/**

* Called when the plug-in is resumed.

*/

public void onResume() {

}

/**

* Called when the plug-in is destroyed.

*/

public void onDestroy() {

}

}`

请注意,您必须在 PhoneGap 的框架中再次转储修改后的 HelloWorldPlugin.java,如图 9–13 所示。您还必须使用 Ant create-D project . path = D:\ PhoneGap-Plugin \ b B- Plugin-Test 删除并重新创建项目,以测试插件。

声明插件的 JavaScript 部分

同样,插件的 JavaScript 部分与插件的 Android JavaScript 部分非常相似。在这种情况下,我们在函数声明中声明所有内容,并调用它。

(function () { var HelloWorld = function () { return { hello: function (message, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, 'HelloWorldPlugin', 'hello', [message]); } } }; ` PhoneGap.addConstructor(function () {

// add the plug-in to window.plugins

PhoneGap.addPlugin('simpleplugin', new HelloWorld());

// register the plug-in on the native side

phonegap.PluginManager.addPlugin('HelloWorldPlugin', 'com.phonegap.plugins.HelloWorldPlugin');

});

})();`

第一步是创建一个名为 HelloWorld 的 JavaScript 对象,并在其中声明一个名为“hello”的函数。该函数在内部调用 PhoneGap 注册的服务,该服务又调用实际的本地类。

现在我们有了这个对象,它将从我们的 HTML 中被调用,我们需要将这个对象注册为 PhoneGap JavaScript 插件。我们还需要将服务名“helloworldplugin”映射到类“com . phonegap . plugins . hello world plugin”,所有这些都是在 PhoneGap.addConstructor()调用中完成的。

我们使用 PhoneGap.addPlugin()将“simpleplugin”名称映射到 JavaScript 插件对象。这将插件公开为 windows.plugins.simpleplugin。

然后我们用 phonegap。PluginManager.addPlugin()将服务名映射到实际的 Java 类。

这就完成了我们创建插件的 JavaScript 部分。我们将把这个 JavaScript 放在项目的 www 目录中。

调用插件

为了调用插件,我们修改了项目 www 目录中的 index.html 文件。

这与我们之前为 Android 和 iPhone 所做的非常相似。

下面是用于调用我们的插件的代码片段:

window.plugins.simpleplugin.hello( document.getElementById("name").value, //success callback function (message) { document.getElementById("output").innerHTML = message; }, //failure callback function () { log("Call to plugin failed"); } );

正如我们前面所做的,我们提供名称;在这种情况下,名称来自输入类型文本元素。然后我们提供一个成功回调和一个失败回调。在成功回调中,我们在 id 为“output”的 div 中设置返回值。

以下是 index.html 页面的完整代码:

`

Simple Plugin Demo

Enter Name

Output:

Say Hello

...

`

最后一步是运行 WebWorks BlackBerry 项目。转到命令提示符,转到项目目录,并运行以下命令:

$>ant build load-simulator

这将打开黑莓模拟器,你可以看到我们的应用在里面运行。在文本框中输入一个值,然后点击按钮。您将看到如图图 9–14 所示的结果。

图 9–14。??【PhoneGap】插件输出

共享 BlackBerry PhoneGap 插件

要共享插件,您需要发布两个文件

helloworldplugin.java

helloworld.js

将上述内容添加到关于如何从 JavaScript 调用插件的文档中。

结论

虽然 JavaScript 对于开发跨移动应用来说是一种快速而灵活的语言,但在实现复杂的处理和后台工作时,JavaScript 有某些固有的局限性。有时有必要使用本机代码来执行繁重的工作。

PhoneGap 的架构允许我们扩展其插件,为我们的 PhoneGap 应用引入本机代码。

',** ** '',** ** '

Business Details

',** ** '
',** ** '

Name

',** ** '
',** ** '

{name}

',** ** '
',** ** '

Address

',** ** '
',** ** '

{formatted_address}

',** ** '
',** ** '

Phone

',** ** '