[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('model-list', function (Y, NAME) { 9 10 /** 11 Provides an API for managing an ordered list of Model instances. 12 13 @module app 14 @submodule model-list 15 @since 3.4.0 16 **/ 17 18 /** 19 Provides an API for managing an ordered list of Model instances. 20 21 In addition to providing convenient `add`, `create`, `reset`, and `remove` 22 methods for managing the models in the list, ModelLists are also bubble targets 23 for events on the model instances they contain. This means, for example, that 24 you can add several models to a list, and then subscribe to the `*:change` event 25 on the list to be notified whenever any model in the list changes. 26 27 ModelLists also maintain sort order efficiently as models are added and removed, 28 based on a custom `comparator` function you may define (if no comparator is 29 defined, models are sorted in insertion order). 30 31 @class ModelList 32 @extends Base 33 @uses ArrayList 34 @constructor 35 @param {Object} config Config options. 36 @param {Model|Model[]|ModelList|Object|Object[]} config.items Model 37 instance, array of model instances, or ModelList to add to this list on 38 init. The `add` event will not be fired for models added on init. 39 @since 3.4.0 40 **/ 41 42 var AttrProto = Y.Attribute.prototype, 43 Lang = Y.Lang, 44 YArray = Y.Array, 45 46 /** 47 Fired when a model is added to the list. 48 49 Listen to the `on` phase of this event to be notified before a model is 50 added to the list. Calling `e.preventDefault()` during the `on` phase will 51 prevent the model from being added. 52 53 Listen to the `after` phase of this event to be notified after a model has 54 been added to the list. 55 56 @event add 57 @param {Model} model The model being added. 58 @param {Number} index The index at which the model will be added. 59 @preventable _defAddFn 60 **/ 61 EVT_ADD = 'add', 62 63 /** 64 Fired when a model is created or updated via the `create()` method, but 65 before the model is actually saved or added to the list. The `add` event 66 will be fired after the model has been saved and added to the list. 67 68 @event create 69 @param {Model} model The model being created/updated. 70 @since 3.5.0 71 **/ 72 EVT_CREATE = 'create', 73 74 /** 75 Fired when an error occurs, such as when an attempt is made to add a 76 duplicate model to the list, or when a sync layer response can't be parsed. 77 78 @event error 79 @param {Any} error Error message, object, or exception generated by the 80 error. Calling `toString()` on this should result in a meaningful error 81 message. 82 @param {String} src Source of the error. May be one of the following (or any 83 custom error source defined by a ModelList subclass): 84 85 * `add`: Error while adding a model (probably because it's already in the 86 list and can't be added again). The model in question will be provided 87 as the `model` property on the event facade. 88 * `parse`: An error parsing a JSON response. The response in question will 89 be provided as the `response` property on the event facade. 90 * `remove`: Error while removing a model (probably because it isn't in the 91 list and can't be removed). The model in question will be provided as 92 the `model` property on the event facade. 93 **/ 94 EVT_ERROR = 'error', 95 96 /** 97 Fired after models are loaded from a sync layer. 98 99 @event load 100 @param {Object} parsed The parsed version of the sync layer's response to 101 the load request. 102 @param {Mixed} response The sync layer's raw, unparsed response to the load 103 request. 104 @since 3.5.0 105 **/ 106 EVT_LOAD = 'load', 107 108 /** 109 Fired when a model is removed from the list. 110 111 Listen to the `on` phase of this event to be notified before a model is 112 removed from the list. Calling `e.preventDefault()` during the `on` phase 113 will prevent the model from being removed. 114 115 Listen to the `after` phase of this event to be notified after a model has 116 been removed from the list. 117 118 @event remove 119 @param {Model} model The model being removed. 120 @param {Number} index The index of the model being removed. 121 @preventable _defRemoveFn 122 **/ 123 EVT_REMOVE = 'remove', 124 125 /** 126 Fired when the list is completely reset via the `reset()` method or sorted 127 via the `sort()` method. 128 129 Listen to the `on` phase of this event to be notified before the list is 130 reset. Calling `e.preventDefault()` during the `on` phase will prevent 131 the list from being reset. 132 133 Listen to the `after` phase of this event to be notified after the list has 134 been reset. 135 136 @event reset 137 @param {Model[]} models Array of the list's new models after the reset. 138 @param {String} src Source of the event. May be either `'reset'` or 139 `'sort'`. 140 @preventable _defResetFn 141 **/ 142 EVT_RESET = 'reset'; 143 144 function ModelList() { 145 ModelList.superclass.constructor.apply(this, arguments); 146 } 147 148 Y.ModelList = Y.extend(ModelList, Y.Base, { 149 // -- Public Properties ---------------------------------------------------- 150 151 /** 152 The `Model` class or subclass of the models in this list. 153 154 The class specified here will be used to create model instances 155 automatically based on attribute hashes passed to the `add()`, `create()`, 156 and `reset()` methods. 157 158 You may specify the class as an actual class reference or as a string that 159 resolves to a class reference at runtime (the latter can be useful if the 160 specified class will be loaded lazily). 161 162 @property model 163 @type Model|String 164 @default Y.Model 165 **/ 166 model: Y.Model, 167 168 // -- Protected Properties ------------------------------------------------- 169 170 /** 171 Total hack to allow us to identify ModelList instances without using 172 `instanceof`, which won't work when the instance was created in another 173 window or YUI sandbox. 174 175 @property _isYUIModelList 176 @type Boolean 177 @default true 178 @protected 179 @since 3.5.0 180 **/ 181 _isYUIModelList: true, 182 183 // -- Lifecycle Methods ---------------------------------------------------- 184 initializer: function (config) { 185 config || (config = {}); 186 187 var model = this.model = config.model || this.model; 188 189 if (typeof model === 'string') { 190 // Look for a namespaced Model class on `Y`. 191 this.model = Y.Object.getValue(Y, model.split('.')); 192 193 if (!this.model) { 194 Y.error('ModelList: Model class not found: ' + model); 195 } 196 } 197 198 this.publish(EVT_ADD, {defaultFn: this._defAddFn}); 199 this.publish(EVT_RESET, {defaultFn: this._defResetFn}); 200 this.publish(EVT_REMOVE, {defaultFn: this._defRemoveFn}); 201 202 this.after('*:idChange', this._afterIdChange); 203 204 this._clear(); 205 206 if (config.items) { 207 this.add(config.items, {silent: true}); 208 } 209 }, 210 211 destructor: function () { 212 this._clear(); 213 }, 214 215 // -- Public Methods ------------------------------------------------------- 216 217 /** 218 Adds the specified model or array of models to this list. You may also pass 219 another ModelList instance, in which case all the models in that list will 220 be added to this one as well. 221 222 @example 223 224 // Add a single model instance. 225 list.add(new Model({foo: 'bar'})); 226 227 // Add a single model, creating a new instance automatically. 228 list.add({foo: 'bar'}); 229 230 // Add multiple models, creating new instances automatically. 231 list.add([ 232 {foo: 'bar'}, 233 {baz: 'quux'} 234 ]); 235 236 // Add all the models in another ModelList instance. 237 list.add(otherList); 238 239 @method add 240 @param {Model|Model[]|ModelList|Object|Object[]} models Model or array of 241 models to add. May be existing model instances or hashes of model 242 attributes, in which case new model instances will be created from the 243 hashes. You may also pass a ModelList instance to add all the models it 244 contains. 245 @param {Object} [options] Data to be mixed into the event facade of the 246 `add` event(s) for the added models. 247 248 @param {Number} [options.index] Index at which to insert the added 249 models. If not specified, the models will automatically be inserted 250 in the appropriate place according to the current sort order as 251 dictated by the `comparator()` method, if any. 252 @param {Boolean} [options.silent=false] If `true`, no `add` event(s) 253 will be fired. 254 255 @return {Model|Model[]} Added model or array of added models. 256 **/ 257 add: function (models, options) { 258 var isList = models._isYUIModelList; 259 260 if (isList || Lang.isArray(models)) { 261 return YArray.map(isList ? models.toArray() : models, function (model, index) { 262 var modelOptions = options || {}; 263 264 // When an explicit insertion index is specified, ensure that 265 // the index is increased by one for each subsequent item in the 266 // array. 267 if ('index' in modelOptions) { 268 modelOptions = Y.merge(modelOptions, { 269 index: modelOptions.index + index 270 }); 271 } 272 273 return this._add(model, modelOptions); 274 }, this); 275 } else { 276 return this._add(models, options); 277 } 278 }, 279 280 /** 281 Define this method to provide a function that takes a model as a parameter 282 and returns a value by which that model should be sorted relative to other 283 models in this list. 284 285 By default, no comparator is defined, meaning that models will not be sorted 286 (they'll be stored in the order they're added). 287 288 @example 289 var list = new Y.ModelList({model: Y.Model}); 290 291 list.comparator = function (model) { 292 return model.get('id'); // Sort models by id. 293 }; 294 295 @method comparator 296 @param {Model} model Model being sorted. 297 @return {Number|String} Value by which the model should be sorted relative 298 to other models in this list. 299 **/ 300 301 // comparator is not defined by default 302 303 /** 304 Creates or updates the specified model on the server, then adds it to this 305 list if the server indicates success. 306 307 @method create 308 @param {Model|Object} model Model to create. May be an existing model 309 instance or a hash of model attributes, in which case a new model instance 310 will be created from the hash. 311 @param {Object} [options] Options to be passed to the model's `sync()` and 312 `set()` methods and mixed into the `create` and `add` event facades. 313 @param {Boolean} [options.silent=false] If `true`, no `add` event(s) will 314 be fired. 315 @param {Function} [callback] Called when the sync operation finishes. 316 @param {Error} callback.err If an error occurred, this parameter will 317 contain the error. If the sync operation succeeded, _err_ will be 318 falsy. 319 @param {Any} callback.response The server's response. 320 @return {Model} Created model. 321 **/ 322 create: function (model, options, callback) { 323 var self = this; 324 325 // Allow callback as second arg. 326 if (typeof options === 'function') { 327 callback = options; 328 options = {}; 329 } 330 331 options || (options = {}); 332 333 if (!model._isYUIModel) { 334 model = new this.model(model); 335 } 336 337 self.fire(EVT_CREATE, Y.merge(options, { 338 model: model 339 })); 340 341 return model.save(options, function (err) { 342 if (!err) { 343 self.add(model, options); 344 } 345 346 if (callback) { 347 callback.apply(null, arguments); 348 } 349 }); 350 }, 351 352 /** 353 Executes the supplied function on each model in this list. 354 355 By default, the callback function's `this` object will refer to the model 356 currently being iterated. Specify a `thisObj` to override the `this` object 357 if desired. 358 359 Note: Iteration is performed on a copy of the internal array of models, so 360 it's safe to delete a model from the list during iteration. 361 362 @method each 363 @param {Function} callback Function to execute on each model. 364 @param {Model} callback.model Model instance. 365 @param {Number} callback.index Index of the current model. 366 @param {ModelList} callback.list The ModelList being iterated. 367 @param {Object} [thisObj] Object to use as the `this` object when executing 368 the callback. 369 @chainable 370 @since 3.6.0 371 **/ 372 each: function (callback, thisObj) { 373 var items = this._items.concat(), 374 i, item, len; 375 376 for (i = 0, len = items.length; i < len; i++) { 377 item = items[i]; 378 callback.call(thisObj || item, item, i, this); 379 } 380 381 return this; 382 }, 383 384 /** 385 Executes the supplied function on each model in this list. Returns an array 386 containing the models for which the supplied function returned a truthy 387 value. 388 389 The callback function's `this` object will refer to this ModelList. Use 390 `Y.bind()` to bind the `this` object to another object if desired. 391 392 @example 393 394 // Get an array containing only the models whose "enabled" attribute is 395 // truthy. 396 var filtered = list.filter(function (model) { 397 return model.get('enabled'); 398 }); 399 400 // Get a new ModelList containing only the models whose "enabled" 401 // attribute is truthy. 402 var filteredList = list.filter({asList: true}, function (model) { 403 return model.get('enabled'); 404 }); 405 406 @method filter 407 @param {Object} [options] Filter options. 408 @param {Boolean} [options.asList=false] If truthy, results will be 409 returned as a new ModelList instance rather than as an array. 410 411 @param {Function} callback Function to execute on each model. 412 @param {Model} callback.model Model instance. 413 @param {Number} callback.index Index of the current model. 414 @param {ModelList} callback.list The ModelList being filtered. 415 416 @return {Model[]|ModelList} Array of models for which the callback function 417 returned a truthy value (empty if it never returned a truthy value). If 418 the `options.asList` option is truthy, a new ModelList instance will be 419 returned instead of an array. 420 @since 3.5.0 421 */ 422 filter: function (options, callback) { 423 var filtered = [], 424 items = this._items, 425 i, item, len, list; 426 427 // Allow options as first arg. 428 if (typeof options === 'function') { 429 callback = options; 430 options = {}; 431 } 432 433 for (i = 0, len = items.length; i < len; ++i) { 434 item = items[i]; 435 436 if (callback.call(this, item, i, this)) { 437 filtered.push(item); 438 } 439 } 440 441 if (options.asList) { 442 list = new this.constructor({model: this.model}); 443 444 if (filtered.length) { 445 list.add(filtered, {silent: true}); 446 } 447 448 return list; 449 } else { 450 return filtered; 451 } 452 }, 453 454 /** 455 If _name_ refers to an attribute on this ModelList instance, returns the 456 value of that attribute. Otherwise, returns an array containing the values 457 of the specified attribute from each model in this list. 458 459 @method get 460 @param {String} name Attribute name or object property path. 461 @return {Any|Any[]} Attribute value or array of attribute values. 462 @see Model.get() 463 **/ 464 get: function (name) { 465 if (this.attrAdded(name)) { 466 return AttrProto.get.apply(this, arguments); 467 } 468 469 return this.invoke('get', name); 470 }, 471 472 /** 473 If _name_ refers to an attribute on this ModelList instance, returns the 474 HTML-escaped value of that attribute. Otherwise, returns an array containing 475 the HTML-escaped values of the specified attribute from each model in this 476 list. 477 478 The values are escaped using `Escape.html()`. 479 480 @method getAsHTML 481 @param {String} name Attribute name or object property path. 482 @return {String|String[]} HTML-escaped value or array of HTML-escaped 483 values. 484 @see Model.getAsHTML() 485 **/ 486 getAsHTML: function (name) { 487 if (this.attrAdded(name)) { 488 return Y.Escape.html(AttrProto.get.apply(this, arguments)); 489 } 490 491 return this.invoke('getAsHTML', name); 492 }, 493 494 /** 495 If _name_ refers to an attribute on this ModelList instance, returns the 496 URL-encoded value of that attribute. Otherwise, returns an array containing 497 the URL-encoded values of the specified attribute from each model in this 498 list. 499 500 The values are encoded using the native `encodeURIComponent()` function. 501 502 @method getAsURL 503 @param {String} name Attribute name or object property path. 504 @return {String|String[]} URL-encoded value or array of URL-encoded values. 505 @see Model.getAsURL() 506 **/ 507 getAsURL: function (name) { 508 if (this.attrAdded(name)) { 509 return encodeURIComponent(AttrProto.get.apply(this, arguments)); 510 } 511 512 return this.invoke('getAsURL', name); 513 }, 514 515 /** 516 Returns the model with the specified _clientId_, or `null` if not found. 517 518 @method getByClientId 519 @param {String} clientId Client id. 520 @return {Model} Model, or `null` if not found. 521 **/ 522 getByClientId: function (clientId) { 523 return this._clientIdMap[clientId] || null; 524 }, 525 526 /** 527 Returns the model with the specified _id_, or `null` if not found. 528 529 Note that models aren't expected to have an id until they're saved, so if 530 you're working with unsaved models, it may be safer to call 531 `getByClientId()`. 532 533 @method getById 534 @param {String|Number} id Model id. 535 @return {Model} Model, or `null` if not found. 536 **/ 537 getById: function (id) { 538 return this._idMap[id] || null; 539 }, 540 541 /** 542 Calls the named method on every model in the list. Any arguments provided 543 after _name_ will be passed on to the invoked method. 544 545 @method invoke 546 @param {String} name Name of the method to call on each model. 547 @param {Any} [args*] Zero or more arguments to pass to the invoked method. 548 @return {Any[]} Array of return values, indexed according to the index of 549 the model on which the method was called. 550 **/ 551 invoke: function (name /*, args* */) { 552 var args = [this._items, name].concat(YArray(arguments, 1, true)); 553 return YArray.invoke.apply(YArray, args); 554 }, 555 556 /** 557 Returns the model at the specified _index_. 558 559 @method item 560 @param {Number} index Index of the model to fetch. 561 @return {Model} The model at the specified index, or `undefined` if there 562 isn't a model there. 563 **/ 564 565 // item() is inherited from ArrayList. 566 567 /** 568 Loads this list of models from the server. 569 570 This method delegates to the `sync()` method to perform the actual load 571 operation, which is an asynchronous action. Specify a _callback_ function to 572 be notified of success or failure. 573 574 If the load operation succeeds, a `reset` event will be fired. 575 576 @method load 577 @param {Object} [options] Options to be passed to `sync()` and to 578 `reset()` when adding the loaded models. It's up to the custom sync 579 implementation to determine what options it supports or requires, if any. 580 @param {Function} [callback] Called when the sync operation finishes. 581 @param {Error} callback.err If an error occurred, this parameter will 582 contain the error. If the sync operation succeeded, _err_ will be 583 falsy. 584 @param {Any} callback.response The server's response. This value will 585 be passed to the `parse()` method, which is expected to parse it and 586 return an array of model attribute hashes. 587 @chainable 588 **/ 589 load: function (options, callback) { 590 var self = this; 591 592 // Allow callback as only arg. 593 if (typeof options === 'function') { 594 callback = options; 595 options = {}; 596 } 597 598 options || (options = {}); 599 600 this.sync('read', options, function (err, response) { 601 var facade = { 602 options : options, 603 response: response 604 }, 605 606 parsed; 607 608 if (err) { 609 facade.error = err; 610 facade.src = 'load'; 611 612 self.fire(EVT_ERROR, facade); 613 } else { 614 // Lazy publish. 615 if (!self._loadEvent) { 616 self._loadEvent = self.publish(EVT_LOAD, { 617 preventable: false 618 }); 619 } 620 621 parsed = facade.parsed = self._parse(response); 622 623 self.reset(parsed, options); 624 self.fire(EVT_LOAD, facade); 625 } 626 627 if (callback) { 628 callback.apply(null, arguments); 629 } 630 }); 631 632 return this; 633 }, 634 635 /** 636 Executes the specified function on each model in this list and returns an 637 array of the function's collected return values. 638 639 @method map 640 @param {Function} fn Function to execute on each model. 641 @param {Model} fn.model Current model being iterated. 642 @param {Number} fn.index Index of the current model in the list. 643 @param {Model[]} fn.models Array of models being iterated. 644 @param {Object} [thisObj] `this` object to use when calling _fn_. 645 @return {Array} Array of return values from _fn_. 646 **/ 647 map: function (fn, thisObj) { 648 return YArray.map(this._items, fn, thisObj); 649 }, 650 651 /** 652 Called to parse the _response_ when the list is loaded from the server. 653 This method receives a server _response_ and is expected to return an array 654 of model attribute hashes. 655 656 The default implementation assumes that _response_ is either an array of 657 attribute hashes or a JSON string that can be parsed into an array of 658 attribute hashes. If _response_ is a JSON string and either `Y.JSON` or the 659 native `JSON` object are available, it will be parsed automatically. If a 660 parse error occurs, an `error` event will be fired and the model will not be 661 updated. 662 663 You may override this method to implement custom parsing logic if necessary. 664 665 @method parse 666 @param {Any} response Server response. 667 @return {Object[]} Array of model attribute hashes. 668 **/ 669 parse: function (response) { 670 if (typeof response === 'string') { 671 try { 672 return Y.JSON.parse(response) || []; 673 } catch (ex) { 674 this.fire(EVT_ERROR, { 675 error : ex, 676 response: response, 677 src : 'parse' 678 }); 679 680 return null; 681 } 682 } 683 684 return response || []; 685 }, 686 687 /** 688 Removes the specified model or array of models from this list. You may also 689 pass another ModelList instance to remove all the models that are in both 690 that instance and this instance, or pass numerical indices to remove the 691 models at those indices. 692 693 @method remove 694 @param {Model|Model[]|ModelList|Number|Number[]} models Models or indices of 695 models to remove. 696 @param {Object} [options] Data to be mixed into the event facade of the 697 `remove` event(s) for the removed models. 698 699 @param {Boolean} [options.silent=false] If `true`, no `remove` event(s) 700 will be fired. 701 702 @return {Model|Model[]} Removed model or array of removed models. 703 **/ 704 remove: function (models, options) { 705 var isList = models._isYUIModelList; 706 707 if (isList || Lang.isArray(models)) { 708 // We can't remove multiple models by index because the indices will 709 // change as we remove them, so we need to get the actual models 710 // first. 711 models = YArray.map(isList ? models.toArray() : models, function (model) { 712 if (Lang.isNumber(model)) { 713 return this.item(model); 714 } 715 716 return model; 717 }, this); 718 719 return YArray.map(models, function (model) { 720 return this._remove(model, options); 721 }, this); 722 } else { 723 return this._remove(models, options); 724 } 725 }, 726 727 /** 728 Completely replaces all models in the list with those specified, and fires a 729 single `reset` event. 730 731 Use `reset` when you want to add or remove a large number of items at once 732 with less overhead, and without firing `add` or `remove` events for each 733 one. 734 735 @method reset 736 @param {Model[]|ModelList|Object[]} [models] Models to add. May be existing 737 model instances or hashes of model attributes, in which case new model 738 instances will be created from the hashes. If a ModelList is passed, all 739 the models in that list will be added to this list. Calling `reset()` 740 without passing in any models will clear the list. 741 @param {Object} [options] Data to be mixed into the event facade of the 742 `reset` event. 743 744 @param {Boolean} [options.silent=false] If `true`, no `reset` event will 745 be fired. 746 747 @chainable 748 **/ 749 reset: function (models, options) { 750 models || (models = []); 751 options || (options = {}); 752 753 var facade = Y.merge({src: 'reset'}, options); 754 755 if (models._isYUIModelList) { 756 models = models.toArray(); 757 } else { 758 models = YArray.map(models, function (model) { 759 return model._isYUIModel ? model : new this.model(model); 760 }, this); 761 } 762 763 facade.models = models; 764 765 if (options.silent) { 766 this._defResetFn(facade); 767 } else { 768 // Sort the models before firing the reset event. 769 if (this.comparator) { 770 models.sort(Y.bind(this._sort, this)); 771 } 772 773 this.fire(EVT_RESET, facade); 774 } 775 776 return this; 777 }, 778 779 /** 780 Executes the supplied function on each model in this list, and stops 781 iterating if the callback returns `true`. 782 783 By default, the callback function's `this` object will refer to the model 784 currently being iterated. Specify a `thisObj` to override the `this` object 785 if desired. 786 787 Note: Iteration is performed on a copy of the internal array of models, so 788 it's safe to delete a model from the list during iteration. 789 790 @method some 791 @param {Function} callback Function to execute on each model. 792 @param {Model} callback.model Model instance. 793 @param {Number} callback.index Index of the current model. 794 @param {ModelList} callback.list The ModelList being iterated. 795 @param {Object} [thisObj] Object to use as the `this` object when executing 796 the callback. 797 @return {Boolean} `true` if the callback returned `true` for any item, 798 `false` otherwise. 799 @since 3.6.0 800 **/ 801 some: function (callback, thisObj) { 802 var items = this._items.concat(), 803 i, item, len; 804 805 for (i = 0, len = items.length; i < len; i++) { 806 item = items[i]; 807 808 if (callback.call(thisObj || item, item, i, this)) { 809 return true; 810 } 811 } 812 813 return false; 814 }, 815 816 /** 817 Forcibly re-sorts the list. 818 819 Usually it shouldn't be necessary to call this method since the list 820 maintains its sort order when items are added and removed, but if you change 821 the `comparator` function after items are already in the list, you'll need 822 to re-sort. 823 824 @method sort 825 @param {Object} [options] Data to be mixed into the event facade of the 826 `reset` event. 827 @param {Boolean} [options.silent=false] If `true`, no `reset` event will 828 be fired. 829 @param {Boolean} [options.descending=false] If `true`, the sort is 830 performed in descending order. 831 @chainable 832 **/ 833 sort: function (options) { 834 if (!this.comparator) { 835 return this; 836 } 837 838 var models = this._items.concat(), 839 facade; 840 841 options || (options = {}); 842 843 models.sort(Y.rbind(this._sort, this, options)); 844 845 facade = Y.merge(options, { 846 models: models, 847 src : 'sort' 848 }); 849 850 if (options.silent) { 851 this._defResetFn(facade); 852 } else { 853 this.fire(EVT_RESET, facade); 854 } 855 856 return this; 857 }, 858 859 /** 860 Override this method to provide a custom persistence implementation for this 861 list. The default method just calls the callback without actually doing 862 anything. 863 864 This method is called internally by `load()` and its implementations relies 865 on the callback being called. This effectively means that when a callback is 866 provided, it must be called at some point for the class to operate correctly. 867 868 @method sync 869 @param {String} action Sync action to perform. May be one of the following: 870 871 * `create`: Store a list of newly-created models for the first time. 872 * `delete`: Delete a list of existing models. 873 * `read` : Load a list of existing models. 874 * `update`: Update a list of existing models. 875 876 Currently, model lists only make use of the `read` action, but other 877 actions may be used in future versions. 878 879 @param {Object} [options] Sync options. It's up to the custom sync 880 implementation to determine what options it supports or requires, if any. 881 @param {Function} [callback] Called when the sync operation finishes. 882 @param {Error} callback.err If an error occurred, this parameter will 883 contain the error. If the sync operation succeeded, _err_ will be 884 falsy. 885 @param {Any} [callback.response] The server's response. This value will 886 be passed to the `parse()` method, which is expected to parse it and 887 return an array of model attribute hashes. 888 **/ 889 sync: function (/* action, options, callback */) { 890 var callback = YArray(arguments, 0, true).pop(); 891 892 if (typeof callback === 'function') { 893 callback(); 894 } 895 }, 896 897 /** 898 Returns an array containing the models in this list. 899 900 @method toArray 901 @return {Model[]} Array containing the models in this list. 902 **/ 903 toArray: function () { 904 return this._items.concat(); 905 }, 906 907 /** 908 Returns an array containing attribute hashes for each model in this list, 909 suitable for being passed to `Y.JSON.stringify()`. 910 911 Under the hood, this method calls `toJSON()` on each model in the list and 912 pushes the results into an array. 913 914 @method toJSON 915 @return {Object[]} Array of model attribute hashes. 916 @see Model.toJSON() 917 **/ 918 toJSON: function () { 919 return this.map(function (model) { 920 return model.toJSON(); 921 }); 922 }, 923 924 // -- Protected Methods ---------------------------------------------------- 925 926 /** 927 Adds the specified _model_ if it isn't already in this list. 928 929 If the model's `clientId` or `id` matches that of a model that's already in 930 the list, an `error` event will be fired and the model will not be added. 931 932 @method _add 933 @param {Model|Object} model Model or object to add. 934 @param {Object} [options] Data to be mixed into the event facade of the 935 `add` event for the added model. 936 @param {Boolean} [options.silent=false] If `true`, no `add` event will be 937 fired. 938 @return {Model} The added model. 939 @protected 940 **/ 941 _add: function (model, options) { 942 var facade, id; 943 944 options || (options = {}); 945 946 if (!model._isYUIModel) { 947 model = new this.model(model); 948 } 949 950 id = model.get('id'); 951 952 if (this._clientIdMap[model.get('clientId')] 953 || (Lang.isValue(id) && this._idMap[id])) { 954 955 this.fire(EVT_ERROR, { 956 error: 'Model is already in the list.', 957 model: model, 958 src : 'add' 959 }); 960 961 return; 962 } 963 964 facade = Y.merge(options, { 965 index: 'index' in options ? options.index : this._findIndex(model), 966 model: model 967 }); 968 969 if (options.silent) { 970 this._defAddFn(facade); 971 } else { 972 this.fire(EVT_ADD, facade); 973 } 974 975 return model; 976 }, 977 978 /** 979 Adds this list as a bubble target for the specified model's events. 980 981 @method _attachList 982 @param {Model} model Model to attach to this list. 983 @protected 984 **/ 985 _attachList: function (model) { 986 // Attach this list and make it a bubble target for the model. 987 model.lists.push(this); 988 model.addTarget(this); 989 }, 990 991 /** 992 Clears all internal state and the internal list of models, returning this 993 list to an empty state. Automatically detaches all models in the list. 994 995 @method _clear 996 @protected 997 **/ 998 _clear: function () { 999 YArray.each(this._items, this._detachList, this); 1000 1001 this._clientIdMap = {}; 1002 this._idMap = {}; 1003 this._items = []; 1004 }, 1005 1006 /** 1007 Compares the value _a_ to the value _b_ for sorting purposes. Values are 1008 assumed to be the result of calling a model's `comparator()` method. You can 1009 override this method to implement custom sorting logic, such as a descending 1010 sort or multi-field sorting. 1011 1012 @method _compare 1013 @param {Mixed} a First value to compare. 1014 @param {Mixed} b Second value to compare. 1015 @return {Number} `-1` if _a_ should come before _b_, `0` if they're 1016 equivalent, `1` if _a_ should come after _b_. 1017 @protected 1018 @since 3.5.0 1019 **/ 1020 _compare: function (a, b) { 1021 return a < b ? -1 : (a > b ? 1 : 0); 1022 }, 1023 1024 /** 1025 Removes this list as a bubble target for the specified model's events. 1026 1027 @method _detachList 1028 @param {Model} model Model to detach. 1029 @protected 1030 **/ 1031 _detachList: function (model) { 1032 var index = YArray.indexOf(model.lists, this); 1033 1034 if (index > -1) { 1035 model.lists.splice(index, 1); 1036 model.removeTarget(this); 1037 } 1038 }, 1039 1040 /** 1041 Returns the index at which the given _model_ should be inserted to maintain 1042 the sort order of the list. 1043 1044 @method _findIndex 1045 @param {Model} model The model being inserted. 1046 @return {Number} Index at which the model should be inserted. 1047 @protected 1048 **/ 1049 _findIndex: function (model) { 1050 var items = this._items, 1051 max = items.length, 1052 min = 0, 1053 item, middle, needle; 1054 1055 if (!this.comparator || !max) { 1056 return max; 1057 } 1058 1059 needle = this.comparator(model); 1060 1061 // Perform an iterative binary search to determine the correct position 1062 // based on the return value of the `comparator` function. 1063 while (min < max) { 1064 middle = (min + max) >> 1; // Divide by two and discard remainder. 1065 item = items[middle]; 1066 1067 if (this._compare(this.comparator(item), needle) < 0) { 1068 min = middle + 1; 1069 } else { 1070 max = middle; 1071 } 1072 } 1073 1074 return min; 1075 }, 1076 1077 /** 1078 Calls the public, overrideable `parse()` method and returns the result. 1079 1080 Override this method to provide a custom pre-parsing implementation. This 1081 provides a hook for custom persistence implementations to "prep" a response 1082 before calling the `parse()` method. 1083 1084 @method _parse 1085 @param {Any} response Server response. 1086 @return {Object[]} Array of model attribute hashes. 1087 @protected 1088 @see ModelList.parse() 1089 @since 3.7.0 1090 **/ 1091 _parse: function (response) { 1092 return this.parse(response); 1093 }, 1094 1095 /** 1096 Removes the specified _model_ if it's in this list. 1097 1098 @method _remove 1099 @param {Model|Number} model Model or index of the model to remove. 1100 @param {Object} [options] Data to be mixed into the event facade of the 1101 `remove` event for the removed model. 1102 @param {Boolean} [options.silent=false] If `true`, no `remove` event will 1103 be fired. 1104 @return {Model} Removed model. 1105 @protected 1106 **/ 1107 _remove: function (model, options) { 1108 var index, facade; 1109 1110 options || (options = {}); 1111 1112 if (Lang.isNumber(model)) { 1113 index = model; 1114 model = this.item(index); 1115 } else { 1116 index = this.indexOf(model); 1117 } 1118 1119 if (index === -1 || !model) { 1120 this.fire(EVT_ERROR, { 1121 error: 'Model is not in the list.', 1122 index: index, 1123 model: model, 1124 src : 'remove' 1125 }); 1126 1127 return; 1128 } 1129 1130 facade = Y.merge(options, { 1131 index: index, 1132 model: model 1133 }); 1134 1135 if (options.silent) { 1136 this._defRemoveFn(facade); 1137 } else { 1138 this.fire(EVT_REMOVE, facade); 1139 } 1140 1141 return model; 1142 }, 1143 1144 /** 1145 Array sort function used by `sort()` to re-sort the models in the list. 1146 1147 @method _sort 1148 @param {Model} a First model to compare. 1149 @param {Model} b Second model to compare. 1150 @param {Object} [options] Options passed from `sort()` function. 1151 @param {Boolean} [options.descending=false] If `true`, the sort is 1152 performed in descending order. 1153 @return {Number} `-1` if _a_ is less than _b_, `0` if equal, `1` if greater 1154 (for ascending order, the reverse for descending order). 1155 @protected 1156 **/ 1157 _sort: function (a, b, options) { 1158 var result = this._compare(this.comparator(a), this.comparator(b)); 1159 1160 // Early return when items are equal in their sort comparison. 1161 if (result === 0) { 1162 return result; 1163 } 1164 1165 // Flips sign when the sort is to be peformed in descending order. 1166 return options && options.descending ? -result : result; 1167 }, 1168 1169 // -- Event Handlers ------------------------------------------------------- 1170 1171 /** 1172 Updates the model maps when a model's `id` attribute changes. 1173 1174 @method _afterIdChange 1175 @param {EventFacade} e 1176 @protected 1177 **/ 1178 _afterIdChange: function (e) { 1179 var newVal = e.newVal, 1180 prevVal = e.prevVal, 1181 target = e.target; 1182 1183 if (Lang.isValue(prevVal)) { 1184 if (this._idMap[prevVal] === target) { 1185 delete this._idMap[prevVal]; 1186 } else { 1187 // The model that changed isn't in this list. Probably just a 1188 // bubbled change event from a nested Model List. 1189 return; 1190 } 1191 } else { 1192 // The model had no previous id. Verify that it exists in this list 1193 // before continuing. 1194 if (this.indexOf(target) === -1) { 1195 return; 1196 } 1197 } 1198 1199 if (Lang.isValue(newVal)) { 1200 this._idMap[newVal] = target; 1201 } 1202 }, 1203 1204 // -- Default Event Handlers ----------------------------------------------- 1205 1206 /** 1207 Default event handler for `add` events. 1208 1209 @method _defAddFn 1210 @param {EventFacade} e 1211 @protected 1212 **/ 1213 _defAddFn: function (e) { 1214 var model = e.model, 1215 id = model.get('id'); 1216 1217 this._clientIdMap[model.get('clientId')] = model; 1218 1219 if (Lang.isValue(id)) { 1220 this._idMap[id] = model; 1221 } 1222 1223 this._attachList(model); 1224 this._items.splice(e.index, 0, model); 1225 }, 1226 1227 /** 1228 Default event handler for `remove` events. 1229 1230 @method _defRemoveFn 1231 @param {EventFacade} e 1232 @protected 1233 **/ 1234 _defRemoveFn: function (e) { 1235 var model = e.model, 1236 id = model.get('id'); 1237 1238 this._detachList(model); 1239 delete this._clientIdMap[model.get('clientId')]; 1240 1241 if (Lang.isValue(id)) { 1242 delete this._idMap[id]; 1243 } 1244 1245 this._items.splice(e.index, 1); 1246 }, 1247 1248 /** 1249 Default event handler for `reset` events. 1250 1251 @method _defResetFn 1252 @param {EventFacade} e 1253 @protected 1254 **/ 1255 _defResetFn: function (e) { 1256 // When fired from the `sort` method, we don't need to clear the list or 1257 // add any models, since the existing models are sorted in place. 1258 if (e.src === 'sort') { 1259 this._items = e.models.concat(); 1260 return; 1261 } 1262 1263 this._clear(); 1264 1265 if (e.models.length) { 1266 this.add(e.models, {silent: true}); 1267 } 1268 } 1269 }, { 1270 NAME: 'modelList' 1271 }); 1272 1273 Y.augment(ModelList, Y.ArrayList); 1274 1275 1276 }, '3.17.2', {"requires": ["array-extras", "array-invoke", "arraylist", "base-build", "escape", "json-parse", "model"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |