1 /** 2 * @fileOverview enhanced base for model with sync 3 * @author yiminghe@gmail.com 4 */ 5 KISSY.add("mvc/model", function (S, Base) { 6 7 var blacklist = [ 8 "idAttribute", 9 "clientId", 10 "urlRoot", 11 "url", 12 "parse", 13 "sync" 14 ]; 15 16 /** 17 * @name Model 18 * @class 19 * Model represent a data record. 20 * @memberOf MVC 21 * @extends Base 22 */ 23 function Model() { 24 var self = this; 25 Model.superclass.constructor.apply(self, arguments); 26 /* 27 should bubble to its collections 28 */ 29 self.publish("*Change", { 30 bubbles:1 31 }); 32 self.collections = {}; 33 } 34 35 S.extend(Model, Base, 36 /** 37 * @lends MVC.Model# 38 */ 39 { 40 41 /** 42 * Add current model instance to a specified collection. 43 * @param {MVC.Collection} c 44 */ 45 addToCollection:function (c) { 46 this.collections[S.stamp(c)] = c; 47 this.addTarget(c); 48 }, 49 /** 50 * Remove current model instance from a specified collection. 51 * @param {MVC.Collection} c 52 */ 53 removeFromCollection:function (c) { 54 delete this.collections[S.stamp(c)]; 55 this.removeTarget(c); 56 }, 57 58 /** 59 * Get current model 's id. 60 */ 61 getId:function () { 62 return this.get(this.get("idAttribute")); 63 }, 64 65 /** 66 * Set current model 's id. 67 * @param id 68 */ 69 setId:function (id) { 70 return this.set(this.get("idAttribute"), id); 71 }, 72 73 __set:function () { 74 this.__isModified = 1; 75 return Model.superclass.__set.apply(this, arguments); 76 }, 77 78 /** 79 * whether it is newly created. 80 * @return {Boolean} 81 */ 82 isNew:function () { 83 return !this.getId(); 84 }, 85 86 /** 87 * whether has been modified since last save. 88 * @return {Boolean} 89 */ 90 isModified:function () { 91 return !!(this.isNew() || this.__isModified); 92 }, 93 94 /** 95 * destroy this model and sync with server. 96 * @param {Object} [opts] destroy config. 97 * @param {Function} opts.success callback when action is done successfully. 98 * @param {Function} opts.error callback when error occurs at action. 99 * @param {Function} opts.complete callback when action is complete. 100 */ 101 destroy:function (opts) { 102 var self = this; 103 opts = opts || {}; 104 var success = opts.success; 105 /** 106 * @ignore 107 */ 108 opts.success = function (resp) { 109 var lists = self.collections; 110 if (resp) { 111 var v = self.get("parse").call(self, resp); 112 if (v) { 113 self.set(v, opts); 114 } 115 } 116 for (var l in lists) { 117 lists[l].remove(self, opts); 118 self.removeFromCollection(lists[l]); 119 } 120 self.fire("destroy"); 121 success && success.apply(this, arguments); 122 }; 123 if (!self.isNew() && opts['delete']) { 124 self.get("sync").call(self, self, 'delete', opts); 125 } else { 126 opts.success(); 127 if (opts.complete) { 128 opts.complete(); 129 } 130 } 131 132 return self; 133 }, 134 135 /** 136 * Load model data from server. 137 * @param {Object} opts Load config. 138 * @param {Function} opts.success callback when action is done successfully. 139 * @param {Function} opts.error callback when error occurs at action. 140 * @param {Function} opts.complete callback when action is complete. 141 */ 142 load:function (opts) { 143 var self = this; 144 opts = opts || {}; 145 var success = opts.success; 146 /** 147 * @ignore 148 */ 149 opts.success = function (resp) { 150 if (resp) { 151 var v = self.get("parse").call(self, resp); 152 if (v) { 153 self.set(v, opts); 154 } 155 } 156 self.__isModified = 0; 157 success && success.apply(this, arguments); 158 }; 159 self.get("sync").call(self, self, 'read', opts); 160 return self; 161 }, 162 163 /** 164 * Save current model 's data to server using sync. 165 * @param {Object} opts Save config. 166 * @param {Function} opts.success callback when action is done successfully. 167 * @param {Function} opts.error callback when error occurs at action. 168 * @param {Function} opts.complete callback when action is complete. 169 */ 170 save:function (opts) { 171 var self = this; 172 opts = opts || {}; 173 var success = opts.success; 174 /** 175 * @ignore 176 */ 177 opts.success = function (resp) { 178 if (resp) { 179 var v = self.get("parse").call(self, resp); 180 if (v) { 181 self.set(v, opts); 182 } 183 } 184 self.__isModified = 0; 185 success && success.apply(this, arguments); 186 }; 187 self.get("sync").call(self, self, self.isNew() ? 'create' : 'update', opts); 188 return self; 189 }, 190 191 /** 192 * Get json representation for current model. 193 * @return {Object} 194 */ 195 toJSON:function () { 196 var ret = this.getAttrVals(); 197 S.each(blacklist, function (b) { 198 delete ret[b]; 199 }); 200 return ret; 201 } 202 203 }, { 204 ATTRS:/** 205 * @lends MVC.Model# 206 */ 207 { 208 /** 209 * Attribute name used to store id from server. 210 * Default: "id". 211 * @type String 212 */ 213 idAttribute:{ 214 value:'id' 215 }, 216 217 /** 218 * Generated client id. 219 * Default call S.guid() 220 * @type Function 221 */ 222 clientId:{ 223 valueFn:function () { 224 return S.guid("mvc-client"); 225 } 226 }, 227 /** 228 * Called to get url for delete/edit/new current model. 229 * Default: collection.url+"/"+mode.id 230 * @type Function 231 */ 232 url:{ 233 value:url 234 }, 235 /** 236 * If current model does not belong to any collection. 237 * Use this attribute value as collection.url in {@link MVC.Model#url} 238 * @type String 239 */ 240 urlRoot:{ 241 value:"" 242 }, 243 /** 244 * Sync model data with server. 245 * Default to call {@link MVC.sync} 246 * @type Function 247 */ 248 sync:{ 249 value:function () { 250 S.require("mvc").sync.apply(this, arguments); 251 } 252 }, 253 /** 254 * parse json from server to get attr/value pairs. 255 * Default to return raw data from server. 256 * @type function 257 */ 258 parse:{ 259 value:function (resp) { 260 return resp; 261 } 262 } 263 } 264 }); 265 266 function getUrl(o) { 267 var u; 268 if (o && (u = o.get("url"))) { 269 if (S.isString(u)) { 270 return u; 271 } 272 return u.call(o); 273 } 274 return u; 275 } 276 277 function url() { 278 var c, 279 cv, 280 collections = this.collections; 281 for (c in collections) { 282 if (collections.hasOwnProperty(c)) { 283 cv = collections[c]; 284 break; 285 } 286 } 287 var base = getUrl(cv) || this.get("urlRoot"); 288 289 if (this.isNew()) { 290 return base; 291 } 292 293 base = base + (base.charAt(base.length - 1) == '/' ? '' : '/'); 294 return base + encodeURIComponent(this.getId()) + "/"; 295 } 296 297 return Model; 298 299 }, { 300 requires:['base'] 301 });