原文:Creating an Online/Offline proxy in Sencha Touch

概述

在Sencha Touch中,一个常见的需求就是,当设备在没有连接互联网的时候,应用程序必须能够继续工作。Sencha Cmd为实现应用程序离线工作提供了一切所需的工具,如自动生成应用程序清单文件,不过,这其中最大问题是如何处理数据。有许多方式可以用来处理数据,而一个常用的技术就是在本地存储代理和AJAX代理之间实现切换。

在本文,ProWeb软件公司的Tom Cooksey将展示如何使用一个代理来实现类似的效果,而且该代理的存储配置对于使用它的程序员来说是完全透明的。

代理

在示例中,虽然是从AJAX代理扩展的,但你可以根据所好,从所喜欢的代理中进行扩展,不过使用AJAX是比较常见的需求,因而使用了这个。在代理中,除了要重写两个方法来处理数据流外,还需要创建几个配置项,这个后面将会进行说明。以下是将要创建的不包含任何逻辑的骨架类:

/*** Offline Proxy* @extend Ext.data.proxy.Ajax*/
Ext.define('proxy.OfflineProxy', {extend: 'Ext.data.proxy.Ajax',alias: 'proxy.offline',config: {storageKey: null,storageFacility: null,online: true},originalCallback: null,/*** Override doRequest so that we can intercept the request and* catch a failed request to fall back to offline* @param operation* @param callback* @param scope* @returns {*}*/doRequest: function(operation, callback, scope) {},/*** Override processResponse so that if we are online we can store the response* into the offline storage method provided and if a response fails,* we can fall back.* @param success* @param operation* @param request* @param response* @param callback* @param scope*/processResponse: function(success, operation, request, response, callback, scope) {}});

方法doRequest的实际用途是用来执行服务器请求的。要重写该方法是因为在这里需要判断设备是离线,还是不能访问服务器,以及是否需要将伪响应返回存储。

方法processResponse是用来解释服务器响应的,重写改方法的主要原因是除了要保留所有原始功能外,还需要将成功获取的数据存储到存储设施。如果获取数据不成功,还需要告诉代理让它再做一次,不过不是使用上面所说的伪响应。

存储设施

代理需要传递一个存储设施供它使用,在这里,它只是一个带有getItem和setItem这两个方法的单例类。也可以使用其他的任何存储设施,不过必须与示例相同的方式来实现API,它才能工作:

/*** A class that gives access into WebSQL storage*/
Ext.define('storage.WebSQL', {singleton: true,config:{/*** The database capacity in bytes (can't be changed after construction). 50MB by default.*/capacity:50 * 1024 * 1024},/*** @private* The websql database object.*/storage:null,connected: false,constructor: function (config) {this.callParent(config);this.storage = openDatabase('storage', '1.0', 'Offline resource storage', this.getCapacity());this.storage.transaction(function (tx) {tx.executeSql('CREATE TABLE IF NOT EXISTS items (key, value)');}, function (error) {console.error('WebSQL: Connection Error');}, function () {console.log('WebSQL: Connected');});},/*** Get an item from the store.* @param key The key to get.* @param callbacks object of success and failure callbacks*/getItem:function (key, callbacks) {this.storage.transaction(function (tx) {tx.executeSql('SELECT * FROM items WHERE key = ?', [key], function (tx, results) {var len = results.rows.length;if (len > 0) {callbacks.success(results.rows.item(0).value)} else {callbacks.failure(); // no result}});}, function (error) {console.log('WebSQL: Error in getItem');callbacks.failure(error);});},/*** Set an item in the store.* @param key The key to set.* @param value The string to store.* @param callbacks object of success and failure callbacks*/setItem:function (key, value, callbacks) {this.storage.transaction(function (tx) {//remove old version firsttx.executeSql('DELETE FROM items WHERE key = ?', [key]);tx.executeSql('INSERT INTO items (key, value) VALUES (?, ?)', [key, value]);}, function (error) {console.log('WebSQL: Error in setItem:' + error.message);callbacks.failure(error.message);}, function () {callbacks.success(); // no value.});}
});

在这里,没什么特别的东西,但要注意的是setItem和getItem方法都要在成功或失败的时候执行回调。另外,在构造函数中创建了SQL数据库,不过这对于一些诸如本地存储这样的简单存储就不需要了。

下面来深入探讨一下setItem方法是如何工作的:

setItem:function (key, value, callbacks) {this.storage.transaction(function (tx) {//remove old version firsttx.executeSql('DELETE FROM items WHERE key = ?', [key]);tx.executeSql('INSERT INTO items (key, value) VALUES (?, ?)', [key, value]);}, function (error) {console.log('WebSQL: Error in setItem:' + error.message);callbacks.failure(error.message);}, function () {callbacks.success(); // no value.});}
});

在这里将会将要设置的key(来自于代理的存储键)、新的值(在当前示例是序列号的JSON对象)和一个包含了回调的对象作为参数接收。代码将根据键值删除旧的引用并插入新的值。

以下这几行:

tx.executeSql('DELETE FROM items WHERE key = ?', [key]);tx.executeSql('INSERT INTO items (key, value) VALUES (?, ?)', [key, value]);

对应的使用本地存储的代码是:

localstorage.removeItem(key);
localstorage.setItem(key, value);

如果该事务执行成功,就要调用传递过来的success回调,否则则调用error回调。

方法getItem的工作方式与之类似:

getItem:function (key, callbacks) {this.storage.transaction(function (tx) {tx.executeSql('SELECT * FROM items WHERE key = ?', [key], function (tx, results) {var len = results.rows.length;if (len > 0) {callbacks.success(results.rows.item(0).value)} else {callbacks.failure(); // no result}});}, function (error) {console.log('WebSQL: Error in getItem');callbacks.failure(error);});}

在这里,只有key和callbacks两个参数。参数key是用来检索数据的,如果找到数据就调用success回调并返回数据,否则,调用error回调。

最终的代理

现在,已经有了存储设施,可以来完成代理了。要实现这个,需要在定义proxy配置项的时候将存储设施传递给它。

doRequest: function(operation, callback, scope) {var that = this,passCallback,request,fakedResponse = {};this.originalCallback = callback;function failedRequest() {fakedResponse.status = 500;fakedResponse.responseText = 'Error';fakedResponse.statusText = 'ERROR';that.processResponse(false, operation, request, fakedResponse, passCallback, scope);}if(this.getOnline()) {console.log('PROXY: Loading from online resource');return this.callParent(arguments);}else{console.log('PROXY: Loading from offline resource');request = this.buildRequest(operation);passCallback = this.createRequestCallback(request, operation, callback, scope);if(this.getStorageKey() && this.getStorageFacility()) {this.getStorageFacility().getItem(this.getStorageKey(),  {success: function(dataString) {fakedResponse.status = 200;fakedResponse.responseText = dataString;fakedResponse.statusText = 'OK';that.processResponse(true, operation, request, fakedResponse, passCallback, scope);},failure: failedRequest});}else{console.error('No storage key or facility for proxy');setTimeout(function() {failedRequest();}, 1);}}},

要重写的第一个方法是doRequest方法。在原来的AJAX类,该方法用来处理实际的服务器请求,而在这里,当设备是在线的时候,将使用callParent方法来调用父类的方法,而如果设备是离线状态,则生成伪响应,从离线存储设施来获取数据。要生成伪响应是因为processResponse方法会分析传递给它的数据以确保是合法响应。伪装的方法是设置正确的http状态代码(200),设置responseText为从存储设施返回的数据,以及设置statusText为OK。伪装后的对象会让processResponse方法认为这是正常的请求响应。这种抽象方法是Sencha框架非常擅长且用来解耦代码的好东西。

processResponse: function(success, operation, request, response, callback, scope) {var that = this;if(success) {console.log('PROXY: Request succeeded');this.callParent(arguments);if(this.getOnline()) {if(this.getStorageKey() && this.getStorageFacility()) {this.getStorageFacility().setItem(this.getStorageKey(), response.responseText, {success: function() {console.log('PROXY: Data stored to offline storage: ' + that.getStorageKey());},failure: function(error) {console.log('PROXY: Error in storing data: ' + that.getStorageKey());}});}else{console.error('PROXY: No storage key or facility for proxy');}}}else{if(this.getOnline()) {//If the request failed and we were online, we need to try and fall back to offlineconsole.log('PROXY: Request failed, will try to fallback to offline');this.setOnline(false);this.doRequest(operation, this.originalCallback, scope);}else{this.callParent(arguments);}}}

第二个要重写的方法是processResponse方法。同样,在正常情况下,当服务器请求成功后,将调用callParent方法,除此之外,还要将请求返回的数据保存打离线存储设施。

在该处理过程中有几个阶段。首先,如果请求的success标志为true(即是从服务器得到了一个有效响应),则要检查代理的配置项online。该值可以在代理初始化的时候传递给代理的。或者,代理可以默认设置该值为true,直到请求失败的时候,再将设备置于离线状态。如果标志为true且存储设施存在,则存储数据并返回。每当请求成功的时候,都需要这样做,这样,每当设备离线的时候,就可以在这个时候访问到最后的数据。

如果请求失败,则设置标志online为false并重新运行doRequest方法,这时候,online标志的值为false,就可以从存储设施返回数据了。

综合使用

当将proxy设置为上面定义的存储的时候,就可以将他们糅合在一起了:

proxy: {type            : 'offline',url             : '/test-api/test-resource.json',storageKey      : 'buttons',storageFacility : storage.WebSQL,reader : {type         : 'json',rootProperty : 'data'}}

正如所看到的,将type设置为offline意味着jangle代理的别名设置为proxy.offline。配置项storageKey就是将请求返回的数据存储到离线存储的键值。在当前示例中,由于存储被定义为buttons,因此存储的键值使用了相同的名字。存储设施(storageFacility)就是上面创建的类,而其他的配置与标准的代码配置没有任何区别。

结果

为了演示这些代码,我们开发了一个Sencha Touch的演示应用程序。此外,下面还有一个屏幕截图。该演示应用程序包含一个工具栏,而它的内容则由服务器端的JSON文件决定。

在第一张图可以看到按钮已经生成了,而咋控制台,可以观察到数据已经被存储到离线存储。

在第二张图,test-resource.json文件已经不能加载了。在这里,只是修改了一下文件名,因此返回了404错误(这意味着设备不能再访问互联网或服务器已经宕机等等)。从控制台日志可以看到,作为替代,从离线版本加载了数据,而按钮也成功加载了。

小结

Sencha类系统的灵活性意味着很容易去扩展和重新利用内置组件和内置功能。通过示例就已经证明了,潜在的困难可以通过挂入已明确定义的工作流和简单添加所需功能来轻易解决。其结果就是可以在保留原有代理的强大功能的同时,添加所需的离线功能,并让开发人员可以完全透明的去使用它。

作者:Tom Cooksey
Tom is the CTO of ProWeb Software, a UK-based Sencha Partner providing dedicated Sencha resources and development internationally. He has extensive experience building web and mobile apps, using Sencha frameworks, JavaScript and Node.js, that have a complex system architecture and compelling user interface.

转载于:https://www.cnblogs.com/muyuge/p/6333660.html

【翻译】在Sencha Touch中创建离线/在线代理相关推荐

  1. 【翻译】在Ext JS和Sencha Touch中创建自己定义布局

    原文:Creating Custom Layouts in Ext JS and Sencha Touch 布局系统是Sencha框架中最强大和最独特的一部分.布局会处理应用程序中每个组件的大小和位置 ...

  2. 【翻译】在Ext JS和Sencha Touch中创建自定义布局

    原文:Creating Custom Layouts in Ext JS and Sencha Touch 布局系统是Sencha框架中最强大和最独特的一部分.布局会处理应用程序中每个组件的大小和位置 ...

  3. 【翻译】Sencha Touch 2入门:创建一个实用的天气应用程序之三

    原文:Getting Started with Sencha Touch 2: Build a Weather Utility App (Part 3) 作者:Lee Boonstra Lee is ...

  4. Tableau使用教程:如何Tableau中添加离线/在线地图

    Tableau使用教程:如何Tableau中添加离线/在线地图 发布时间:2019-04-03 版权: 相关软件:BIGEMAP GIS Office软件 BIGEMAP可以提供Tableau多样化的 ...

  5. 【翻译】Sencha Touch 2入门:创建一个实用的天气应用程序之二

    原文地址:http://www.sencha.com/blog/getting-started-with-sencha-touch-2-build-a-weather-utility-app-part ...

  6. sencha touch 入门系列 (三)sencha touch 项目创建

    通过上一章节的学习,我们的开发环境已经配置好了,接下来我们开始创建第一个sencha touch的项目,网络上很多sencha touch的教程都是手动搭建项目的, 不过手动搭建的项目缺少一些senc ...

  7. Sencha Touch中 xclass和xtype区别

    Sencha Touch 交流 QQ 群 224711028 欢迎您的加入. 1.xclass 就是 Ext.create(xclass) 和 xtype一样的性质,不一定非要是自己创建的. 2.xt ...

  8. 【翻译】Sencha Cmd中脚本压缩方法之比较

    概述 这么多年来,Web开发人员都被告知应优化他们生产环境的代码.传统的方法是将应用程序的所有脚步进行压缩以减少应用程序的大小,以便让应用程序加载更快.而Sencha Cmd这么多年以来已经将这个过程 ...

  9. sencha touch中实现页面之间的互相跳转

    这两天应为一个页面跳转把我整的死去活来啊!官网的API不给力啊!资料太少了,害的我浪费了好长时间,最终还是弄好了,发现了一个关键问题所在:如果要使用Ext.getCmp()来得到视图或者页面组建就得在 ...

最新文章

  1. 新书上市 | 6岁的gRPC,终于出书了!
  2. 用css动画写一个下红包雨的效果
  3. [Linux C]递归遍历指定目录,以目录树形式展示
  4. 在Nginx/Tengine服务器上安装SSL证书
  5. rabbitmq多个消费者_为什么要选择RabbitMQ,RabbitMQ简介,各种MQ选型对比
  6. (65)SPI外设驱动用户接收模块(四)(第13天)
  7. 报表或BI的价值在哪?
  8. 子组件调用父组件方法
  9. 百倍性能的PL/SQL优化案例(r11笔记第13天)
  10. EMNLP2018 - 语言理解+对话系统的最新进展
  11. 动态生成实物文件思路
  12. 从IBM的计划中分析出中国重新相当然的错误选择吗
  13. 《Using OpenRefine》翻译~1
  14. ​Python优化机制:常量折叠
  15. Linux SD卡驱动开发(三) —— SD 卡驱动分析CORE篇
  16. 分而治之——最大子列和
  17. 风险模型 - 变量筛选
  18. Vue3 router 默认加载 views 文件夹下全部vue文件
  19. # Itext Pdf 合并拆分
  20. geem2登陆器修改服务器列表,Gee引擎怎么更换登陆器皮肤 GeeM2传奇编辑自定义皮肤的方法讲解...

热门文章

  1. 雅虎网站页面性能优化的34条黄金守则(转)
  2. Android源码学习之如何使用eclipse+NDK
  3. 使用 iTextSharp 生成 PDF 表格
  4. IEEE 1588-2002 Precision Time Protocol(PTP)
  5. 3G时代 一起走近无线运维的3A标准——柳州市劳动和社会保障局
  6. python 主线程_Python threading多线程模块
  7. python安装jupyterlab_【python】jupyter lab安装与配置
  8. hdp对应hadoop的版本_好程序员大数据学习路线分享hadoop的知识总结
  9. 吞噬星空怎么会有鸿蒙,论吞噬星空与鸿蒙的关系
  10. java jpanel 叠加_java – 如何在JPanel上叠加,调整大小和居中组件?