From 5953a3f874b43bb9461cfb22b78ee2e633037a31 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 17 Sep 2014 15:51:09 -0700 Subject: [PATCH] Allow widget views to be loaded from require modules This required refactoring things that create widget views to use callbacks instead of return values. --- IPython/html/static/widgets/js/manager.js | 64 ++++++++++------- IPython/html/static/widgets/js/widget.js | 26 +++---- IPython/html/static/widgets/js/widget_box.js | 12 ++-- .../widgets/js/widget_selectioncontainer.js | 70 ++++++++++--------- IPython/html/widgets/widget.py | 2 + 5 files changed, 98 insertions(+), 76 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 38626c8fc..54d51985f 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -63,15 +63,17 @@ define([ console.log("Could not determine where the display" + " message was from. Widget will not be displayed"); } else { - var view = this.create_view(model, {cell: cell}); - if (view === null) { - console.error("View creation failed", model); - } - this._handle_display_view(view); - if (cell.widget_subarea) { - cell.widget_subarea.append(view.$el); - } - view.trigger('displayed'); + var that = this; + this.create_view(model, {cell: cell, callback: function(view) { + if (view === null) { + console.error("View creation failed", model); + } + that._handle_display_view(view); + if (cell.widget_subarea) { + cell.widget_subarea.append(view.$el); + } + view.trigger('displayed'); + }}); } }; @@ -89,28 +91,38 @@ define([ } } }; + WidgetManager.prototype.create_view = function(model, options, view) { // Creates a view for a particular model. - var view_name = model.get('_view_name'); - var ViewType = WidgetManager._view_types[view_name]; - 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 (view !== undefined) { - options.cell = view.options.cell; + 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 (view !== undefined) { + options.cell = view.options.cell; + } + + // Create and render the view... + var parameters = {model: model, options: options}; + view = new ViewType(parameters); + view.render(); + model.on('destroy', view.remove, view); + options.callback(view); } - - // Create and render the view... - var parameters = {model: model, options: options}; - view = new ViewType(parameters); - view.render(); - model.on('destroy', view.remove, view); - return view; } - return null; + + var view_name = model.get('_view_name'); + var view_mod = model.get('_view_module'); + if (view_mod !== '') { + console.log(view_mod); + require([view_mod], function(module) { + instantiate_view(module[view_name]) + }); + } else { + instantiate_view(WidgetManager._view_types[view_name]); + } }; 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 dd17dedc9..4fa87768e 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -308,7 +308,7 @@ define(["widgets/js/manager", // Update view to be consistent with this.model }, - create_child_view: function(child_model, options) { + create_child_view: function(child_model, callback, options) { // Create and return a child view. // // -given a model and (optionally) a view name if the view name is @@ -317,18 +317,20 @@ define(["widgets/js/manager", // 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. - options = $.extend({ parent: this }, options || {}); - var child_view = this.model.widget_manager.create_view(child_model, options, this); - - // Associate the view id with the model id. - if (this.child_model_views[child_model.id] === undefined) { - this.child_model_views[child_model.id] = []; - } - this.child_model_views[child_model.id].push(child_view.id); + var that = this; + options = $.extend({ parent: this, callback: 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. - this.child_views[child_view.id] = child_view; - return child_view; + // Remember the view by id. + that.child_views[child_view.id] = child_view; + callback(child_view); + }}, options || {}); + + this.model.widget_manager.create_view(child_model, options, this); }, 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 be54fab2b..39e461225 100644 --- a/IPython/html/static/widgets/js/widget_box.js +++ b/IPython/html/static/widgets/js/widget_box.js @@ -74,12 +74,14 @@ define([ add_child_model: function(model) { // Called when a model is added to the children list. - var view = this.create_child_view(model); - this.$box.append(view.$el); + var that = this; + this.create_child_view(model, function(view) { + that.$box.append(view.$el); - // Trigger the displayed event of the child view. - this.after_displayed(function() { - view.trigger('displayed'); + // Trigger the displayed event of the child view. + that.after_displayed(function() { + view.trigger('displayed'); + }); }); }, }); diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js index 4ed19c2bb..98c682512 100644 --- a/IPython/html/static/widgets/js/widget_selectioncontainer.js +++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js @@ -81,7 +81,6 @@ define([ add_child_model: function(model) { // Called when a child is added to children list. - var view = this.create_child_view(model); var index = this.containers.length; var uuid = utils.uuid(); var accordion_group = $('
') @@ -114,14 +113,17 @@ define([ var container_index = this.containers.push(accordion_group) - 1; accordion_group.container_index = container_index; this.model_containers[model.id] = accordion_group; - accordion_inner.append(view.$el); + + this.create_child_view(model, function(view) { + accordion_inner.append(view.$el); - this.update(); - this.update_titles(); + that.update(); + that.update_titles(); - // Trigger the displayed event of the child view. - this.after_displayed(function() { - view.trigger('displayed'); + // Trigger the displayed event of the child view. + that.after_displayed(function() { + view.trigger('displayed'); + }); }); }, }); @@ -176,7 +178,6 @@ define([ add_child_model: function(model) { // Called when a child is added to children list. - var view = this.create_child_view(model); var index = this.containers.length; var uuid = utils.uuid(); @@ -184,33 +185,36 @@ define([ var tab = $('
  • ') .css('list-style-type', 'none') .appendTo(this.$tabs); - 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: this}); - that.touch(); - that.select_page(index); + this.create_child_view(model, 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 contents_div = $('
    ', {id: uuid}) + .addClass('tab-pane') + .addClass('fade') + .append(view.$el) + .appendTo(that.$tab_contents); + view.parent_container = contents_div; + + // Trigger the displayed event of the child view. + that.after_displayed(function() { + view.trigger('displayed'); }); - tab.tab_text_index = this.containers.push(tab_text) - 1; - - var contents_div = $('
    ', {id: uuid}) - .addClass('tab-pane') - .addClass('fade') - .append(view.$el) - .appendTo(this.$tab_contents); - view.parent_container = contents_div; - - // Trigger the displayed event of the child view. - this.after_displayed(function() { - view.trigger('displayed'); }); }, diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py index eba315f53..81d7b9ae4 100644 --- a/IPython/html/widgets/widget.py +++ b/IPython/html/widgets/widget.py @@ -100,6 +100,8 @@ class Widget(LoggingConfigurable): #------------------------------------------------------------------------- _model_name = Unicode('WidgetModel', help="""Name of the backbone model registered in the front-end to create and sync this widget with.""") + _view_module = Unicode('', help="""A requirejs module in which to find _view_name. + If empty, look in the global registry.""", sync=True) _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end to use to represent the widget.""", sync=True) comm = Instance('IPython.kernel.comm.Comm')