1 // ========================================================================== 2 // Project: The M-Project - Mobile HTML5 Application Framework 3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved. 4 // (c) 2011 panacoda GmbH. All rights reserved. 5 // Creator: Sebastian 6 // Date: 25.02.2010 7 // License: Dual licensed under the MIT or GPL Version 2 licenses. 8 // http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE 9 // http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE 10 // ========================================================================== 11 12 M.TARGET_REMOTE = 'remote'; 13 M.TARGET_LOCAL = 'local'; 14 M.TARGET_BOTH = 'both'; 15 16 M.PRIO_REMOTE = 'prio_remote'; 17 M.PRIO_LOCAL = 'prio_local'; 18 M.PRIO_BOTH = 'prio_both'; 19 20 21 m_require('core/utility/logger.js'); 22 23 /** 24 * @class 25 * 26 * 27 * 28 * @extends M.Object 29 */ 30 M.DataProviderHybrid = M.Object.extend( 31 /** @scope M.DataProviderHybrid.prototype */ { 32 33 /** 34 * The type of this object. 35 * 36 * @type String 37 */ 38 type: 'M.DataProviderHybrid', 39 40 /** 41 * Indicates whether data provider operates asynchronously or not. 42 * 43 * @type Boolean 44 */ 45 isAsync: YES, 46 47 /** 48 * 49 * @type Object 50 */ 51 localProvider: null, 52 53 /** 54 * 55 * @type Object 56 */ 57 remoteProvider: null, 58 59 /** 60 * Defines the operation type: 1 for 'remote' or 'local', 2 for 'both' 61 * @type Number 62 */ 63 usedProviders: 0, 64 65 callbackCounter: 0, 66 67 onSuccess: null, 68 69 onError: null, 70 71 callbacks: { 72 success: { 73 local: null, 74 remote: null 75 }, 76 error: { 77 local: null, 78 remote: null 79 } 80 }, 81 82 /** 83 * 84 * @type Object 85 */ 86 config: null, 87 88 configure: function(obj) { 89 var dp = this.extend({ 90 config:obj 91 }); 92 if(!dp.config.local) { 93 throw M.Error.extend({ 94 code: M.ERR_MODEL_PROVIDER_NOT_SET, 95 msg: 'No local data provider passed' 96 }); 97 } 98 if(!dp.config.remote) { 99 throw M.Error.extend({ 100 code: M.ERR_MODEL_PROVIDER_NOT_SET, 101 msg: 'No remote data provider passed' 102 }); 103 } 104 dp.localProvider = dp.config.local; 105 dp.remoteProvider = dp.config.remote; 106 107 // maybe some value checking before 108 return dp; 109 }, 110 111 /** 112 * 113 */ 114 find: function(obj) { 115 this.crud(obj, 'find'); 116 }, 117 118 /** 119 * 120 */ 121 save: function(obj) { 122 this.crud(obj, 'save'); 123 }, 124 125 /** 126 * 127 */ 128 del: function(obj) { 129 this.crud(obj, 'del'); 130 }, 131 132 133 /** 134 * 135 * @param {Obj} obj The param obj 136 * @param {String} op The operation to be performed on the actual data provider 137 */ 138 crud: function(obj, op) { 139 140 obj.target = obj.target || M.TARGET_BOTH; 141 142 if(!obj.prio) { 143 if(obj.target === M.TARGET_BOTH) { 144 obj.prio = M.PRIO_BOTH; 145 } else { 146 if(obj.target === M.TARGET_LOCAL) { 147 obj.prio = M.PRIO_LOCAL; 148 } else if(obj.target === M.PRIO_REMOTE) { 149 obj.prio = M.PRIO_REMOTE; 150 } 151 } 152 } 153 154 this.callbackCounter = 0; 155 this.setOriginCallbacks(obj); 156 /* set intermediate callbacks for data provider call */ 157 this.setIntermediateCallbacks(obj); 158 159 switch(obj.target) { 160 161 case M.TARGET_LOCAL: 162 this.usedProviders = 1; 163 this.localProvider[op](obj); 164 break; 165 166 case M.TARGET_REMOTE: 167 this.usedProviders = 1; 168 this.remoteProvider[op](obj); 169 break; 170 171 case M.TARGET_BOTH: 172 this.usedProviders = 2; 173 this.localProvider[op](obj); 174 this.remoteProvider[op](obj); 175 break; 176 } 177 }, 178 179 setOriginCallbacks: function(obj) { 180 if (obj.onSuccess && obj.onSuccess.target && obj.onSuccess.action) { 181 obj.onSuccess = this.bindToCaller(obj.onSuccess.target, obj.onSuccess.target[obj.onSuccess.action]); 182 this.onSuccess = obj.onSuccess; 183 } else if(obj.onSuccess === 'function') { 184 this.onSuccess = obj.onSuccess; 185 } 186 187 if (obj.onError && obj.onError.target && obj.onError.action) { 188 obj.onError = this.bindToCaller(obj.onError.target, obj.onError.target[obj.onSuccess.action]); 189 this.onError = obj.onError; 190 } else if(obj.onError === 'function') { 191 this.onError = obj.onError; 192 } 193 }, 194 195 setIntermediateCallbacks: function(obj) { 196 obj.onSuccess = { 197 target: this, 198 action: 'handleSuccessCallback' 199 }; 200 obj.onError = { 201 target: this, 202 action: 'handleErrorCallback' 203 }; 204 }, 205 206 handleSuccessCallback: function(res, obj, dp) { 207 208 if(dp.type === this.localProvider.type) { 209 this.callbacks.success.local = {result: res, param: obj, dataProvider:dp}; 210 } else if(dp.type === this.remoteProvider.type) { 211 this.callbacks.success.remote = {result: res, param: obj, dataProvider:dp}; 212 } 213 214 this.callbackCounter = this.callbackCounter + 1; 215 216 if(this.callbackCounter === this.usedProviders) { 217 this.calculateOperationState(); 218 } 219 }, 220 221 handleErrorCallback: function(err, obj, dp) { 222 223 if(dp.type === this.localProvider.type) { 224 this.callbacks.error.local = {err: err, param: obj, dataProvider:dp}; 225 } else if(dp.type === this.remoteProvider.type) { 226 this.callbacks.error.remote = {err: err, param: obj, dataProvider:dp}; 227 228 // TODO: put into remote data providers 229 obj.model.state_remote = M.STATE_FAIL; 230 } 231 232 this.callbackCounter = this.callbackCounter + 1; 233 234 /* if this is the last callback */ 235 if(this.callbackCounter === this.usedProviders) { 236 this.calculateOperationState(); 237 } 238 }, 239 240 calculateOperationState: function(obj) { 241 switch(obj.prio) { 242 case M.PRIO_LOCAL: 243 if(!this.callbacks.success.local) { 244 this.onError(this.error.local.err, obj); 245 } else { 246 this.onSuccess(this.success.local.result, obj); 247 } 248 case M.PRIO_REMOTE: 249 if(!this.callbacks.error.local) { 250 this.onError(this.error.remote.err, obj); 251 } else { 252 this.onSuccess(this.success.remote.result, obj); 253 } 254 case M.PRIO_BOTH: 255 /* if one of the callback failed */ 256 if(!this.callbacks.success.local || !this.callbacks.success.remote) { 257 /* return remote error */ 258 this.onError(this.error.remote.err, obj); 259 } else { /* if both callbacks have been success callbacks */ 260 this.onSuccess(this.success.remote.result, obj); 261 } 262 break; 263 } 264 } 265 });