From d6414f0f9e6b7feb5b4f3e14bf8e80b84f46953e 2014-10-21 17:11:41 From: Jonathan Frederic Date: 2014-10-21 17:11:41 Subject: [PATCH] Merge pull request #6494 from takluyver/widget-comm-require Allow widget views to be loaded from require modules --- diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index 114ca96..dd3472c 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/namespace" ], function (_, Backbone, $, IPython) { - + "use strict"; //-------------------------------------------------------------------- // WidgetManager class //-------------------------------------------------------------------- @@ -52,15 +52,14 @@ 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) { + that._handle_display_view(view); + if (cell.widget_subarea) { + cell.widget_subarea.append(view.$el); + } + view.trigger('displayed'); + }}); } }; @@ -78,28 +77,43 @@ define([ } } }; + - WidgetManager.prototype.create_view = function(model, options, view) { + WidgetManager.prototype.create_view = function(model, options) { // 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 view_mod = model.get('_view_module'); + var errback = options.errback || function(err) {console.log(err);}; + + 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; + } + + // Create and render the view... + var parameters = {model: model, options: options}; + var view = new ViewType(parameters); + view.render(); + model.on('destroy', view.remove, view); + options.callback(view); + } else { + errback({unknown_view: true, view_name: view_name, + view_module: view_mod}); } + }; - // 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; + if (view_mod) { + require([view_mod], function(module) { + instantiate_view(module[view_name]); + }, errback); + } else { + instantiate_view(WidgetManager._view_types[view_name]); } - return null; }; 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 154b916..d60b384 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -317,18 +317,21 @@ 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; + var old_callback = options.callback || function(view) {}; + 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; + old_callback(child_view); + }}, options || {}); + + this.model.widget_manager.create_view(child_model, options); }, 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 be54fab..1d2edcb 100644 --- a/IPython/html/static/widgets/js/widget_box.js +++ b/IPython/html/static/widgets/js/widget_box.js @@ -74,13 +74,15 @@ 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, {callback: 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 4ed19c2..40b6e43 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,15 +113,18 @@ 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, {callback: 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,34 +185,37 @@ 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, {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 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'); - }); + }}); }, update: function(options) { diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py index 62448d2..86e85aa 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')