PageRenderTime 29ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/files/yui/3.17.2/lazy-model-list/lazy-model-list.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 525 lines | 190 code | 72 blank | 263 comment | 33 complexity | 4318af18b0501f0665d1157ab77ccbd5 MD5 | raw file
  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. YUI.add('lazy-model-list', function (Y, NAME) {
  8. /**
  9. Provides the LazyModelList class, which is a ModelList subclass that manages
  10. plain objects instead of fully instantiated model instances.
  11. @module app
  12. @submodule lazy-model-list
  13. @since 3.6.0
  14. **/
  15. /**
  16. LazyModelList is a subclass of ModelList that maintains a list of plain
  17. JavaScript objects rather than a list of Model instances. This makes it
  18. well-suited for managing large amounts of data (on the order of thousands of
  19. items) that would tend to bog down a vanilla ModelList.
  20. The API presented by LazyModelList is the same as that of ModelList, except that
  21. in every case where ModelList would provide a Model instance, LazyModelList
  22. provides a plain JavaScript object. LazyModelList also provides a `revive()`
  23. method that can convert the plain object at a given index into a full Model
  24. instance.
  25. Since the items stored in a LazyModelList are plain objects and not full Model
  26. instances, there are a few caveats to be aware of:
  27. * Since items are plain objects and not Model instances, they contain
  28. properties rather than Model attributes. To retrieve a property, use
  29. `item.foo` rather than `item.get('foo')`. To set a property, use
  30. `item.foo = 'bar'` rather than `item.set('foo', 'bar')`.
  31. * Model attribute getters and setters aren't supported, since items in the
  32. LazyModelList are stored and manipulated as plain objects with simple
  33. properties rather than YUI attributes.
  34. * Changes made to the plain object version of an item will not trigger or
  35. bubble up Model `change` events. However, once an item is revived into a
  36. full Model using the `revive()` method, changes to that Model instance
  37. will trigger and bubble change events as expected.
  38. * Custom `idAttribute` fields are not supported.
  39. * `id` and `clientId` properties _are_ supported. If an item doesn't have a
  40. `clientId` property, one will be generated automatically when the item is
  41. added to a LazyModelList.
  42. LazyModelList is generally much more memory efficient than ModelList when
  43. managing large numbers of items, and adding/removing items is significantly
  44. faster. However, the tradeoff is that LazyModelList is only well-suited for
  45. storing very simple items without complex attributes, and consumers must
  46. explicitly revive items into full Model instances as needed (this is not done
  47. transparently for performance reasons).
  48. @class LazyModelList
  49. @extends ModelList
  50. @constructor
  51. @since 3.6.0
  52. **/
  53. var AttrProto = Y.Attribute.prototype,
  54. GlobalEnv = YUI.namespace('Env.Model'),
  55. Lang = Y.Lang,
  56. YArray = Y.Array,
  57. EVT_ADD = 'add',
  58. EVT_ERROR = 'error',
  59. EVT_RESET = 'reset';
  60. Y.LazyModelList = Y.Base.create('lazyModelList', Y.ModelList, [], {
  61. // -- Lifecycle ------------------------------------------------------------
  62. initializer: function () {
  63. this.after('*:change', this._afterModelChange);
  64. },
  65. // -- Public Methods -------------------------------------------------------
  66. /**
  67. Deletes the specified model from the model cache to release memory. The
  68. model won't be destroyed or removed from the list, just freed from the
  69. cache; it can still be instantiated again using `revive()`.
  70. If no model or model index is specified, all cached models in this list will
  71. be freed.
  72. Note: Specifying an index is faster than specifying a model instance, since
  73. the latter requires an `indexOf()` call.
  74. @method free
  75. @param {Model|Number} [model] Model or index of the model to free. If not
  76. specified, all instantiated models in this list will be freed.
  77. @chainable
  78. @see revive()
  79. **/
  80. free: function (model) {
  81. var index;
  82. if (model) {
  83. index = Lang.isNumber(model) ? model : this.indexOf(model);
  84. if (index >= 0) {
  85. // We don't detach the model because it's not being removed from
  86. // the list, just being freed from memory. If something else
  87. // still holds a reference to it, it may still bubble events to
  88. // the list, but that's okay.
  89. //
  90. // `this._models` is a sparse array, which ensures that the
  91. // indices of models and items match even if we don't have model
  92. // instances for all items.
  93. delete this._models[index];
  94. }
  95. } else {
  96. this._models = [];
  97. }
  98. return this;
  99. },
  100. /**
  101. Overrides ModelList#get() to return a map of property values rather than
  102. performing attribute lookups.
  103. @method get
  104. @param {String} name Property name.
  105. @return {String[]} Array of property values.
  106. @see ModelList.get()
  107. **/
  108. get: function (name) {
  109. if (this.attrAdded(name)) {
  110. return AttrProto.get.apply(this, arguments);
  111. }
  112. return YArray.map(this._items, function (item) {
  113. return item[name];
  114. });
  115. },
  116. /**
  117. Overrides ModelList#getAsHTML() to return a map of HTML-escaped property
  118. values rather than performing attribute lookups.
  119. @method getAsHTML
  120. @param {String} name Property name.
  121. @return {String[]} Array of HTML-escaped property values.
  122. @see ModelList.getAsHTML()
  123. **/
  124. getAsHTML: function (name) {
  125. if (this.attrAdded(name)) {
  126. return Y.Escape.html(AttrProto.get.apply(this, arguments));
  127. }
  128. return YArray.map(this._items, function (item) {
  129. return Y.Escape.html(item[name]);
  130. });
  131. },
  132. /**
  133. Overrides ModelList#getAsURL() to return a map of URL-encoded property
  134. values rather than performing attribute lookups.
  135. @method getAsURL
  136. @param {String} name Property name.
  137. @return {String[]} Array of URL-encoded property values.
  138. @see ModelList.getAsURL()
  139. **/
  140. getAsURL: function (name) {
  141. if (this.attrAdded(name)) {
  142. return encodeURIComponent(AttrProto.get.apply(this, arguments));
  143. }
  144. return YArray.map(this._items, function (item) {
  145. return encodeURIComponent(item[name]);
  146. });
  147. },
  148. /**
  149. Returns the index of the given object or Model instance in this
  150. LazyModelList.
  151. @method indexOf
  152. @param {Model|Object} needle The object or Model instance to search for.
  153. @return {Number} Item index, or `-1` if not found.
  154. @see ModelList.indexOf()
  155. **/
  156. indexOf: function (model) {
  157. return YArray.indexOf(model && model._isYUIModel ?
  158. this._models : this._items, model);
  159. },
  160. /**
  161. Overrides ModelList#reset() to work with plain objects.
  162. @method reset
  163. @param {Object[]|Model[]|ModelList} [models] Models to add.
  164. @param {Object} [options] Options.
  165. @chainable
  166. @see ModelList.reset()
  167. **/
  168. reset: function (items, options) {
  169. items || (items = []);
  170. options || (options = {});
  171. var facade = Y.merge({src: 'reset'}, options);
  172. // Convert `items` into an array of plain objects, since we don't want
  173. // model instances.
  174. items = items._isYUIModelList ? items.map(this._modelToObject) :
  175. YArray.map(items, this._modelToObject);
  176. facade.models = items;
  177. if (options.silent) {
  178. this._defResetFn(facade);
  179. } else {
  180. // Sort the items before firing the reset event.
  181. if (this.comparator) {
  182. items.sort(Y.bind(this._sort, this));
  183. }
  184. this.fire(EVT_RESET, facade);
  185. }
  186. return this;
  187. },
  188. /**
  189. Revives an item (or all items) into a full Model instance. The _item_
  190. argument may be the index of an object in this list, an actual object (which
  191. must exist in the list), or may be omitted to revive all items in the list.
  192. Once revived, Model instances are attached to this list and cached so that
  193. reviving them in the future doesn't require another Model instantiation. Use
  194. the `free()` method to explicitly uncache and detach a previously revived
  195. Model instance.
  196. Note: Specifying an index rather than an object will be faster, since
  197. objects require an `indexOf()` lookup in order to retrieve the index.
  198. @method revive
  199. @param {Number|Object} [item] Index of the object to revive, or the object
  200. itself. If an object, that object must exist in this list. If not
  201. specified, all items in the list will be revived and an array of models
  202. will be returned.
  203. @return {Model|Model[]|null} Revived Model instance, array of revived Model
  204. instances, or `null` if the given index or object was not found in this
  205. list.
  206. @see free()
  207. **/
  208. revive: function (item) {
  209. var i, len, models;
  210. if (item || item === 0) {
  211. return this._revive(Lang.isNumber(item) ? item :
  212. this.indexOf(item));
  213. } else {
  214. models = [];
  215. for (i = 0, len = this._items.length; i < len; i++) {
  216. models.push(this._revive(i));
  217. }
  218. return models;
  219. }
  220. },
  221. /**
  222. Overrides ModelList#toJSON() to use toArray() instead, since it's more
  223. efficient for LazyModelList.
  224. @method toJSON
  225. @return {Object[]} Array of objects.
  226. @see ModelList.toJSON()
  227. **/
  228. toJSON: function () {
  229. return this.toArray();
  230. },
  231. // -- Protected Methods ----------------------------------------------------
  232. /**
  233. Overrides ModelList#add() to work with plain objects.
  234. @method _add
  235. @param {Object|Model} item Object or model to add.
  236. @param {Object} [options] Options.
  237. @return {Object} Added item.
  238. @protected
  239. @see ModelList._add()
  240. **/
  241. _add: function (item, options) {
  242. var facade;
  243. options || (options = {});
  244. // If the item is a model instance, convert it to a plain object.
  245. item = this._modelToObject(item);
  246. // Ensure that the item has a clientId.
  247. if (!('clientId' in item)) {
  248. item.clientId = this._generateClientId();
  249. }
  250. if (this._isInList(item)) {
  251. this.fire(EVT_ERROR, {
  252. error: 'Model is already in the list.',
  253. model: item,
  254. src : 'add'
  255. });
  256. return;
  257. }
  258. facade = Y.merge(options, {
  259. index: 'index' in options ? options.index : this._findIndex(item),
  260. model: item
  261. });
  262. options.silent ? this._defAddFn(facade) : this.fire(EVT_ADD, facade);
  263. return item;
  264. },
  265. /**
  266. Overrides ModelList#clear() to support `this._models`.
  267. @method _clear
  268. @protected
  269. @see ModelList.clear()
  270. **/
  271. _clear: function () {
  272. YArray.each(this._models, this._detachList, this);
  273. this._clientIdMap = {};
  274. this._idMap = {};
  275. this._items = [];
  276. this._models = [];
  277. },
  278. /**
  279. Generates an ad-hoc clientId for a non-instantiated Model.
  280. @method _generateClientId
  281. @return {String} Unique clientId.
  282. @protected
  283. **/
  284. _generateClientId: function () {
  285. GlobalEnv.lastId || (GlobalEnv.lastId = 0);
  286. return this.model.NAME + '_' + (GlobalEnv.lastId += 1);
  287. },
  288. /**
  289. Returns `true` if the given item is in this list, `false` otherwise.
  290. @method _isInList
  291. @param {Object} item Plain object item.
  292. @return {Boolean} `true` if the item is in this list, `false` otherwise.
  293. @protected
  294. **/
  295. _isInList: function (item) {
  296. return !!(('clientId' in item && this._clientIdMap[item.clientId]) ||
  297. ('id' in item && this._idMap[item.id]));
  298. },
  299. /**
  300. Converts a Model instance into a plain object. If _model_ is not a Model
  301. instance, it will be returned as is.
  302. This method differs from Model#toJSON() in that it doesn't delete the
  303. `clientId` property.
  304. @method _modelToObject
  305. @param {Model|Object} model Model instance to convert.
  306. @return {Object} Plain object.
  307. @protected
  308. **/
  309. _modelToObject: function (model) {
  310. if (model._isYUIModel) {
  311. model = model.getAttrs();
  312. delete model.destroyed;
  313. delete model.initialized;
  314. }
  315. return model;
  316. },
  317. /**
  318. Overrides ModelList#_remove() to convert Model instances to indices
  319. before removing to ensure consistency in the `remove` event facade.
  320. @method _remove
  321. @param {Object|Model} item Object or model to remove.
  322. @param {Object} [options] Options.
  323. @return {Object} Removed object.
  324. @protected
  325. **/
  326. _remove: function (item, options) {
  327. // If the given item is a model instance, turn it into an index before
  328. // calling the parent _remove method, since we only want to deal with
  329. // the plain object version.
  330. if (item._isYUIModel) {
  331. item = this.indexOf(item);
  332. }
  333. return Y.ModelList.prototype._remove.call(this, item, options);
  334. },
  335. /**
  336. Revives a single model at the specified index and returns it. This is the
  337. underlying implementation for `revive()`.
  338. @method _revive
  339. @param {Number} index Index of the item to revive.
  340. @return {Model} Revived model.
  341. @protected
  342. **/
  343. _revive: function (index) {
  344. var item, model;
  345. if (index < 0) {
  346. return null;
  347. }
  348. item = this._items[index];
  349. if (!item) {
  350. return null;
  351. }
  352. model = this._models[index];
  353. if (!model) {
  354. model = new this.model(item);
  355. // The clientId attribute is read-only, but revived models should
  356. // have the same clientId as the original object, so we need to set
  357. // it manually.
  358. model._set('clientId', item.clientId);
  359. this._attachList(model);
  360. this._models[index] = model;
  361. }
  362. return model;
  363. },
  364. // -- Event Handlers -------------------------------------------------------
  365. /**
  366. Handles `change` events on revived models and updates the original objects
  367. with the changes.
  368. @method _afterModelChange
  369. @param {EventFacade} e
  370. @protected
  371. **/
  372. _afterModelChange: function (e) {
  373. var changed = e.changed,
  374. item = this._clientIdMap[e.target.get('clientId')],
  375. name;
  376. if (item) {
  377. for (name in changed) {
  378. if (changed.hasOwnProperty(name)) {
  379. item[name] = changed[name].newVal;
  380. }
  381. }
  382. }
  383. },
  384. // -- Default Event Handlers -----------------------------------------------
  385. /**
  386. Overrides ModelList#_defAddFn() to support plain objects.
  387. @method _defAddFn
  388. @param {EventFacade} e
  389. @protected
  390. **/
  391. _defAddFn: function (e) {
  392. var item = e.model;
  393. this._clientIdMap[item.clientId] = item;
  394. if (Lang.isValue(item.id)) {
  395. this._idMap[item.id] = item;
  396. }
  397. this._items.splice(e.index, 0, item);
  398. },
  399. /**
  400. Overrides ModelList#_defRemoveFn() to support plain objects.
  401. @method _defRemoveFn
  402. @param {EventFacade} e
  403. @protected
  404. **/
  405. _defRemoveFn: function (e) {
  406. var index = e.index,
  407. item = e.model,
  408. model = this._models[index];
  409. delete this._clientIdMap[item.clientId];
  410. if ('id' in item) {
  411. delete this._idMap[item.id];
  412. }
  413. if (model) {
  414. this._detachList(model);
  415. }
  416. this._items.splice(index, 1);
  417. this._models.splice(index, 1);
  418. }
  419. });
  420. }, '3.17.2', {"requires": ["model-list"]});