標籤:sencha touch 代理
原文: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 first tx.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 first tx.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 offline console.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.
【翻譯】在Sencha Touch中建立離線/線上代理