The goal in re-writing ModelList is to take advantage of the features in LazyModelList which improve performance, which is mainly to not immediately regenerate plain JavaScript objects that are added to the ModelList.
Right now, a ModelList is sometimes used like this inside a View:
template: function(data, options) {
/* Interpolates data into a string of HTML */
},
initializer: function () {
var list = this.get('modelList');
// Set the event handlers to re-render on changes in the Model List.
list.after(['add', 'remove', 'reset'], this.render, this);
// Load data from an external data source, such as a REST API.
list.load();
},
render: function () {
// Convert the Models in a ModelList back to an array of attribute hashes
var data = this.get('modelList').toJSON();
// Render the template into HTML and set it in the View's container
this.get('container').setHTML(this.template(data));
}
In these cases, the ModelList is used just for its abstraction over the sync layer and not for any of the features that a Model provides. The data that's being loaded into the ModelList is unnecessarily being turned into Models, and then back again into plain JavaScript objects to be used in a template.
With many Models, this could be a significant performance hit in an application, since it takes up unnecessary memory.
Right now, the best way of solving this problem is to use a LazyModelList instead of a regular ModelList. A LazyModelList provides the same functions as a regular ModelList, but stores data as regular objects instead of Models. Any object in a LazyModelList can be turned back into a complete Model using the revive()
method, and removed from the Model cache using the free()
method.
This works, but not as many people are using it as they should, since it's a separate class and strangely extended from ModelList.
We want to bring in some of the features that are in LazyModelList into ModelList, so that those performance-improving features are used under the hood.
Like LazyModelList, instead of creating complete Models when loading data, we want to initially store only the pure JavaScript objects inside ModelList internally.
Only when methods that directly return a Model are used will those specific objects be revived into Models. These methods are:
add
create
each
filter
getByClientId
getById
item
map
remove
[1]some
[1]: remove
will return a Model, but the Model will not be added to the cache inside of the ModelList, since it's been removed.
When these methods are called, the JavaScript object will be revived into a Model, and for most of them, they'll be added to an internal Model cache inside of ModelList. That way, when accessing the object as a Model again, it does not need to be revived once more.
Like LazyModelList, the ModelList will now listen to a *:change
event, that updates the plain JavaScript objects inside whenever a Model's attributes have been changed as well.
An additional method, free([model])
will be added to ModelList that acts similar to the free
method in LazyModelList, which is to clear out the revived Models from the cache. There will be no revive
method on ModelList, though, as the methods above will automatically revive them into full models.
Backwards compatibility will be maintained, since all existing methods will still return the same values with the same API.
The iteration methods (each
, filter
, map
, and possibly some
) will revive every model they go through in order to maintain backwards compatibility.
These might not be necessary for every case, though, since sometimes we might just want to manipulate object properties, and not have to revive the entire Model to do so.
This brings up a few questions though:
-
Should these iteration methods (that act on the raw JavaScript objects) be separate methods? If not, should we just pass an optional variable into the existing methods that show that they're "lazy"? For example, we could do
ModelList.each(fn, [thisObj], [isLazy])
instead. -
If I change a property on a plain JavaScript object, and there's a revived Model of it, how should that be handled? This goes into two-way data-binding territory, which we probably don't want to get into. The best (and simplest) option, in my opinion, is to just invalidate the cache of revived models when a raw iteration method is used.
Post any feedback on the yui-contrib discussion group thread here!