1 // ========================================================================== 2 // Project: The M-Project - Mobile HTML5 Application Framework 3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved. 4 // Creator: Sebastian 5 // Date: 15.11.2010 6 // License: Dual licensed under the MIT or GPL Version 2 licenses. 7 // http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE 8 // http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE 9 // ========================================================================== 10 11 m_require('core/datastore/data_provider.js'); 12 13 /** 14 * @class 15 * 16 * Encapsulates access to LocalStorage (in-browser key value store). 17 * LocalStorage is an in-browser key-value store to persist data. 18 * This data provider persists model records as JSON strings with their name and id as key. 19 * When fetching these strings from storage, their automatically converted in their corresponding model records. 20 * 21 * @extends M.DataProvider 22 */ 23 M.LocalStorageProvider = M.DataProvider.extend( 24 /** @scope M.LocalStorageProvider.prototype */ { 25 26 /** 27 * The type of this object. 28 * @type String 29 */ 30 type: 'M.LocalStorageProvider', 31 32 /** 33 * Saves a model record to the local storage 34 * The key is the model record's name combined with id, value is stringified object 35 * e.g. 36 * Note_123 => '{ text: 'buy some food' }' 37 * 38 * @param {Object} that (is a model). 39 * @returns {Boolean} Boolean indicating whether save was successful (YES|true) or not (NO|false). 40 */ 41 save: function(obj) { 42 try { 43 console.log(obj); 44 localStorage.setItem(obj.model.name + '_' + obj.model.id, JSON.stringify(obj.model.record)); 45 return YES; 46 } catch(e) { 47 M.Logger.log(M.WARN, 'Error saving ' + obj.model.record + ' to localStorage with ID: ' + obj.model.name + '_' + that.id); 48 return NO; 49 } 50 51 }, 52 53 /** 54 * deletes a model from the local storage 55 * key defines which one to delete 56 * e.g. key: 'Note_123' 57 * 58 * @param {Object} obj The param obj, includes model 59 * @returns {Boolean} Boolean indicating whether save was successful (YES|true) or not (NO|false). 60 */ 61 del: function(obj) { 62 try { 63 if(localStorage.getItem(obj.model.name + '_' + obj.model.id)){ // check if key-value pair exists 64 localStorage.removeItem(obj.model.name + '_' + obj.model.id); 65 return YES; 66 } 67 return NO; 68 } catch(e) { 69 M.Logger.log(M.WARN, 'Error removing ID: ' + obj.model.name + '_' + obj.model.id + ' from localStorage'); 70 return NO; 71 } 72 }, 73 74 /** 75 * Finds all models of type defined by modelName that match a key or a simple query. 76 * A simple query example: 'price < 2.21' 77 * Right now, no AND or OR joins possible, just one query constraint. 78 * 79 * If no query is passed, all models are returned by calling findAll() 80 * @param {Object} The param object containing e.g. the query or the key. 81 * @returns {Object|Boolean} Returns an object if find is done with a key, an array of objects when a query is given or no 82 * parameter passed. 83 */ 84 find: function(obj) { 85 if(obj.key) { 86 console.log('got the key...'); 87 var record = this.findByKey(obj); 88 if(!record) { 89 return NO; 90 } 91 /*construct new model record with the saved id*/ 92 var reg = new RegExp('^' + obj.model.name + '_([0-9]+)').exec(obj.key); 93 var m_id = reg && reg[1] ? reg[1] : null; 94 if (!m_id) { 95 M.Logger.log('retrieved model has no valid key: ' + obj.key, M.ERROR); 96 return NO; 97 } 98 var m = obj.model.createRecord($.extend(record, {id: m_id, state: M.STATE_VALID})); 99 return m; 100 } 101 102 if(obj.query){ 103 /** 104 * RegEx to match simple queries. E.g.: 105 * username = 'paul' 106 * price < 12.23 107 * result >= -23 108 * Captures: 109 * 1: identifier ( e.g. price ) => (\w*) 110 * 2: operator ( e.g. < ) => ([<>!=]{1,2}) (actually !! is also allowed but will result in an error 111 * 3: value ( e.g. 12.23 ) => String or Number: (['"]\w*['"]|(-)?\d+(\.\d+)?) 112 */ 113 var query_regex = /^\s*(\w*)\s*([<>!=]{1,2})\s*(['"]?\w*['"]?|(-)?\d+(\.\d+)?)\s*$/; 114 var regexec = query_regex.exec(obj.query); 115 if(regexec) { 116 var ident = regexec[1]; 117 var op = regexec[2]; 118 var val = regexec[3].replace(/['"]/g, "");/* delete quotes from captured string, needs to be done in regex*/ 119 var res = this.findAll(obj); 120 switch(op) { 121 case '=': 122 res = _.select(res, function(o){ 123 return o.record[ident] === val; 124 }); 125 break; 126 case '!=': 127 res = _.select(res, function(o){ 128 return o.record[ident] !== val; 129 }); 130 break; 131 case '<': 132 res = _.select(res, function(o){ 133 return o.record[ident] < val; 134 }); 135 break; 136 case '>': 137 res = _.select(res, function(o){ 138 return o.record[ident] > val; 139 }); 140 break; 141 case '<=': 142 res = _.select(res, function(o){ 143 return o.record[ident] <= val; 144 }); 145 break; 146 case '>=': 147 res = _.select(res, function(o){ 148 return o.record[ident] >= val; 149 }); 150 break; 151 default: 152 M.Logger.log('Unknown operator in query: ' + op, M.WARN); 153 res = []; 154 break; 155 } 156 } else{ 157 M.Logger.log('Query does not satisfy query grammar.', M.WARN); 158 res = []; 159 } 160 161 return res; 162 163 } else { /* if no query is passed, all models for modelName shall be returned */ 164 return this.findAll(obj); 165 } 166 }, 167 168 /** 169 * Finds a record identified by the key. 170 * 171 * @param {Object} The param object containing e.g. the query or the key. 172 * @returns {Object|Boolean} Returns an object identified by key, correctly built as a model record by calling 173 * or a boolean (NO|false) if no key is given or the key does not exist in LocalStorage. 174 * parameter passed. 175 */ 176 findByKey: function(obj) { 177 if(obj.key) { 178 if(localStorage.getItem(obj.key)) { // if key is available 179 return this.buildRecord(obj.key, obj) 180 } else { 181 return NO; 182 } 183 } 184 M.Logger.log("Please provide a key.", M.WARN); 185 return NO; 186 }, 187 188 /** 189 * Returns all models defined by modelName. 190 * 191 * Models are saved with key: Modelname_ID, e.g. Note_123 192 * 193 * @param {Object} obj The param obj, includes model 194 * @returns {Object} The array of fetched objects/model records. If no records the array is empty. 195 */ 196 findAll: function(obj) { 197 var result = []; 198 for (var i = 0; i < localStorage.length; i++){ 199 var k = localStorage.key(i); 200 regexResult = new RegExp('^' + obj.model.name + '_').exec(k); 201 if(regexResult) { 202 var record = this.buildRecord(k, obj);//JSON.parse(localStorage.getItem(k)); 203 204 /*construct new model record with the saved id*/ 205 var reg = new RegExp('^' + obj.model.name + '_([0-9]+)').exec(k); 206 var m_id = reg && reg[1] ? reg[1] : null; 207 if (!m_id) { 208 M.Logger.log('Model Record id not correct: ' + m_id, M.ERROR); 209 continue; // if m_id does not exist, continue with next record element 210 } 211 var m = obj.model.createRecord($.extend(record, {id: m_id, state: M.STATE_VALID})); 212 213 result.push(m); 214 } 215 } 216 return result; 217 }, 218 219 /** 220 * Fetches a record from LocalStorage and checks whether automatic parsing by JSON.parse set the elements right. 221 * Means: check whether resulting object's properties have the data type define by their model attribute object. 222 * E.g. String containing a date is automatically transfered into a M.Date object when the model attribute has the data type 223 * 'Date' set for this property. 224 * 225 * @param {String} key The key to fetch the element from LocalStorage 226 * @param {Object} obj The param object, includes model 227 * @returns {Object} record The record object. Includes all model record properties with correctly set data types. 228 */ 229 buildRecord: function(key, obj) { 230 var record = JSON.parse(localStorage.getItem(key)); 231 for(var i in record) { 232 if(obj.model.__meta[i] && typeof(record[i]) !== obj.model.__meta[i].dataType.toLowerCase()) { 233 switch(obj.model.__meta[i].dataType) { 234 case 'Date': 235 record[i] = M.Date.create(record[i]); 236 break; 237 } 238 } 239 } 240 return record; 241 }, 242 243 /** 244 * Returns all keys for model defined by modelName. 245 * 246 * @param {Object} obj The param obj, includes model 247 * @returns {Object} keys All keys for model records in LocalStorage for a certain model identified by the model's name. 248 */ 249 allKeys: function(obj) { 250 var keys = []; 251 for (var i = 0; i < localStorage.length; i++){ 252 var k = localStorage.key(i) 253 regexResult = new RegExp('^' + obj.model.name + '_').exec(k); 254 if(regexResult) { 255 keys.push(k); 256 } 257 } 258 return keys; 259 } 260 261 });