1 /** 2 * @fileOverview collection of models 3 * @author yiminghe@gmail.com 4 */ 5 KISSY.add("mvc/collection", function (S, Event, Model, Base) { 6 7 function findModelIndex(mods, mod, comparator) { 8 var i = mods.length; 9 if (comparator) { 10 var k = comparator(mod); 11 for (i = 0; i < mods.length; i++) { 12 var k2 = comparator(mods[i]); 13 if (k < k2) { 14 break; 15 } 16 } 17 } 18 return i; 19 } 20 21 /** 22 * @name Collection 23 * @class 24 * Collection. A list of model. 25 * @memberOf MVC 26 * @extends Base 27 */ 28 function Collection() { 29 Collection.superclass.constructor.apply(this, arguments); 30 } 31 32 Collection.ATTRS = 33 /** 34 * @lends MVC.Collection# 35 */ 36 { 37 /** 38 * Model constructor with in current collection. 39 * @type MVC.Model 40 */ 41 model:{ 42 value:Model 43 }, 44 /** 45 * Model list. 46 * @type MVC.Model[] 47 */ 48 models:{ 49 /* 50 normalize model list 51 @param models 52 */ 53 setter:function (models) { 54 var prev = this.get("models"); 55 this.remove(prev, {silent:1}); 56 this.add(models, {silent:1}); 57 return this.get("models"); 58 }, 59 value:[] 60 }, 61 /** 62 * Get url for sending data to server. 63 * @type String|Function 64 */ 65 url:{ 66 value:"" 67 }, 68 /** 69 * Comparator function for index getter when adding model. 70 * default to append to last of current model list. 71 * @type Function 72 */ 73 comparator:{}, 74 /** 75 * Sync function to sync data with server. 76 * Default to call {@link MVC.sync} 77 * @type Function 78 */ 79 sync:{ 80 value:function () { 81 S.require("mvc").sync.apply(this, arguments); 82 } 83 }, 84 /** 85 * Get structured data from raw data returned from server. 86 * default to return raw data from server. 87 * @type Function 88 */ 89 parse:{ 90 value:function (resp) { 91 return resp; 92 } 93 } 94 }; 95 96 S.extend(Collection, Base, 97 /** 98 * @lends MVC.Collection# 99 */ 100 { 101 /** 102 * Sort model list according {@link MVC.Collection#comparator}. 103 */ 104 sort:function () { 105 var comparator = this.get("comparator"); 106 if (comparator) { 107 this.get("models").sort(function (a, b) { 108 return comparator(a) - comparator(b); 109 }); 110 } 111 }, 112 113 /** 114 * Get json representation of this collection. 115 * @return Object[] 116 */ 117 toJSON:function () { 118 return S.map(this.get("models"), function (m) { 119 return m.toJSON(); 120 }); 121 }, 122 123 /** 124 * Add a model to current collection. 125 * @param {Object|MVC.Model} model Model or json data to be added. 126 * @param {object} [opts] Add config 127 * @param {Function} opts.silent Whether to fire add event. 128 */ 129 add:function (model, opts) { 130 var self = this, 131 ret = true; 132 if (S.isArray(model)) { 133 var orig = [].concat(model); 134 S.each(orig, function (m) { 135 var t = self._add(m, opts); 136 ret = ret && t; 137 }); 138 } else { 139 ret = self._add(model, opts); 140 } 141 return ret; 142 }, 143 144 /** 145 * Remove an existing model from current collection. 146 * @param {MVC.Model} model Model to be removed. 147 * @param {object} [opts] Remove config. 148 * @param {Function} opts.silent Whether to fire remove event. 149 */ 150 remove:function (model, opts) { 151 var self = this; 152 if (S.isArray(model)) { 153 var orig = [].concat(model); 154 S.each(orig, function (m) { 155 self._remove(m, opts); 156 }); 157 } else if (model) { 158 self._remove(model, opts); 159 } 160 }, 161 162 /** 163 * Get model at specified index. 164 * @param {Number} i Specified index. 165 */ 166 at:function (i) { 167 return this.get("models")[i]; 168 }, 169 170 _normModel:function (model) { 171 var ret = true; 172 if (!(model instanceof Model)) { 173 var data = model, 174 modelConstructor = this.get("model"); 175 model = new modelConstructor(); 176 ret = model.set(data, { 177 silent:1 178 }); 179 } 180 return ret && model; 181 }, 182 183 /** 184 * Initialize model list by loading data using sync mechanism. 185 * @param {object} opts Load config. 186 * @param {Function} opts.success Callback when load is successful. 187 * @param {Function} opts.error Callback when error occurs on loading. 188 * @param {Function} opts.complete Callback when load is complete. 189 */ 190 load:function (opts) { 191 var self = this; 192 opts = opts || {}; 193 var success = opts.success; 194 /** 195 * @ignore 196 */ 197 opts.success = function (resp) { 198 if (resp) { 199 var v = self.get("parse").call(self, resp); 200 if (v) { 201 self.set("models", v, opts); 202 } 203 } 204 // https://github.com/kissyteam/kissy/issues/138 205 S.each(self.get("models"), function (m) { 206 m.__isModified = 0; 207 }); 208 success && success.apply(this, arguments); 209 }; 210 self.get("sync").call(self, self, 'read', opts); 211 return self; 212 }, 213 214 /** 215 * Add a model to current collection by provide json data. 216 * @param {Object} model Json data represent model data. 217 * @param {object} opts Create config. 218 * @param {Function} opts.success Callback when create is successful. 219 * @param {Function} opts.error Callback when error occurs on creating. 220 * @param {Function} opts.complete Callback when create is complete. 221 * @param {Function} opts.silent Whether to fire add event. 222 */ 223 create:function (model, opts) { 224 var self = this; 225 opts = opts || {}; 226 model = this._normModel(model); 227 if (model) { 228 model.addToCollection(self); 229 var success = opts.success; 230 opts.success = function () { 231 self.add(model, opts); 232 success && success(); 233 }; 234 model.save(opts); 235 } 236 return model; 237 }, 238 239 _add:function (model, opts) { 240 model = this._normModel(model); 241 if (model) { 242 opts = opts || {}; 243 var index = findModelIndex(this.get("models"), model, this.get("comparator")); 244 this.get("models").splice(index, 0, model); 245 model.addToCollection(this); 246 if (!opts['silent']) { 247 this.fire("add", { 248 model:model 249 }); 250 } 251 } 252 return model; 253 }, 254 255 /** 256 * not call model.destroy ,maybe model belongs to multiple collections 257 * @private 258 */ 259 _remove:function (model, opts) { 260 opts = opts || {}; 261 var index = S.indexOf(model, this.get("models")); 262 if (index != -1) { 263 this.get("models").splice(index, 1); 264 model.removeFromCollection(this); 265 } 266 if (!opts['silent']) { 267 this.fire("remove", { 268 model:model 269 }); 270 } 271 }, 272 273 /** 274 * Get model instance by id. 275 * @param {String} id 276 */ 277 getById:function (id) { 278 var models = this.get("models"); 279 for (var i = 0; i < models.length; i++) { 280 var model = models[i]; 281 if (model.getId() === id) { 282 return model; 283 } 284 } 285 return null; 286 }, 287 288 /** 289 * Get model instance by client id. 290 * @param {String} cid Client id auto generated by model. 291 */ 292 getByCid:function (cid) { 293 var models = this.get("models"); 294 for (var i = 0; i < models.length; i++) { 295 var model = models[i]; 296 if (model.get("clientId") === cid) { 297 return model; 298 } 299 } 300 return null; 301 } 302 303 }); 304 305 return Collection; 306 307 }, { 308 requires:['event', './model', 'base'] 309 });