Refactor the do_diff and manual child view lists into a separate ViewList object

This commit is contained in:
Jason Grout 2014-11-19 23:38:49 +00:00
parent 2b96a97118
commit 88ed50afad
2 changed files with 97 additions and 27 deletions

View File

@ -325,6 +325,7 @@ define(["widgets/js/manager",
pop_child_view: function(child_model) {
// Delete a child view that was previously created using create_child_view.
console.error("Deprecated use of WidgetView.pop_child_view; use a ViewList instead");
var view_ids = this.child_model_views[child_model.id];
if (view_ids !== undefined) {
@ -358,6 +359,8 @@ define(["widgets/js/manager",
// Callback that is called for each item added.
// Walk the lists until an unequal entry is found.
console.error("Deprecated _do_diff; use a ViewList or related class instead");
var i;
for (i = 0; i < new_list.length; i++) {
if (i >= old_list.length || new_list[i] !== old_list[i]) {
@ -588,11 +591,87 @@ define(["widgets/js/manager",
},
});
var ViewList = function(create_view, remove_view, context) {
// * create_view and remove_view are default functions called when adding or removing views
// * create_view takes a model and creates a view or a promise for a view for that model
// * remove_view takes a view and destroys it (including calling `.remove()`)
// * each time the update() function is called with a new list, the create and destroy
// callbacks will be called in an order so that if you append the views created in the
// create callback, you will duplicate the order of the list.
// * the remove callback defaults to just removing the view (if you pass in null)
// * the context defaults to the ViewList. If you pass another context, the create and remove
// will be called in that context.
this.initialize.apply(this, arguments);
this.state_change = Promise.resolve();
}
_.extend(ViewList.prototype, {
initialize: function(create_view, remove_view, context) {
this._handler_context = context || this;
this._models = [];
this.views = [];
this._create_view = create_view;
this._remove_view = remove_view || function(view) {view.remove()};
},
update: function(new_models, create_view, remove_view, context) {
// the create_view, remove_view, and context arguments override the defaults
// specified when the list is created.
// returns a promise that resolves after this update is done
var remove = remove_view || this._remove_view;
var create = create_view || this._create_view;
var context = context || this._handler_context;
var added_views = [];
var that = this;
this.state_change = this.state_change.then(function() {
var i;
// first, skip past the beginning of the lists if they are identical
for (i = 0; i < new_models.length; i++) {
if (i >= that._models.length || new_models[i] !== that._models[i]) {
break;
}
}
var first_removed = i;
// Remove the non-matching items from the old list.
for (var j = first_removed; j < that._models.length; j++) {
remove.call(context, that.views[j]);
}
// Add the rest of the new list items.
for (; i < new_models.length; i++) {
added_views.push(create.call(context, new_models[i]));
}
that._models = new_models;
return Promise.all(added_views, function(added) {
that.views = that.views.slice(0,first_removed).concat(added);
});
});
return this.state_change;
},
remove: function() {
// removes every view in our list; convenience function for `.update([])`
// that should be faster
// returns a promise that resolves after this removal is done
var that = this;
this.state_change = this.state_change.then(function() {
for (var i = 0, len=that.views.length; i <len; i++) {
that._remove_view.call(that._handler_context, that.views[i]);
};
that._models = [];
that.views = [];
});
return this.state_change;
},
});
var widget = {
'WidgetModel': WidgetModel,
'WidgetView': WidgetView,
'DOMWidgetView': DOMWidgetView,
'ViewList': ViewList,
};
// For backwards compatability.

View File

@ -12,16 +12,17 @@ define([
initialize: function(){
// Public constructor
BoxView.__super__.initialize.apply(this, arguments);
this.model.on('change:children', function(model, value) {
this.update_children(model.previous('children'), value);
this.children_views = new widget.ViewList(this.add_child_model, null, this);
this.listenTo(this.model, 'change:children', function(model, value) {
this.children_views.update(value);
}, this);
this.model.on('change:overflow_x', function(model, value) {
this.listenTo(this.model, 'change:overflow_x', function(model, value) {
this.update_overflow_x();
}, this);
this.model.on('change:overflow_y', function(model, value) {
this.listenTo(this.model, 'change:overflow_y', function(model, value) {
this.update_overflow_y();
}, this);
this.model.on('change:box_style', function(model, value) {
this.listenTo(this.model, 'change:box_style', function(model, value) {
this.update_box_style();
}, this);
},
@ -35,7 +36,7 @@ define([
// Called when view is rendered.
this.$box = this.$el;
this.$box.addClass('widget-box');
this.update_children([], this.model.get('children'));
this.children_views.update(this.model.get('children'));
this.update_overflow_x();
this.update_overflow_y();
this.update_box_style('');
@ -61,18 +62,6 @@ define([
this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
},
update_children: function(old_list, new_list) {
// Called when the children list changes.
this.do_diff(old_list, new_list,
$.proxy(this.remove_child_model, this),
$.proxy(this.add_child_model, this));
},
remove_child_model: function(model) {
// Called when a model is removed from the children list.
this.pop_child_view(model).remove();
},
add_child_model: function(model) {
// Called when a model is added to the children list.
var that = this;
@ -88,16 +77,21 @@ define([
return view;
}, utils.reject("Couldn't add child view to box", true));
},
remove: function() {
BoxView.__super__.remove.apply(this, arguments);
this.children_views.remove();
},
});
var FlexBoxView = BoxView.extend({
render: function(){
FlexBoxView.__super__.render.apply(this);
this.model.on('change:orientation', this.update_orientation, this);
this.model.on('change:flex', this._flex_changed, this);
this.model.on('change:pack', this._pack_changed, this);
this.model.on('change:align', this._align_changed, this);
this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
this.listenTo(this.model, 'change:flex', this._flex_changed, this);
this.listenTo(this.model, 'change:pack', this._pack_changed, this);
this.listenTo(this.model, 'change:align', this._align_changed, this);
this._flex_changed();
this._pack_changed();
this._align_changed();
@ -244,10 +238,7 @@ define([
this._shown_once = false;
this.popped_out = true;
this.update_children([], this.model.get('children'));
this.model.on('change:children', function(model, value) {
this.update_children(model.previous('children'), value);
}, this);
this.children_views.update(this.model.get('children'))
},
hide: function() {