diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js
index d1af78c..4aba4d2 100644
--- a/IPython/html/static/widgets/js/manager.js
+++ b/IPython/html/static/widgets/js/manager.js
@@ -86,6 +86,7 @@
cell.widget_area.show();
this._handle_display_view(view);
cell.widget_subarea.append(view.$el);
+ view.trigger('displayed');
}
}
};
diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js
index 1f48bee..6c711a9 100644
--- a/IPython/html/static/widgets/js/widget.js
+++ b/IPython/html/static/widgets/js/widget.js
@@ -83,7 +83,6 @@ function(WidgetManager, _, Backbone){
break;
case 'display':
this.widget_manager.display_view(msg, this);
- this.trigger('displayed');
break;
}
},
@@ -283,8 +282,10 @@ function(WidgetManager, _, Backbone){
// Public constructor.
this.model.on('change',this.update,this);
this.options = parameters.options;
- this.child_views = [];
+ this.child_model_views = {};
+ this.child_views = {};
this.model.views.push(this);
+ this.id = this.id || IPython.utils.uuid();
},
update: function(){
@@ -302,19 +303,39 @@ function(WidgetManager, _, Backbone){
// TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
// it would be great to have the widget manager add the cell metadata
// to the subview without having to add it here.
- var child_view = this.model.widget_manager.create_view(child_model, options || {}, this);
- this.child_views[child_model.id] = child_view;
+ options = $.extend({ parent: this }, options || {});
+ var child_view = this.model.widget_manager.create_view(child_model, options, this);
+
+ // Associate the view id with the model id.
+ if (this.child_model_views[child_model.id] === undefined) {
+ this.child_model_views[child_model.id] = [];
+ }
+ this.child_model_views[child_model.id].push(child_view.id);
+
+ // Remember the view by id.
+ this.child_views[child_view.id] = child_view;
return child_view;
},
- delete_child_view: function(child_model, options) {
+ pop_child_view: function(child_model) {
// Delete a child view that was previously created using create_child_view.
- var view = this.child_views[child_model.id];
- if (view !== undefined) {
- delete this.child_views[child_model.id];
- view.remove();
+ 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);
child_model.views.pop(view);
+
+ // 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) {
@@ -330,16 +351,23 @@ function(WidgetManager, _, Backbone){
// 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;
+ }
+ }
- // removed items
- _.each(_.difference(old_list, new_list), function(item, index, list) {
- removed_callback(item);
- }, this);
+ // Remove the non-matching items from the old list.
+ for (var j = i; j < old_list.length; j++) {
+ removed_callback(old_list[j]);
+ }
- // added items
- _.each(_.difference(new_list, old_list), function(item, index, list) {
- added_callback(item);
- }, this);
+ // Add the rest of the new list items.
+ for (i; i < new_list.length; i++) {
+ added_callback(new_list[i]);
+ }
},
callbacks: function(){
diff --git a/IPython/html/static/widgets/js/widget_container.js b/IPython/html/static/widgets/js/widget_container.js
index f7cad2a..02bcf03 100644
--- a/IPython/html/static/widgets/js/widget_container.js
+++ b/IPython/html/static/widgets/js/widget_container.js
@@ -21,21 +21,20 @@ define(["widgets/js/widget"], function(WidgetManager) {
// Called when view is rendered.
this.$el.addClass('widget-container')
.addClass('vbox');
- this.children={};
- this.update_children([], this.model.get('_children'));
- this.model.on('change:_children', function(model, value, options) {
- this.update_children(model.previous('_children'), value);
+ 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.update();
// Trigger model displayed events for any models that are child to
// this model when this model is displayed.
var that = this;
- this.model.on('displayed', function(){
+ this.on('displayed', function(){
that.is_displayed = true;
for (var property in that.child_views) {
if (that.child_views.hasOwnProperty(property)) {
- that.child_views[property].model.trigger('displayed');
+ that.child_views[property].trigger('displayed');
}
}
});
@@ -51,8 +50,7 @@ define(["widgets/js/widget"], function(WidgetManager) {
remove_child_model: function(model) {
// Called when a model is removed from the children list.
- this.child_views[model.id].remove();
- this.delete_child_view(model);
+ this.pop_child_view(model).remove();
},
add_child_model: function(model) {
@@ -62,7 +60,7 @@ define(["widgets/js/widget"], function(WidgetManager) {
// Trigger the displayed event if this model is displayed.
if (this.is_displayed) {
- model.trigger('displayed');
+ view.trigger('displayed');
}
},
@@ -81,7 +79,6 @@ define(["widgets/js/widget"], function(WidgetManager) {
render: function(){
// Called when view is rendered.
var that = this;
- this.children={};
this.$el.on("remove", function(){
that.$backdrop.remove();
@@ -187,19 +184,19 @@ define(["widgets/js/widget"], function(WidgetManager) {
this._shown_once = false;
this.popped_out = true;
- this.update_children([], this.model.get('_children'));
- this.model.on('change:_children', function(model, value, options) {
- this.update_children(model.previous('_children'), value);
+ 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.update();
// Trigger model displayed events for any models that are child to
// this model when this model is displayed.
- this.model.on('displayed', function(){
+ this.on('displayed', function(){
that.is_displayed = true;
for (var property in that.child_views) {
if (that.child_views.hasOwnProperty(property)) {
- that.child_views[property].model.trigger('displayed');
+ that.child_views[property].trigger('displayed');
}
}
});
@@ -257,8 +254,7 @@ define(["widgets/js/widget"], function(WidgetManager) {
remove_child_model: function(model) {
// Called when a child is removed from children list.
- this.child_views[model.id].remove();
- this.delete_child_view(model);
+ this.pop_child_view(model).remove();
},
add_child_model: function(model) {
@@ -268,7 +264,7 @@ define(["widgets/js/widget"], function(WidgetManager) {
// Trigger the displayed event if this model is displayed.
if (this.is_displayed) {
- model.trigger('displayed');
+ view.trigger('displayed');
}
},
diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js
index f7a6c6e..6a94205 100644
--- a/IPython/html/static/widgets/js/widget_int.js
+++ b/IPython/html/static/widgets/js/widget_int.js
@@ -55,6 +55,7 @@ define(["widgets/js/widget"], function(WidgetManager){
// one-to-one mapping with the corrosponding keys of the model.
var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
var that = this;
+ that.$slider.slider({});
_.each(jquery_slider_keys, function(key, i) {
var model_value = that.model.get(key);
if (model_value !== undefined) {
diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js
index e7981de..7dd49e0 100644
--- a/IPython/html/static/widgets/js/widget_selectioncontainer.js
+++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js
@@ -25,9 +25,9 @@ define(["widgets/js/widget"], function(WidgetManager){
.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.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);
@@ -36,14 +36,14 @@ define(["widgets/js/widget"], function(WidgetManager){
this.update_titles(value);
}, this);
var that = this;
- this.model.on('displayed', function() {
+ this.on('displayed', function() {
this.update_titles();
// Trigger model displayed events for any models that are child to
// this model when this model is displayed.
that.is_displayed = true;
for (var property in that.child_views) {
if (that.child_views.hasOwnProperty(property)) {
- that.child_views[property].model.trigger('displayed');
+ that.child_views[property].trigger('displayed');
}
}
}, this);
@@ -92,7 +92,7 @@ define(["widgets/js/widget"], function(WidgetManager){
this.containers.splice(accordion_group.container_index, 1);
delete this.model_containers[model.id];
accordion_group.remove();
- this.delete_child_view(model);
+ this.pop_child_view(model);
},
add_child_model: function(model) {
@@ -137,7 +137,7 @@ define(["widgets/js/widget"], function(WidgetManager){
// Trigger the displayed event if this model is displayed.
if (this.is_displayed) {
- model.trigger('displayed');
+ view.trigger('displayed');
}
},
});
@@ -163,18 +163,18 @@ define(["widgets/js/widget"], function(WidgetManager){
.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.update_children([], this.model.get('children'));
+ this.model.on('change:children', function(model, value, options) {
+ this.update_children(model.previous('children'), value);
}, this);
// Trigger model displayed events for any models that are child to
// this model when this model is displayed.
- this.model.on('displayed', function(){
+ this.on('displayed', function(){
that.is_displayed = true;
for (var property in that.child_views) {
if (that.child_views.hasOwnProperty(property)) {
- that.child_views[property].model.trigger('displayed');
+ that.child_views[property].trigger('displayed');
}
}
});
@@ -190,12 +190,11 @@ define(["widgets/js/widget"], function(WidgetManager){
remove_child_model: function(model) {
// Called when a child is removed from children list.
- var view = this.child_views[model.id];
+ 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();
- this.delete_child_view(model);
},
add_child_model: function(model) {
@@ -234,7 +233,7 @@ define(["widgets/js/widget"], function(WidgetManager){
// Trigger the displayed event if this model is displayed.
if (this.is_displayed) {
- model.trigger('displayed');
+ view.trigger('displayed');
}
},
diff --git a/IPython/html/widgets/widget_container.py b/IPython/html/widgets/widget_container.py
index f70fa6b..dce2a2f 100644
--- a/IPython/html/widgets/widget_container.py
+++ b/IPython/html/widgets/widget_container.py
@@ -2,58 +2,29 @@
Represents a container that can be used to group other widgets.
"""
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, the IPython Development Team.
-#
+
+# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
from .widget import DOMWidget
from IPython.utils.traitlets import Unicode, Tuple, TraitError
-#-----------------------------------------------------------------------------
-# Classes
-#-----------------------------------------------------------------------------
-
class ContainerWidget(DOMWidget):
_view_name = Unicode('ContainerView', sync=True)
# Child widgets in the container.
# Using a tuple here to force reassignment to update the list.
# When a proper notifying-list trait exists, that is what should be used here.
- children = Tuple()
- _children = Tuple(sync=True)
-
+ children = Tuple(sync=True)
def __init__(self, **kwargs):
super(ContainerWidget, self).__init__(**kwargs)
self.on_displayed(ContainerWidget._fire_children_displayed)
def _fire_children_displayed(self):
- for child in self._children:
+ for child in self.children:
child._handle_displayed()
- def _children_changed(self, name, old, new):
- """Validate children list.
-
- Makes sure only one instance of any given model can exist in the
- children list.
- An excellent post on uniqifiers is available at
- http://www.peterbe.com/plog/uniqifiers-benchmark
- which provides the inspiration for using this implementation. Below
- I've implemented the `f5` algorithm using Python comprehensions."""
- if new is not None:
- seen = {}
- def add_item(i):
- seen[i.model_id] = True
- return i
- self._children = [add_item(i) for i in new if not i.model_id in seen]
-
class PopupWidget(ContainerWidget):
_view_name = Unicode('PopupView', sync=True)