From 4beda5d0fb143b0638ebce82e2c0168775cfe928 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Thu, 30 Oct 2014 11:26:57 -0700 Subject: [PATCH] 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); }); }); });