mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-15 04:00:34 +08:00
Merge pull request #6990 from jasongrout/viewlists
Refactor the do_diff and manual child view lists into a separate ViewList object
This commit is contained in:
commit
684b31bea1
@ -62,7 +62,7 @@ define([
|
||||
dummy.replaceWith(view.$el);
|
||||
view.trigger('displayed');
|
||||
return view;
|
||||
}, utils.reject('Could not display view'));
|
||||
}).catch(utils.reject('Could not display view', true));
|
||||
}
|
||||
};
|
||||
|
||||
@ -103,7 +103,7 @@ define([
|
||||
view.listenTo(model, 'destroy', view.remove);
|
||||
view.render();
|
||||
return view;
|
||||
}, utils.reject("Couldn't create a view for model id '" + String(model.id) + "'"));
|
||||
}).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true));
|
||||
});
|
||||
return model.state_change;
|
||||
};
|
||||
@ -178,7 +178,7 @@ define([
|
||||
return this.create_model({
|
||||
model_name: msg.content.data.model_name,
|
||||
model_module: msg.content.data.model_module,
|
||||
comm: comm}).catch(utils.reject("Couldn't create a model."));
|
||||
comm: comm}).catch(utils.reject("Couldn't create a model.", true));
|
||||
};
|
||||
|
||||
WidgetManager.prototype.create_model = function (options) {
|
||||
|
@ -95,7 +95,7 @@ define(["widgets/js/manager",
|
||||
} finally {
|
||||
that.state_lock = null;
|
||||
}
|
||||
}, utils.reject("Couldn't set model state", true));
|
||||
}).catch(utils.reject("Couldn't set model state", true));
|
||||
},
|
||||
|
||||
_handle_status: function (msg, callbacks) {
|
||||
@ -292,8 +292,6 @@ define(["widgets/js/manager",
|
||||
// Public constructor.
|
||||
this.model.on('change',this.update,this);
|
||||
this.options = parameters.options;
|
||||
this.child_model_views = {};
|
||||
this.child_views = {};
|
||||
this.id = this.id || utils.uuid();
|
||||
this.model.views[this.id] = this;
|
||||
this.on('displayed', function() {
|
||||
@ -311,69 +309,7 @@ define(["widgets/js/manager",
|
||||
// Create and promise that resolves to a child view of a given model
|
||||
var that = this;
|
||||
options = $.extend({ parent: this }, options || {});
|
||||
return this.model.widget_manager.create_view(child_model, options).then(function(child_view) {
|
||||
// Associate the view id with the model id.
|
||||
if (that.child_model_views[child_model.id] === undefined) {
|
||||
that.child_model_views[child_model.id] = [];
|
||||
}
|
||||
that.child_model_views[child_model.id].push(child_view.id);
|
||||
// Remember the view by id.
|
||||
that.child_views[child_view.id] = child_view;
|
||||
return child_view;
|
||||
}, utils.reject("Couldn't create child view"));
|
||||
},
|
||||
|
||||
pop_child_view: function(child_model) {
|
||||
// Delete a child view that was previously created using create_child_view.
|
||||
var view_ids = this.child_model_views[child_model.id];
|
||||
if (view_ids !== undefined) {
|
||||
|
||||
// Only delete the first view in the list.
|
||||
var view_id = view_ids[0];
|
||||
var view = this.child_views[view_id];
|
||||
delete this.child_views[view_id];
|
||||
view_ids.splice(0,1);
|
||||
delete child_model.views[view_id];
|
||||
|
||||
// Remove the view list specific to this model if it is empty.
|
||||
if (view_ids.length === 0) {
|
||||
delete this.child_model_views[child_model.id];
|
||||
}
|
||||
return view;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
do_diff: function(old_list, new_list, removed_callback, added_callback) {
|
||||
// Difference a changed list and call remove and add callbacks for
|
||||
// each removed and added item in the new list.
|
||||
//
|
||||
// Parameters
|
||||
// ----------
|
||||
// old_list : array
|
||||
// new_list : array
|
||||
// removed_callback : Callback(item)
|
||||
// Callback that is called for each item removed.
|
||||
// added_callback : Callback(item)
|
||||
// Callback that is called for each item added.
|
||||
|
||||
// Walk the lists until an unequal entry is found.
|
||||
var i;
|
||||
for (i = 0; i < new_list.length; i++) {
|
||||
if (i >= old_list.length || new_list[i] !== old_list[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the non-matching items from the old list.
|
||||
for (var j = i; j < old_list.length; j++) {
|
||||
removed_callback(old_list[j]);
|
||||
}
|
||||
|
||||
// Add the rest of the new list items.
|
||||
for (; i < new_list.length; i++) {
|
||||
added_callback(new_list[i]);
|
||||
}
|
||||
return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
|
||||
},
|
||||
|
||||
callbacks: function(){
|
||||
@ -533,11 +469,8 @@ define(["widgets/js/manager",
|
||||
if ($el===undefined) {
|
||||
$el = this.$el;
|
||||
}
|
||||
this.do_diff(old_classes, new_classes, function(removed) {
|
||||
$el.removeClass(removed);
|
||||
}, function(added) {
|
||||
$el.addClass(added);
|
||||
});
|
||||
_.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
|
||||
_.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
|
||||
},
|
||||
|
||||
update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
|
||||
@ -588,11 +521,93 @@ 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 returns a view or a promise for a view for that model
|
||||
// * remove_view takes a view and destroys it (including calling `view.remove()`)
|
||||
// * each time the update() function is called with a new list, the create and remove
|
||||
// callbacks will be called in an order so that if you append the views created in the
|
||||
// create callback and remove the views in the remove callback, you will duplicate
|
||||
// the order of the list.
|
||||
// * the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
|
||||
// * the context defaults to the created ViewList. If you pass another context, the create and remove
|
||||
// will be called in that context.
|
||||
|
||||
this.initialize.apply(this, arguments);
|
||||
};
|
||||
|
||||
_.extend(ViewList.prototype, {
|
||||
initialize: function(create_view, remove_view, context) {
|
||||
this.state_change = Promise.resolve();
|
||||
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;
|
||||
if (create === undefined || remove === undefined){
|
||||
console.error("Must define a create a remove function");
|
||||
}
|
||||
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]));
|
||||
}
|
||||
// make a copy of the input array
|
||||
that._models = new_models.slice();
|
||||
return Promise.all(added_views).then(function(added) {
|
||||
Array.prototype.splice.apply(that.views, [first_removed, that.views.length].concat(added));
|
||||
return that.views;
|
||||
});
|
||||
});
|
||||
return this.state_change;
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
// removes every view in the 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; i < that.views.length; 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.
|
||||
|
@ -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;
|
||||
@ -86,7 +75,15 @@ define([
|
||||
view.trigger('displayed');
|
||||
});
|
||||
return view;
|
||||
}, utils.reject("Couldn't add child view to box", true));
|
||||
}).catch(utils.reject("Couldn't add child view to box", true));
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
// We remove this widget before removing the children as an optimization
|
||||
// we want to remove the entire container from the DOM first before
|
||||
// removing each individual child separately.
|
||||
BoxView.__super__.remove.apply(this, arguments);
|
||||
this.children_views.remove();
|
||||
},
|
||||
});
|
||||
|
||||
@ -94,10 +91,10 @@ define([
|
||||
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 +241,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() {
|
||||
|
@ -9,18 +9,23 @@ define([
|
||||
], function(widget, utils, $){
|
||||
|
||||
var AccordionView = widget.DOMWidgetView.extend({
|
||||
initialize: function(){
|
||||
AccordionView.__super__.initialize.apply(this, arguments);
|
||||
|
||||
this.containers = [];
|
||||
this.model_containers = {};
|
||||
this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
|
||||
this.listenTo(this.model, 'change:children', function(model, value) {
|
||||
this.children_views.update(value);
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function(){
|
||||
// Called when view is rendered.
|
||||
var guid = 'panel-group' + utils.uuid();
|
||||
this.$el
|
||||
.attr('id', guid)
|
||||
.addClass('panel-group');
|
||||
this.containers = [];
|
||||
this.model_containers = {};
|
||||
this.update_children([], this.model.get('children'));
|
||||
this.model.on('change:children', function(model, value, options) {
|
||||
this.update_children(model.previous('children'), value);
|
||||
}, this);
|
||||
this.model.on('change:selected_index', function(model, value, options) {
|
||||
this.update_selected_index(model.previous('selected_index'), value, options);
|
||||
}, this);
|
||||
@ -31,6 +36,7 @@ define([
|
||||
this.on('displayed', function() {
|
||||
this.update_titles();
|
||||
}, this);
|
||||
this.children_views.update(this.model.get('children'));
|
||||
},
|
||||
|
||||
update_titles: function(titles) {
|
||||
@ -61,25 +67,18 @@ define([
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
update_children: function(old_list, new_list) {
|
||||
// Called when the children list is modified.
|
||||
this.do_diff(old_list,
|
||||
new_list,
|
||||
$.proxy(this.remove_child_model, this),
|
||||
$.proxy(this.add_child_model, this));
|
||||
},
|
||||
|
||||
remove_child_model: function(model) {
|
||||
remove_child_view: function(view) {
|
||||
// Called when a child is removed from children list.
|
||||
// TODO: does this handle two different views of the same model as children?
|
||||
var model = view.model;
|
||||
var accordion_group = this.model_containers[model.id];
|
||||
this.containers.splice(accordion_group.container_index, 1);
|
||||
delete this.model_containers[model.id];
|
||||
accordion_group.remove();
|
||||
this.pop_child_view(model);
|
||||
},
|
||||
|
||||
add_child_model: function(model) {
|
||||
add_child_view: function(model) {
|
||||
// Called when a child is added to children list.
|
||||
var index = this.containers.length;
|
||||
var uuid = utils.uuid();
|
||||
@ -126,7 +125,15 @@ define([
|
||||
view.trigger('displayed');
|
||||
});
|
||||
return view;
|
||||
}, utils.reject("Couldn't add child view to box", true));
|
||||
}).catch(utils.reject("Couldn't add child view to box", true));
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
// We remove this widget before removing the children as an optimization
|
||||
// we want to remove the entire container from the DOM first before
|
||||
// removing each individual child separately.
|
||||
AccordionView.__super__.remove.apply(this, arguments);
|
||||
this.children_views.remove();
|
||||
},
|
||||
});
|
||||
|
||||
@ -134,8 +141,13 @@ define([
|
||||
var TabView = widget.DOMWidgetView.extend({
|
||||
initialize: function() {
|
||||
// Public constructor.
|
||||
this.containers = [];
|
||||
TabView.__super__.initialize.apply(this, arguments);
|
||||
|
||||
this.containers = [];
|
||||
this.children_views = new widget.ViewList(this.add_child_view, this.remove_child_view, this);
|
||||
this.listenTo(this.model, 'change:children', function(model, value) {
|
||||
this.children_views.update(value);
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function(){
|
||||
@ -149,11 +161,7 @@ define([
|
||||
this.$tab_contents = $('<div />', {id: uuid + 'Content'})
|
||||
.addClass('tab-content')
|
||||
.appendTo(this.$el);
|
||||
this.containers = [];
|
||||
this.update_children([], this.model.get('children'));
|
||||
this.model.on('change:children', function(model, value, options) {
|
||||
this.update_children(model.previous('children'), value);
|
||||
}, this);
|
||||
this.children_views.update(this.model.get('children'));
|
||||
},
|
||||
|
||||
update_attr: function(name, value) {
|
||||
@ -161,24 +169,15 @@ define([
|
||||
this.$tabs.css(name, value);
|
||||
},
|
||||
|
||||
update_children: function(old_list, new_list) {
|
||||
// Called when the children list is modified.
|
||||
this.do_diff(old_list,
|
||||
new_list,
|
||||
$.proxy(this.remove_child_model, this),
|
||||
$.proxy(this.add_child_model, this));
|
||||
},
|
||||
|
||||
remove_child_model: function(model) {
|
||||
remove_child_view: function(view) {
|
||||
// Called when a child is removed from children list.
|
||||
var view = this.pop_child_view(model);
|
||||
this.containers.splice(view.parent_tab.tab_text_index, 1);
|
||||
view.parent_tab.remove();
|
||||
view.parent_container.remove();
|
||||
view.remove();
|
||||
},
|
||||
|
||||
add_child_model: function(model) {
|
||||
add_child_view: function(model) {
|
||||
// Called when a child is added to children list.
|
||||
var index = this.containers.length;
|
||||
var uuid = utils.uuid();
|
||||
@ -221,7 +220,7 @@ define([
|
||||
view.trigger('displayed');
|
||||
});
|
||||
return view;
|
||||
}, utils.reject("Couldn't add child view to box", true));
|
||||
}).catch(utils.reject("Couldn't add child view to box", true));
|
||||
},
|
||||
|
||||
update: function(options) {
|
||||
@ -254,6 +253,14 @@ define([
|
||||
.removeClass('active');
|
||||
this.containers[index].tab('show');
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
// We remove this widget before removing the children as an optimization
|
||||
// we want to remove the entire container from the DOM first before
|
||||
// removing each individual child separately.
|
||||
TabView.__super__.remove.apply(this, arguments);
|
||||
this.children_views.remove();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user