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: 28.10.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.STATE_UNDEFINED = 'state_undefined'; 12 M.STATE_NEW = 'state_new'; 13 M.STATE_INSYNCPOS = 'state_insyncpos'; 14 M.STATE_INSYNCNEG = 'state_insyncneg'; 15 M.STATE_LOCALCHANGED = 'state_localchange'; 16 M.STATE_VALID = 'state_valid'; 17 M.STATE_INVALID = 'state_invalid'; 18 M.STATE_DELETED = 'state_deleted'; 19 20 m_require('core/foundation/model_registry.js'); 21 22 /** 23 * @class 24 * 25 * M.Model is the prototype for every model and for every model record (a model itself is the blueprint for a model record). 26 * Models hold the business data of an application respectively the application's state. It's usually the part of an application that is persisted to storage. 27 * M.Model acts as the gatekeeper to storage. It uses data provider for persistence and validators to validate its records. 28 * 29 * @extends M.Object 30 */ 31 M.Model = M.Object.extend( 32 /** @scope M.Model.prototype */ { 33 /** 34 * The type of this object. 35 * 36 * @type String 37 */ 38 type: 'M.Model', 39 40 /** 41 * The name of the model. 42 * 43 * @type String 44 */ 45 name: '', 46 47 /** 48 * Unique identifier for the model record. 49 * 50 * Note: Unique doesn't mean that this id is a global unique ID, it is just unique 51 * for records of this type of model. 52 * 53 * @type Number 54 */ 55 id: null, 56 57 /** 58 * The model's record defines the properties that are semantically bound to this model: 59 * e.g. a person's record is in simplest case: firstname, lastname, age. 60 * 61 * @type Object record 62 */ 63 record: null, 64 65 /** 66 * Object containing all meta information for the object's properties 67 * @type Object 68 */ 69 __meta: {}, 70 71 /** 72 * Manages records of this model 73 * @type Object 74 */ 75 recordManager: null, 76 77 /** 78 * 79 * @param obj 80 */ 81 records: null, 82 83 /** 84 * A constant defining the model's state. Important e.g. for syncing storage 85 * @type String 86 */ 87 state: M.STATE_UNDEFINED, 88 89 90 /** 91 * determines whether model shall be validated before saving to storage or not. 92 * @type Boolean 93 */ 94 usesValidation: YES, 95 96 /** 97 * The model's data provider. 98 * 99 * Needs a refactoring, also in connection with storageEngine. 100 * 101 * @type Object 102 */ 103 dataProvider: null, 104 105 /** 106 * Creates a new record of the model, means an instance of the model based on the blueprint. 107 * You pass the object's specific attributes to it as an object. 108 * 109 * @param {Object} obj The specific attributes as an object, e.g. {firstname: 'peter', lastname ='fox'} 110 * @returns {Object} The model record with the passed properties set. State depends on newly creation or fetch from storage: if 111 * from storage then state is M.STATE_NEW or 'state_new', if fetched from database then it is M.STATE_VALID or 'state_valid' 112 */ 113 createRecord: function(obj) { 114 var modelRecord = this.extend({ 115 id: obj.id ? obj.id : M.Application.modelRegistry.getNextId(this.name), 116 record: obj 117 }); 118 modelRecord.state = obj.state ? obj.state : M.STATE_NEW; 119 delete obj.state; 120 delete modelRecord.record.id; 121 return modelRecord; 122 }, 123 124 /** 125 * Create defines a new model blueprint. It is passed an object with the model's attributes and the model's business logic 126 * and after it the type of data provider to use. 127 * 128 * @param {Object} obj An object defining the model's 129 * @param {Object} dp The data provider to use, e. g. M.LocalStorageProvider 130 * @returns {Object} The model blueprint: acts as blueprint to all records created with @link M.Model#createRecord 131 */ 132 create: function(obj, dp) { 133 var model = M.Model.extend({ 134 __meta: {}, 135 name: obj.__name__, 136 dataProvider: dp, 137 usesValidation: obj.usesValidation === null || obj.usesValidation === undefined ? this.usesValidation : obj.usesValidation 138 }); 139 delete obj.__name__; 140 delete obj.usesValidation; 141 142 for(var prop in obj) { 143 if(typeof(obj[prop]) === 'function') { 144 model[prop] = obj[prop]; 145 } else if(obj[prop].type === 'M.ModelAttribute') { 146 model.__meta[prop] = obj[prop]; 147 } 148 } 149 150 model.recordManager = M.RecordManager.extend({}); 151 model.records = model.recordManager.records; 152 153 M.Application.modelRegistry.register(model.name); 154 155 /* Re-set the just registered model's id, if there is a value stored */ 156 /* Model Registry stores the current id of a model type into localStorage */ 157 var id = localStorage.getItem(model.name); 158 if(id) { 159 M.Application.modelRegistry.setId(model.name, parseInt(id)); 160 } 161 return model; 162 }, 163 164 /** 165 * Returns a M.ModelAttribute object to map an attribute in our record. 166 * 167 * @param {String} type type of the attribute 168 * @param {Object} opts options for the attribute, like required flag and validators array 169 * @returns {Object} An M.ModelAttribute object configured with the type and options passed to the function. 170 */ 171 attr: function(type, opts) { 172 return M.ModelAttribute.attr(type, opts); 173 }, 174 175 /* 176 * get and set methods for encapsulated attribute access 177 */ 178 179 /** 180 * Get attribute propName from model 181 * @param {String} propName the name of the property whose value shall be returned 182 * @returns {Object|String} value of property 183 */ 184 get: function(propName) { 185 return this.record[propName]; 186 }, 187 188 /** 189 * Set attribute propName of model with value val 190 * @param {String} propName the name of the property whose value shall be set 191 * @param {String|Object} val the new value 192 */ 193 set: function(propName, val) { 194 this.record[propName] = val; 195 }, 196 197 /** 198 * Validates the model, means calling validate for each property. 199 * @returns {Boolean} Indicating whether this record is valid (YES|true) or not (NO|false). 200 */ 201 validate: function() { 202 var isValid = YES; 203 var validationErrorOccured = NO; 204 /* clear validation error buffer before validation */ 205 M.Validator.clearErrorBuffer(); 206 207 /* 208 * validationBasis depends on the state of the model: if the model is in state NEW, all properties (__meta includes all) 209 * shall be considered for validation. if model is in another state, the model's record is used. example: the model is loaded from 210 * a database with only two properties included (select name, age FROM...). record now only contains these two properties but __meta 211 * still has all properties listed. models are valid if loaded from database so when saved again only the loaded properties need to get 212 * validated because all others have not been touched. that's why then record is used. 213 * */ 214 var validationBasis = this.state === M.STATE_NEW ? this.__meta : this.record; 215 216 for (var i in validationBasis) { 217 var prop = this.__meta[i]; 218 var obj = { 219 value: this.record[i], 220 modelId: this.name + '_' + this.id, 221 property: i 222 }; 223 if (!prop.validate(obj)) { 224 isValid = NO; 225 } 226 } 227 /* set state of model */ 228 /*if(!isValid) { 229 this.state = M.STATE_INVALID; 230 } else { 231 this.state = M.STATE_VALID; 232 }*/ 233 return isValid; 234 }, 235 236 /* CRUD Methods below */ 237 /** 238 * Calls the corresponding find() of the data provider to fetch data based on the passed query or key. 239 * 240 * @param {Object} obj The param object with query or key and callbacks. 241 * @returns {Boolean|Object} Depends on data provider used. When WebSQL used, a boolean is returned, the find result is returned asynchronously, 242 * because the call itself is asynchronous. If LocalStorage is used, the result of the query is returned. 243 */ 244 find: function(obj){ 245 /* extends the given obj with self as model property in obj */ 246 return this.dataProvider.find( $.extend(obj, {model: this}) ); 247 }, 248 249 /** 250 * Create or update a record in storage if it is valid (first check this). 251 * 252 * @param {Object} obj The param object with query and callbacks. 253 * @returns {Boolean} The result of the data provider function call. Is a boolean. With LocalStorage used, it indicates if the save operation was successful. 254 * When WebSQL is used, the result of the save operation returns asynchronously. The result then is just the standard result returned by the web sql provider's save method 255 * which does not necessarily indicate whether the operation was successful, because the operation is asynchronous, means the operation's end is not predictable. 256 */ 257 save: function(obj) { 258 obj = obj ? obj: {}; 259 if(!this.id) { 260 return NO; 261 } 262 var isValid = YES; 263 264 if(this.usesValidation) { 265 isValid = this.validate(); 266 } 267 268 if(isValid) { 269 return this.dataProvider.save($.extend(obj, {model: this})); 270 } 271 }, 272 273 /** 274 * Delete a record in storage. 275 * @returns {Boolean} Indicating whether deletion was successful or not (only with synchronous data providers, e.g. LocalStorage). When asynchronous data providers 276 * are used, e.g. WebSQL provider the real result comes asynchronous and here just the result of the del() function call of the @link M.WebSqlProvider is used. 277 */ 278 del: function() { 279 if(!this.id) { 280 return NO; 281 } 282 283 var isDel = this.dataProvider.del({model: this}); 284 if(isDel) { 285 this.state = M.STATE_DELETED; 286 return YES 287 } 288 289 } 290 291 });