diff --git a/IPython/html/static/notebook/js/widget.js b/IPython/html/static/notebook/js/widget.js new file mode 100644 index 000000000..dca305b28 --- /dev/null +++ b/IPython/html/static/notebook/js/widget.js @@ -0,0 +1,229 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2013 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// WidgetModel, WidgetView, and WidgetManager +//============================================================================ +/** + * Base Widget classes + * @module IPython + * @namespace IPython + * @submodule widget + */ + + +// require(['components/underscore/underscore-min.js', +// 'components/backbone/backbone-min.js'], + +var IPython = function (IPython) { + "use strict"; + + //----------------------------------------------------------------------- + // WidgetModel class + //----------------------------------------------------------------------- + var WidgetModel = Backbone.Model.extend({}); + + + //----------------------------------------------------------------------- + // WidgetView class + //----------------------------------------------------------------------- + var WidgetView = Backbone.View.extend({ + + initialize: function() { + this.model.on('change',this.refresh,this); + }, + + refresh: function() { + this.update(); + + if (this.model.css != undefined) { + for (var selector in this.model.css) { + if (this.model.css.hasOwnProperty(selector)) { + + // Get the elements via the css selector. If the selector is + // blank, assume the current element is the target. + var elements = this.$el.find(selector); + if (selector=='') { + elements = this.$el; + } + + // Apply the css traits to all elements that match the selector. + if (elements.length>0){ + var css_traits = this.model.css[selector]; + for (var css_key in css_traits) { + if (css_traits.hasOwnProperty(css_key)) { + elements.css(css_key, css_traits[css_key]); + } + } + } + } + } + } + }, + }); + + + //----------------------------------------------------------------------- + // WidgetManager class + //----------------------------------------------------------------------- + // Public constructor + var WidgetManager = function(comm_manager){ + this.comm_manager = comm_manager; + this.widget_model_types = {}; + this.widget_view_types = {}; + this.model_widget_views = {}; + + var that = this; + Backbone.sync = function(method, model, options, error) { + var result = that.send_sync(method, model); + if (options.success) { + options.success(result); + } + }; + } + + // Register a widget model type. + WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) { + + // Register the widget with the comm manager. Make sure to pass this object's context + // in so `this` works in the call back. + this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this)); + + // Register the types of the model and view correspong to this widget type. Later + // the widget manager will initialize these when the comm is opened. + this.widget_model_types[widget_model_name] = widget_model_type; + } + + // Register a widget view type. + WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) { + this.widget_view_types[widget_view_name] = widget_view_type; + } + + // Handle when a comm is opened. + WidgetManager.prototype.handle_com_open = function (comm, msg) { + var widget_type_name = msg.content.target_name; + + // Create the corresponding widget model. + var widget_model = new this.widget_model_types[widget_type_name]; + + // Remember comm associated with the model. + widget_model.comm = comm; + comm.model = widget_model; + + // Create an array to remember the views associated with the model. + widget_model.views = []; + + // Add a handle to delete the control when the comm is closed. + var that = this; + var handle_close = function(msg) { + that.handle_comm_closed(comm, msg); + } + comm.on_close(handle_close); + + // Handle incomming messages. + var handle_msg = function(msg) { + that.handle_comm_msg(comm, msg); + } + comm.on_msg(handle_msg); + } + + // Create view that represents the model. + WidgetManager.prototype.show_view = function (widget_area, widget_model, widget_view_name) { + var widget_view = new this.widget_view_types[widget_view_name]({model: widget_model}); + widget_view.render(); + widget_model.views.push(widget_view); + + // Add the view's element to cell's widget div. + widget_area + .append($("
").append(widget_view.$el)); + + // Update the view based on the model contents. + widget_view.refresh(); + } + + // Handle incomming comm msg. + WidgetManager.prototype.handle_comm_msg = function (comm, msg) { + // Different logic for different methods. + var method = msg.content.data.method; + switch (method){ + case 'show': + + // TODO: Get cell from registered output handler. + var cell = IPython.notebook.get_cell(IPython.notebook.get_selected_index()-1); + var widget_subarea = cell.element.find('.widget_area').find('.widget_subarea'); + + if (msg.content.data.parent != undefined) { + var find_results = widget_subarea.find("." + msg.content.data.parent); + if (find_results.length > 0) { + widget_subarea = find_results; + } + } + + this.show_view(widget_subarea, comm.model, msg.content.data.view_name); + break; + case 'update': + this.handle_update(comm, msg.content.data.state); + break; + } + } + + // Handle when a widget is updated via the python side. + WidgetManager.prototype.handle_update = function (comm, state) { + for (var key in state) { + if (state.hasOwnProperty(key)) { + if (key=="_css"){ + comm.model.css = state[key]; + } else { + comm.model.set(key, state[key]); + } + } + } + comm.model.save(); + } + + // Handle when a widget is closed. + WidgetManager.prototype.handle_comm_closed = function (comm, msg) { + for (var view_index in comm.model.views) { + var view = comm.model.views[view_index]; + view.remove(); + } + } + + // Get the cell output area corresponding to the comm. + WidgetManager.prototype._get_comm_outputarea = function (comm) { + // TODO: get element from comm instead of guessing + var cell = IPython.notebook.get_cell(IPython.notebook.get_selected_index()) + return cell.output_area; + } + + // Send widget state to python backend. + WidgetManager.prototype.send_sync = function (method, model) { + + // Create a callback for the output if the widget has an output area associate with it. + var callbacks = {}; + var comm = model.comm; + var outputarea = this._get_comm_outputarea(comm); + if (outputarea != null) { + callbacks = { + iopub : { + output : $.proxy(outputarea.handle_output, outputarea), + clear_output : $.proxy(outputarea.handle_clear_output, outputarea)} + }; + }; + + var data = {sync_method: method, sync_data: model.toJSON()}; + comm.send(data, callbacks); + return data; + } + + IPython.WidgetManager = WidgetManager; + IPython.WidgetModel = WidgetModel; + IPython.WidgetView = WidgetView; + + return IPython; + +};