为 Visualforce 创建移动组件
本文介绍了如何构建利用 jQuery Mobile 等移动框架的 Visualforce 组件,并轻松生成使用 Salesforce 数据的移动应用。
假设您是贵组织 Salesforce 实例的主要开发人员之一,并且您正在寻找改进组织的方法。您的组织用户相当精通技术,您越来越注意到他们会在会议上携带 iPad,并在工作外使用 Android 手机。您自己最近购买了一部 Android 手机,因此您从移动设备登录到您的 Salesforce 组织,以了解其体验。从 Android 浏览器上看,它看起来与桌面浏览器相同。在使用 Visualforce 应用时,被迫放大才能导航和使用会有些不便,但勉强可用。随着越来越多的用户携带移动设备,您有机会学习新技术。
如果您对 Web 开发非常熟悉,对于许多 Visualforce 开发人员来说,这很可能是事实,那么您已经走在了移动优化应用程序开发的一半路程上。HTML5 运动正在全面展开,并且有许多经过移动优化的 Web 框架可以轻松利用其功能。jQuery Mobile 就是这样一个移动框架,它是一个长期的框架,因为它得到了 jQuery 基金会以及贡献开发人员和插件作者社区的支持。
本文介绍了如何构建利用 jQuery Mobile 等移动框架的 Visualforce 组件,并轻松生成使用 Salesforce 数据的移动应用。为了开始,让我们快速浏览一下 jQuery Mobile,以便熟悉它,因为我们将在本文中使用它的术语。
介绍 jQuery Mobile 页面
让我们从高层次的角度来看一个 jQuery Mobile 页面。该框架在很大程度上依赖于用 `data-role` 属性标记元素来实现块级页面布局,并指定各种值之一。页面加载时,jQuery Mobile 框架会搜索文档中的这些元素并接管它们,添加使其具有移动应用特性的行为和样式。例如,值为 `page` 的 `data-role` 属性将元素定义为 jQuery Mobile 应用程序最基本的构建块——单个移动优化页面。当解析页面时,jQuery Mobile 会用其内容填充屏幕,将其视为移动应用中的单个页面。
<!-- Ex. jQuery Mobile Page --> <div data-role="page"> <h1>My Page</h1> </div><!-- /page -->
还有其他 data-role 属性,它们被设计为一起使用以创建移动应用程序。`page` 部分顶部有一个 `header` 部分,中间有一个 `content` 部分,底部有一个 `footer` 部分,看起来效果最好。当具有这些 `data-role` 值的元素嵌套在 `page` 内时,框架会确保其外观符合预期,并且在不同设备上看起来一致。除了嵌套元素,您还可以水平组合应用程序部分。如果一个 `page` 部分有 同级的 `page` 部分,jQuery Mobile 在加载应用程序时只显示第一个 `page` 部分,并将其他部分从 DOM 中移除,以保持其精简和响应。不用担心,这些页面会被缓存,可以通过哈希链接(通过页面 ID)显示它们,并带有页面加载和过渡动画。
<!-- Ex. jQuery Mobile Page 2 --> <div data-role="page2"> <div data-role="header"> <h1>My Title</h1> </div><!-- /header --> <div data-role="content"> <p>Hello world</p> </div><!-- /content --> </div><!-- /page -->
理解 Visualforce 移动组件
jQuery Mobile 具有列表视图、导航和各种表单输入,非常适合在移动设备上显示和输入数据。将此与数据源(如您的 Salesforce 组织)相结合,您可以快速创建包含有意义且有用数据的移动应用程序。考虑到这一点,让我们来看看 Mobile Components for Visualforce 库。Mobile Components for Visualforce 是一个开源库,它通过提供易于使用的组件,让 Visualforce 开发人员可以轻松使用移动应用程序框架,如 jQuery Mobile。在撰写本文时,该库相对较新,并且不支持所有类型的移动视图和控件,但它具有强大的核心,可以轻松创建新组件来弥补这些差距。
下图说明了 Mobile Components for Visualforce 的架构。
Visualforce 页面包含各种组件,包括 Mobile Components for Visualforce 提供的移动组件。移动组件依赖于 JavaScript 控制器、JavaScript Remoting 桥和 Visualforce 控制器来与 Force.com 数据库进行交互。
构建自定义 Visualforce 移动组件
现在您已经了解了 Mobile Components for Visualforce 的架构,让我们创建一个新组件,以便您确切地看到它是如何完成的。当 Mobile Components for Visualforce 项目首次开源时,它只包含少量组件,包括 List、Navigation 和 Detail 组件。然而,jQuery Mobile 框架支持许多其他组件,这意味着该项目还有很大的发展空间。
下图左侧展示了 Mobile Components for Visualforce 中已有的 `List` 组件的示例。但是,如果能显示您组织用户的列表,并附带他们的个人资料图片,如下图右侧所示,那不是很棒吗?
jQuery Mobile 具有此类列表,称为 Thumbnails List,这为我们提供了一个很好的起点。在本文中,我们将详细介绍构建此类组件的代码,从宏观上解释每个代码片段的职责,再到微观上展示和解释实现细节。通过这种方法,本文旨在为希望创建其他类型组件的读者提供介绍和参考。
跟随操作
如果您想有一个可用的示例来跟随操作
- 创建一个新的、免费的 Developer Edition (DE) 组织。
- 安装 示例托管包 以构建与新 `ThumbnailsList` 组件相关的所有内容。
该包包含
- Mobile Components for Visualforce 框架的自定义分支,其中包含一些提供的组件的修改版本,例如 Page
- `ThumbnailList` 组件,带有名为 `ThumbnailList` 的演示页面
审查自定义 Visualforce 移动组件
以下代码是自定义的 `ThumbnailList` 组件。如果您在 DE 组织中安装了示例包,可以通过点击 **Setup > Develop > Components > ThumbnailList** 来查找此代码。
<apex:component controller="ThumbnailListController"> <!-- Content --> <apex:attribute name="sObjectType" type="String" required="true" assignTo="{!config.sObjectType}" description=""/> <apex:attribute name="filter" type="String" required="false" assignTo="{!config.filter}" description="owner|recent|follower"/> <apex:attribute name="filterClause" type="String" required="false" assignTo="{!config.filterClause}" description="SOQL WHERE clause to use to filter list records."/> <!-- UI --> <apex:attribute name="imageUrlField" type="String" required="true" assignTo="{!config.imageUrlField}" description=""/> <apex:attribute name="labelField" type="String" required="true" assignTo="{!config.labelField}" description=""/> <apex:attribute name="subLabelField" type="String" required="true" assignTo="{!config.subLabelField}" description=""/> <apex:attribute name="listItemStyleClass" type="String" assignTo="{!config.listItemStyleClass}" description=""/> <apex:attribute name="sortByField" type="String" required="true" assignTo="{!config.sortByField}" description=""/> <apex:attribute name="listDividerStyleClass" type="String" assignTo="{!config.listDividerStyleClass}" description=""/> <apex:attribute name="listDividerStyleClass" type="String" assignTo="{!config.listDividerStyleClass}" description=""/> <apex:attribute name="listFilter" type="Boolean" default="false" description=""/> <!-- Behaviour --> <apex:attribute name="nextPage" type="String" assignTo="{!config.nextPage}" description=""/> <apex:attribute name="jsCtlrName" type="String" assignTo="{!config.jsCtlrName}" default="$V.ThumbnailListController" description="Custom JavaScript handler to manage client-side lifecycle and behavior."/> <apex:attribute name="debug" type="Boolean" assignTo="{!config.debug}" default="false" description=""/> <!-- VF/HTML --> <apex:includeScript value="{!URLFOR($Resource[ThumbnailListJS])}"/> <apex:includeScript value="{!URLFOR($Resource.ICanHaz)}"/> <apex:outputPanel layout="inline" id="list"> <ul data-role="listview" data-filter="{!listFilter}"></ul> </apex:outputPanel> <script>$V.App.registerComponent('{!$Component.list}', {!configAsJson});</script> </apex:component>
- 第 1 行:请注意,该组件使用自定义 Visualforce 控制器 ThumbnailListController。我们稍后将检查此控制器。
- 第 3-20 行:这些是组件接受的运行时参数。当 Visualforce 页面使用此组件时,该组件会将关联的属性值传递给其 Visualforce 控制器,然后 Visualforce 控制器再将其传递给其 JavaScript 控制器进行引用。请注意,每个属性的 `assignTo` 参数都引用 Visualforce 控制器上定义的 `config` 对象。我们将在后续部分回顾这两个控制器。
- 第 23 行:此行包含自定义 JavaScript 控制器,它作为静态资源存储在数据库中。我们稍后将回顾此控制器。
- 第 24 行:下一节中的示例 Visualforce 页面包含一个列表项模板,该模板使用 Mustache 标签,这是一个众所周知的无逻辑模板工具。因此,我们需要一个 JavaScript 库来解析标签并用数据填充模板。我们为该组件使用的支持 Mustache 的模板工具称为 ICanHaz,它是免费开源的代码。此行包含 ICanHaz,它也作为静态资源存储在我们的数据库中。
- 第 26-28 行:一个标准的 outputPanel,我们将把生成的列表项附加到该面板中。
- 第 30 行:所有组件都必须在框架中注册才能在页面请求时渲染,这就是此行正在做的事情。为了便于将来讨论,请注意,JavaScript 控制器在这里接收存储在 Visualforce 控制器中的 `config` 对象。此 `config` 对象由 Visualforce 控制器和 JavaScript 控制器共同使用,因为它在它们之间传递重要信息,例如 Visualforce 控制器的名称、列表的根元素,以及移动应用程序是否处于调试模式并允许调试消息。此函数参数需要一个 JSON 字符串,因此 Visualforce 控制器中有一个方法 `getConfigAsJson`,该方法将 `config` 对象序列化为 JSON。
审查示例 Visualforce 页面
现在让我们回顾一下使用新 ThumbnailList 组件的示例 Visualforce 页面。如果您在 DE 组织中安装了示例包,可以通过点击 **Setup > Develop > Pages > ThumbnailList** 来查找此代码。
<!-- ThumbnailList.page --> <apex:page showHeader="false" standardStylesheets="false" cache="false" doctype="html-5.0"> <!-- Templates --> <script id="rowItemTempl" type="text/html"> {{#records}} <li data-corners="false" data-shadow="false" data-iconshadow="true" data-wrapperels="div" data-icon="arrow-r" data-iconpos="right" data-theme="c" data-item-context="{{Id}}" class="ui-btn ui-btn-icon-right ui-li-has-arrow ui-li ui-li-has-thumb ui-btn-up-c"> <div class="ui-btn-inner ui-li"> <div class="ui-btn-text"> <a href="https://codeproject.org.cn/ThumbnailList#userDetail" class="ui-link-inherit"> <img src="{{FullPhotoUrl}}" class="ui-li-thumb"> <h3 class="ui-li-heading">{{Name}}</h3> <p class="ui-li-desc">{{Phone}}</p> </a> </div> <span class="ui-icon ui-icon-arrow-r ui-icon-shadow"> </span> </div> </li> {{/records}} </script> <!-- Declare a new app, with one page. --> <c:App debug="true"> <c:Page name="list" theme="touch" debug="true"> <c:Header > <h1 style="font-size: 20px; margin: 0px;">All Users</h1> </c:Header> <c:Content > <c:ThumbnailList sObjectType="User" imageUrlField="FullPhotoUrl" labelField="Name" subLabelField="Phone" sortByField="Name" listFilter="true" filter="recent" debug="true"/> </c:Content> </c:Page> </c:App> <style> [data-role="panel"][data-id="main"] [data-role="page"].ui-page .ui-content { background: white; } .ui-body-touch, .ui-overlay-touch { font-family: Helvetica, Arial, sans-serif } </style> </apex:page>
首先,让我们讨论由 `