From ff680583c180fa34f4b54fcbc40caa13d4973f37 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 30 Oct 2014 15:27:59 +0000 Subject: [PATCH 01/40] initial take on promises --- IPython/html/static/widgets/js/manager.js | 103 ++++++++++------------ 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 242c67975..b7d2aeaa9 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -84,13 +84,16 @@ define([ var view_name = model.get('_view_name'); var view_mod = model.get('_view_module'); - var error = options.error || function(error) { console.log(error); }; + var options = options || {}; + + return new Promise(function(resolve, reject) { + var instantiate_view = function(ViewType) { + if (ViewType === undefined) { + reject(Error("Unknown view, module: "+view_mod+", view: "+view_name)); + } - var instantiate_view = function(ViewType) { - if (ViewType) { // If a view is passed into the method, use that view's cell as // the cell for the view that is created. - options = options || {}; if (options.parent !== undefined) { options.cell = options.parent.options.cell; } @@ -99,22 +102,18 @@ define([ var parameters = {model: model, options: options}; var view = new ViewType(parameters); view.render(); - model.on('destroy', view.remove, view); - if (options.success) { - options.success(view); - } + view.listenTo(model, 'destroy', view.remove); + resolve(view); + }; + + + if (view_mod) { + require([view_mod], function(module) { + instantiate_view(module[view_name]); + }, reject); } else { - error({unknown_view: true, view_name: view_name, - view_module: view_mod}); + instantiate_view(WidgetManager._view_types[view_name]); } - }; - - if (view_mod) { - require([view_mod], function(module) { - instantiate_view(module[view_name]); - }, error); - } else { - instantiate_view(WidgetManager._view_types[view_name]); } }; @@ -196,7 +195,7 @@ define([ }; WidgetManager.prototype.create_model = function (options) { - // Create and return a new widget model. + // Create and return a promise to create a new widget model. // // Minimally, one must provide the model_name and widget_class // parameters to create a model from Javascript. @@ -220,56 +219,46 @@ define([ // widget_class: (optional) string // Target name of the widget in the back-end. // comm: (optional) Comm - // success: (optional) callback - // Callback for when the model was created successfully. - // error: (optional) callback - // Callback for when the model wasn't created. // init_state_callback: (optional) callback // Called when the first state push from the back-end is // recieved. Allows you to modify the model after it's // complete state is filled and synced. - // Make default callbacks if not specified. - var error = options.error || function(error) { console.log(error); }; - // Create a comm if it wasn't provided. var comm = options.comm; if (!comm) { comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class}); } - // Create a new model that is connected to the comm. - var that = this; - var instantiate_model = function(ModelType) { - var model_id = comm.comm_id; - var widget_model = new ModelType(that, model_id, comm, options.init_state_callback); - widget_model.on('comm:close', function () { - delete that._models[model_id]; - }); - that._models[model_id] = widget_model; - if (options.success) { - options.success(widget_model); - } - }; - - // Get the model type using require or through the registry. - var widget_type_name = options.model_name; - var widget_module = options.model_module; - if (widget_module) { - - // Load the module containing the widget model - require([widget_module], function(mod) { - if (mod[widget_type_name]) { - instantiate_model(mod[widget_type_name]); - } else { - error("Error creating widget model: " + widget_type_name - + " not found in " + widget_module); + return new Promise(function(resolve, reject) { + // Create a new model that is connected to the comm. + var that = this; + var instantiate_model = function(ModelType) { + if (ModelType === undefined) { + reject(Error("Error creating widget model: " + widget_type_name + + " not found in " + widget_module)); } - }, error); - } else { - - // No module specified, load from the global models registry - instantiate_model(WidgetManager._model_types[widget_type_name]); + var model_id = comm.comm_id; + var widget_model = new ModelType(that, model_id, comm, options.init_state_callback); + widget_model.once('comm:close', function () { + delete that._models[model_id]; + }); + that._models[model_id] = widget_model; + resolve(widget_model); + }; + + // Get the model type using require or through the registry. + var widget_type_name = options.model_name; + var widget_module = options.model_module; + if (widget_module) { + // Load the module containing the widget model + require([widget_module], function(mod) { + instantiate_model(mod[widget_type_name]); + }, reject); + } else { + // No module specified, load from the global models registry + instantiate_model(WidgetManager._model_types[widget_type_name]); + } } }; From 4beda5d0fb143b0638ebce82e2c0168775cfe928 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Thu, 30 Oct 2014 11:26:57 -0700 Subject: [PATCH 02/40] Initial stab at adding promises to the widget framework. --- IPython/html/static/base/js/utils.js | 28 ++++++ IPython/html/static/widgets/js/manager.js | 117 +++++++++------------- IPython/html/static/widgets/js/widget.js | 8 +- IPython/html/tests/widgets/manager.js | 7 +- 4 files changed, 82 insertions(+), 78 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 013389cd4..9d103ab02 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -605,6 +605,33 @@ define([ $.ajax(url, settings); }); }; + + var try_load = function(class_name, module_name, registry) { + // Tries to load a class + // + // Tries to load a class from a module using require.js, if a module + // is specified, otherwise tries to load a class from the global + // registry, if the global registry is provided. + return new Promise(function(resolve, reject) { + + // Try loading the view module using require.js + if (module_name) { + require([module_name], function(module) { + if (module[class_name] === undefined) { + reject(Error('Class not found in module.')); + } else { + resolve(module[class_name]); + } + }, reject); + } else { + if (registry && registry[class_name]) { + resolve(registry[class_name]); + } else { + reject(Error('Class not found in registry.')); + } + } + }); + }; var utils = { regex_split : regex_split, @@ -635,6 +662,7 @@ define([ XHR_ERROR : XHR_ERROR, wrap_ajax_error : wrap_ajax_error, promising_ajax : promising_ajax, + try_load: try_load, }; // Backwards compatability. diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index b7d2aeaa9..0ee7b49b8 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -5,8 +5,9 @@ define([ "underscore", "backbone", "jquery", + "base/js/utils", "base/js/namespace" -], function (_, Backbone, $, IPython) { +], function (_, Backbone, $, utils, IPython) { "use strict"; //-------------------------------------------------------------------- // WidgetManager class @@ -52,14 +53,20 @@ define([ console.log("Could not determine where the display" + " message was from. Widget will not be displayed"); } else { + var dummy = null; + if (cell.widget_subarea) { + dummy = $('
'); + cell.widget_subarea.append(dummy); + } + var that = this; - this.create_view(model, {cell: cell, success: function(view) { + this.create_view(model, {cell: cell}).then(function(view) { that._handle_display_view(view); - if (cell.widget_subarea) { - cell.widget_subarea.append(view.$el); + if (dummy) { + dummy.replaceWith(view.$el); } view.trigger('displayed'); - }}); + }, function(error) { console.error(error); }); } }; @@ -70,30 +77,25 @@ define([ if (this.keyboard_manager) { this.keyboard_manager.register_events(view.$el); - if (view.additional_elements) { - for (var i = 0; i < view.additional_elements.length; i++) { - this.keyboard_manager.register_events(view.additional_elements[i]); - } - } + if (view.additional_elements) { + for (var i = 0; i < view.additional_elements.length; i++) { + this.keyboard_manager.register_events(view.additional_elements[i]); + } + } } }; WidgetManager.prototype.create_view = function(model, options) { // Creates a view for a particular model. - - var view_name = model.get('_view_name'); - var view_mod = model.get('_view_module'); - var options = options || {}; - return new Promise(function(resolve, reject) { - var instantiate_view = function(ViewType) { - if (ViewType === undefined) { - reject(Error("Unknown view, module: "+view_mod+", view: "+view_name)); - } + var view_name = model.get('_view_name'); + var view_module = model.get('_view_module'); + utils.try_load(view_name, view_module, WidgetManager._view_types).then(function(ViewType){ // If a view is passed into the method, use that view's cell as // the cell for the view that is created. + options = options || {}; if (options.parent !== undefined) { options.cell = options.parent.options.cell; } @@ -102,19 +104,10 @@ define([ var parameters = {model: model, options: options}; var view = new ViewType(parameters); view.render(); - view.listenTo(model, 'destroy', view.remove); + model.on('destroy', view.remove, view); resolve(view); - }; - - - if (view_mod) { - require([view_mod], function(module) { - instantiate_view(module[view_name]); - }, reject); - } else { - instantiate_view(WidgetManager._view_types[view_name]); - } - } + }, reject); + }); }; WidgetManager.prototype.get_msg_cell = function (msg_id) { @@ -205,8 +198,9 @@ define([ // JS: // IPython.notebook.kernel.widget_manager.create_model({ // model_name: 'WidgetModel', - // widget_class: 'IPython.html.widgets.widget_int.IntSlider', - // init_state_callback: function(model) { console.log('Create success!', model); }}); + // widget_class: 'IPython.html.widgets.widget_int.IntSlider'}) + // .then(function(model) { console.log('Create success!', model); }, + // function(error) { console.error(error); }); // // Parameters // ---------- @@ -219,50 +213,33 @@ define([ // widget_class: (optional) string // Target name of the widget in the back-end. // comm: (optional) Comm - // init_state_callback: (optional) callback - // Called when the first state push from the back-end is - // recieved. Allows you to modify the model after it's - // complete state is filled and synced. - - // Create a comm if it wasn't provided. - var comm = options.comm; - if (!comm) { - comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class}); - } - return new Promise(function(resolve, reject) { - // Create a new model that is connected to the comm. - var that = this; - var instantiate_model = function(ModelType) { - if (ModelType === undefined) { - reject(Error("Error creating widget model: " + widget_type_name - + " not found in " + widget_module)); - } - var model_id = comm.comm_id; - var widget_model = new ModelType(that, model_id, comm, options.init_state_callback); - widget_model.once('comm:close', function () { - delete that._models[model_id]; - }); - that._models[model_id] = widget_model; - resolve(widget_model); - }; - + // Get the model type using require or through the registry. var widget_type_name = options.model_name; var widget_module = options.model_module; - if (widget_module) { - // Load the module containing the widget model - require([widget_module], function(mod) { - instantiate_model(mod[widget_type_name]); + var that = this; + utils.try_load(widget_type_name, widget_module, WidgetManager._model_types) + .then(function(ModelType) { + + // Create a comm if it wasn't provided. + var comm = options.comm; + if (!comm) { + comm = that.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class}); + } + + var model_id = comm.comm_id; + var widget_model = new ModelType(that, model_id, comm); + widget_model.on('comm:close', function () { + delete that._models[model_id]; + }); + that._models[model_id] = widget_model; + reolve(widget_model); }, reject); - } else { - // No module specified, load from the global models registry - instantiate_model(WidgetManager._model_types[widget_type_name]); - } - } + }); }; - // Backwards compatability. + // Backwards compatibility. IPython.WidgetManager = WidgetManager; return {'WidgetManager': WidgetManager}; diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index ea5c062c5..499dd694f 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -328,7 +328,9 @@ define(["widgets/js/manager", // to the subview without having to add it here. var that = this; var old_callback = options.callback || function(view) {}; - options = $.extend({ parent: this, success: function(child_view) { + options = $.extend({ parent: this }, options || {}); + + 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] = []; @@ -338,9 +340,7 @@ define(["widgets/js/manager", // Remember the view by id. that.child_views[child_view.id] = child_view; old_callback(child_view); - }}, options || {}); - - this.model.widget_manager.create_view(child_model, options); + }, function(error) { console.error(error); }); }, pop_child_view: function(child_model) { diff --git a/IPython/html/tests/widgets/manager.js b/IPython/html/tests/widgets/manager.js index fed79fd85..3a0120325 100644 --- a/IPython/html/tests/widgets/manager.js +++ b/IPython/html/tests/widgets/manager.js @@ -18,12 +18,11 @@ casper.notebook_test(function () { this.evaluate(function() { IPython.notebook.kernel.widget_manager.create_model({ model_name: 'WidgetModel', - widget_class: 'IPython.html.widgets.widget_int.IntSlider', - init_state_callback: function(model) { + widget_class: 'IPython.html.widgets.widget_int.IntSlider'}) + .then(function(model) { console.log('Create success!', model); window.slider_id = model.id; - } - }); + }, function(error) { console.log(error); }); }); }); From 78c0bbdb4dcbbc0e264675428cb8cd6d03807806 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Thu, 30 Oct 2014 11:50:53 -0700 Subject: [PATCH 03/40] Fix view rendering order. --- IPython/html/static/widgets/js/manager.js | 4 +- IPython/html/static/widgets/js/widget.js | 36 ++++++------ IPython/html/static/widgets/js/widget_box.js | 8 ++- .../widgets/js/widget_selectioncontainer.js | 56 ++++++++++--------- 4 files changed, 55 insertions(+), 49 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 0ee7b49b8..60c0f44f8 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -66,7 +66,7 @@ define([ dummy.replaceWith(view.$el); } view.trigger('displayed'); - }, function(error) { console.error(error); }); + }, console.error); } }; @@ -200,7 +200,7 @@ define([ // model_name: 'WidgetModel', // widget_class: 'IPython.html.widgets.widget_int.IntSlider'}) // .then(function(model) { console.log('Create success!', model); }, - // function(error) { console.error(error); }); + // console.error); // // Parameters // ---------- diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 499dd694f..0cebd8898 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -322,25 +322,25 @@ define(["widgets/js/manager", // // -given a model and (optionally) a view name if the view name is // not given, it defaults to the model's default view attribute. - - // 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 that = this; - var old_callback = options.callback || function(view) {}; - options = $.extend({ parent: this }, options || {}); - - 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); + return new Promise(function(resolve, reject) { + // 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 that = this; + options = $.extend({ parent: this }, options || {}); + + 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; - old_callback(child_view); - }, function(error) { console.error(error); }); + // Remember the view by id. + that.child_views[child_view.id] = child_view; + resolve(child_view); + }, reject); + }); }, pop_child_view: function(child_model) { diff --git a/IPython/html/static/widgets/js/widget_box.js b/IPython/html/static/widgets/js/widget_box.js index 1d2edcbf0..323c5d8d0 100644 --- a/IPython/html/static/widgets/js/widget_box.js +++ b/IPython/html/static/widgets/js/widget_box.js @@ -75,14 +75,16 @@ define([ add_child_model: function(model) { // Called when a model is added to the children list. var that = this; - this.create_child_view(model, {callback: function(view) { - that.$box.append(view.$el); + var dummy = $('
'); + that.$box.append(dummy); + this.create_child_view(model).then(function(view) { + dummy.replaceWith(view.$el); // Trigger the displayed event of the child view. that.after_displayed(function() { view.trigger('displayed'); }); - }}); + }, console.error); }, }); diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js index 40b6e4307..90603c25c 100644 --- a/IPython/html/static/widgets/js/widget_selectioncontainer.js +++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js @@ -114,9 +114,10 @@ define([ accordion_group.container_index = container_index; this.model_containers[model.id] = accordion_group; - this.create_child_view(model, {callback: function(view) { - accordion_inner.append(view.$el); - + var dummy = $('
'); + accordion_inner.append(dummy); + this.create_child_view(model).then(function(view) { + dummy.replaceWith(view.$el); that.update(); that.update_titles(); @@ -124,7 +125,7 @@ define([ that.after_displayed(function() { view.trigger('displayed'); }); - }}); + }, console.error); }, }); @@ -186,36 +187,39 @@ define([ .css('list-style-type', 'none') .appendTo(this.$tabs); - this.create_child_view(model, {callback: function(view) { - view.parent_tab = tab; - var tab_text = $('') - .attr('href', '#' + uuid) - .attr('data-toggle', 'tab') - .text('Page ' + index) - .appendTo(tab) - .click(function (e) { - - // Calling model.set will trigger all of the other views of the - // model to update. - that.model.set("selected_index", index, {updated_view: that}); - that.touch(); - that.select_page(index); - }); - tab.tab_text_index = that.containers.push(tab_text) - 1; + var tab_text = $('') + .attr('href', '#' + uuid) + .attr('data-toggle', 'tab') + .text('Page ' + index) + .appendTo(tab) + .click(function (e) { + + // Calling model.set will trigger all of the other views of the + // model to update. + that.model.set("selected_index", index, {updated_view: that}); + that.touch(); + that.select_page(index); + }); + tab.tab_text_index = that.containers.push(tab_text) - 1; + + var dummy = $('
'); + var contents_div = $('
', {id: uuid}) + .addClass('tab-pane') + .addClass('fade') + .append(dummy) + .appendTo(that.$tab_contents); - var contents_div = $('
', {id: uuid}) - .addClass('tab-pane') - .addClass('fade') - .append(view.$el) - .appendTo(that.$tab_contents); + this.create_child_view(model).then(function(view) { + dummy.replaceWith(view.$el); + view.parent_tab = tab; view.parent_container = contents_div; // Trigger the displayed event of the child view. that.after_displayed(function() { view.trigger('displayed'); }); - }}); + }, console.error); }, update: function(options) { From 5a745be1388efb2d77d426aad81a74e5e2e731f2 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Thu, 30 Oct 2014 14:56:04 -0700 Subject: [PATCH 04/40] Add Promise support to models. --- IPython/html/static/widgets/js/manager.js | 40 +++++++----- IPython/html/static/widgets/js/widget.js | 79 +++++++++++++++-------- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 60c0f44f8..07dc38348 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -172,11 +172,19 @@ define([ WidgetManager.prototype.get_model = function (model_id) { // Look-up a model instance by its id. - var model = this._models[model_id]; - if (model !== undefined && model.id == model_id) { - return model; + var that = this; + var model = that._models[model_id]; + if (model !== undefined) { + return new Promise(function(resolve, reject){ + if (model instanceof Promise) { + model.then(resolve, reject); + } else { + resolve(model); + } + }); + } else { + return undefined; } - return null; }; WidgetManager.prototype._handle_comm_open = function (comm, msg) { @@ -213,30 +221,32 @@ define([ // widget_class: (optional) string // Target name of the widget in the back-end. // comm: (optional) Comm - return new Promise(function(resolve, reject) { + + // Create a comm if it wasn't provided. + var comm = options.comm; + if (!comm) { + comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class}); + } + + var that = this; + var model_id = comm.comm_id; + var promise = new Promise(function(resolve, reject) { // Get the model type using require or through the registry. var widget_type_name = options.model_name; var widget_module = options.model_module; - var that = this; utils.try_load(widget_type_name, widget_module, WidgetManager._model_types) .then(function(ModelType) { - - // Create a comm if it wasn't provided. - var comm = options.comm; - if (!comm) { - comm = that.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class}); - } - - var model_id = comm.comm_id; var widget_model = new ModelType(that, model_id, comm); widget_model.on('comm:close', function () { delete that._models[model_id]; }); that._models[model_id] = widget_model; - reolve(widget_model); + resolve(widget_model); }, reject); }); + this._models[model_id] = promise; + return promise; }; // Backwards compatibility. diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 0cebd8898..6e99190a1 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -92,11 +92,22 @@ define(["widgets/js/manager", // Handle when a widget is updated via the python side. this.state_lock = state; try { - var that = this; - WidgetModel.__super__.set.apply(this, [Object.keys(state).reduce(function(obj, key) { - obj[key] = that._unpack_models(state[key]); - return obj; - }, {})]); + var state_keys = []; + var state_values = []; + for (var state_key in Object.keys(state)) { + if (state.hasOwnProperty(state_key)) { + state_keys.push(state_key); + state_values.push(this._unpack_models(state[state_key])); + } + } + + Promise.all(state_values).then(function(promise_values){ + var unpacked_state = {}; + for (var i = 0; i < state_keys.length; i++) { + unpacked_state[state_keys[i]] = promise_values[i]; + } + WidgetModel.__super__.set.apply(this, [unpacked_state]); + }, console.error); } finally { this.state_lock = null; } @@ -254,30 +265,42 @@ define(["widgets/js/manager", // Replace model ids with models recursively. var that = this; var unpacked; - if ($.isArray(value)) { - unpacked = []; - _.each(value, function(sub_value, key) { - unpacked.push(that._unpack_models(sub_value)); - }); - return unpacked; - - } else if (value instanceof Object) { - unpacked = {}; - _.each(value, function(sub_value, key) { - unpacked[key] = that._unpack_models(sub_value); - }); - return unpacked; + return new Promise(function(resolve, reject) { - } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") { - var model = this.widget_manager.get_model(value.slice(10, value.length)); - if (model) { - return model; + if ($.isArray(value)) { + unpacked = []; + _.each(value, function(sub_value, key) { + unpacked.push(that._unpack_models(sub_value)); + }); + Promise.all(unpacked).then(resolve, reject); + + } else if (value instanceof Object) { + unpacked_values = []; + unpacked_keys = []; + _.each(value, function(sub_value, key) { + unpacked_keys.push(key); + unpacked_values.push(that._unpack_models(sub_value)); + }); + + Promise.all(unpacked_values).then(function(promise_values) { + unpacked = {}; + for (var i = 0; i < unpacked_keys.length; i++) { + unpacked[unpacked_keys[i]] = promise_values[i]; + } + resolve(unpacked); + }, reject); + + } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") { + var model = this.widget_manager.get_model(value.slice(10, value.length)); + if (model) { + model.then(resolve, reject); + } else { + resolve(value); + } } else { - return value; - } - } else { - return value; - } + resolve(value); + } + }); }, on_some_change: function(keys, callback, context) { @@ -322,11 +345,11 @@ define(["widgets/js/manager", // // -given a model and (optionally) a view name if the view name is // not given, it defaults to the model's default view attribute. + var that = this; return new Promise(function(resolve, reject) { // 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 that = this; options = $.extend({ parent: this }, options || {}); this.model.widget_manager.create_view(child_model, options).then(function(child_view) { From 2eae36ac9a5f9a03faa1d5e85a9b13bbf8b46137 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 31 Oct 2014 09:01:06 -0700 Subject: [PATCH 05/40] Use load_class in comms instead of callbacks. --- IPython/html/static/services/kernels/comm.js | 27 +++----------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index f25517b82..2ef5d339c 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -67,7 +67,8 @@ define([ var content = msg.content; var that = this; - var instantiate_comm = function(target) { + utils.load_class(content.target_name, content.target_module, this.targets) + .then(function(target) { var comm = new Comm(content.target_name, content.comm_id); that.register_comm(comm); try { @@ -77,29 +78,7 @@ define([ comm.close(); that.unregister_comm(comm); } - }; - - if (content.target_module) { - // Load requirejs module for comm target - require([content.target_module], function(mod) { - var target = mod[content.target_name]; - if (target !== undefined) { - instantiate_comm(target) - } else { - console.log("Comm target " + content.target_name + - " not found in module " + content.target_module); - } - }, function(err) { console.log(err); }); - } else { - // No requirejs module specified: look for target in registry - var f = this.targets[content.target_name]; - if (f === undefined) { - console.log("No such target registered: ", content.target_name); - console.log("Available targets are: ", this.targets); - return; - } - instantiate_comm(f) - } + }, console.error); }; CommManager.prototype.comm_close = function (msg) { From c41dcb35e9f49ed125c13d9c02957a622269522d Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 31 Oct 2014 11:00:39 -0700 Subject: [PATCH 06/40] Proxy console.error calls --- IPython/html/static/services/kernels/comm.js | 2 +- IPython/html/static/widgets/js/manager.js | 4 ++-- IPython/html/static/widgets/js/widget.js | 2 +- IPython/html/static/widgets/js/widget_box.js | 2 +- IPython/html/static/widgets/js/widget_selectioncontainer.js | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index 2ef5d339c..1a3aebaac 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -78,7 +78,7 @@ define([ comm.close(); that.unregister_comm(comm); } - }, console.error); + }, $.proxy(console.error, console)); }; CommManager.prototype.comm_close = function (msg) { diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 07dc38348..64eed3636 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -66,7 +66,7 @@ define([ dummy.replaceWith(view.$el); } view.trigger('displayed'); - }, console.error); + }, $.proxy(console.error, console)); } }; @@ -208,7 +208,7 @@ define([ // model_name: 'WidgetModel', // widget_class: 'IPython.html.widgets.widget_int.IntSlider'}) // .then(function(model) { console.log('Create success!', model); }, - // console.error); + // $.proxy(console.error, console)); // // Parameters // ---------- diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 6e99190a1..dbc98d328 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -107,7 +107,7 @@ define(["widgets/js/manager", unpacked_state[state_keys[i]] = promise_values[i]; } WidgetModel.__super__.set.apply(this, [unpacked_state]); - }, console.error); + }, $.proxy(console.error, console)); } finally { this.state_lock = null; } diff --git a/IPython/html/static/widgets/js/widget_box.js b/IPython/html/static/widgets/js/widget_box.js index 323c5d8d0..ae0a1f2e5 100644 --- a/IPython/html/static/widgets/js/widget_box.js +++ b/IPython/html/static/widgets/js/widget_box.js @@ -84,7 +84,7 @@ define([ that.after_displayed(function() { view.trigger('displayed'); }); - }, console.error); + }, $.proxy(console.error, console)); }, }); diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js index 90603c25c..5e26d0f3d 100644 --- a/IPython/html/static/widgets/js/widget_selectioncontainer.js +++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js @@ -125,7 +125,7 @@ define([ that.after_displayed(function() { view.trigger('displayed'); }); - }, console.error); + }, $.proxy(console.error, console)); }, }); @@ -219,7 +219,7 @@ define([ that.after_displayed(function() { view.trigger('displayed'); }); - }, console.error); + }, $.proxy(console.error, console)); }, update: function(options) { From 4412c129293760588439dbae48ebe95f5cac834d Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 31 Oct 2014 16:12:17 +0000 Subject: [PATCH 07/40] Simplify code by using Promises in a better way; try_load -> load --- IPython/html/static/base/js/utils.js | 10 +-- IPython/html/static/widgets/js/manager.js | 81 ++++++++--------------- 2 files changed, 33 insertions(+), 58 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 9d103ab02..aeec8a3ee 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -606,7 +606,7 @@ define([ }); }; - var try_load = function(class_name, module_name, registry) { + var load = function(class_name, module_name, registry) { // Tries to load a class // // Tries to load a class from a module using require.js, if a module @@ -618,7 +618,7 @@ define([ if (module_name) { require([module_name], function(module) { if (module[class_name] === undefined) { - reject(Error('Class not found in module.')); + reject(new Error('Class not found in module.')); } else { resolve(module[class_name]); } @@ -627,12 +627,12 @@ define([ if (registry && registry[class_name]) { resolve(registry[class_name]); } else { - reject(Error('Class not found in registry.')); + reject(new Error('Class not found in registry.')); } } }); }; - + var utils = { regex_split : regex_split, uuid : uuid, @@ -662,7 +662,7 @@ define([ XHR_ERROR : XHR_ERROR, wrap_ajax_error : wrap_ajax_error, promising_ajax : promising_ajax, - try_load: try_load, + load: load, }; // Backwards compatability. diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 64eed3636..10ffe0bda 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -87,27 +87,22 @@ define([ WidgetManager.prototype.create_view = function(model, options) { - // Creates a view for a particular model. - return new Promise(function(resolve, reject) { - var view_name = model.get('_view_name'); - var view_module = model.get('_view_module'); - utils.try_load(view_name, view_module, WidgetManager._view_types).then(function(ViewType){ - - // If a view is passed into the method, use that view's cell as - // the cell for the view that is created. - options = options || {}; - if (options.parent !== undefined) { - options.cell = options.parent.options.cell; - } - - // Create and render the view... - var parameters = {model: model, options: options}; - var view = new ViewType(parameters); - view.render(); - model.on('destroy', view.remove, view); - resolve(view); - }, reject); - }); + // Creates a promise for a view of a given model + return utils.load(model.get('_view_name'), model.get('_view_module'), + WidgetManager._view_types).then(function(ViewType) { + // If a view is passed into the method, use that view's cell as + // the cell for the view that is created. + options = options || {}; + if (options.parent !== undefined) { + options.cell = options.parent.options.cell; + } + // Create and render the view... + var parameters = {model: model, options: options}; + var view = new ViewType(parameters); + view.listenTo(model, 'destroy', view.remove); + view.render(); + return view; + }); }; WidgetManager.prototype.get_msg_cell = function (msg_id) { @@ -171,20 +166,7 @@ define([ }; WidgetManager.prototype.get_model = function (model_id) { - // Look-up a model instance by its id. - var that = this; - var model = that._models[model_id]; - if (model !== undefined) { - return new Promise(function(resolve, reject){ - if (model instanceof Promise) { - model.then(resolve, reject); - } else { - resolve(model); - } - }); - } else { - return undefined; - } + return that._models[model_id]; }; WidgetManager.prototype._handle_comm_open = function (comm, msg) { @@ -196,7 +178,7 @@ define([ }; WidgetManager.prototype.create_model = function (options) { - // Create and return a promise to create a new widget model. + // Create and return a promise for a new widget model // // Minimally, one must provide the model_name and widget_class // parameters to create a model from Javascript. @@ -230,23 +212,16 @@ define([ var that = this; var model_id = comm.comm_id; - var promise = new Promise(function(resolve, reject) { - - // Get the model type using require or through the registry. - var widget_type_name = options.model_name; - var widget_module = options.model_module; - utils.try_load(widget_type_name, widget_module, WidgetManager._model_types) - .then(function(ModelType) { - var widget_model = new ModelType(that, model_id, comm); - widget_model.on('comm:close', function () { - delete that._models[model_id]; - }); - that._models[model_id] = widget_model; - resolve(widget_model); - }, reject); - }); - this._models[model_id] = promise; - return promise; + var model_promise = utils.load(options.model_name, options.model_module, WidgetManager._model_types) + .then(function(ModelType) { + var widget_model = new ModelType(that, model_id, comm); + widget_model.once('comm:close', function () { + delete that._models[model_id]; + }); + return widget_model; + }); + this._models[model_id] = model_promise; + return model_promise; }; // Backwards compatibility. From 686e73dfdfe4ff4be28a9117652c640bafd95cda Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 31 Oct 2014 20:37:06 +0000 Subject: [PATCH 08/40] More simplifications due to promises --- IPython/html/static/base/js/utils.js | 16 +++ IPython/html/static/widgets/js/manager.js | 2 +- IPython/html/static/widgets/js/widget.js | 129 ++++++------------- IPython/html/static/widgets/js/widget_box.js | 4 +- 4 files changed, 61 insertions(+), 90 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index aeec8a3ee..ff9febf9c 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -633,6 +633,21 @@ define([ }); }; + var resolve_dict = function(d) { + var keys = Object.keys(d); + var values = []; + keys.forEach(function(key) { + values.push(key); + }); + return Promise.all(values).then(function(v) { + d = {}; + for(var i=0; i'); that.$box.append(dummy); this.create_child_view(model).then(function(view) { - dummy.replaceWith(view.$el); + dummy.replaceWith(view.el); // Trigger the displayed event of the child view. that.after_displayed(function() { view.trigger('displayed'); }); - }, $.proxy(console.error, console)); + }); }, }); From 1b9948d1786a1e9bdb82a42427f33b633ef0b1eb Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 31 Oct 2014 20:45:16 +0000 Subject: [PATCH 09/40] Add some error handling for creating views and models --- IPython/html/static/widgets/js/manager.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index f809f1b5b..501b84095 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -102,6 +102,9 @@ define([ view.listenTo(model, 'destroy', view.remove); view.render(); return view; + }, function(error) { + console.error(error); + return Promise.reject(error); }); }; @@ -219,6 +222,10 @@ define([ delete that._models[model_id]; }); return widget_model; + }, function(error) { + delete that._models[model_id]; + console.error(error); + return Promise.reject(error); }); this._models[model_id] = model_promise; return model_promise; From fc94383487f90099b6dd452ff8e52cd9859c6002 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 31 Oct 2014 22:45:06 +0000 Subject: [PATCH 10/40] Better error messages with correct stack traces --- IPython/html/static/base/js/utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index ff9febf9c..b6cc19411 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -618,7 +618,8 @@ define([ if (module_name) { require([module_name], function(module) { if (module[class_name] === undefined) { - reject(new Error('Class not found in module.')); + console.error('Class '+class_name+' not found in module '+module_name) + reject(); } else { resolve(module[class_name]); } @@ -627,7 +628,8 @@ define([ if (registry && registry[class_name]) { resolve(registry[class_name]); } else { - reject(new Error('Class not found in registry.')); + console.error('Class '+class_name+' not found in registry ', registry); + reject(); } } }); From f11a51bb779f591e40682b2a4a69cb44f981c783 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 31 Oct 2014 22:45:25 +0000 Subject: [PATCH 11/40] Dictionary key/value typo --- IPython/html/static/base/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index b6cc19411..5e44f55e1 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -639,7 +639,7 @@ define([ var keys = Object.keys(d); var values = []; keys.forEach(function(key) { - values.push(key); + values.push(d[key]); }); return Promise.all(values).then(function(v) { d = {}; From 8e769a012f0612e3b9f80b2ea922fb7edd9f8b18 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 31 Oct 2014 22:46:13 +0000 Subject: [PATCH 12/40] Load the utils module --- IPython/html/static/widgets/js/widget.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 3ba18b894..daa85fef2 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -3,10 +3,11 @@ define(["widgets/js/manager", "underscore", - "backbone", - "jquery", + "backbone", + "jquery", + "base/js/utils", "base/js/namespace", -], function(widgetmanager, _, Backbone, $, IPython){ +], function(widgetmanager, _, Backbone, $, utils, IPython){ var WidgetModel = Backbone.Model.extend({ constructor: function (widget_manager, model_id, comm) { @@ -259,7 +260,7 @@ define(["widgets/js/manager", _.each(value, function(sub_value, key) { unpacked[key] = that._unpack_models(sub_value) }); - return util.resolve_dict(unpacked); + return utils.resolve_dict(unpacked); } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") { // get_model returns a promise already return this.widget_manager.get_model(value.slice(10, value.length)); @@ -292,7 +293,7 @@ define(["widgets/js/manager", this.options = parameters.options; this.child_model_views = {}; this.child_views = {}; - this.id = this.id || IPython.utils.uuid(); + this.id = this.id || utils.uuid(); this.model.views[this.id] = this; this.on('displayed', function() { this.is_displayed = true; From a4331a93ccf37dbf0166b642b0b5bc1c21a28437 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 31 Oct 2014 22:46:45 +0000 Subject: [PATCH 13/40] Fix some this vs. that errors, as well as returning some promises to wait for fulfillment --- IPython/html/static/widgets/js/widget.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index daa85fef2..568eaaf52 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -69,10 +69,11 @@ define(["widgets/js/manager", _handle_comm_msg: function (msg) { // Handle incoming comm msg. var method = msg.content.data.method; + var that = this; switch (method) { case 'update': this.state_change = this.state_change.then(function() { - this.set_state(msg.content.data.state); + return that.set_state(msg.content.data.state); }); break; case 'custom': @@ -87,10 +88,10 @@ define(["widgets/js/manager", set_state: function (state) { var that = this; // Handle when a widget is updated via the python side. - this._unpack_models(state).then(function(state) { + return this._unpack_models(state).then(function(state) { that.state_lock = state; try { - WidgetModel.__super__.set.call(this, state); + WidgetModel.__super__.set.call(that, state); } finally { that.state_lock = null; } From b16b2e8749cb367fc2499ac5b711b97581db9567 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 31 Oct 2014 16:44:25 -0700 Subject: [PATCH 14/40] Make display also pend on set_state. --- IPython/html/static/widgets/js/manager.js | 36 +++++++++++------------ IPython/html/static/widgets/js/widget.js | 4 ++- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 501b84095..caa8fd9bb 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -85,27 +85,27 @@ define([ } }; - WidgetManager.prototype.create_view = function(model, options) { // Creates a promise for a view of a given model return utils.load(model.get('_view_name'), model.get('_view_module'), - WidgetManager._view_types).then(function(ViewType) { - // If a view is passed into the method, use that view's cell as - // the cell for the view that is created. - options = options || {}; - if (options.parent !== undefined) { - options.cell = options.parent.options.cell; - } - // Create and render the view... - var parameters = {model: model, options: options}; - var view = new ViewType(parameters); - view.listenTo(model, 'destroy', view.remove); - view.render(); - return view; - }, function(error) { - console.error(error); - return Promise.reject(error); - }); + WidgetManager._view_types).then(function(ViewType) { + + // If a view is passed into the method, use that view's cell as + // the cell for the view that is created. + options = options || {}; + if (options.parent !== undefined) { + options.cell = options.parent.options.cell; + } + // Create and render the view... + var parameters = {model: model, options: options}; + var view = new ViewType(parameters); + view.listenTo(model, 'destroy', view.remove); + view.render(); + return view; + }, function(error) { + console.error(error); + return Promise.reject(error); + }); }; WidgetManager.prototype.get_msg_cell = function (msg_id) { diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 568eaaf52..0c3cbbf2e 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -80,7 +80,9 @@ define(["widgets/js/manager", this.trigger('msg:custom', msg.content.data.content); break; case 'display': - this.widget_manager.display_view(msg, this); + this.state_change = this.state_change.then(function () { + that.widget_manager.display_view(msg, that); + }); break; } }, From 1d1572421fadcd3cdf5cbcaa5e5b18660e3af753 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 3 Nov 2014 13:35:19 -0800 Subject: [PATCH 15/40] Add a WrappedError class --- IPython/html/static/base/js/utils.js | 57 ++++++++++++++++-- IPython/html/static/services/kernels/comm.js | 11 ++-- IPython/html/static/widgets/js/manager.js | 61 +++++++++++--------- IPython/html/static/widgets/js/widget.js | 2 +- IPython/html/static/widgets/js/widget_box.js | 5 +- 5 files changed, 95 insertions(+), 41 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 5e44f55e1..5ea6705cd 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -606,7 +606,7 @@ define([ }); }; - var load = function(class_name, module_name, registry) { + var load_class = function(class_name, module_name, registry) { // Tries to load a class // // Tries to load a class from a module using require.js, if a module @@ -618,8 +618,7 @@ define([ if (module_name) { require([module_name], function(module) { if (module[class_name] === undefined) { - console.error('Class '+class_name+' not found in module '+module_name) - reject(); + reject(new Error('Class '+class_name+' not found in module '+module_name)); } else { resolve(module[class_name]); } @@ -628,14 +627,18 @@ define([ if (registry && registry[class_name]) { resolve(registry[class_name]); } else { - console.error('Class '+class_name+' not found in registry ', registry); - reject(); + reject(new Error({ + message: 'Class '+class_name+' not found in registry ', + registry: registry + })); } } }); }; var resolve_dict = function(d) { + // Resolve a promiseful dictionary. + // Returns a single Promise. var keys = Object.keys(d); var values = []; keys.forEach(function(key) { @@ -650,6 +653,46 @@ define([ }); }; + var WrappedError = function(message, error){ + // Wrappable Error class + + // The Error class doesn't actually act on `this`. Instead it always + // returns a new instance of Error. Here we capture that instance so we + // can apply it's properties to `this`. + var tmp = Error.apply(this, [message]); + + // Copy the properties of the error over to this. + var properties = Object.getOwnPropertyNames(tmp); + for (var i = 0; i < properties.length; i++) { + this[properties[i]] = tmp[properties[i]]; + } + + // Keep a stack of the original error messages. + if (error instanceof WrappedError) { + this.error_stack = error.error_stack; + } else { + this.error_stack = [error]; + } + this.error_stack.push(tmp); + + return this; + }; + + WrappedError.prototype = Object.create(Error.prototype, {}); + + var reject = function(message, log) { + // Creates a wrappable Promise rejection function. + // + // Creates a function that returns a Promise.reject with a new WrappedError + // that has the provided message and wraps the original error that + // caused the promise to reject. + return function(error) { + var wrapped_error = new WrappedError(message, error); + if (log) console.error(wrapped_error); + return Promise.reject(wrapped_error); + }; + }; + var utils = { regex_split : regex_split, uuid : uuid, @@ -679,8 +722,10 @@ define([ XHR_ERROR : XHR_ERROR, wrap_ajax_error : wrap_ajax_error, promising_ajax : promising_ajax, - load: load, + load_class: load_class, resolve_dict: resolve_dict, + WrappedError: WrappedError, + reject: reject, }; // Backwards compatability. diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index 1a3aebaac..0c99ad273 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -67,18 +67,21 @@ define([ var content = msg.content; var that = this; - utils.load_class(content.target_name, content.target_module, this.targets) - .then(function(target) { + return utils.load_class(content.target_name, content.target_module, + this.targets).then(function(target) { + var comm = new Comm(content.target_name, content.comm_id); that.register_comm(comm); try { target(comm, msg); } catch (e) { - console.log("Exception opening new comm:", e, e.stack, msg); comm.close(); that.unregister_comm(comm); + var error = new utils.WrappedError("Exception opening new comm", e); + return Promise.reject(error); } - }, $.proxy(console.error, console)); + return comm; + }, utils.reject('Could not open comm', true)); }; CommManager.prototype.comm_close = function (msg) { diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index caa8fd9bb..c3c5ccee5 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -48,26 +48,33 @@ define([ //-------------------------------------------------------------------- WidgetManager.prototype.display_view = function(msg, model) { // Displays a view for a particular model. - var cell = this.get_msg_cell(msg.parent_header.msg_id); - if (cell === null) { - console.log("Could not determine where the display" + - " message was from. Widget will not be displayed"); - } else { - var dummy = null; - if (cell.widget_subarea) { - dummy = $('
'); - cell.widget_subarea.append(dummy); + return new Promise(function(resolve, reject) { + var cell = this.get_msg_cell(msg.parent_header.msg_id); + if (cell === null) { + reject(new Error("Could not determine where the display" + + " message was from. Widget will not be displayed")); + } else { + var dummy = null; + if (cell.widget_subarea) { + dummy = $('
'); + cell.widget_subarea.append(dummy); + } + + var that = this; + this.create_view(model, {cell: cell}).then(function(view) { + that._handle_display_view(view); + if (dummy) { + dummy.replaceWith(view.$el); + } + view.trigger('displayed'); + resolve(view); + }, function(error) { + reject(new utils.WrappedError('Could not display view', error)); + }); } + }); - var that = this; - this.create_view(model, {cell: cell}).then(function(view) { - that._handle_display_view(view); - if (dummy) { - dummy.replaceWith(view.$el); - } - view.trigger('displayed'); - }); - } + }; WidgetManager.prototype._handle_display_view = function (view) { @@ -79,7 +86,7 @@ define([ if (view.additional_elements) { for (var i = 0; i < view.additional_elements.length; i++) { - this.keyboard_manager.register_events(view.additional_elements[i]); + this.keyboard_manager.register_events(view.additional_elements[i]); } } } @@ -87,7 +94,7 @@ define([ WidgetManager.prototype.create_view = function(model, options) { // Creates a promise for a view of a given model - return utils.load(model.get('_view_name'), model.get('_view_module'), + return utils.load_class(model.get('_view_name'), model.get('_view_module'), WidgetManager._view_types).then(function(ViewType) { // If a view is passed into the method, use that view's cell as @@ -102,10 +109,7 @@ define([ view.listenTo(model, 'destroy', view.remove); view.render(); return view; - }, function(error) { - console.error(error); - return Promise.reject(error); - }); + }, utils.reject("Couldn't create a view for model id '" + String(model.id) + "'")); }; WidgetManager.prototype.get_msg_cell = function (msg_id) { @@ -177,7 +181,7 @@ define([ this.create_model({ model_name: msg.content.data.model_name, model_module: msg.content.data.model_module, - comm: comm}); + comm: comm}).handle($.proxy(console.error, error)); }; WidgetManager.prototype.create_model = function (options) { @@ -215,17 +219,18 @@ define([ var that = this; var model_id = comm.comm_id; - var model_promise = utils.load(options.model_name, options.model_module, WidgetManager._model_types) + var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types) .then(function(ModelType) { var widget_model = new ModelType(that, model_id, comm); widget_model.once('comm:close', function () { delete that._models[model_id]; }); return widget_model; + }, function(error) { delete that._models[model_id]; - console.error(error); - return Promise.reject(error); + var wrapped_error = new utils.WrappedError("Couldn't create model", error); + return Promise.reject(wrapped_error); }); this._models[model_id] = model_promise; return model_promise; diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 0c3cbbf2e..ae228676d 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -322,7 +322,7 @@ define(["widgets/js/manager", // 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) { diff --git a/IPython/html/static/widgets/js/widget_box.js b/IPython/html/static/widgets/js/widget_box.js index 7e1811e8e..42ce9fb09 100644 --- a/IPython/html/static/widgets/js/widget_box.js +++ b/IPython/html/static/widgets/js/widget_box.js @@ -4,8 +4,9 @@ define([ "widgets/js/widget", "jqueryui", + "base/js/utils", "bootstrap", -], function(widget, $){ +], function(widget, $, utils){ var BoxView = widget.DOMWidgetView.extend({ initialize: function(){ @@ -84,7 +85,7 @@ define([ that.after_displayed(function() { view.trigger('displayed'); }); - }); + }, utils.reject("Couldn't add child view to box", true)); }, }); From c67dcc0dc361ce24f0be3091d46b2af9efea6a16 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 3 Nov 2014 14:06:38 -0800 Subject: [PATCH 16/40] Finished adding error handling. --- IPython/html/static/widgets/js/widget.js | 6 +++--- IPython/html/static/widgets/js/widget_box.js | 3 ++- .../static/widgets/js/widget_selectioncontainer.js | 10 ++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index ae228676d..55412bb92 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -97,7 +97,7 @@ define(["widgets/js/manager", } finally { that.state_lock = null; } - }, $.proxy(console.error, console)); + }, utils.reject("Couldn't set model state", true)); }, _handle_status: function (msg, callbacks) { @@ -259,9 +259,9 @@ define(["widgets/js/manager", }); return Promise.all(unpacked); } else if (value instanceof Object) { - var unpacked = {}; + unpacked = {}; _.each(value, function(sub_value, key) { - unpacked[key] = that._unpack_models(sub_value) + unpacked[key] = that._unpack_models(sub_value); }); return utils.resolve_dict(unpacked); } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") { diff --git a/IPython/html/static/widgets/js/widget_box.js b/IPython/html/static/widgets/js/widget_box.js index 42ce9fb09..abcc8babd 100644 --- a/IPython/html/static/widgets/js/widget_box.js +++ b/IPython/html/static/widgets/js/widget_box.js @@ -78,13 +78,14 @@ define([ var that = this; var dummy = $('
'); that.$box.append(dummy); - this.create_child_view(model).then(function(view) { + return this.create_child_view(model).then(function(view) { dummy.replaceWith(view.el); // Trigger the displayed event of the child view. that.after_displayed(function() { view.trigger('displayed'); }); + return view; }, utils.reject("Couldn't add child view to box", true)); }, }); diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js index 5e26d0f3d..f7cdd0758 100644 --- a/IPython/html/static/widgets/js/widget_selectioncontainer.js +++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js @@ -116,7 +116,7 @@ define([ var dummy = $('
'); accordion_inner.append(dummy); - this.create_child_view(model).then(function(view) { + return this.create_child_view(model).then(function(view) { dummy.replaceWith(view.$el); that.update(); that.update_titles(); @@ -125,7 +125,8 @@ define([ that.after_displayed(function() { view.trigger('displayed'); }); - }, $.proxy(console.error, console)); + return view; + }, utils.reject("Couldn't add child view to box", true)); }, }); @@ -210,7 +211,7 @@ define([ .append(dummy) .appendTo(that.$tab_contents); - this.create_child_view(model).then(function(view) { + return this.create_child_view(model).then(function(view) { dummy.replaceWith(view.$el); view.parent_tab = tab; view.parent_container = contents_div; @@ -219,7 +220,8 @@ define([ that.after_displayed(function() { view.trigger('displayed'); }); - }, $.proxy(console.error, console)); + return view; + }, utils.reject("Couldn't add child view to box", true)); }, update: function(options) { From 3870bb571b221a59ce5f4fc5204dff13afc03076 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 3 Nov 2014 14:28:50 -0800 Subject: [PATCH 17/40] Use rsvp.js for Promises --- IPython/html/static/base/js/utils.js | 45 ++++++++++++++++---- IPython/html/static/services/kernels/comm.js | 5 ++- IPython/html/static/widgets/js/manager.js | 9 ++-- IPython/html/static/widgets/js/widget.js | 9 ++-- 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 5ea6705cd..7e8d4bed5 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -5,7 +5,8 @@ define([ 'base/js/namespace', 'jquery', 'codemirror/lib/codemirror', -], function(IPython, $, CodeMirror){ + 'components/rsvp/rsvp.min', +], function(IPython, $, CodeMirror, rsvp){ "use strict"; IPython.load_extensions = function () { @@ -606,13 +607,41 @@ define([ }); }; + var WrappedError = function(message, error){ + // Wrappable Error class + + // The Error class doesn't actually act on `this`. Instead it always + // returns a new instance of Error. Here we capture that instance so we + // can apply it's properties to `this`. + var tmp = Error.apply(this, [message]); + + // Copy the properties of the error over to this. + var properties = Object.getOwnPropertyNames(tmp); + for (var i = 0; i < properties.length; i++) { + this[properties[i]] = tmp[properties[i]]; + } + + // Keep a stack of the original error messages. + if (error instanceof WrappedError) { + this.error_stack = error.error_stack; + } else { + this.error_stack = [error]; + } + this.error_stack.push(tmp); + + return this; + }; + + WrappedError.prototype = Object.create(Error.prototype, {}); + + var load_class = function(class_name, module_name, registry) { // Tries to load a class // // Tries to load a class from a module using require.js, if a module // is specified, otherwise tries to load a class from the global // registry, if the global registry is provided. - return new Promise(function(resolve, reject) { + return new rsvp.Promise(function(resolve, reject) { // Try loading the view module using require.js if (module_name) { @@ -638,13 +667,13 @@ define([ var resolve_dict = function(d) { // Resolve a promiseful dictionary. - // Returns a single Promise. + // Returns a single rsvp.Promise. var keys = Object.keys(d); var values = []; keys.forEach(function(key) { values.push(d[key]); }); - return Promise.all(values).then(function(v) { + return rsvp.Promise.all(values).then(function(v) { d = {}; for(var i=0; i Date: Mon, 3 Nov 2014 16:41:34 -0800 Subject: [PATCH 18/40] Bug fixes --- IPython/html/static/base/js/utils.js | 2 +- IPython/html/static/services/kernels/comm.js | 7 ++++--- IPython/html/static/widgets/js/manager.js | 14 ++++++-------- IPython/html/static/widgets/js/widget.js | 14 ++++++++++---- IPython/html/templates/page.html | 1 + 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 7e8d4bed5..5ffe5e693 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -5,7 +5,7 @@ define([ 'base/js/namespace', 'jquery', 'codemirror/lib/codemirror', - 'components/rsvp/rsvp.min', + 'rsvp', ], function(IPython, $, CodeMirror, rsvp){ "use strict"; diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index c9a06354c..1e74b445e 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -5,7 +5,7 @@ define([ 'base/js/namespace', 'jquery', 'base/js/utils', - 'components/rsvp/rsvp.min', + 'rsvp', ], function(IPython, $, utils, rsvp) { "use strict"; @@ -78,8 +78,9 @@ define([ } catch (e) { comm.close(); that.unregister_comm(comm); - var error = new utils.WrappedError("Exception opening new comm", e); - return rsvp.Promise.reject(error); + var wrapped_error = new utils.WrappedError("Exception opening new comm", e); + console.error(wrapped_error); + return rsvp.Promise.reject(wrapped_error); } return comm; }, utils.reject('Could not open comm', true)); diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index cf0e94b6c..7f3f70e79 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -7,7 +7,7 @@ define([ "jquery", "base/js/utils", "base/js/namespace", - 'components/rsvp/rsvp.min', + 'rsvp', ], function (_, Backbone, $, utils, IPython, rsvp) { "use strict"; //-------------------------------------------------------------------- @@ -49,8 +49,9 @@ define([ //-------------------------------------------------------------------- WidgetManager.prototype.display_view = function(msg, model) { // Displays a view for a particular model. + var that = this; return new rsvp.Promise(function(resolve, reject) { - var cell = this.get_msg_cell(msg.parent_header.msg_id); + var cell = that.get_msg_cell(msg.parent_header.msg_id); if (cell === null) { reject(new Error("Could not determine where the display" + " message was from. Widget will not be displayed")); @@ -61,8 +62,7 @@ define([ cell.widget_subarea.append(dummy); } - var that = this; - this.create_view(model, {cell: cell}).then(function(view) { + that.create_view(model, {cell: cell}).then(function(view) { that._handle_display_view(view); if (dummy) { dummy.replaceWith(view.$el); @@ -74,8 +74,6 @@ define([ }); } }); - - }; WidgetManager.prototype._handle_display_view = function (view) { @@ -174,7 +172,7 @@ define([ }; WidgetManager.prototype.get_model = function (model_id) { - return that._models[model_id]; + return this._models[model_id]; }; WidgetManager.prototype._handle_comm_open = function (comm, msg) { @@ -182,7 +180,7 @@ define([ this.create_model({ model_name: msg.content.data.model_name, model_module: msg.content.data.model_module, - comm: comm}).handle($.proxy(console.error, error)); + comm: comm}).catch($.proxy(console.error, console)); }; WidgetManager.prototype.create_model = function (options) { diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index b45d4340d..e6019c53e 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -7,7 +7,7 @@ define(["widgets/js/manager", "jquery", "base/js/utils", "base/js/namespace", - "components/rsvp/rsvp.min", + "rsvp", ], function(widgetmanager, _, Backbone, $, utils, IPython, rsvp){ var WidgetModel = Backbone.Model.extend({ @@ -75,15 +75,21 @@ define(["widgets/js/manager", case 'update': this.state_change = this.state_change.then(function() { return that.set_state(msg.content.data.state); - }); + }).catch(utils.reject({ + message: "Couldn't process update msg", + model_id: that.id + }, true)); break; case 'custom': this.trigger('msg:custom', msg.content.data.content); break; case 'display': this.state_change = this.state_change.then(function () { - that.widget_manager.display_view(msg, that); - }); + return that.widget_manager.display_view(msg, that); + }).catch(utils.reject({ + message: "Couldn't process display msg", + model_id: that.id + }, true)); break; } }, diff --git a/IPython/html/templates/page.html b/IPython/html/templates/page.html index 93ba4b5f7..28d3005cc 100644 --- a/IPython/html/templates/page.html +++ b/IPython/html/templates/page.html @@ -31,6 +31,7 @@ codemirror: 'components/codemirror', termjs: "components/term.js/src/term", contents: '{{ contents_js_source }}', + rsvp: "components/rsvp/rsvp", }, shim: { underscore: { From 77789daa122e9061c049a11dae94da7bca6fbc35 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 3 Nov 2014 17:14:55 -0800 Subject: [PATCH 19/40] Test fixes --- IPython/html/tests/widgets/widget.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/IPython/html/tests/widgets/widget.js b/IPython/html/tests/widgets/widget.js index e79a91544..d3c6b6d2a 100644 --- a/IPython/html/tests/widgets/widget.js +++ b/IPython/html/tests/widgets/widget.js @@ -59,13 +59,27 @@ casper.notebook_test(function () { JSON.stringify(input) + ' passed through Model._pack_model unchanged'); }; var test_unpack = function (input) { - var output = that.evaluate(function(input) { + that.thenEvaluate(function(input) { + window.results = undefined; var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined); - var results = model._unpack_models(input); - return results; + model._unpack_models(input).then(function(results) { + window.results = results; + }); }, {input: input}); - that.test.assert(recursive_compare(input, output), - JSON.stringify(input) + ' passed through Model._unpack_model unchanged'); + + that.waitFor(function check() { + return that.evaluate(function() { + return window.results; + }); + }); + + that.then(function() { + var results = that.evaluate(function() { + return window.results; + }); + that.test.assert(recursive_compare(input, results), + JSON.stringify(input) + ' passed through Model._unpack_model unchanged'); + }); }; var test_packing = function(input) { test_pack(input); @@ -84,7 +98,7 @@ casper.notebook_test(function () { test_packing([String('hi'), Date("Thu Nov 13 2014 13:46:21 GMT-0500")]) // Test multi-set, single touch code. First create a custom widget. - this.evaluate(function() { + this.thenEvaluate(function() { var MultiSetView = IPython.DOMWidgetView.extend({ render: function(){ this.model.set('a', 1); From 66caa322c5206abc5622b3a5087638441aa8ec14 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 4 Nov 2014 10:24:16 -0800 Subject: [PATCH 20/40] Make Page Error output clearer --- IPython/html/tests/util.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index 464751cae..6cdbd5cdc 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -655,7 +655,10 @@ casper.print_log = function () { casper.on("page.error", function onError(msg, trace) { // show errors in the browser - this.echo("Page Error!"); + this.echo("Page Error"); + this.echo(" Message: " + msg); + this.echo(" Call stack:"); + var local_path = this.get_notebook_server(); for (var i = 0; i < trace.length; i++) { var frame = trace[i]; var file = frame.file; @@ -664,12 +667,15 @@ casper.on("page.error", function onError(msg, trace) { if (file === "phantomjs://webpage.evaluate()") { file = "evaluate"; } - this.echo("line " + frame.line + " of " + file); - if (frame.function.length > 0) { - this.echo("in " + frame.function); + // remove the version tag from the path + file = file.replace(/(\?v=[0-9abcdef]+)/, ''); + // remove the local address from the beginning of the path + if (file.indexOf(local_path) === 0) { + file = file.substr(local_path.length); } + this.echo(" line " + frame.line + " of " + file + + (frame.function.length > 0) ? " in " + frame.function: ""); } - this.echo(msg); }); From 17108fed3b405b6ed7e3252d10d4492948243d8b Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 4 Nov 2014 12:25:47 -0800 Subject: [PATCH 21/40] Make errors clearer! --- IPython/html/tests/util.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index 6cdbd5cdc..2125afc4b 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -22,6 +22,25 @@ casper.open_new_notebook = function () { }); this.waitFor(this.page_loaded); + this.thenEvaluate(function(){ + var fix_function = function(f, context) { + return function() { + var pretty_arguments = []; + for (var i = 0; i < arguments.length; i++) { + var value = arguments[i]; + if (value instanceof Object) { + pretty_arguments.push(JSON.stringify(value, null, ' ')); + } else { + pretty_arguments.push(value); + } + } + f.apply(context, pretty_arguments); + }; + }; + console.log = fix_function(console.log, console); + console.error = fix_function(console.error, console); + }); + // Make sure the kernel has started this.waitFor(this.kernel_running); // track the IPython busy/idle state @@ -673,8 +692,8 @@ casper.on("page.error", function onError(msg, trace) { if (file.indexOf(local_path) === 0) { file = file.substr(local_path.length); } - this.echo(" line " + frame.line + " of " + file + - (frame.function.length > 0) ? " in " + frame.function: ""); + var frame_text = (frame.function.length > 0) ? " in " + frame.function : ""; + this.echo(" line " + frame.line + " of " + file + frame_text); } }); @@ -686,7 +705,8 @@ casper.capture_log = function () { this.on('remote.message', function(msg) { captured_log.push(msg); }); - + + var that = this; this.test.on("test.done", function (result) { // test.done runs per-file, // but suiteResults is per-suite (directory) @@ -702,9 +722,13 @@ casper.capture_log = function () { if (current_errors > seen_errors && captured_log.length > 0) { casper.echo("\nCaptured console.log:"); for (var i = 0; i < captured_log.length; i++) { - casper.echo(" " + captured_log[i]); + var output = String(captured_log[i]).split('\n'); + for (var j = 0; j < output.length; j++) { + casper.echo(" " + output[j]); + } } } + seen_errors = current_errors; captured_log = []; }); From fed878fed97ab61a6ae843f15379ee2170c00ef7 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 4 Nov 2014 12:28:51 -0800 Subject: [PATCH 22/40] Don't throw Errors with Objects as the message/ --- IPython/html/static/base/js/utils.js | 5 +---- IPython/html/static/widgets/js/widget.js | 10 ++-------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 5ffe5e693..4a13efec9 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -656,10 +656,7 @@ define([ if (registry && registry[class_name]) { resolve(registry[class_name]); } else { - reject(new Error({ - message: 'Class '+class_name+' not found in registry ', - registry: registry - })); + reject(new Error('Class '+class_name+' not found in registry ')); } } }); diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index e6019c53e..9d98bed70 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -75,10 +75,7 @@ define(["widgets/js/manager", case 'update': this.state_change = this.state_change.then(function() { return that.set_state(msg.content.data.state); - }).catch(utils.reject({ - message: "Couldn't process update msg", - model_id: that.id - }, true)); + }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true)); break; case 'custom': this.trigger('msg:custom', msg.content.data.content); @@ -86,10 +83,7 @@ define(["widgets/js/manager", case 'display': this.state_change = this.state_change.then(function () { return that.widget_manager.display_view(msg, that); - }).catch(utils.reject({ - message: "Couldn't process display msg", - model_id: that.id - }, true)); + }).catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true)); break; } }, From 26d012b3b7e0a557fa40f260871a2c6d9c24b539 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 4 Nov 2014 12:32:53 -0800 Subject: [PATCH 23/40] Add comment clarifying new hook function, Refined Page Error output. --- IPython/html/tests/util.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index 2125afc4b..9caf2dae2 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -22,8 +22,11 @@ casper.open_new_notebook = function () { }); this.waitFor(this.page_loaded); + // Hook the log and error methods of the console, forcing them to + // serialize their arguments before printing. This allows the + // Objects to cross into the phantom/slimer regime for display. this.thenEvaluate(function(){ - var fix_function = function(f, context) { + var serialize_arguments = function(f, context) { return function() { var pretty_arguments = []; for (var i = 0; i < arguments.length; i++) { @@ -37,8 +40,8 @@ casper.open_new_notebook = function () { f.apply(context, pretty_arguments); }; }; - console.log = fix_function(console.log, console); - console.error = fix_function(console.error, console); + console.log = serialize_arguments(console.log, console); + console.error = serialize_arguments(console.error, console); }); // Make sure the kernel has started @@ -675,7 +678,7 @@ casper.print_log = function () { casper.on("page.error", function onError(msg, trace) { // show errors in the browser this.echo("Page Error"); - this.echo(" Message: " + msg); + this.echo(" Message: " + msg.split('\n').join('\n ')); this.echo(" Call stack:"); var local_path = this.get_notebook_server(); for (var i = 0; i < trace.length; i++) { From 6ee932f2989bc02c7b36e9df1494c9f2bd472390 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 4 Nov 2014 13:44:13 -0800 Subject: [PATCH 24/40] Add rsvp to setupbase --- IPython/html/tests/util.js | 9 ++++++++- setupbase.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index 9caf2dae2..1dc3f1b10 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -32,7 +32,14 @@ casper.open_new_notebook = function () { for (var i = 0; i < arguments.length; i++) { var value = arguments[i]; if (value instanceof Object) { - pretty_arguments.push(JSON.stringify(value, null, ' ')); + var name = value.name || 'Object'; + // Print a JSON string representation of the object. + // If we don't do this, [Object object] gets printed + // by casper, which is useless. The long regular + // expression reduces the verbosity of the JSON. + pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ') + .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n') + .replace(/\n(\s+)?\n/g, '\n')); } else { pretty_arguments.push(value); } diff --git a/setupbase.py b/setupbase.py index 3dbc6b53d..6d2458421 100644 --- a/setupbase.py +++ b/setupbase.py @@ -159,6 +159,7 @@ def find_package_data(): pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"), pjoin(components, "marked", "lib", "marked.js"), pjoin(components, "requirejs", "require.js"), + pjoin(components, "rsvp", "rsvp.js"), pjoin(components, "underscore", "underscore-min.js"), pjoin(components, "moment", "moment.js"), pjoin(components, "moment", "min", "moment.min.js"), From b3c49fce665b6d1f7b9d75781b77cc2565b1eb95 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Wed, 5 Nov 2014 17:59:41 -0800 Subject: [PATCH 25/40] Move the display Promise into a lower level method, into the create view method of the widget manager. This makes sure that views created by parent views are also accounted for in the state/display order guarantee. --- IPython/html/static/widgets/js/manager.js | 9 ++++++++- IPython/html/static/widgets/js/widget.js | 7 ++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 7f3f70e79..e98a350ad 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -93,7 +93,12 @@ define([ WidgetManager.prototype.create_view = function(model, options) { // Creates a promise for a view of a given model - return utils.load_class(model.get('_view_name'), model.get('_view_module'), + + // Make sure the view creation is not out of order with + // any state updates. + model.state_change = model.state_change.then(function() { + console.log('create_view ' + model.id); + return utils.load_class(model.get('_view_name'), model.get('_view_module'), WidgetManager._view_types).then(function(ViewType) { // If a view is passed into the method, use that view's cell as @@ -109,6 +114,8 @@ define([ view.render(); return view; }, utils.reject("Couldn't create a view for model id '" + String(model.id) + "'")); + }); + return model.state_change; }; WidgetManager.prototype.get_msg_cell = function (msg_id) { diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 9d98bed70..42ea44fe4 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -81,9 +81,8 @@ define(["widgets/js/manager", this.trigger('msg:custom', msg.content.data.content); break; case 'display': - this.state_change = this.state_change.then(function () { - return that.widget_manager.display_view(msg, that); - }).catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true)); + that.widget_manager.display_view(msg, that) + .catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true)); break; } }, @@ -94,6 +93,8 @@ define(["widgets/js/manager", return this._unpack_models(state).then(function(state) { that.state_lock = state; try { + console.log('set_state ' + that.id); + console.log(state); WidgetModel.__super__.set.call(that, state); } finally { that.state_lock = null; From 8c149c91127ea89602eaeebfa706431ded58c3b6 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Wed, 5 Nov 2014 23:16:07 -0800 Subject: [PATCH 26/40] Current state with lots and lots of debugging junk --- IPython/html/static/widgets/js/manager.js | 7 +++++- IPython/html/static/widgets/js/widget.js | 7 ++++-- IPython/html/static/widgets/js/widget_int.js | 6 ++--- .../static/widgets/js/widget_selection.js | 8 +++---- .../html/static/widgets/js/widget_string.js | 4 ++-- IPython/html/tests/util.js | 22 +++++++++++++++++++ IPython/html/tests/widgets/widget_bool.js | 4 ++-- 7 files changed, 44 insertions(+), 14 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index e98a350ad..9ab72adb1 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -97,7 +97,12 @@ define([ // Make sure the view creation is not out of order with // any state updates. model.state_change = model.state_change.then(function() { - console.log('create_view ' + model.id); + try { + console.log('create_view ' + model.id); + console.log(' _view_name ' + model.get('_view_name')); + console.log(' _view_module ' + model.get('_view_module')); + } catch (e) { } + return utils.load_class(model.get('_view_name'), model.get('_view_module'), WidgetManager._view_types).then(function(ViewType) { diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 42ea44fe4..630345ae8 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -70,6 +70,7 @@ define(["widgets/js/manager", _handle_comm_msg: function (msg) { // Handle incoming comm msg. var method = msg.content.data.method; + console.log(method); var that = this; switch (method) { case 'update': @@ -81,8 +82,9 @@ define(["widgets/js/manager", this.trigger('msg:custom', msg.content.data.content); break; case 'display': - that.widget_manager.display_view(msg, that) - .catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true)); + this.state_change = this.state_change.then(function() { + return that.widget_manager.display_view(msg, that); + }).catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true)); break; } }, @@ -99,6 +101,7 @@ define(["widgets/js/manager", } finally { that.state_lock = null; } + return rsvp.Promise.resolve(); }, utils.reject("Couldn't set model state", true)); }, diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js index 3fd0ae0de..c6278cbe0 100644 --- a/IPython/html/static/widgets/js/widget_int.js +++ b/IPython/html/static/widgets/js/widget_int.js @@ -150,7 +150,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } @@ -308,7 +308,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -416,7 +416,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } return ProgressView.__super__.update.apply(this); diff --git a/IPython/html/static/widgets/js/widget_selection.js b/IPython/html/static/widgets/js/widget_selection.js index a40433cef..f441bd474 100644 --- a/IPython/html/static/widgets/js/widget_selection.js +++ b/IPython/html/static/widgets/js/widget_selection.js @@ -94,7 +94,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -219,7 +219,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -326,7 +326,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -441,7 +441,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } diff --git a/IPython/html/static/widgets/js/widget_string.js b/IPython/html/static/widgets/js/widget_string.js index 535a80c8f..3464c9cd3 100644 --- a/IPython/html/static/widgets/js/widget_string.js +++ b/IPython/html/static/widgets/js/widget_string.js @@ -101,7 +101,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -177,7 +177,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index 1dc3f1b10..14f910bc7 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -744,4 +744,26 @@ casper.capture_log = function () { }); }; +casper.interact = function() { + // Start an interactive Javascript console. + var system = require('system'); + system.stdout.writeLine('JS interactive console.'); + system.stdout.writeLine('Type `exit` to quit.'); + + function read_line() { + system.stdout.writeLine('JS: '); + var line = system.stdin.readLine(); + return line; + } + + var input = read_line(); + while (input.trim() != 'exit') { + var output = this.evaluate(function(code) { + return String(eval(code)); + }, {code: input}); + system.stdout.writeLine('\nOut: ' + output); + input = read_line(); + } +}; + casper.capture_log(); diff --git a/IPython/html/tests/widgets/widget_bool.js b/IPython/html/tests/widgets/widget_bool.js index 35e755be2..8b8cb0510 100644 --- a/IPython/html/tests/widgets/widget_bool.js +++ b/IPython/html/tests/widgets/widget_bool.js @@ -13,7 +13,6 @@ casper.notebook_test(function () { 'display(bool_widgets[1])\n' + 'print("Success")'); this.execute_cell_then(bool_index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create bool widget cell executed with correct output.'); @@ -48,7 +47,6 @@ casper.notebook_test(function () { this.test.assert(this.cell_element_function(index, '.widget-area .widget-subarea button', 'hasClass', ['active']), 'Toggle button is toggled.'); - }); index = this.append_cell( @@ -57,6 +55,8 @@ casper.notebook_test(function () { 'print("Success")'); this.execute_cell_then(index, function(index){ + this.interact(); + this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Change bool widget value cell executed with correct output.'); From bed3a4cfdadd42b50809222d2f717350893df2ca Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 7 Nov 2014 12:23:04 -0800 Subject: [PATCH 27/40] use es6 --- IPython/html/static/base/js/utils.js | 15 +++++++-------- IPython/html/static/notebook/js/main.js | 1 + IPython/html/static/services/kernels/comm.js | 5 ++--- IPython/html/static/widgets/js/manager.js | 7 +++---- IPython/html/static/widgets/js/widget.js | 11 +++++------ IPython/html/templates/page.html | 1 - 6 files changed, 18 insertions(+), 22 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 4a13efec9..a0d9c3cd9 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -5,8 +5,7 @@ define([ 'base/js/namespace', 'jquery', 'codemirror/lib/codemirror', - 'rsvp', -], function(IPython, $, CodeMirror, rsvp){ +], function(IPython, $, CodeMirror){ "use strict"; IPython.load_extensions = function () { @@ -641,7 +640,7 @@ define([ // Tries to load a class from a module using require.js, if a module // is specified, otherwise tries to load a class from the global // registry, if the global registry is provided. - return new rsvp.Promise(function(resolve, reject) { + return new Promise(function(resolve, reject) { // Try loading the view module using require.js if (module_name) { @@ -664,13 +663,13 @@ define([ var resolve_dict = function(d) { // Resolve a promiseful dictionary. - // Returns a single rsvp.Promise. + // Returns a single Promise. var keys = Object.keys(d); var values = []; keys.forEach(function(key) { values.push(d[key]); }); - return rsvp.Promise.all(values).then(function(v) { + return Promise.all(values).then(function(v) { d = {}; for(var i=0; i 3.0 window.CodeMirror = CodeMirror; diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index 1e74b445e..adf748c79 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -5,8 +5,7 @@ define([ 'base/js/namespace', 'jquery', 'base/js/utils', - 'rsvp', -], function(IPython, $, utils, rsvp) { +], function(IPython, $, utils) { "use strict"; //----------------------------------------------------------------------- @@ -80,7 +79,7 @@ define([ that.unregister_comm(comm); var wrapped_error = new utils.WrappedError("Exception opening new comm", e); console.error(wrapped_error); - return rsvp.Promise.reject(wrapped_error); + return Promise.reject(wrapped_error); } return comm; }, utils.reject('Could not open comm', true)); diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 9ab72adb1..4a2a59aa3 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -7,8 +7,7 @@ define([ "jquery", "base/js/utils", "base/js/namespace", - 'rsvp', -], function (_, Backbone, $, utils, IPython, rsvp) { +], function (_, Backbone, $, utils, IPython) { "use strict"; //-------------------------------------------------------------------- // WidgetManager class @@ -50,7 +49,7 @@ define([ WidgetManager.prototype.display_view = function(msg, model) { // Displays a view for a particular model. var that = this; - return new rsvp.Promise(function(resolve, reject) { + return new Promise(function(resolve, reject) { var cell = that.get_msg_cell(msg.parent_header.msg_id); if (cell === null) { reject(new Error("Could not determine where the display" + @@ -241,7 +240,7 @@ define([ }, function(error) { delete that._models[model_id]; var wrapped_error = new utils.WrappedError("Couldn't create model", error); - return rsvp.Promise.reject(wrapped_error); + return Promise.reject(wrapped_error); }); this._models[model_id] = model_promise; return model_promise; diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 630345ae8..0814988b9 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -7,8 +7,7 @@ define(["widgets/js/manager", "jquery", "base/js/utils", "base/js/namespace", - "rsvp", -], function(widgetmanager, _, Backbone, $, utils, IPython, rsvp){ +], function(widgetmanager, _, Backbone, $, utils, IPython){ var WidgetModel = Backbone.Model.extend({ constructor: function (widget_manager, model_id, comm) { @@ -23,7 +22,7 @@ define(["widgets/js/manager", // An ID unique to this model. // comm : Comm instance (optional) this.widget_manager = widget_manager; - this.state_change = rsvp.Promise.resolve(); + this.state_change = Promise.resolve(); this._buffered_state_diff = {}; this.pending_msgs = 0; this.msg_buffer = null; @@ -101,7 +100,7 @@ define(["widgets/js/manager", } finally { that.state_lock = null; } - return rsvp.Promise.resolve(); + return Promise.resolve(); }, utils.reject("Couldn't set model state", true)); }, @@ -262,7 +261,7 @@ define(["widgets/js/manager", _.each(value, function(sub_value, key) { unpacked.push(that._unpack_models(sub_value)); }); - return rsvp.Promise.all(unpacked); + return Promise.all(unpacked); } else if (value instanceof Object) { unpacked = {}; _.each(value, function(sub_value, key) { @@ -273,7 +272,7 @@ define(["widgets/js/manager", // get_model returns a promise already return this.widget_manager.get_model(value.slice(10, value.length)); } else { - return rsvp.Promise.resolve(value); + return Promise.resolve(value); } }, diff --git a/IPython/html/templates/page.html b/IPython/html/templates/page.html index 28d3005cc..93ba4b5f7 100644 --- a/IPython/html/templates/page.html +++ b/IPython/html/templates/page.html @@ -31,7 +31,6 @@ codemirror: 'components/codemirror', termjs: "components/term.js/src/term", contents: '{{ contents_js_source }}', - rsvp: "components/rsvp/rsvp", }, shim: { underscore: { From b76d4d5c05cf1cf4e2e5584450aa3d7ca4214b2f Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 10 Nov 2014 10:41:17 -0800 Subject: [PATCH 28/40] Use es6-promise polyfill --- IPython/html/static/notebook/js/main.js | 3 ++- IPython/html/templates/page.html | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js index 02e746883..41870ba8f 100644 --- a/IPython/html/static/notebook/js/main.js +++ b/IPython/html/static/notebook/js/main.js @@ -2,6 +2,7 @@ // Distributed under the terms of the Modified BSD License. require([ + 'es6promise', 'base/js/namespace', 'jquery', 'notebook/js/notebook', @@ -25,6 +26,7 @@ require([ // only loaded, not used, please keep sure this is loaded last 'custom/custom' ], function( + es6promise, IPython, $, notebook, @@ -49,7 +51,6 @@ require([ custom ) { "use strict"; - console.log(promise); // compat with old IPython, remove for IPython > 3.0 window.CodeMirror = CodeMirror; diff --git a/IPython/html/templates/page.html b/IPython/html/templates/page.html index 93ba4b5f7..f024bb53e 100644 --- a/IPython/html/templates/page.html +++ b/IPython/html/templates/page.html @@ -27,9 +27,9 @@ bootstrap: 'components/bootstrap/js/bootstrap.min', bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min', jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min', - moment: "components/moment/moment", + moment: 'components/moment/moment', codemirror: 'components/codemirror', - termjs: "components/term.js/src/term", + termjs: 'components/term.js/src/term', contents: '{{ contents_js_source }}', }, shim: { From 56c5020a843a198ef8ce6f2d29272835703bc699 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 10 Nov 2014 11:17:09 -0800 Subject: [PATCH 29/40] bool_test passing with slimerjs --- IPython/html/static/widgets/js/manager.js | 5 ---- IPython/html/static/widgets/js/widget.js | 7 +---- IPython/html/tests/util.js | 9 ++++++ IPython/html/tests/widgets/widget_bool.js | 34 ++++++++++++----------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 4a2a59aa3..7cd73dcb1 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -96,11 +96,6 @@ define([ // Make sure the view creation is not out of order with // any state updates. model.state_change = model.state_change.then(function() { - try { - console.log('create_view ' + model.id); - console.log(' _view_name ' + model.get('_view_name')); - console.log(' _view_module ' + model.get('_view_module')); - } catch (e) { } return utils.load_class(model.get('_view_name'), model.get('_view_module'), WidgetManager._view_types).then(function(ViewType) { diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 0814988b9..771dc3556 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -69,7 +69,6 @@ define(["widgets/js/manager", _handle_comm_msg: function (msg) { // Handle incoming comm msg. var method = msg.content.data.method; - console.log(method); var that = this; switch (method) { case 'update': @@ -81,9 +80,7 @@ define(["widgets/js/manager", this.trigger('msg:custom', msg.content.data.content); break; case 'display': - this.state_change = this.state_change.then(function() { - return that.widget_manager.display_view(msg, that); - }).catch(utils.reject("Couldn't process display msg for model id '" + String(that.id) + "'", true)); + return that.widget_manager.display_view(msg, that); break; } }, @@ -94,8 +91,6 @@ define(["widgets/js/manager", return this._unpack_models(state).then(function(state) { that.state_lock = state; try { - console.log('set_state ' + that.id); - console.log(state); WidgetModel.__super__.set.call(that, state); } finally { that.state_lock = null; diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index 14f910bc7..6cd998101 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -314,6 +314,15 @@ casper.execute_cell_then = function(index, then_callback, expect_failure) { return return_val; }; +casper.waitfor_cell_element = function(index, selector){ + // Utility function that allows us to easily wait for an element + // within a cell. Uses JQuery selector to look for the element. + var that = this; + this.waitFor(function() { + return that.cell_element_exists(index, selector); + }, function() { console.log('FOUND!'); }); +}; + casper.cell_element_exists = function(index, selector){ // Utility function that allows us to easily check if an element exists // within a cell. Uses JQuery selector to look for the element. diff --git a/IPython/html/tests/widgets/widget_bool.js b/IPython/html/tests/widgets/widget_bool.js index 8b8cb0510..fa818d770 100644 --- a/IPython/html/tests/widgets/widget_bool.js +++ b/IPython/html/tests/widgets/widget_bool.js @@ -1,12 +1,12 @@ // Test widget bool class casper.notebook_test(function () { - index = this.append_cell( - 'from IPython.html import widgets\n' + - 'from IPython.display import display, clear_output\n' + - 'print("Success")'); - this.execute_cell_then(index); + // index = this.append_cell( + // 'print("Success")'); + // this.execute_cell_then(index); var bool_index = this.append_cell( + 'from IPython.html import widgets\n' + + 'from IPython.display import display, clear_output\n' + 'bool_widgets = [widgets.Checkbox(description="Title", value=True),\n' + ' widgets.ToggleButton(description="Title", value=True)]\n' + 'display(bool_widgets[0])\n' + @@ -15,36 +15,41 @@ casper.notebook_test(function () { this.execute_cell_then(bool_index, function(index){ this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create bool widget cell executed with correct output.'); + }); - this.test.assert(this.cell_element_exists(index, + this.waitfor_cell_element(bool_index, '.widget-area .widget-subarea .widget-hbox input'); + this.waitfor_cell_element(bool_index, '.widget-area .widget-subarea button'); + + this.then(function() { + this.test.assert(this.cell_element_exists(bool_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, + this.test.assert(this.cell_element_exists(bool_index, '.widget-area .widget-subarea .widget-hbox input'), 'Checkbox exists.'); - this.test.assert(this.cell_element_function(index, + this.test.assert(this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']), 'Checkbox is checked.'); - this.test.assert(this.cell_element_exists(index, + this.test.assert(this.cell_element_exists(bool_index, '.widget-area .widget-subarea .widget-hbox .widget-label'), 'Checkbox label exists.'); - this.test.assert(this.cell_element_function(index, + this.test.assert(this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox .widget-label', 'html')=="Title", 'Checkbox labeled correctly.'); - this.test.assert(this.cell_element_exists(index, + this.test.assert(this.cell_element_exists(bool_index, '.widget-area .widget-subarea button'), 'Toggle button exists.'); - this.test.assert(this.cell_element_function(index, + this.test.assert(this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'html')=="Title", 'Toggle button labeled correctly.'); - this.test.assert(this.cell_element_function(index, + this.test.assert(this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'hasClass', ['active']), 'Toggle button is toggled.'); }); @@ -54,9 +59,6 @@ casper.notebook_test(function () { 'bool_widgets[1].value = False\n' + 'print("Success")'); this.execute_cell_then(index, function(index){ - - this.interact(); - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Change bool widget value cell executed with correct output.'); From 0d591619c6bd5f8fa6f127bc3d27e1f436cd2a7e Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 10 Nov 2014 12:36:39 -0800 Subject: [PATCH 30/40] Make all tests async display safe --- IPython/html/static/widgets/js/widget.js | 2 +- IPython/html/tests/util.js | 31 ++++++++++-- IPython/html/tests/widgets/widget.js | 2 +- IPython/html/tests/widgets/widget_bool.js | 35 +++++++------ IPython/html/tests/widgets/widget_box.js | 50 ++++++++++++------- IPython/html/tests/widgets/widget_button.js | 28 ++++++----- IPython/html/tests/widgets/widget_float.js | 35 ++++++++----- IPython/html/tests/widgets/widget_image.js | 14 ++++-- IPython/html/tests/widgets/widget_int.js | 40 ++++++++------- .../html/tests/widgets/widget_selection.js | 19 +++++-- .../widgets/widget_selectioncontainer.js | 29 +++++++---- IPython/html/tests/widgets/widget_string.js | 46 +++++++++-------- 12 files changed, 203 insertions(+), 128 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 771dc3556..b7730fe34 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -80,7 +80,7 @@ define(["widgets/js/manager", this.trigger('msg:custom', msg.content.data.content); break; case 'display': - return that.widget_manager.display_view(msg, that); + this.widget_manager.display_view(msg, that); break; } }, diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index 6cd998101..61f0a23c0 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -180,10 +180,31 @@ casper.wait_for_widget = function (widget_info) { // widget_info : object // Object which contains info related to the widget. The model_id property // is used to identify the widget. + + // Clear the results of a previous query, if they exist. Make sure a + // dictionary exists to store the async results in. + this.thenEvaluate(function(model_id) { + if (window.pending_msgs === undefined) { + window.pending_msgs = {}; + } else { + window.pending_msgs[model_id] = -1; + } + }, {model_id: widget_info.model_id}); + + // Wait for the pending messages to be 0. this.waitFor(function () { - var pending = this.evaluate(function (m) { - return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs; - }, {m: widget_info.model_id}); + var pending = this.evaluate(function (model_id) { + + // Get the model. Once the model is had, store it's pending_msgs + // count in the window's dictionary. + IPython.notebook.kernel.widget_manager.get_model(model_id) + .then(function(model) { + window.pending_msgs[model_id] = model.pending_msgs; + }); + + // Return the pending_msgs result. + return window.pending_msgs[model_id]; + }, {model_id: widget_info.model_id}); if (pending === 0) { return true; @@ -314,13 +335,13 @@ casper.execute_cell_then = function(index, then_callback, expect_failure) { return return_val; }; -casper.waitfor_cell_element = function(index, selector){ +casper.wait_for_element = function(index, selector){ // Utility function that allows us to easily wait for an element // within a cell. Uses JQuery selector to look for the element. var that = this; this.waitFor(function() { return that.cell_element_exists(index, selector); - }, function() { console.log('FOUND!'); }); + }); }; casper.cell_element_exists = function(index, selector){ diff --git a/IPython/html/tests/widgets/widget.js b/IPython/html/tests/widgets/widget.js index d3c6b6d2a..3f0c35d11 100644 --- a/IPython/html/tests/widgets/widget.js +++ b/IPython/html/tests/widgets/widget.js @@ -131,7 +131,7 @@ casper.notebook_test(function () { multiset.model_id = this.get_output_cell(index).text.trim(); }); - this.wait_for_widget(multiset); + this.wait_for_widget(multiset); index = this.append_cell( 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))'); diff --git a/IPython/html/tests/widgets/widget_bool.js b/IPython/html/tests/widgets/widget_bool.js index fa818d770..3586271cb 100644 --- a/IPython/html/tests/widgets/widget_bool.js +++ b/IPython/html/tests/widgets/widget_bool.js @@ -1,9 +1,7 @@ // Test widget bool class casper.notebook_test(function () { - // index = this.append_cell( - // 'print("Success")'); - // this.execute_cell_then(index); + // Create a checkbox and togglebutton. var bool_index = this.append_cell( 'from IPython.html import widgets\n' + 'from IPython.display import display, clear_output\n' + @@ -17,20 +15,24 @@ casper.notebook_test(function () { 'Create bool widget cell executed with correct output.'); }); - this.waitfor_cell_element(bool_index, '.widget-area .widget-subarea .widget-hbox input'); - this.waitfor_cell_element(bool_index, '.widget-area .widget-subarea button'); + // Wait for the widgets to actually display. + var widget_checkbox_selector = '.widget-area .widget-subarea .widget-hbox input'; + var widget_togglebutton_selector = '.widget-area .widget-subarea button'; + this.wait_for_element(bool_index, widget_checkbox_selector); + this.wait_for_element(bool_index, widget_togglebutton_selector); + // Continue the tests. this.then(function() { this.test.assert(this.cell_element_exists(bool_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); this.test.assert(this.cell_element_exists(bool_index, - '.widget-area .widget-subarea .widget-hbox input'), + widget_checkbox_selector), 'Checkbox exists.'); this.test.assert(this.cell_element_function(bool_index, - '.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']), + widget_checkbox_selector, 'prop', ['checked']), 'Checkbox is checked.'); this.test.assert(this.cell_element_exists(bool_index, @@ -42,18 +44,19 @@ casper.notebook_test(function () { 'Checkbox labeled correctly.'); this.test.assert(this.cell_element_exists(bool_index, - '.widget-area .widget-subarea button'), + widget_togglebutton_selector), 'Toggle button exists.'); this.test.assert(this.cell_element_function(bool_index, - '.widget-area .widget-subarea button', 'html')=="Title", + widget_togglebutton_selector, 'html')=="Title", 'Toggle button labeled correctly.'); this.test.assert(this.cell_element_function(bool_index, - '.widget-area .widget-subarea button', 'hasClass', ['active']), + widget_togglebutton_selector, 'hasClass', ['active']), 'Toggle button is toggled.'); }); + // Try changing the state of the widgets programatically. index = this.append_cell( 'bool_widgets[0].value = False\n' + 'bool_widgets[1].value = False\n' + @@ -63,25 +66,25 @@ casper.notebook_test(function () { 'Change bool widget value cell executed with correct output.'); this.test.assert(! this.cell_element_function(bool_index, - '.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']), + widget_checkbox_selector, 'prop', ['checked']), 'Checkbox is not checked. (1)'); this.test.assert(! this.cell_element_function(bool_index, - '.widget-area .widget-subarea button', 'hasClass', ['active']), + widget_togglebutton_selector, 'hasClass', ['active']), 'Toggle button is not toggled. (1)'); // Try toggling the bool by clicking on the checkbox. - this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox input', 'click'); + this.cell_element_function(bool_index, widget_checkbox_selector, 'click'); this.test.assert(this.cell_element_function(bool_index, - '.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']), + widget_checkbox_selector, 'prop', ['checked']), 'Checkbox is checked. (2)'); // Try toggling the bool by clicking on the toggle button. - this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click'); + this.cell_element_function(bool_index, widget_togglebutton_selector, 'click'); this.test.assert(this.cell_element_function(bool_index, - '.widget-area .widget-subarea button', 'hasClass', ['active']), + widget_togglebutton_selector, 'hasClass', ['active']), 'Toggle button is toggled. (3)'); }); diff --git a/IPython/html/tests/widgets/widget_box.js b/IPython/html/tests/widgets/widget_box.js index 6e1f242b7..617423d0c 100644 --- a/IPython/html/tests/widgets/widget_box.js +++ b/IPython/html/tests/widgets/widget_box.js @@ -1,12 +1,10 @@ // Test container class casper.notebook_test(function () { - index = this.append_cell( - 'from IPython.html import widgets\n' + - 'from IPython.display import display, clear_output\n' + - 'print("Success")'); - this.execute_cell_then(index); + // Create a box widget. var container_index = this.append_cell( + 'from IPython.html import widgets\n' + + 'from IPython.display import display, clear_output\n' + 'container = widgets.Box()\n' + 'button = widgets.Button()\n'+ 'container.children = [button]\n'+ @@ -14,24 +12,32 @@ casper.notebook_test(function () { 'container._dom_classes = ["my-test-class"]\n'+ 'print("Success")\n'); this.execute_cell_then(container_index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create container cell executed with correct output.'); + }); - this.test.assert(this.cell_element_exists(index, + // Wait for the widgets to actually display. + var widget_box_selector = '.widget-area .widget-subarea .widget-box'; + var widget_box_button_selector = '.widget-area .widget-subarea .widget-box button'; + this.wait_for_element(container_index, widget_box_selector); + this.wait_for_element(container_index, widget_box_button_selector); + + // Continue with the tests. + this.then(function() { + this.test.assert(this.cell_element_exists(container_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, - '.widget-area .widget-subarea .widget-box'), + this.test.assert(this.cell_element_exists(container_index, + widget_box_selector), 'Widget container exists.'); - this.test.assert(this.cell_element_exists(index, + this.test.assert(this.cell_element_exists(container_index, '.widget-area .widget-subarea .my-test-class'), '_dom_classes works.'); - this.test.assert(this.cell_element_exists(index, - '.widget-area .widget-subarea .my-test-class button'), + this.test.assert(this.cell_element_exists(container_index, + widget_box_button_selector), 'Container parent/child relationship works.'); }); @@ -61,20 +67,26 @@ casper.notebook_test(function () { '_dom_classes can be used to remove a class.'); }); - index = this.append_cell( + var boxalone_index = this.append_cell( 'display(button)\n'+ 'print("Success")\n'); - this.execute_cell_then(index, function(index){ - + this.execute_cell_then(boxalone_index, function(index){ this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Display container child executed with correct output.'); + }); + + // Wait for the widget to actually display. + var widget_button_selector = '.widget-area .widget-subarea button'; + this.wait_for_element(boxalone_index, widget_button_selector); - this.test.assert(! this.cell_element_exists(index, - '.widget-area .widget-subarea .widget-box'), + // Continue with the tests. + this.then(function() { + this.test.assert(! this.cell_element_exists(boxalone_index, + widget_box_selector), 'Parent container not displayed.'); - this.test.assert(this.cell_element_exists(index, - '.widget-area .widget-subarea button'), + this.test.assert(this.cell_element_exists(boxalone_index, + widget_button_selector), 'Child displayed.'); }); }); \ No newline at end of file diff --git a/IPython/html/tests/widgets/widget_button.js b/IPython/html/tests/widgets/widget_button.js index 3329a89bd..80f86732a 100644 --- a/IPython/html/tests/widgets/widget_button.js +++ b/IPython/html/tests/widgets/widget_button.js @@ -1,12 +1,8 @@ // Test widget button class casper.notebook_test(function () { - index = this.append_cell( + var button_index = this.append_cell( 'from IPython.html import widgets\n' + 'from IPython.display import display, clear_output\n' + - 'print("Success")'); - this.execute_cell_then(index); - - var button_index = this.append_cell( 'button = widgets.Button(description="Title")\n' + 'display(button)\n' + 'print("Success")\n' + @@ -14,24 +10,30 @@ casper.notebook_test(function () { ' display("Clicked")\n' + 'button.on_click(handle_click)'); this.execute_cell_then(button_index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create button cell executed with correct output.'); + }); + + // Wait for the widgets to actually display. + var widget_button_selector = '.widget-area .widget-subarea button'; + this.wait_for_element(button_index, widget_button_selector); - this.test.assert(this.cell_element_exists(index, + // Continue with the tests. + this.then(function() { + this.test.assert(this.cell_element_exists(button_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, - '.widget-area .widget-subarea button'), + this.test.assert(this.cell_element_exists(button_index, + widget_button_selector), 'Widget button exists.'); - this.test.assert(this.cell_element_function(index, - '.widget-area .widget-subarea button', 'html')=='Title', + this.test.assert(this.cell_element_function(button_index, + widget_button_selector, 'html')=='Title', 'Set button description.'); - this.cell_element_function(index, - '.widget-area .widget-subarea button', 'click'); + this.cell_element_function(button_index, + widget_button_selector, 'click'); }); this.wait_for_output(button_index, 1); diff --git a/IPython/html/tests/widgets/widget_float.js b/IPython/html/tests/widgets/widget_float.js index 291efb314..6b18dad1f 100644 --- a/IPython/html/tests/widgets/widget_float.js +++ b/IPython/html/tests/widgets/widget_float.js @@ -1,30 +1,34 @@ // Test widget float class casper.notebook_test(function () { - index = this.append_cell( - 'from IPython.html import widgets\n' + - 'from IPython.display import display, clear_output\n' + - 'print("Success")'); - this.execute_cell_then(index); - var float_text = {}; float_text.query = '.widget-area .widget-subarea .my-second-float-text input'; float_text.index = this.append_cell( + 'from IPython.html import widgets\n' + + 'from IPython.display import display, clear_output\n' + 'float_widget = widgets.FloatText()\n' + 'display(float_widget)\n' + 'float_widget._dom_classes = ["my-second-float-text"]\n' + 'print(float_widget.model_id)\n'); this.execute_cell_then(float_text.index, function(index){ float_text.model_id = this.get_output_cell(index).text.trim(); - - this.test.assert(this.cell_element_exists(index, + }); + + // Wait for the widget to actually display. + this.wait_for_element(float_text.index, float_text.query); + + // Continue with the tests + this.then(function(){ + this.test.assert(this.cell_element_exists(float_text.index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, float_text.query), + this.test.assert(this.cell_element_exists(float_text.index, float_text.query), 'Widget float textbox exists.'); this.cell_element_function(float_text.index, float_text.query, 'val', ['']); + console.log('send keys'); this.sendKeys(float_text.query, '1.05'); + console.log('send keys done'); }); this.wait_for_widget(float_text); @@ -64,18 +68,23 @@ casper.notebook_test(function () { '[display(floatrange[i]) for i in range(2)]\n' + 'print("Success")\n'); this.execute_cell_then(slider.index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create float range cell executed with correct output.'); + }); + + // Wait for the widgets to actually display. + this.wait_for_element(slider.index, slider.query); + this.wait_for_element(slider.index, float_text_query); - this.test.assert(this.cell_element_exists(index, + this.then(function(){ + this.test.assert(this.cell_element_exists(slider.index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, slider.query), + this.test.assert(this.cell_element_exists(slider.index, slider.query), 'Widget slider exists.'); - this.test.assert(this.cell_element_exists(index, float_text_query), + this.test.assert(this.cell_element_exists(slider.index, float_text_query), 'Widget float textbox exists.'); }); diff --git a/IPython/html/tests/widgets/widget_image.js b/IPython/html/tests/widgets/widget_image.js index ba10d3543..507215543 100644 --- a/IPython/html/tests/widgets/widget_image.js +++ b/IPython/html/tests/widgets/widget_image.js @@ -26,19 +26,23 @@ casper.notebook_test(function () { 'display(image)\n' + 'print("Success")\n'); this.execute_cell_then(image_index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create image executed with correct output.'); + }); + + // Wait for the widget to actually display. + var img_selector = '.widget-area .widget-subarea img'; + this.wait_for_element(image_index, img_selector); - this.test.assert(this.cell_element_exists(index, + this.then(function(){ + this.test.assert(this.cell_element_exists(image_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - var img_sel = '.widget-area .widget-subarea img'; - this.test.assert(this.cell_element_exists(index, img_sel), 'Image exists.'); + this.test.assert(this.cell_element_exists(image_index, img_selector), 'Image exists.'); // Verify that the image's base64 data has made it into the DOM. - var img_src = this.cell_element_function(image_index, img_sel, 'attr', ['src']); + var img_src = this.cell_element_function(image_index, img_selector, 'attr', ['src']); this.test.assert(img_src.indexOf(test_jpg) > -1, 'Image src data exists.'); }); }); \ No newline at end of file diff --git a/IPython/html/tests/widgets/widget_int.js b/IPython/html/tests/widgets/widget_int.js index 086235198..8ca6ca81a 100644 --- a/IPython/html/tests/widgets/widget_int.js +++ b/IPython/html/tests/widgets/widget_int.js @@ -1,26 +1,28 @@ // Test widget int class casper.notebook_test(function () { - index = this.append_cell( - 'from IPython.html import widgets\n' + - 'from IPython.display import display, clear_output\n' + - 'print("Success")'); - this.execute_cell_then(index); - var int_text = {}; int_text.query = '.widget-area .widget-subarea .my-second-int-text input'; int_text.index = this.append_cell( + 'from IPython.html import widgets\n' + + 'from IPython.display import display, clear_output\n' + 'int_widget = widgets.IntText()\n' + 'display(int_widget)\n' + 'int_widget._dom_classes = ["my-second-int-text"]\n' + 'print(int_widget.model_id)\n'); this.execute_cell_then(int_text.index, function(index){ int_text.model_id = this.get_output_cell(index).text.trim(); - - this.test.assert(this.cell_element_exists(index, + }); + + // Wait for the widget to actually display. + this.wait_for_element(int_text.index, int_text.query); + + // Continue with the tests. + this.then(function() { + this.test.assert(this.cell_element_exists(int_text.index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, int_text.query), + this.test.assert(this.cell_element_exists(int_text.index, int_text.query), 'Widget int textbox exists.'); this.cell_element_function(int_text.index, int_text.query, 'val', ['']); @@ -54,13 +56,6 @@ casper.notebook_test(function () { this.test.assertEquals(this.get_output_cell(index).text, '12\n', 'Invald int textbox value caught and filtered.'); }); - - index = this.append_cell( - 'from IPython.html import widgets\n' + - 'from IPython.display import display, clear_output\n' + - 'print("Success")'); - this.execute_cell_then(index); - var slider_query = '.widget-area .widget-subarea .slider'; var int_text2 = {}; @@ -73,15 +68,22 @@ casper.notebook_test(function () { 'print(intrange[0].model_id)\n'); this.execute_cell_then(int_text2.index, function(index){ int_text2.model_id = this.get_output_cell(index).text.trim(); + }); + + // Wait for the widgets to actually display. + this.wait_for_element(int_text2.index, int_text2.query); + this.wait_for_element(int_text2.index, slider_query); - this.test.assert(this.cell_element_exists(index, + // Continue with the tests. + this.then(function(){ + this.test.assert(this.cell_element_exists(int_text2.index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, slider_query), + this.test.assert(this.cell_element_exists(int_text2.index, slider_query), 'Widget slider exists.'); - this.test.assert(this.cell_element_exists(index, int_text2.query), + this.test.assert(this.cell_element_exists(int_text2.index, int_text2.query), 'Widget int textbox exists.'); }); diff --git a/IPython/html/tests/widgets/widget_selection.js b/IPython/html/tests/widgets/widget_selection.js index e4e4524f2..ea87a3830 100644 --- a/IPython/html/tests/widgets/widget_selection.js +++ b/IPython/html/tests/widgets/widget_selection.js @@ -58,21 +58,30 @@ casper.notebook_test(function () { this.execute_cell_then(selection_index, function(index){ this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create selection cell executed with correct output.'); + }); + + // Wait for the widgets to actually display. + this.wait_for_element(selection_index, combo_selector); + this.wait_for_element(selection_index, multibtn_selector); + this.wait_for_element(selection_index, radio_selector); + this.wait_for_element(selection_index, list_selector); - this.test.assert(this.cell_element_exists(index, + // Continue with the tests. + this.then(function() { + this.test.assert(this.cell_element_exists(selection_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, combo_selector), + this.test.assert(this.cell_element_exists(selection_index, combo_selector), 'Widget combobox exists.'); - this.test.assert(this.cell_element_exists(index, multibtn_selector), + this.test.assert(this.cell_element_exists(selection_index, multibtn_selector), 'Widget multibutton exists.'); - this.test.assert(this.cell_element_exists(index, radio_selector), + this.test.assert(this.cell_element_exists(selection_index, radio_selector), 'Widget radio buttons exists.'); - this.test.assert(this.cell_element_exists(index, list_selector), + this.test.assert(this.cell_element_exists(selection_index, list_selector), 'Widget list exists.'); // Verify that no items are selected. diff --git a/IPython/html/tests/widgets/widget_selectioncontainer.js b/IPython/html/tests/widgets/widget_selectioncontainer.js index 57e9bd1c2..d8680addd 100644 --- a/IPython/html/tests/widgets/widget_selectioncontainer.js +++ b/IPython/html/tests/widgets/widget_selectioncontainer.js @@ -18,20 +18,22 @@ casper.notebook_test(function () { 'multicontainer.selected_index = 0\n' + 'print("Success")\n'); this.execute_cell_then(multicontainer1_index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create multicontainer cell executed with correct output. (1)'); + }); + + // Wait for the widget to actually display. + this.wait_for_element(multicontainer1_index, multicontainer1_query); - this.test.assert(this.cell_element_exists(index, + // Continue with the tests. + this.then(function() { + this.test.assert(this.cell_element_exists(multicontainer1_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, multicontainer1_query), + this.test.assert(this.cell_element_exists(multicontainer1_index, multicontainer1_query), 'Widget tab list exists.'); - this.test.assert(this.cell_element_exists(index, multicontainer1_query), - 'First widget tab list exists.'); - // JQuery selector is 1 based this.click(multicontainer1_query + ' li:nth-child(2) a'); }); @@ -74,23 +76,28 @@ casper.notebook_test(function () { 'multicontainer.selected_index = 0\n' + 'print("Success")\n'); this.execute_cell_then(multicontainer2_index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create multicontainer cell executed with correct output. (2)'); + }); + + // Wait for the widget to actually display. + this.wait_for_element(multicontainer2_index, multicontainer2_query); - this.test.assert(this.cell_element_exists(index, + // Continue with the tests. + this.then(function() { + this.test.assert(this.cell_element_exists(multicontainer2_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, multicontainer2_query), + this.test.assert(this.cell_element_exists(multicontainer2_index, multicontainer2_query), 'Widget accordion exists.'); - this.test.assert(this.cell_element_exists(index, multicontainer2_query + + this.test.assert(this.cell_element_exists(multicontainer2_index, multicontainer2_query + ' .panel:nth-child(1) .panel-collapse'), 'First accordion page exists.'); // JQuery selector is 1 based - this.test.assert(this.cell_element_function(index, multicontainer2_query + + this.test.assert(this.cell_element_function(multicontainer2_index, multicontainer2_query + ' .panel.panel-default:nth-child(3) .panel-heading .accordion-toggle', 'html')=='good', 'Accordion page title set (before display).'); diff --git a/IPython/html/tests/widgets/widget_string.js b/IPython/html/tests/widgets/widget_string.js index 54e669d10..c01afb749 100644 --- a/IPython/html/tests/widgets/widget_string.js +++ b/IPython/html/tests/widgets/widget_string.js @@ -1,12 +1,8 @@ // Test widget string class casper.notebook_test(function () { - index = this.append_cell( + var string_index = this.append_cell( 'from IPython.html import widgets\n' + 'from IPython.display import display, clear_output\n' + - 'print("Success")'); - this.execute_cell_then(index); - - var string_index = this.append_cell( 'string_widget = [widgets.Text(value = "xyz", placeholder = "abc"),\n' + ' widgets.Textarea(value = "xyz", placeholder = "def"),\n' + ' widgets.HTML(value = "xyz"),\n' + @@ -14,40 +10,50 @@ casper.notebook_test(function () { '[display(widget) for widget in string_widget]\n'+ 'print("Success")'); this.execute_cell_then(string_index, function(index){ - this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 'Create string widget cell executed with correct output.'); + }); + + // Wait for the widget to actually display. + var textbox_selector = '.widget-area .widget-subarea .widget-hbox input[type=text]'; + var textarea_selector = '.widget-area .widget-subarea .widget-hbox textarea'; + var latex_selector = '.widget-area .widget-subarea div span.MathJax_Preview'; + this.wait_for_element(string_index, textbox_selector); + this.wait_for_element(string_index, textarea_selector); + this.wait_for_element(string_index, latex_selector); - this.test.assert(this.cell_element_exists(index, + // Continue with the tests. + this.then(function(){ + this.test.assert(this.cell_element_exists(string_index, '.widget-area .widget-subarea'), 'Widget subarea exists.'); - this.test.assert(this.cell_element_exists(index, - '.widget-area .widget-subarea .widget-hbox input[type=text]'), + this.test.assert(this.cell_element_exists(string_index, + textbox_selector), 'Textbox exists.'); - this.test.assert(this.cell_element_exists(index, - '.widget-area .widget-subarea .widget-hbox textarea'), + this.test.assert(this.cell_element_exists(string_index, + textarea_selector), 'Textarea exists.'); - this.test.assert(this.cell_element_function(index, - '.widget-area .widget-subarea .widget-hbox textarea', 'val')=='xyz', + this.test.assert(this.cell_element_function(string_index, + textarea_selector, 'val')=='xyz', 'Python set textarea value.'); - this.test.assert(this.cell_element_function(index, - '.widget-area .widget-subarea .widget-hbox input[type=text]', 'val')=='xyz', + this.test.assert(this.cell_element_function(string_index, + textbox_selector, 'val')=='xyz', 'Python set textbox value.'); this.test.assert(this.cell_element_exists(string_index, - '.widget-area .widget-subarea div span.MathJax_Preview'), + latex_selector), 'MathJax parsed the LaTeX successfully.'); - this.test.assert(this.cell_element_function(index, - '.widget-area .widget-subarea .widget-hbox textarea', 'attr', ['placeholder'])=='def', + this.test.assert(this.cell_element_function(string_index, + textarea_selector, 'attr', ['placeholder'])=='def', 'Python set textarea placeholder.'); - this.test.assert(this.cell_element_function(index, - '.widget-area .widget-subarea .widget-hbox input[type=text]', 'attr', ['placeholder'])=='abc', + this.test.assert(this.cell_element_function(string_index, + textbox_selector, 'attr', ['placeholder'])=='abc', 'Python set textbox placehoder.'); }); }); From 2d80b7239b6545d0be81b1a168272ecde15462ce Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Wed, 12 Nov 2014 15:05:36 -0800 Subject: [PATCH 31/40] Let Travis know about es6-promise, instead of rsvp which is not used anymore. --- setupbase.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setupbase.py b/setupbase.py index 6d2458421..3dbc6b53d 100644 --- a/setupbase.py +++ b/setupbase.py @@ -159,7 +159,6 @@ def find_package_data(): pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"), pjoin(components, "marked", "lib", "marked.js"), pjoin(components, "requirejs", "require.js"), - pjoin(components, "rsvp", "rsvp.js"), pjoin(components, "underscore", "underscore-min.js"), pjoin(components, "moment", "moment.js"), pjoin(components, "moment", "min", "moment.min.js"), From b56fc606adace863d03982ee5fa337a983bc51dd Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Thu, 13 Nov 2014 16:53:22 -0800 Subject: [PATCH 32/40] Promise the messages to the model --- IPython/html/static/services/kernels/comm.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index adf748c79..7a9787c3c 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -120,6 +120,11 @@ define([ this.target_name = target_name; this.comm_id = comm_id || utils.uuid(); this._msg_callback = this._close_callback = null; + + var that = this; + this.msg_promise = new Promise(function(resolve, reject) { + that.resolve_msg_promise = resolve; + }); }; // methods for sending messages @@ -155,6 +160,7 @@ define([ Comm.prototype.on_msg = function (callback) { this._register_callback('msg', callback); + this.resolve_msg_promise(); }; Comm.prototype.on_close = function (callback) { @@ -175,7 +181,10 @@ define([ }; Comm.prototype.handle_msg = function (msg) { - this._maybe_callback('msg', msg); + var that = this; + this.msg_promise.then(function() { + that._maybe_callback('msg', msg); + }); }; Comm.prototype.handle_close = function (msg) { From eb319c8aabb2350013dd8ad67a7836f431340c41 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 14 Nov 2014 08:50:30 -0800 Subject: [PATCH 33/40] Promise logic is infectious like a disease --- IPython/html/static/services/kernels/comm.js | 55 ++++++++++++-------- IPython/html/tests/widgets/widget_float.js | 2 - 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index 7a9787c3c..1e7335003 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -51,7 +51,7 @@ define([ CommManager.prototype.register_comm = function (comm) { // Register a comm in the mapping - this.comms[comm.comm_id] = comm; + this.comms[comm.comm_id] = new Promise(function(resolve) {resolve(comm);}); comm.kernel = this.kernel; return comm.comm_id; }; @@ -66,12 +66,13 @@ define([ CommManager.prototype.comm_open = function (msg) { var content = msg.content; var that = this; - - return utils.load_class(content.target_name, content.target_module, + var comm_id = content.comm_id; + + this.comms[comm_id] = utils.load_class(content.target_name, content.target_module, this.targets).then(function(target) { - var comm = new Comm(content.target_name, content.comm_id); - that.register_comm(comm); + var comm = new Comm(content.target_name, comm_id); + comm.kernel = that.kernel; try { target(comm, msg); } catch (e) { @@ -83,33 +84,40 @@ define([ } return comm; }, utils.reject('Could not open comm', true)); + return this.comms[comm_id]; }; - CommManager.prototype.comm_close = function (msg) { + CommManager.prototype.comm_close = function(msg) { var content = msg.content; - var comm = this.comms[content.comm_id]; - if (comm === undefined) { + if (!this.comms[content.comm_id]) { + console.error('Comm promise not found for comm id ' + content.comm_id); return; } - this.unregister_comm(comm); - try { - comm.handle_close(msg); - } catch (e) { - console.log("Exception closing comm: ", e, e.stack, msg); - } + + this.comms[content.comm_id].then(function(comm) { + this.unregister_comm(comm); + try { + comm.handle_close(msg); + } catch (e) { + console.log("Exception closing comm: ", e, e.stack, msg); + } + }); }; - CommManager.prototype.comm_msg = function (msg) { + CommManager.prototype.comm_msg = function(msg) { var content = msg.content; - var comm = this.comms[content.comm_id]; - if (comm === undefined) { + if (!this.comms[content.comm_id]) { + console.error('Comm promise not found for comm id ' + content.comm_id); return; } - try { - comm.handle_msg(msg); - } catch (e) { - console.log("Exception handling comm msg: ", e, e.stack, msg); - } + + this.comms[content.comm_id].then(function(comm) { + try { + comm.handle_msg(msg); + } catch (e) { + console.log("Exception handling comm msg: ", e, e.stack, msg); + } + }); }; //----------------------------------------------------------------------- @@ -182,8 +190,9 @@ define([ Comm.prototype.handle_msg = function (msg) { var that = this; - this.msg_promise.then(function() { + this.msg_promise = this.msg_promise.then(function() { that._maybe_callback('msg', msg); + return Promise.resolve(); }); }; diff --git a/IPython/html/tests/widgets/widget_float.js b/IPython/html/tests/widgets/widget_float.js index 6b18dad1f..d253a80b7 100644 --- a/IPython/html/tests/widgets/widget_float.js +++ b/IPython/html/tests/widgets/widget_float.js @@ -26,9 +26,7 @@ casper.notebook_test(function () { 'Widget float textbox exists.'); this.cell_element_function(float_text.index, float_text.query, 'val', ['']); - console.log('send keys'); this.sendKeys(float_text.query, '1.05'); - console.log('send keys done'); }); this.wait_for_widget(float_text); From 93cedc167eeb1d1c42b09f127dc9faee8a2248b8 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 14 Nov 2014 11:38:31 -0800 Subject: [PATCH 34/40] Address @takluyver 's comments --- IPython/html/static/services/kernels/comm.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index 1e7335003..6cdce036a 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -51,7 +51,7 @@ define([ CommManager.prototype.register_comm = function (comm) { // Register a comm in the mapping - this.comms[comm.comm_id] = new Promise(function(resolve) {resolve(comm);}); + this.comms[comm.comm_id] = Promise.resolve(comm); comm.kernel = this.kernel; return comm.comm_id; }; @@ -74,7 +74,10 @@ define([ var comm = new Comm(content.target_name, comm_id); comm.kernel = that.kernel; try { - target(comm, msg); + var response = target(comm, msg); + if (response instanceof Promise) { + return response.then(function() { Promise.resolve(comm); }); + } } catch (e) { comm.close(); that.unregister_comm(comm); @@ -82,7 +85,7 @@ define([ console.error(wrapped_error); return Promise.reject(wrapped_error); } - return comm; + return Promise.resolve(comm); }, utils.reject('Could not open comm', true)); return this.comms[comm_id]; }; From fe398593e758075107d43183825ac23d9be97cf6 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 14 Nov 2014 14:24:26 -0800 Subject: [PATCH 35/40] Typo fix --- IPython/html/static/services/kernels/comm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index 6cdce036a..156e10b83 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -76,7 +76,7 @@ define([ try { var response = target(comm, msg); if (response instanceof Promise) { - return response.then(function() { Promise.resolve(comm); }); + return response.then(function() { return Promise.resolve(comm); }); } } catch (e) { comm.close(); From 3c949aad92f66e2a89b7a3480eb37e8dfee14055 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 14 Nov 2014 14:25:19 -0800 Subject: [PATCH 36/40] Remove message promise. --- IPython/html/static/services/kernels/comm.js | 12 +----------- IPython/html/static/widgets/js/manager.js | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index 156e10b83..d254c945c 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -131,11 +131,6 @@ define([ this.target_name = target_name; this.comm_id = comm_id || utils.uuid(); this._msg_callback = this._close_callback = null; - - var that = this; - this.msg_promise = new Promise(function(resolve, reject) { - that.resolve_msg_promise = resolve; - }); }; // methods for sending messages @@ -171,7 +166,6 @@ define([ Comm.prototype.on_msg = function (callback) { this._register_callback('msg', callback); - this.resolve_msg_promise(); }; Comm.prototype.on_close = function (callback) { @@ -192,11 +186,7 @@ define([ }; Comm.prototype.handle_msg = function (msg) { - var that = this; - this.msg_promise = this.msg_promise.then(function() { - that._maybe_callback('msg', msg); - return Promise.resolve(); - }); + that._maybe_callback('msg', msg); }; Comm.prototype.handle_close = function (msg) { diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 7cd73dcb1..31252acc6 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -183,7 +183,7 @@ define([ WidgetManager.prototype._handle_comm_open = function (comm, msg) { // Handle when a comm is opened. - this.create_model({ + return this.create_model({ model_name: msg.content.data.model_name, model_module: msg.content.data.model_module, comm: comm}).catch($.proxy(console.error, console)); From 907bde3b25a5ada8f7a7ac8632b4f3ca34aa3444 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 14 Nov 2014 17:33:10 -0800 Subject: [PATCH 37/40] Address review comments --- IPython/html/static/services/kernels/comm.js | 14 +++++++------- IPython/html/static/widgets/js/manager.js | 2 +- IPython/html/static/widgets/js/widget_int.js | 6 +++--- IPython/html/static/widgets/js/widget_selection.js | 8 ++++---- IPython/html/static/widgets/js/widget_string.js | 4 ++-- IPython/html/tests/widgets/widget.js | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index d254c945c..d189d862b 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -92,12 +92,12 @@ define([ CommManager.prototype.comm_close = function(msg) { var content = msg.content; - if (!this.comms[content.comm_id]) { + if (this.comms[content.comm_id] === undefined) { console.error('Comm promise not found for comm id ' + content.comm_id); return; } - this.comms[content.comm_id].then(function(comm) { + this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) { this.unregister_comm(comm); try { comm.handle_close(msg); @@ -109,12 +109,12 @@ define([ CommManager.prototype.comm_msg = function(msg) { var content = msg.content; - if (!this.comms[content.comm_id]) { + if (this.comms[content.comm_id] === undefined) { console.error('Comm promise not found for comm id ' + content.comm_id); return; } - this.comms[content.comm_id].then(function(comm) { + this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) { try { comm.handle_msg(msg); } catch (e) { @@ -174,7 +174,7 @@ define([ // methods for handling incoming messages - Comm.prototype._maybe_callback = function (key, msg) { + Comm.prototype._callback = function (key, msg) { var callback = this['_' + key + '_callback']; if (callback) { try { @@ -186,11 +186,11 @@ define([ }; Comm.prototype.handle_msg = function (msg) { - that._maybe_callback('msg', msg); + this._callback('msg', msg); }; Comm.prototype.handle_close = function (msg) { - this._maybe_callback('close', msg); + this._callback('close', msg); }; // For backwards compatability. diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 31252acc6..b4d9d8440 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -186,7 +186,7 @@ define([ return this.create_model({ model_name: msg.content.data.model_name, model_module: msg.content.data.model_module, - comm: comm}).catch($.proxy(console.error, console)); + comm: comm}).catch(utils.reject("Couldn't create a model.")); }; WidgetManager.prototype.create_model = function (options) { diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js index c6278cbe0..3fd0ae0de 100644 --- a/IPython/html/static/widgets/js/widget_int.js +++ b/IPython/html/static/widgets/js/widget_int.js @@ -150,7 +150,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } @@ -308,7 +308,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -416,7 +416,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } return ProgressView.__super__.update.apply(this); diff --git a/IPython/html/static/widgets/js/widget_selection.js b/IPython/html/static/widgets/js/widget_selection.js index f441bd474..a40433cef 100644 --- a/IPython/html/static/widgets/js/widget_selection.js +++ b/IPython/html/static/widgets/js/widget_selection.js @@ -94,7 +94,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -219,7 +219,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -326,7 +326,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -441,7 +441,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } diff --git a/IPython/html/static/widgets/js/widget_string.js b/IPython/html/static/widgets/js/widget_string.js index 3464c9cd3..535a80c8f 100644 --- a/IPython/html/static/widgets/js/widget_string.js +++ b/IPython/html/static/widgets/js/widget_string.js @@ -101,7 +101,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } @@ -177,7 +177,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - // MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } } diff --git a/IPython/html/tests/widgets/widget.js b/IPython/html/tests/widgets/widget.js index 3f0c35d11..d3c6b6d2a 100644 --- a/IPython/html/tests/widgets/widget.js +++ b/IPython/html/tests/widgets/widget.js @@ -131,7 +131,7 @@ casper.notebook_test(function () { multiset.model_id = this.get_output_cell(index).text.trim(); }); - this.wait_for_widget(multiset); + this.wait_for_widget(multiset); index = this.append_cell( 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))'); From cfc45918bb7fdf76762fe473f12b2ae849d686b3 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 14 Nov 2014 18:02:50 -0800 Subject: [PATCH 38/40] Rebase fixes --- IPython/html/static/notebook/js/main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js index 41870ba8f..6c4a6a663 100644 --- a/IPython/html/static/notebook/js/main.js +++ b/IPython/html/static/notebook/js/main.js @@ -2,7 +2,6 @@ // Distributed under the terms of the Modified BSD License. require([ - 'es6promise', 'base/js/namespace', 'jquery', 'notebook/js/notebook', @@ -26,7 +25,6 @@ require([ // only loaded, not used, please keep sure this is loaded last 'custom/custom' ], function( - es6promise, IPython, $, notebook, From 61ebd40206bbc689d27aaf2b77cb6ba43af3ba6b Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 17 Nov 2014 11:44:10 -0800 Subject: [PATCH 39/40] Bug fix, promise not resolving. --- IPython/html/static/services/kernels/comm.js | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/html/static/services/kernels/comm.js b/IPython/html/static/services/kernels/comm.js index d189d862b..42fe272ab 100644 --- a/IPython/html/static/services/kernels/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -120,6 +120,7 @@ define([ } catch (e) { console.log("Exception handling comm msg: ", e, e.stack, msg); } + return Promise.resolve(comm); }); }; From f9d730d01ee7565a8f69d11b81a964109e49ef9c Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 17 Nov 2014 14:17:17 -0800 Subject: [PATCH 40/40] Address @takluyver 's review comments --- IPython/html/static/base/js/utils.js | 4 ++-- IPython/html/static/widgets/js/manager.js | 15 +++++---------- IPython/html/static/widgets/js/widget.js | 4 ++-- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index a0d9c3cd9..fba5baac4 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -661,7 +661,7 @@ define([ }); }; - var resolve_dict = function(d) { + var resolve_promises_dict = function(d) { // Resolve a promiseful dictionary. // Returns a single Promise. var keys = Object.keys(d); @@ -749,7 +749,7 @@ define([ promising_ajax : promising_ajax, WrappedError: WrappedError, load_class: load_class, - resolve_dict: resolve_dict, + resolve_promises_dict: resolve_promises_dict, reject: reject, }; diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index b4d9d8440..1507e5f96 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -54,18 +54,12 @@ define([ if (cell === null) { reject(new Error("Could not determine where the display" + " message was from. Widget will not be displayed")); - } else { - var dummy = null; - if (cell.widget_subarea) { - dummy = $('
'); - cell.widget_subarea.append(dummy); - } - + } else if (cell.widget_subarea) { + var dummy = $('
'); + cell.widget_subarea.append(dummy); that.create_view(model, {cell: cell}).then(function(view) { that._handle_display_view(view); - if (dummy) { - dummy.replaceWith(view.$el); - } + dummy.replaceWith(view.$el); view.trigger('displayed'); resolve(view); }, function(error) { @@ -178,6 +172,7 @@ define([ }; WidgetManager.prototype.get_model = function (model_id) { + // Get a promise for a model by model id. return this._models[model_id]; }; diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index b7730fe34..00a4ca740 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -80,7 +80,7 @@ define(["widgets/js/manager", this.trigger('msg:custom', msg.content.data.content); break; case 'display': - this.widget_manager.display_view(msg, that); + this.widget_manager.display_view(msg, this); break; } }, @@ -262,7 +262,7 @@ define(["widgets/js/manager", _.each(value, function(sub_value, key) { unpacked[key] = that._unpack_models(sub_value); }); - return utils.resolve_dict(unpacked); + return utils.resolve_promises_dict(unpacked); } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") { // get_model returns a promise already return this.widget_manager.get_model(value.slice(10, value.length));