PhoneGap-入门指南-三-

163 阅读42分钟

PhoneGap 入门指南(三)

原文:Beginning PhoneGap

协议:CC BY-NC-SA 4.0

五、使用 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 为您提供以下优势:

  1. 触摸优化的丰富小部件集,以及支持点击、双击、滑动、点击并按住、挤压并旋转、滑动和手势的触摸事件。
  2. 新时代网络标准 HTML5 和 CSS3。
  3. 与 PhoneGap 集成。
  4. 支持 iOS、Android 和黑莓,以及这些设备的原生主题
  5. 对 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 所示的结构。

images

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

集成 Sencha 和 PhoneGap

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

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

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

  1. 将 sencha-touch.js JavaScript 文件添加到 www/lib。
  2. 将 resources/css 文件夹添加到 www/lib
  3. 将所有应用代码放在一个名为 app/app.js 的文件中,这将是我们的主 JavaScript 文件。

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

images

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

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

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

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

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

初始化煎茶触摸

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

  1. Sencha Touch 样式表
  2. 谷歌地图 JavaScript
  3. 我们的应用 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 中,我们将声明以下内容:

  1. 布局:“卡片”-布局是卡片布局,这意味着它是一叠卡片,我们一次只展示一张卡片
  2. full screen:true–指定此面板将占用 100%的可用宽度和高度,并自动将其自身绘制到页面上
  3. items: [searchPanel,tabResultPanel,favourites,result detail panel]–要添加到此面板的子组件数组。由于我们使用的是“卡片”布局,它将一次显示一个子组件。我们的主面板中有四个子面板:searchPanel、tabResultPanel、favourites 和 resultDetailPanel。searchPanel 和 tabResultPanel 的声明方式与 mainPanel 相同。默认情况下,searchPanel 是可见的卡片,而其他面板隐藏在 searchPanel 后面
  4. docket items:[]–用于声明停靠的小部件,通常用于工具栏按钮。
  5. dockedItems 内部有一个由 JSON 表示声明的工具栏。这个工具栏有两个按钮和一个分隔它们的间隔。
    1. 对于主页按钮,我们使用图标:“主页”
    2. 对于最喜欢的按钮,我们使用图标:“星形”

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

`//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 显示了该面板的外观。

images

图 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 显示了搜索面板的显示方式。

images

图 5-4。 应用搜索面板

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

  1. 显示搜索结果的列表视图
  2. 显示搜索结果的地图视图

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

`var tabResultPanel = new Ext.TabPanel({ **    layout: 'fit',** **    tabBar: {** **        dock: 'bottom',** **        layout: {** **            pack: 'center'** **        }** **    },** **    items: [result, map],**

});`

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

images

图 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: ['',** **          '

',** **          '
<imgsrc="{icon}" />
',** **          '
', '

{name}

',** **          '

{vicinity}

',** **          '
',** **          '
',** **          '']** **    listeners: {** **        el: {** **            tap: detailClickHandler,** **            //function which** **            //will handle tap event** **        }** **    }** });`

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

images

图 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** **    }** **});** images

图 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": [] }

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

  1. 将上述 JSON 显示为表的一部分的模板
  2. 一个按钮,将允许用户添加这个地方作为收藏或删除它作为收藏。

因此,我们使用一个名为 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: ['<table>',** **          '<tr>',** **          '<td>',** **          '</td>',** **          '<td>',** **          '<h1 class="bold">Business Details</h1>',** **          '</td>',** **          '</tr>',** **          '<tr>',** **          '<td>',** **          '<h1 class="bold">Name</h1>',** **          '</td>',** **          '<td>',** **          '<h1>{name}</h1>',** **          '</td>',** **          '</tr>',** **          '<tr>',** **          '<td>',** **          '<h1 class="bold">Address</h1>',** **          '</td>',** **          '<td>',** **          '<h1>{formatted_address}</h1>',** **          '</td>',** **          '</tr>',** **          '<tr>',** **          '<td>',** **          '<h1 class="bold">Phone</h1>',** **          '</td>',** `**          '',** **          '

{formatted_phone_number}

',** **          '',** **          '',** **          '',** **          '',** **          '

Rating

',** **          '',** **          '',** **          '

{rating}

',** **          '',** **          '',** **          '',** **          '',** **          '

Home Page

',** **          '',** **          '',** **          'Home Page',** **          '',** **          '',** **          ''**

**    ]** });`

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

images

图 5-8。 地点详情面板

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

`var favorites = new Ext.Component({ **    title: 'Favotites',** **    iconMask: true,** **    iconCls: 'organize',**

**    cls: 'timeline',** **    scroll: 'vertical',** **    tpl: ['',** **          '

',** **          '
<imgsrc="{icon}" />
',** **          '
',** **          '

{name}

',** **          '

{vicinity}

',** **          '
',** **          '
',** **          ''],** **    listeners: {** **        el: {** **            tap: detailClickHandler,** **        }** **    }** });` images

图 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 中进行同样的操作。

步骤很简单。

  1. 从 PhoneGap 获取地理位置
  2. 在 getcurrentPosition 的成功调用中,通过调用 Ext.ajax.request(url,successCallback,failureCallback)来发起 Ajax 调用
  3. 在 Ext.ajax.request 的 successCallback 中,会得到 JSON 字符串。
    1. 将这个 JSON 字符串转换成 JSON 对象
    2. 通过调用 result.update(obj.results)填充结果面板;
    3. 在谷歌地图上填充市场

`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 = 'maps.googleapis.com/maps/api/pl…'** **                  + 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    ** **            //code.google.com/apis/maps/d…** **            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 服务器获取地点信息甚至更容易。你需要的东西甚至更少。

  1. 通过调用 Ext.ajax.request(url,successCallback,failureCallback)启动 Ajax 调用
  2. 在 Ext.ajax.request 的 successCallback 中,会得到 JSON 字符串。
    1. 将这个 JSON 字符串转换成 JSON 对象
    2. 通过调用 placedetailspanel . update(obj . result)填充结果面板;
    3. 此外,我们将通过调用 isFav()函数来检查这个地方是否是收藏夹
      1. 如果这个地方是一个收藏,我们将这个按钮重命名为“从收藏中删除”
      2. 否则,我们将该按钮重命名为“添加到收藏夹”

`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: 'maps.googleapis.com/maps/api/pl…'** **                            + 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);** **        }** **    }**`

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

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

  1. 向收藏表添加位置
  2. 从收藏夹表中移除一个位置
  3. 检查某个位置是否已经在收藏表中
  4. 从收藏夹表中获取所有条目。

为了在各种函数调用之间传递位置条目,我们在该应用中所做的是声明一个名为 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                                                          .x-tabbar{                 padding-top: 10px;!important;                border-bottom: 2px solid #306aa1 !important;             }             .place {                 padding: 10px 0 10px 68px;                 border-top: 1px solid #ccc;                 min-height: 68px;                 background-color: #fff;             }             .place h2 {                 font-weight:bold;             }             .place .icon {                 position: absolute;                 left: 10px;             }` `            .place .icon img{                 height:24px;                 width: 24px;             }             .bold{                 font-weight: bold;             }                         `
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: ['',                   '

',                   '
<imgsrc="{icon}" />
',                   '
',                   '

{name}

',

            '

{vicinity}

', '
', '
', ''

            ],             listeners: {                 el: {                     tap: detailClickHandler,                     delegate: '.place'

                }             }

        });

        var favorites = new Ext.Component({ title: 'Favotites',             iconMask: true,             iconCls: 'organize',

            cls: 'timeline',             scroll: 'vertical',             tpl: ['',                   '

',                   '
<imgsrc="{icon}" />
',                   '
',                   '

{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 = 'maps.googleapis.com/maps/api/pl…'                          + 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 //code.google.com/apis/maps/d…                     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: 'maps.googleapis.com/maps/api/pl…' + 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 所示的值。您需要选中“使用谷歌网络工具包”,取消选中“使用谷歌应用引擎”

images

图 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 文件。在该文件中,添加以下条目:

<inherits name='de.kurka.phonegap.PhoneGap' /> <set-property name="user.agent" value="safari" />

请注意,通过将 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 标记之后添加以下内容:

<script type="text/javascript"> document.addEventListener("deviceready", (function(){ PhoneGap.available = true;}), false); </script>

现在,打开 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 项目设置正确。

images

图 6–2。 在 Chrome 浏览器中运行 GWT 项目

下一步是实际利用 PhoneGap API 将 GWT 项目编译成一个 web 应用。

PhoneGap GWT 库的一个好处是,当作为 GWT web 应用启动时,它模仿 PhoneGap 库。该库根据以下指令提供替代功能:

  1. 如果在 Android 或 iPhone 上运行,请使用 PhoneGap JavaScript。
  2. 否则,使用内部模拟类并给出虚拟值。

首先使用延迟绑定创建 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 上。

images

图 6–3。 在 Chrome 浏览器中运行 GWT PhoneGap 项目

最后一步是将这个项目编译成 web 应用。右键单击项目,选择 Google 选项,然后单击名为“GWT 编译”的菜单选项您将看到如图图 6–4 所示的对话框。单击编译。

images

图 6–4。 GWT 编译屏幕

编译完成后,在 Eclipse 中刷新您的项目,您应该会看到如图 Figure 6–5 所示的目录结构。在 war 文件夹中,您应该会看到一个 phonegap_gwt_helloworld 文件夹,其中包含许多 HTML 和 JavaScript 文件,如下所示。

images

图 6–5。 GWT 战争目录结构 GWT 编译后

构建 PhoneGap Android 应用

构建 PhoneGap GWT 应用的最后一步是创建一个 Android PhoneGap 项目,如图 6–6 和图 6–7 所示,然后将 GWT 生成的 web 应用复制到 assets/www 文件夹中。

第一步是创建一个 Android 项目。

images

图 6–6。 Android 创建项目屏幕

images

图 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 所示的文件夹结构。

images

图 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”的文件夹中的文件。

images

图 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 版本,如下:

<script type="text/javascript" src=”phonegap.0.9.4.js”></script>

接下来,侦听 deviceready 事件,以确定 PhoneGap 库是否可以使用:

<script type="text/javascript">     document.addEventListener(         "deviceready",         (function() {             PhoneGap.available = true;         }),         false); </script>

在此将 PhoneGap.available 变量显式设置为 true。这是 Android 平台的必经步骤。

以下是 PhoneGap_gWT_Helloworld.html 的完整源代码:

`

                                                                                                  Gwt PhoneGap Demo                                                                                                               

         

`

运行此代码后,仿真器上的屏幕应显示为图 6–10 中的示例。

images

**图 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 应用时经历的最大痛苦是以下循环:

  1. 开发一个 eclipse/xcode 或无关的 IDE
  2. 编译二进制可执行文件并将其放在设备/仿真器上
  3. 在设备/模拟器上测试 PhoneGap 应用
  4. 调整代码并从第 1 步开始重复

显然,这一循环非常耗时且令人沮丧。如果你是一个有经验的 JavaScript 开发人员,这对你来说将是一场噩梦。

JavaScript 开发人员习惯于在以下现代浏览器上使用便捷的工具:

  1. 火狐浏览器
  2. 旅行队
  3. 微软公司出品的 web 浏览器

(注意,对于 iPhone 和 Android 开发,ie 浏览器没什么用。我们建议 iPhone 和 Android 开发使用 Firefox、Safari 或 Chrome。)

这些工具很少是开发人员的扩展。Firefox 有自己的 firebug,这是 javascript/html 调试工具系列的第一款,它不仅允许您调试页面元素(DOM 结构),还允许您调试脚本、样式表和网络。它还允许您动态地更改这些内容,并立即进行测试。Chrome 和 Safari 自带开发工具。Internet Explorer 也不例外,它有自己的扩展来做类似的事情。

显然,我们在开发 PhoneGap 时需要类似的东西。这里列出我们的两个要求。我们需要以下内容:

  1. 使用 PhoneGap 模拟器在浏览器世界中创建和测试 PhoneGap 之外的应用的能力
  2. 一旦我们将 PhoneGap 应用部署在某个仿真器或设备上,就可以对其进行调试

本章我们将讨论如何使用 PhoneGap 模拟器和远程调试工具。

Chrome PhoneGap 模拟器–使用 Ripple

Ripple 是一个多平台移动平台模拟器,来自一家名为 tinyHippos 的公司。最近,这家公司被动态研究公司(RIM)收购。Ripple 出现的主要原因是为了减少当今移动 web 开发人员面临的挑战,这些挑战是由移动操作系统世界的巨大碎片化造成的。

Ripple 是一个 Chrome 扩展,提供以下模拟:

  1. 语音间隙
  2. Webworks(来自黑莓)
  3. web works-平板电脑-操作系统(来自黑莓)
  4. 移动网络
  5. 陆军妇女队
  6. 歌剧
  7. 沃达丰

出于本书的目的,我们将只关注 Ripple 的 PhoneGap 仿真。

安装波纹

Ripple 唯一的先决条件是你需要 Chrome 浏览器。任何支持扩展的版本都可以,不用担心安装哪个版本的 Chrome。但是,我们建议使用最新版本。

打开 Chrome 并访问网站—[ripple.tinyhippos.com](http://ripple.tinyhippos.com)/

您将看到如图图 7–1 所示的页面;你所需要做的就是点击“获取波纹”按钮。

images

图 7–1。 涟漪首页

这将带您进入图 7–2 中显示的页面。现在点击“安装”

images

**图 7–2。**安装 Ripple 作为 Chrome 扩展

正如我们之前提到的,Ripple 是 Chrome 的扩展。点击“安装”进入 Chrome 网上商店。当你点击“添加到 Chrome”时,扩展/插件实际上是从 Chrome 网络商店下载的,并自动安装在你的 Chrome 浏览器中(参见 Figure 7–3)。

images

图 7–3。 从 Chrome 网上商店安装 Ripple

一旦插件安装完毕,你会看到一个类似于图 7–3 所示的屏幕。

images

图 7–4。 在 Chrome 上安装波纹

为了验证插件已经正确安装,在 Chrome 中打开一个类似[www.google.com](http://www.google.com)的站点,右键点击页面。如果插件已经正确安装,您应该会看到一个“模拟器”启用/禁用选项。这在图 7–5 中进行了描述。

images

图 7–5。 在 Chrome 上右击选项打开波纹模拟器

继续点击“模拟器”并选择“启用”如果你看到图 7–6 中的屏幕,你的 Ripple 插件工作正常。要退出该屏幕,只需右击并选择仿真器- >禁用。

images

图 7–6。 第一次发射涟漪

为 PhoneGap 有效地使用 Chrome

在我们继续在 Chrome 上使用 Ripple 之前。让我们来了解一下 PhoneGap 如何有效地使用 Chrome。PhoneGap 与移动网络应用有相似之处,但在许多方面也有所不同。让我们列出两个主要的区别。

  1. PhoneGap 应用是基于 HTML/JavaScript 的应用,但它们从不被托管。它们被捆绑为本地移动应用的一部分,显示在应用内部的嵌入式浏览器中。这类似于在 Chrome 上从磁盘测试 HTML/JavaScript 应用。Chrome 需要调整以支持从磁盘运行 html/javascript 应用。
  2. 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 应用的先决条件:

  1. 该应用需要是一个没有插件的纯欧米茄应用。
  2. 您需要从所有 HTML 文件中删除对 PhoneGap JavaScript 的任何引用。
针对 Ripple 调整您的应用

让我们举一个来自第二章的代码例子。

我们将致力于指南针应用的例子。从这个 URL-[beginingphonegap.googlecode.com/files/compass.png](http://beginingphonegap.googlecode.com/files/compass.png)获取指南针图像。该图像显示在图 7–7 中。

images

**图 7–7。**PhoneGap 应用中使用的指南针图像

现在,让我们修改 index.html 文件,如下所示。请注意,我们已经删除了对 phonegap.js 的任何引用。这是目前使用 Ripple 的先决条件。Ripple 正在与 PhoneGap 合作,以消除这种变化。希望在即将发布的版本中,我们会看到这个需求消失。

`

                          PhoneGap         

       function onDeviceReady() {            var button = document.getElementById("capture");            var compassOptions = {                frequency: 1000            };            navigator.compass.watchHeading(onSuccess, onError, compassOptions);        };

       function onSuccess(heading) {            var image = document.getElementById('compass');            var headingDiv = document.getElementById('compassHeading');            headingDiv.innerHTML = heading;            var reverseHeading = 360 - heading;            image.style.webkitTransform = "rotate(" + reverseHeading + "deg)";        }

       function onError(error) {            alert('code: ' + error.code + '\n' + 'message: ' + error.message + '\n');        }

        /** Called when browser load this page*/

       function init() {            document.addEventListener("deviceready", onDeviceReady, false);        }              

             

            Compass         

                                                                                              
                    Compass Heading                                      
                        ....                     
                
                    Degrees                 
                      

`

你的应用目录看起来会像图 7–8。它包含应用的所有 HTML、JavaScript 和 CSS 文件。

images

图 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)。

images

图 7–9。 允许访问文件的网址为波纹扩展名

在 Chrome 中加载应用

现在在 Chrome 中加载指南针应用。在右上角,您会看到波纹图标。点击该项目,为该应用启用 Ripple。这显示在图 7–10 中。

请注意,因为我们用上述标志启动了 Chrome,这就是 Chrome 能够从本地文件系统加载 HTML 文件的原因。此外,如果 PhoneGap 应用使用 Ajax 加载数据,它也可以在 Chrome 中工作,因为我们已经禁用了单一来源策略。

images

图 7–10。 在 Chrome 浏览器中加载指南针 app

启用纹波

第一次启用 Ripple 时,您会看到 Figure 7–11。我们需要选择 PhoneGap 选项。

images

图 7–11。 为指南针 app 启用纹波

玩波纹设置

现在我们已经启用了 Ripple,我们将看到网页已经改变。以前占据整个屏幕的应用现在看起来不同了。这是因为现在 Ripple 是主要应用。Ripple 在 iframe 中加载我们自己的应用,并注入自身来模拟 PhoneGap 环境。在主页上,Ripple 提供了许多控件来改变 PhoneGap 模拟的设备的状态和属性(参见图 7–12)。

images

**图 7–12。**Chrome 浏览器中加载的指南针应用启用了 Ripple

用纹波测试应用

测试中的应用是一个 compass 应用。为了测试该应用,我们将使用右下角红色标记的地理和指南针控件(参见图 7–12)。如果我们改变方向,这意味着我们正在模拟用户移动他/她的设备的罗盘方位。

正如您在图 7–13 中看到的,当我们改变航向时,我们可以看到罗盘图像围绕中心旋转。

images

图 7–13。 用 Ripple 模仿 PhoneGap 的 Compass api

模拟 PhoneGap 只是涟漪的一个角度。Ripple 允许开发人员使用常规的浏览器工具来调试和更改应用的 DOM 和 CSS。在图 7–14 中,我们右击图像并点击“检查元素”然后我们可以检查被检查的 DOM 元素的 css 样式,在我们的例子中是 html img。

images

图 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 应用。

images

图 7–15。 远程调试架构

设置远程调试

进行远程调试的第一步是在浏览器中打开[debug.phonegap.com](http://debug.phonegap.com)。在这里你可以提供一个你自己的向导(就像我们一样)或者使用服务器随机分配的向导。

images

图 7–16。?? 使用debug.phonegap.com调试你的 PhoneGap 应用

在 PhoneGap 应用中注入远程调试

下一步是复制来自[debug.phonegap.com](http://debug.phonegap.com)的 JavaScript 片段,并将其注入到 PhoneGap 应用中。这在下面用下划线表示。

`

             PhoneGap                   javascript is loaded */             function onDeviceReady(){                 document.getElementById("deviceName").innerHTML = device.name;                 document.getElementById("phoneGapVersion").innerHTML = device.phonegap;                 document.getElementById("mobilePlatform").innerHTML = device.platform;                 document.getElementById("platformVersion").innerHTML = device.version;                 document.getElementById("uuid").innerHTML = device.uuid;             }

            /** Called when browser load this page*/             function init(){                 document.addEventListener("deviceready", onDeviceReady, false);             }                                     

Device Info

                                                                                                                                                                                                                                                                                                                             
                    Device Name                                  
                    PhoneGap Version                                  
                    Mobile Platform                                  
                    Platform Version                                  
                     UUID                                  
    

`
调试和修改 DOM 元素

下一步是在模拟器或设备上启动 PhoneGap 应用。当这个应用启动时,运行在其中的 JavaScript 将与 debug.phonegap.com 服务器通信。然后,我们将为远程调试做好准备(参见图 7–17)。

images

图 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 的。

images

图 7–18。 日志消息显示一个 Android 应用连接到了debug.phonegap.com

现在移动到元素选项卡(参见图 7–19)。在这里,您将能够在 PhoneGap 应用中看到页面的 DOM 元素。

images

图 7–19。 上检查 DOM

乐趣不止于此。现在您可以更改 DOM 元素(参见 Figure 7–20)。为此,双击任何 DOM 元素(在我们的例子中是 TD)并手动输入样式部分。在我们的例子中,我们将向包含 0.9.5 文本的 td 添加一个样式“style='background:red '”。现在,我们将切换到运行在模拟器中的 PhoneGap 应用,以查看即将生效的更改。

images

图 7–20。 *在【debug.phonegap.com】*上改变一个 DOM 属性

现在,包含“0.9.5”的 TD 元素的背景色变为红色(参见图 7–21)。这种调试帮助我们在设备/仿真器上实时调试应用。

images

图 7–21。 [debug.phonegap.com](http://debug.phonegap.com)所做的更改反映在一个 Android 模拟器上

debug.phonegap.com的问题

到目前为止,我们已经看到[debug.phonegap.com](http://debug.phonegap.com)的使用在检查真实设备或仿真器内部运行的内容时非常有用。但是,我们不想使用它,原因如下:

  1. 在开发过程中,我们不想使用外部服务器。
  2. 我们希望节省带宽并提高调试速度。

注: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)。

images

图 7–22。 关于如何安装 Weinre 的说明

访问[phonegap.github.com/weinre/Running.html](http://phonegap.github.com/weinre/Running.html)获取如何运行的说明(参见图 7–23)。

images

图 7–23。 关于如何运行的说明

结论

当开发 PhoneGap 应用时,使用 iPhone/Android 模拟器来测试代码是非常耗时和令人沮丧的。为了节省时间和精力并简化开发,请使用 Ripple PhoneGap 模拟器。充分利用 Chrome 的开发者工具,加速你的开发。对于远程调试,使用[debug.phonegap.com](http://debug.phonegap.com)或本地安装的 Weinre 服务器。这有助于您了解当应用在 iPhone/Android 模拟器或实际设备上运行时,HTML DOM 实际上发生了什么。