##// END OF EJS Templates
Re-decoupled comm_id from widget models
Jonathan Frederic -
Show More
@@ -1,208 +1,210 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // WidgetModel, WidgetView, and WidgetManager
9 // WidgetModel, WidgetView, and WidgetManager
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * Base Widget classes
12 * Base Widget classes
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule widget
15 * @submodule widget
16 */
16 */
17
17
18 (function () {
18 (function () {
19 "use strict";
19 "use strict";
20
20
21 // Use require.js 'define' method so that require.js is intelligent enough to
21 // Use require.js 'define' method so that require.js is intelligent enough to
22 // syncronously load everything within this file when it is being 'required'
22 // syncronously load everything within this file when it is being 'required'
23 // elsewhere.
23 // elsewhere.
24 define(["underscore",
24 define(["underscore",
25 "backbone",
25 "backbone",
26 ], function (underscore, backbone) {
26 ], function (underscore, backbone) {
27
27
28 //--------------------------------------------------------------------
28 //--------------------------------------------------------------------
29 // WidgetManager class
29 // WidgetManager class
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 var WidgetManager = function () {
31 var WidgetManager = function () {
32 this.comm_manager = null;
32 this.comm_manager = null;
33 this.widget_model_types = {};
33 this.widget_model_types = {};
34 this.widget_view_types = {};
34 this.widget_view_types = {};
35 this._model_instances = {};
35 this._model_instances = {};
36
36
37 Backbone.sync = function (method, model, options, error) {
37 Backbone.sync = function (method, model, options, error) {
38 var result = model._handle_sync(method, options);
38 var result = model._handle_sync(method, options);
39 if (options.success) {
39 if (options.success) {
40 options.success(result);
40 options.success(result);
41 }
41 }
42 };
42 };
43 };
43 };
44
44
45
45
46 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
46 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
47 this.comm_manager = comm_manager;
47 this.comm_manager = comm_manager;
48
48
49 // Register already-registered widget model types with the comm manager.
49 // Register already-registered widget model types with the comm manager.
50 for (var widget_model_name in this.widget_model_types) {
50 for (var widget_model_name in this.widget_model_types) {
51 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
51 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
52 }
52 }
53 };
53 };
54
54
55
55
56 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
56 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
57 // Register the widget with the comm manager. Make sure to pass this object's context
57 // Register the widget with the comm manager. Make sure to pass this object's context
58 // in so `this` works in the call back.
58 // in so `this` works in the call back.
59 if (this.comm_manager !== null) {
59 if (this.comm_manager !== null) {
60 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
60 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
61 }
61 }
62 this.widget_model_types[widget_model_name] = widget_model_type;
62 this.widget_model_types[widget_model_name] = widget_model_type;
63 };
63 };
64
64
65
65
66 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
66 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
67 this.widget_view_types[widget_view_name] = widget_view_type;
67 this.widget_view_types[widget_view_name] = widget_view_type;
68 };
68 };
69
70
69 WidgetManager.prototype.handle_msg = function(msg, model) {
71 WidgetManager.prototype.handle_msg = function(msg, model) {
70 var method = msg.content.data.method;
72 var method = msg.content.data.method;
71 switch (method) {
73 switch (method) {
72 case 'display':
74 case 'display':
73 var cell = this.get_msg_cell(msg.parent_header.msg_id);
75 var cell = this.get_msg_cell(msg.parent_header.msg_id);
74 if (cell === null) {
76 if (cell === null) {
75 console.log("Could not determine where the display" +
77 console.log("Could not determine where the display" +
76 " message was from. Widget will not be displayed");
78 " message was from. Widget will not be displayed");
77 } else {
79 } else {
78 var view = this.create_view(model,
80 var view = this.create_view(model,
79 msg.content.data.view_name, cell);
81 msg.content.data.view_name, cell);
80 if (view !== undefined
82 if (view !== undefined
81 && cell.widget_subarea !== undefined
83 && cell.widget_subarea !== undefined
82 && cell.widget_subarea !== null) {
84 && cell.widget_subarea !== null) {
83 cell.widget_area.show();
85 cell.widget_area.show();
84 cell.widget_subarea.append(view.$el);
86 cell.widget_subarea.append(view.$el);
85 }
87 }
86 }
88 }
87 break;
89 break;
88 }
90 }
89 }
91 }
90
92
91 WidgetManager.prototype.create_view = function(model, view_name, cell) {
93 WidgetManager.prototype.create_view = function(model, view_name, cell) {
92 view_name = view_name || model.get('default_view_name');
94 view_name = view_name || model.get('default_view_name');
93 var ViewType = this.widget_view_types[view_name];
95 var ViewType = this.widget_view_types[view_name];
94 if (ViewType !== undefined && ViewType !== null) {
96 if (ViewType !== undefined && ViewType !== null) {
95 var view = new ViewType({model: model, widget_manager: this, cell: cell});
97 var view = new ViewType({model: model, widget_manager: this, cell: cell});
96 view.render();
98 view.render();
97 model.views.push(view);
99 model.views.push(view);
98 model.on('destroy', view.remove, view);
100 model.on('destroy', view.remove, view);
99 /*
101 /*
100 // TODO: handle view deletion. Don't forget to delete child views
102 // TODO: handle view deletion. Don't forget to delete child views
101 var that = this;
103 var that = this;
102 view.$el.on("remove", function () {
104 view.$el.on("remove", function () {
103 var index = that.views.indexOf(view);
105 var index = that.views.indexOf(view);
104 if (index > -1) {
106 if (index > -1) {
105 that.views.splice(index, 1);
107 that.views.splice(index, 1);
106 }
108 }
107 view.remove(); // Clean-up view
109 view.remove(); // Clean-up view
108
110
109 // Close the comm if there are no views left.
111 // Close the comm if there are no views left.
110 if (that.views.length() === 0) {
112 if (that.views.length() === 0) {
111 //trigger comm close event?
113 //trigger comm close event?
112 }
114 }
113
115
114
116
115 if (that.comm !== undefined) {
117 if (that.comm !== undefined) {
116 that.comm.close();
118 that.comm.close();
117 delete that.comm.model; // Delete ref so GC will collect widget model.
119 delete that.comm.model; // Delete ref so GC will collect widget model.
118 delete that.comm;
120 delete that.comm;
119 }
121 }
120 delete that.widget_id; // Delete id from model so widget manager cleans up.
122 delete that.model_id; // Delete id from model so widget manager cleans up.
121 });
123 });
122 */
124 */
123 return view;
125 return view;
124 }
126 }
125 },
127 },
126
128
127 WidgetManager.prototype.get_msg_cell = function (msg_id) {
129 WidgetManager.prototype.get_msg_cell = function (msg_id) {
128 var cell = null;
130 var cell = null;
129 // First, check to see if the msg was triggered by cell execution.
131 // First, check to see if the msg was triggered by cell execution.
130 if (IPython.notebook !== undefined && IPython.notebook !== null) {
132 if (IPython.notebook !== undefined && IPython.notebook !== null) {
131 cell = IPython.notebook.get_msg_cell(msg_id);
133 cell = IPython.notebook.get_msg_cell(msg_id);
132 }
134 }
133 if (cell !== null) {
135 if (cell !== null) {
134 return cell
136 return cell
135 }
137 }
136 // Second, check to see if a get_cell callback was defined
138 // Second, check to see if a get_cell callback was defined
137 // for the message. get_cell callbacks are registered for
139 // for the message. get_cell callbacks are registered for
138 // widget messages, so this block is actually checking to see if the
140 // widget messages, so this block is actually checking to see if the
139 // message was triggered by a widget.
141 // message was triggered by a widget.
140 var kernel = this.get_kernel();
142 var kernel = this.get_kernel();
141 if (kernel !== undefined && kernel !== null) {
143 if (kernel !== undefined && kernel !== null) {
142 var callbacks = kernel.get_callbacks_for_msg(msg_id);
144 var callbacks = kernel.get_callbacks_for_msg(msg_id);
143 if (callbacks !== undefined &&
145 if (callbacks !== undefined &&
144 callbacks.iopub !== undefined &&
146 callbacks.iopub !== undefined &&
145 callbacks.iopub.get_cell !== undefined) {
147 callbacks.iopub.get_cell !== undefined) {
146
148
147 return callbacks.iopub.get_cell();
149 return callbacks.iopub.get_cell();
148 }
150 }
149 }
151 }
150
152
151 // Not triggered by a cell or widget (no get_cell callback
153 // Not triggered by a cell or widget (no get_cell callback
152 // exists).
154 // exists).
153 return null;
155 return null;
154 };
156 };
155
157
156
158
157 WidgetManager.prototype.get_model = function (widget_id) {
159 WidgetManager.prototype.get_model = function (model_id) {
158 var model = this._model_instances[widget_id];
160 var model = this._model_instances[model_id];
159 if (model !== undefined && model.id == widget_id) {
161 if (model !== undefined && model.id == model_id) {
160 return model;
162 return model;
161 }
163 }
162 return null;
164 return null;
163 };
165 };
164
166
165
167
166 WidgetManager.prototype.get_kernel = function () {
168 WidgetManager.prototype.get_kernel = function () {
167 if (this.comm_manager === null) {
169 if (this.comm_manager === null) {
168 return null;
170 return null;
169 } else {
171 } else {
170 return this.comm_manager.kernel;
172 return this.comm_manager.kernel;
171 }
173 }
172 };
174 };
173
175
174
176
175 WidgetManager.prototype.on_create_widget = function (callback) {
177 WidgetManager.prototype.on_create_widget = function (callback) {
176 this._create_widget_callback = callback;
178 this._create_widget_callback = callback;
177 };
179 };
178
180
179
181
180 WidgetManager.prototype._handle_create_widget = function (widget_model) {
182 WidgetManager.prototype._handle_create_widget = function (widget_model) {
181 if (this._create_widget_callback) {
183 if (this._create_widget_callback) {
182 try {
184 try {
183 this._create_widget_callback(widget_model);
185 this._create_widget_callback(widget_model);
184 } catch (e) {
186 } catch (e) {
185 console.log("Exception in WidgetManager callback", e, widget_model);
187 console.log("Exception in WidgetManager callback", e, widget_model);
186 }
188 }
187 }
189 }
188 };
190 };
189
191
190
192
191 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
193 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
192 var widget_type_name = msg.content.target_name;
194 var widget_type_name = msg.content.target_name;
193 var widget_model = new this.widget_model_types[widget_type_name](this, comm.comm_id, comm);
195 var widget_model = new this.widget_model_types[widget_type_name](this, comm.comm_id, comm);
194 this._model_instances[comm.comm_id] = widget_model;
196 this._model_instances[comm.comm_id] = widget_model; // comm_id == model_id
195 this._handle_create_widget(widget_model);
197 this._handle_create_widget(widget_model);
196 };
198 };
197
199
198 //--------------------------------------------------------------------
200 //--------------------------------------------------------------------
199 // Init code
201 // Init code
200 //--------------------------------------------------------------------
202 //--------------------------------------------------------------------
201 IPython.WidgetManager = WidgetManager;
203 IPython.WidgetManager = WidgetManager;
202 if (IPython.widget_manager === undefined || IPython.widget_manager === null) {
204 if (IPython.widget_manager === undefined || IPython.widget_manager === null) {
203 IPython.widget_manager = new WidgetManager();
205 IPython.widget_manager = new WidgetManager();
204 }
206 }
205
207
206 return IPython.widget_manager;
208 return IPython.widget_manager;
207 });
209 });
208 }());
210 }());
@@ -1,345 +1,345 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Base Widget Model and View classes
9 // Base Widget Model and View classes
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgetmanager",
17 define(["notebook/js/widgetmanager",
18 "underscore",
18 "underscore",
19 "backbone"],
19 "backbone"],
20 function(widget_manager, underscore, backbone){
20 function(widget_manager, underscore, backbone){
21
21
22 //--------------------------------------------------------------------
22 //--------------------------------------------------------------------
23 // WidgetModel class
23 // WidgetModel class
24 //--------------------------------------------------------------------
24 //--------------------------------------------------------------------
25 var WidgetModel = Backbone.Model.extend({
25 var WidgetModel = Backbone.Model.extend({
26 constructor: function (widget_manager, widget_id, comm) {
26 constructor: function (widget_manager, model_id, comm) {
27 this.widget_manager = widget_manager;
27 this.widget_manager = widget_manager;
28 this.pending_msgs = 0;
28 this.pending_msgs = 0;
29 this.msg_throttle = 3;
29 this.msg_throttle = 3;
30 this.msg_buffer = null;
30 this.msg_buffer = null;
31 this.id = widget_id;
31 this.id = model_id;
32 this.views = [];
32 this.views = [];
33
33
34 if (comm !== undefined) {
34 if (comm !== undefined) {
35 // Remember comm associated with the model.
35 // Remember comm associated with the model.
36 this.comm = comm;
36 this.comm = comm;
37 comm.model = this;
37 comm.model = this;
38
38
39 // Hook comm messages up to model.
39 // Hook comm messages up to model.
40 comm.on_close($.proxy(this._handle_comm_closed, this));
40 comm.on_close($.proxy(this._handle_comm_closed, this));
41 comm.on_msg($.proxy(this._handle_comm_msg, this));
41 comm.on_msg($.proxy(this._handle_comm_msg, this));
42 }
42 }
43 return Backbone.Model.apply(this);
43 return Backbone.Model.apply(this);
44 },
44 },
45
45
46 send: function (content, callbacks) {
46 send: function (content, callbacks) {
47 if (this.comm !== undefined) {
47 if (this.comm !== undefined) {
48 var data = {method: 'custom', custom_content: content};
48 var data = {method: 'custom', custom_content: content};
49 this.comm.send(data, callbacks);
49 this.comm.send(data, callbacks);
50 }
50 }
51 },
51 },
52
52
53 // Handle when a widget is closed.
53 // Handle when a widget is closed.
54 _handle_comm_closed: function (msg) {
54 _handle_comm_closed: function (msg) {
55 this.trigger('comm:close');
55 this.trigger('comm:close');
56 delete this.comm.model; // Delete ref so GC will collect widget model.
56 delete this.comm.model; // Delete ref so GC will collect widget model.
57 delete this.comm;
57 delete this.comm;
58 delete this.widget_id; // Delete id from model so widget manager cleans up.
58 delete this.model_id; // Delete id from model so widget manager cleans up.
59 // TODO: Handle deletion, like this.destroy(), and delete views, etc.
59 // TODO: Handle deletion, like this.destroy(), and delete views, etc.
60 },
60 },
61
61
62
62
63 // Handle incoming comm msg.
63 // Handle incoming comm msg.
64 _handle_comm_msg: function (msg) {
64 _handle_comm_msg: function (msg) {
65 var method = msg.content.data.method;
65 var method = msg.content.data.method;
66 switch (method) {
66 switch (method) {
67 case 'update':
67 case 'update':
68 this.apply_update(msg.content.data.state);
68 this.apply_update(msg.content.data.state);
69 break;
69 break;
70 case 'custom':
70 case 'custom':
71 this.trigger('msg:custom', msg.content.data.custom_content);
71 this.trigger('msg:custom', msg.content.data.custom_content);
72 break;
72 break;
73 default:
73 default:
74 // pass on to widget manager
74 // pass on to widget manager
75 this.widget_manager.handle_msg(msg, this);
75 this.widget_manager.handle_msg(msg, this);
76 }
76 }
77 },
77 },
78
78
79
79
80 // Handle when a widget is updated via the python side.
80 // Handle when a widget is updated via the python side.
81 apply_update: function (state) {
81 apply_update: function (state) {
82 this.updating = true;
82 this.updating = true;
83 try {
83 try {
84 for (var key in state) {
84 for (var key in state) {
85 if (state.hasOwnProperty(key)) {
85 if (state.hasOwnProperty(key)) {
86 this.set(key, state[key]);
86 this.set(key, state[key]);
87 }
87 }
88 }
88 }
89 //TODO: are there callbacks that make sense in this case? If so, attach them here as an option
89 //TODO: are there callbacks that make sense in this case? If so, attach them here as an option
90 this.save();
90 this.save();
91 } finally {
91 } finally {
92 this.updating = false;
92 this.updating = false;
93 }
93 }
94 },
94 },
95
95
96
96
97 _handle_status: function (msg, callbacks) {
97 _handle_status: function (msg, callbacks) {
98 //execution_state : ('busy', 'idle', 'starting')
98 //execution_state : ('busy', 'idle', 'starting')
99 if (this.comm !== undefined && msg.content.execution_state ==='idle') {
99 if (this.comm !== undefined && msg.content.execution_state ==='idle') {
100 // Send buffer if this message caused another message to be
100 // Send buffer if this message caused another message to be
101 // throttled.
101 // throttled.
102 if (this.msg_buffer !== null &&
102 if (this.msg_buffer !== null &&
103 this.msg_throttle === this.pending_msgs) {
103 this.msg_throttle === this.pending_msgs) {
104 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
104 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
105 this.comm.send(data, callbacks);
105 this.comm.send(data, callbacks);
106 this.msg_buffer = null;
106 this.msg_buffer = null;
107 } else {
107 } else {
108 // Only decrease the pending message count if the buffer
108 // Only decrease the pending message count if the buffer
109 // doesn't get flushed (sent).
109 // doesn't get flushed (sent).
110 --this.pending_msgs;
110 --this.pending_msgs;
111 }
111 }
112 }
112 }
113 },
113 },
114
114
115
115
116 // Custom syncronization logic.
116 // Custom syncronization logic.
117 _handle_sync: function (method, options) {
117 _handle_sync: function (method, options) {
118 var model_json = this.toJSON();
118 var model_json = this.toJSON();
119 var attr;
119 var attr;
120
120
121 // Only send updated state if the state hasn't been changed
121 // Only send updated state if the state hasn't been changed
122 // during an update.
122 // during an update.
123 if (this.comm !== undefined) {
123 if (this.comm !== undefined) {
124 if (!this.updating) {
124 if (!this.updating) {
125 if (this.pending_msgs >= this.msg_throttle) {
125 if (this.pending_msgs >= this.msg_throttle) {
126 // The throttle has been exceeded, buffer the current msg so
126 // The throttle has been exceeded, buffer the current msg so
127 // it can be sent once the kernel has finished processing
127 // it can be sent once the kernel has finished processing
128 // some of the existing messages.
128 // some of the existing messages.
129 if (method=='patch') {
129 if (method=='patch') {
130 if (this.msg_buffer === null) {
130 if (this.msg_buffer === null) {
131 this.msg_buffer = $.extend({}, model_json); // Copy
131 this.msg_buffer = $.extend({}, model_json); // Copy
132 }
132 }
133 for (attr in options.attrs) {
133 for (attr in options.attrs) {
134 this.msg_buffer[attr] = options.attrs[attr];
134 this.msg_buffer[attr] = options.attrs[attr];
135 }
135 }
136 } else {
136 } else {
137 this.msg_buffer = $.extend({}, model_json); // Copy
137 this.msg_buffer = $.extend({}, model_json); // Copy
138 }
138 }
139
139
140 } else {
140 } else {
141 // We haven't exceeded the throttle, send the message like
141 // We haven't exceeded the throttle, send the message like
142 // normal. If this is a patch operation, just send the
142 // normal. If this is a patch operation, just send the
143 // changes.
143 // changes.
144 var send_json = model_json;
144 var send_json = model_json;
145 if (method =='patch') {
145 if (method =='patch') {
146 send_json = {};
146 send_json = {};
147 for (attr in options.attrs) {
147 for (attr in options.attrs) {
148 send_json[attr] = options.attrs[attr];
148 send_json[attr] = options.attrs[attr];
149 }
149 }
150 }
150 }
151
151
152 var data = {method: 'backbone', sync_method: method, sync_data: send_json};
152 var data = {method: 'backbone', sync_method: method, sync_data: send_json};
153 this.comm.send(data, options.callbacks);
153 this.comm.send(data, options.callbacks);
154 this.pending_msgs++;
154 this.pending_msgs++;
155 }
155 }
156 }
156 }
157 }
157 }
158
158
159 // Since the comm is a one-way communication, assume the message
159 // Since the comm is a one-way communication, assume the message
160 // arrived.
160 // arrived.
161 return model_json;
161 return model_json;
162 },
162 },
163
163
164 });
164 });
165
165
166
166
167 //--------------------------------------------------------------------
167 //--------------------------------------------------------------------
168 // WidgetView class
168 // WidgetView class
169 //--------------------------------------------------------------------
169 //--------------------------------------------------------------------
170 var BaseWidgetView = Backbone.View.extend({
170 var BaseWidgetView = Backbone.View.extend({
171 initialize: function(options) {
171 initialize: function(options) {
172 this.model.on('change',this.update,this);
172 this.model.on('change',this.update,this);
173 this.widget_manager = options.widget_manager;
173 this.widget_manager = options.widget_manager;
174 this.comm_manager = options.widget_manager.comm_manager;
174 this.comm_manager = options.widget_manager.comm_manager;
175 this.cell = options.cell;
175 this.cell = options.cell;
176 this.child_views = [];
176 this.child_views = [];
177 },
177 },
178
178
179 update: function(){
179 update: function(){
180 // update view to be consistent with this.model
180 // update view to be consistent with this.model
181 // triggered on model change
181 // triggered on model change
182 },
182 },
183
183
184 child_view: function(comm_id, view_name) {
184 child_view: function(model_id, view_name) {
185 // create and return a child view, given a comm id for a model and (optionally) a view name
185 // create and return a child view, given a comm id for a model and (optionally) a view name
186 // if the view name is not given, it defaults to the model's default view attribute
186 // if the view name is not given, it defaults to the model's default view attribute
187 var child_model = this.comm_manager.comms[comm_id].model;
187 var child_model = this.widget_manager.get_model(model_id);
188 var child_view = this.widget_manager.create_view(child_model, view_name, this.cell);
188 var child_view = this.widget_manager.create_view(child_model, view_name, this.cell);
189 this.child_views[comm_id] = child_view;
189 this.child_views[model_id] = child_view;
190 return child_view;
190 return child_view;
191 },
191 },
192
192
193 update_child_views: function(old_list, new_list) {
193 update_child_views: function(old_list, new_list) {
194 // this function takes an old list and new list of comm ids
194 // this function takes an old list and new list of comm ids
195 // views in child_views that correspond to deleted ids are deleted
195 // views in child_views that correspond to deleted ids are deleted
196 // views corresponding to added ids are added child_views
196 // views corresponding to added ids are added child_views
197
197
198 // delete old views
198 // delete old views
199 _.each(_.difference(old_list, new_list), function(element, index, list) {
199 _.each(_.difference(old_list, new_list), function(element, index, list) {
200 var view = this.child_views[element];
200 var view = this.child_views[element];
201 delete this.child_views[element];
201 delete this.child_views[element];
202 view.remove();
202 view.remove();
203 }, this);
203 }, this);
204
204
205 // add new views
205 // add new views
206 _.each(_.difference(new_list, old_list), function(element, index, list) {
206 _.each(_.difference(new_list, old_list), function(element, index, list) {
207 // this function adds the view to the child_views dictionary
207 // this function adds the view to the child_views dictionary
208 this.child_view(element);
208 this.child_view(element);
209 }, this);
209 }, this);
210 },
210 },
211
211
212
212
213
213
214 render: function(){
214 render: function(){
215 // render the view. By default, this is only called the first time the view is created
215 // render the view. By default, this is only called the first time the view is created
216 },
216 },
217 send: function (content) {
217 send: function (content) {
218 this.model.send(content, this._callbacks());
218 this.model.send(content, this._callbacks());
219 },
219 },
220
220
221 touch: function () {
221 touch: function () {
222 this.model.save(this.model.changedAttributes(), {patch: true, callbacks: this._callbacks()});
222 this.model.save(this.model.changedAttributes(), {patch: true, callbacks: this._callbacks()});
223 },
223 },
224
224
225 _callbacks: function () {
225 _callbacks: function () {
226 // callback handlers specific to this view's cell
226 // callback handlers specific to this view's cell
227 var callbacks = {};
227 var callbacks = {};
228 var cell = this.cell;
228 var cell = this.cell;
229 if (cell !== null) {
229 if (cell !== null) {
230 // Try to get output handlers
230 // Try to get output handlers
231 var handle_output = null;
231 var handle_output = null;
232 var handle_clear_output = null;
232 var handle_clear_output = null;
233 if (cell.output_area !== undefined && cell.output_area !== null) {
233 if (cell.output_area !== undefined && cell.output_area !== null) {
234 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
234 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
235 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
235 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
236 }
236 }
237
237
238 // Create callback dict using what is known
238 // Create callback dict using what is known
239 var that = this;
239 var that = this;
240 callbacks = {
240 callbacks = {
241 iopub : {
241 iopub : {
242 output : handle_output,
242 output : handle_output,
243 clear_output : handle_clear_output,
243 clear_output : handle_clear_output,
244
244
245 status : function (msg) {
245 status : function (msg) {
246 that.model._handle_status(msg, that._callbacks());
246 that.model._handle_status(msg, that._callbacks());
247 },
247 },
248
248
249 // Special function only registered by widget messages.
249 // Special function only registered by widget messages.
250 // Allows us to get the cell for a message so we know
250 // Allows us to get the cell for a message so we know
251 // where to add widgets if the code requires it.
251 // where to add widgets if the code requires it.
252 get_cell : function () {
252 get_cell : function () {
253 return cell;
253 return cell;
254 },
254 },
255 },
255 },
256 };
256 };
257 }
257 }
258 return callbacks;
258 return callbacks;
259 },
259 },
260
260
261 });
261 });
262
262
263 var WidgetView = BaseWidgetView.extend({
263 var WidgetView = BaseWidgetView.extend({
264 initialize: function (options) {
264 initialize: function (options) {
265 // TODO: make changes more granular (e.g., trigger on visible:change)
265 // TODO: make changes more granular (e.g., trigger on visible:change)
266 this.model.on('change', this.update, this);
266 this.model.on('change', this.update, this);
267 this.model.on('msg:custom', this.on_msg, this);
267 this.model.on('msg:custom', this.on_msg, this);
268 BaseWidgetView.prototype.initialize.apply(this, arguments);
268 BaseWidgetView.prototype.initialize.apply(this, arguments);
269 },
269 },
270
270
271 on_msg: function(msg) {
271 on_msg: function(msg) {
272 switch(msg.msg_type) {
272 switch(msg.msg_type) {
273 case 'add_class':
273 case 'add_class':
274 this.add_class(msg.selector, msg.class_list);
274 this.add_class(msg.selector, msg.class_list);
275 break;
275 break;
276 case 'remove_class':
276 case 'remove_class':
277 this.remove_class(msg.selector, msg.class_list);
277 this.remove_class(msg.selector, msg.class_list);
278 break;
278 break;
279 }
279 }
280 },
280 },
281
281
282 add_class: function (selector, class_list) {
282 add_class: function (selector, class_list) {
283 var elements = this._get_selector_element(selector);
283 var elements = this._get_selector_element(selector);
284 if (elements.length > 0) {
284 if (elements.length > 0) {
285 elements.addClass(class_list);
285 elements.addClass(class_list);
286 }
286 }
287 },
287 },
288
288
289 remove_class: function (selector, class_list) {
289 remove_class: function (selector, class_list) {
290 var elements = this._get_selector_element(selector);
290 var elements = this._get_selector_element(selector);
291 if (elements.length > 0) {
291 if (elements.length > 0) {
292 elements.removeClass(class_list);
292 elements.removeClass(class_list);
293 }
293 }
294 },
294 },
295
295
296 update: function () {
296 update: function () {
297 // the very first update seems to happen before the element is finished rendering
297 // the very first update seems to happen before the element is finished rendering
298 // so we use setTimeout to give the element time to render
298 // so we use setTimeout to give the element time to render
299 var e = this.$el;
299 var e = this.$el;
300 var visible = this.model.get('visible');
300 var visible = this.model.get('visible');
301 setTimeout(function() {e.toggle(visible)},0);
301 setTimeout(function() {e.toggle(visible)},0);
302
302
303 var css = this.model.get('_css');
303 var css = this.model.get('_css');
304 if (css === undefined) {return;}
304 if (css === undefined) {return;}
305 for (var selector in css) {
305 for (var selector in css) {
306 if (css.hasOwnProperty(selector)) {
306 if (css.hasOwnProperty(selector)) {
307 // Apply the css traits to all elements that match the selector.
307 // Apply the css traits to all elements that match the selector.
308 var elements = this._get_selector_element(selector);
308 var elements = this._get_selector_element(selector);
309 if (elements.length > 0) {
309 if (elements.length > 0) {
310 var css_traits = css[selector];
310 var css_traits = css[selector];
311 for (var css_key in css_traits) {
311 for (var css_key in css_traits) {
312 if (css_traits.hasOwnProperty(css_key)) {
312 if (css_traits.hasOwnProperty(css_key)) {
313 elements.css(css_key, css_traits[css_key]);
313 elements.css(css_key, css_traits[css_key]);
314 }
314 }
315 }
315 }
316 }
316 }
317 }
317 }
318 }
318 }
319 },
319 },
320
320
321 _get_selector_element: function (selector) {
321 _get_selector_element: function (selector) {
322 // Get the elements via the css selector. If the selector is
322 // Get the elements via the css selector. If the selector is
323 // blank, apply the style to the $el_to_style element. If
323 // blank, apply the style to the $el_to_style element. If
324 // the $el_to_style element is not defined, use apply the
324 // the $el_to_style element is not defined, use apply the
325 // style to the view's element.
325 // style to the view's element.
326 var elements;
326 var elements;
327 if (selector === undefined || selector === null || selector === '') {
327 if (selector === undefined || selector === null || selector === '') {
328 if (this.$el_to_style === undefined) {
328 if (this.$el_to_style === undefined) {
329 elements = this.$el;
329 elements = this.$el;
330 } else {
330 } else {
331 elements = this.$el_to_style;
331 elements = this.$el_to_style;
332 }
332 }
333 } else {
333 } else {
334 elements = this.$el.find(selector);
334 elements = this.$el.find(selector);
335 }
335 }
336 return elements;
336 return elements;
337 },
337 },
338 });
338 });
339
339
340 IPython.WidgetModel = WidgetModel;
340 IPython.WidgetModel = WidgetModel;
341 IPython.WidgetView = WidgetView;
341 IPython.WidgetView = WidgetView;
342 IPython.BaseWidgetView = BaseWidgetView;
342 IPython.BaseWidgetView = BaseWidgetView;
343
343
344 return widget_manager;
344 return widget_manager;
345 });
345 });
@@ -1,446 +1,450 b''
1 """Base Widget class. Allows user to create widgets in the backend that render
1 """Base Widget class. Allows user to create widgets in the backend that render
2 in the IPython notebook frontend.
2 in the IPython notebook frontend.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from copy import copy
15 from copy import copy
16 from glob import glob
16 from glob import glob
17 import uuid
17 import uuid
18 import sys
18 import sys
19 import os
19 import os
20 import inspect
20 import inspect
21 import types
21 import types
22
22
23 import IPython
23 import IPython
24 from IPython.kernel.comm import Comm
24 from IPython.kernel.comm import Comm
25 from IPython.config import LoggingConfigurable
25 from IPython.config import LoggingConfigurable
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
27 from IPython.display import Javascript, display
27 from IPython.display import Javascript, display
28 from IPython.utils.py3compat import string_types
28 from IPython.utils.py3compat import string_types
29
29
30 from .widget_view import ViewWidget
30 from .widget_view import ViewWidget
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Classes
33 # Classes
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class BaseWidget(LoggingConfigurable):
36 class BaseWidget(LoggingConfigurable):
37
37
38 # Shared declarations (Class level)
38 # Shared declarations (Class level)
39 _keys = List(Unicode, default_value = [],
39 _keys = List(Unicode, default_value = [],
40 help="List of keys comprising the state of the model.", allow_none=False)
40 help="List of keys comprising the state of the model.", allow_none=False)
41 widget_construction_callback = None
41 widget_construction_callback = None
42
42
43 def on_widget_constructed(callback):
43 def on_widget_constructed(callback):
44 """Class method, registers a callback to be called when a widget is
44 """Class method, registers a callback to be called when a widget is
45 constructed. The callback must have the following signature:
45 constructed. The callback must have the following signature:
46 callback(widget)"""
46 callback(widget)"""
47 BaseWidget.widget_construction_callback = callback
47 BaseWidget.widget_construction_callback = callback
48
48
49 def _handle_widget_constructed(widget):
49 def _handle_widget_constructed(widget):
50 """Class method, called when a widget is constructed."""
50 """Class method, called when a widget is constructed."""
51 if BaseWidget.widget_construction_callback is not None and callable(BaseWidget.widget_construction_callback):
51 if BaseWidget.widget_construction_callback is not None and callable(BaseWidget.widget_construction_callback):
52 BaseWidget.widget_construction_callback(widget)
52 BaseWidget.widget_construction_callback(widget)
53
53
54
54
55
55
56 # Public declarations (Instance level)
56 # Public declarations (Instance level)
57 target_name = Unicode('widget', help="""Name of the backbone model
57 target_name = Unicode('widget', help="""Name of the backbone model
58 registered in the frontend to create and sync this widget with.""")
58 registered in the frontend to create and sync this widget with.""")
59 default_view_name = Unicode(help="""Default view registered in the frontend
59 default_view_name = Unicode(help="""Default view registered in the frontend
60 to use to represent the widget.""")
60 to use to represent the widget.""")
61
61
62 # Private/protected declarations
62 # Private/protected declarations
63 # todo: change this to a context manager
63 # todo: change this to a context manager
64 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
64 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
65 _displayed = False
65 _displayed = False
66 _comm = Instance('IPython.kernel.comm.Comm')
66 _comm = Instance('IPython.kernel.comm.Comm')
67
67
68 def __init__(self, **kwargs):
68 def __init__(self, **kwargs):
69 """Public constructor
69 """Public constructor
70 """
70 """
71 self._display_callbacks = []
71 self._display_callbacks = []
72 self._msg_callbacks = []
72 self._msg_callbacks = []
73 super(BaseWidget, self).__init__(**kwargs)
73 super(BaseWidget, self).__init__(**kwargs)
74
74
75 self.on_trait_change(self._handle_property_changed, self.keys)
75 self.on_trait_change(self._handle_property_changed, self.keys)
76 BaseWidget._handle_widget_constructed(self)
76 BaseWidget._handle_widget_constructed(self)
77
77
78 def __del__(self):
78 def __del__(self):
79 """Object disposal"""
79 """Object disposal"""
80 self.close()
80 self.close()
81
81
82
82
83 def close(self):
83 def close(self):
84 """Close method. Closes the widget which closes the underlying comm.
84 """Close method. Closes the widget which closes the underlying comm.
85 When the comm is closed, all of the widget views are automatically
85 When the comm is closed, all of the widget views are automatically
86 removed from the frontend."""
86 removed from the frontend."""
87 self._close_communication()
87 self._close_communication()
88
88
89
89
90 # Properties
90 # Properties
91 @property
91 @property
92 def keys(self):
92 def keys(self):
93 keys = ['default_view_name']
93 keys = ['default_view_name']
94 keys.extend(self._keys)
94 keys.extend(self._keys)
95 return keys
95 return keys
96
96
97 @property
97 @property
98 def comm(self):
98 def comm(self):
99 if self._comm is None:
99 if self._comm is None:
100 self._open_communication()
100 self._open_communication()
101 return self._comm
101 return self._comm
102
102
103 @property
104 def model_id(self):
105 return self._comm.comm_id
106
103 # Event handlers
107 # Event handlers
104 def _handle_msg(self, msg):
108 def _handle_msg(self, msg):
105 """Called when a msg is recieved from the frontend"""
109 """Called when a msg is recieved from the frontend"""
106 data = msg['content']['data']
110 data = msg['content']['data']
107 method = data['method']
111 method = data['method']
108
112
109 # Handle backbone sync methods CREATE, PATCH, and UPDATE
113 # Handle backbone sync methods CREATE, PATCH, and UPDATE
110 if method == 'backbone':
114 if method == 'backbone':
111 if 'sync_method' in data and 'sync_data' in data:
115 if 'sync_method' in data and 'sync_data' in data:
112 sync_method = data['sync_method']
116 sync_method = data['sync_method']
113 sync_data = data['sync_data']
117 sync_data = data['sync_data']
114 self._handle_recieve_state(sync_data) # handles all methods
118 self._handle_recieve_state(sync_data) # handles all methods
115
119
116 # Handle a custom msg from the front-end
120 # Handle a custom msg from the front-end
117 elif method == 'custom':
121 elif method == 'custom':
118 if 'custom_content' in data:
122 if 'custom_content' in data:
119 self._handle_custom_msg(data['custom_content'])
123 self._handle_custom_msg(data['custom_content'])
120
124
121 def _handle_custom_msg(self, content):
125 def _handle_custom_msg(self, content):
122 """Called when a custom msg is recieved."""
126 """Called when a custom msg is recieved."""
123 for handler in self._msg_callbacks:
127 for handler in self._msg_callbacks:
124 if callable(handler):
128 if callable(handler):
125 argspec = inspect.getargspec(handler)
129 argspec = inspect.getargspec(handler)
126 nargs = len(argspec[0])
130 nargs = len(argspec[0])
127
131
128 # Bound methods have an additional 'self' argument
132 # Bound methods have an additional 'self' argument
129 if isinstance(handler, types.MethodType):
133 if isinstance(handler, types.MethodType):
130 nargs -= 1
134 nargs -= 1
131
135
132 # Call the callback
136 # Call the callback
133 if nargs == 1:
137 if nargs == 1:
134 handler(content)
138 handler(content)
135 elif nargs == 2:
139 elif nargs == 2:
136 handler(self, content)
140 handler(self, content)
137 else:
141 else:
138 raise TypeError('Widget msg callback must ' \
142 raise TypeError('Widget msg callback must ' \
139 'accept 1 or 2 arguments, not %d.' % nargs)
143 'accept 1 or 2 arguments, not %d.' % nargs)
140
144
141
145
142 def _handle_recieve_state(self, sync_data):
146 def _handle_recieve_state(self, sync_data):
143 """Called when a state is recieved from the frontend."""
147 """Called when a state is recieved from the frontend."""
144 # Use _keys instead of keys - Don't get retrieve the css from the client side.
148 # Use _keys instead of keys - Don't get retrieve the css from the client side.
145 for name in self._keys:
149 for name in self._keys:
146 if name in sync_data:
150 if name in sync_data:
147 try:
151 try:
148 self._property_lock = (name, sync_data[name])
152 self._property_lock = (name, sync_data[name])
149 setattr(self, name, sync_data[name])
153 setattr(self, name, sync_data[name])
150 finally:
154 finally:
151 self._property_lock = (None, None)
155 self._property_lock = (None, None)
152
156
153
157
154 def _handle_property_changed(self, name, old, new):
158 def _handle_property_changed(self, name, old, new):
155 """Called when a property has been changed."""
159 """Called when a property has been changed."""
156 # Make sure this isn't information that the front-end just sent us.
160 # Make sure this isn't information that the front-end just sent us.
157 if self._property_lock[0] != name and self._property_lock[1] != new:
161 if self._property_lock[0] != name and self._property_lock[1] != new:
158 # Send new state to frontend
162 # Send new state to frontend
159 self.send_state(key=name)
163 self.send_state(key=name)
160
164
161 def _handle_displayed(self, **kwargs):
165 def _handle_displayed(self, **kwargs):
162 """Called when a view has been displayed for this widget instance
166 """Called when a view has been displayed for this widget instance
163
167
164 Parameters
168 Parameters
165 ----------
169 ----------
166 [view_name]: unicode (optional kwarg)
170 [view_name]: unicode (optional kwarg)
167 Name of the view that was displayed."""
171 Name of the view that was displayed."""
168 for handler in self._display_callbacks:
172 for handler in self._display_callbacks:
169 if callable(handler):
173 if callable(handler):
170 argspec = inspect.getargspec(handler)
174 argspec = inspect.getargspec(handler)
171 nargs = len(argspec[0])
175 nargs = len(argspec[0])
172
176
173 # Bound methods have an additional 'self' argument
177 # Bound methods have an additional 'self' argument
174 if isinstance(handler, types.MethodType):
178 if isinstance(handler, types.MethodType):
175 nargs -= 1
179 nargs -= 1
176
180
177 # Call the callback
181 # Call the callback
178 if nargs == 0:
182 if nargs == 0:
179 handler()
183 handler()
180 elif nargs == 1:
184 elif nargs == 1:
181 handler(self)
185 handler(self)
182 elif nargs == 2:
186 elif nargs == 2:
183 handler(self, kwargs.get('view_name', None))
187 handler(self, kwargs.get('view_name', None))
184 else:
188 else:
185 handler(self, **kwargs)
189 handler(self, **kwargs)
186
190
187 # Public methods
191 # Public methods
188 def send_state(self, key=None):
192 def send_state(self, key=None):
189 """Sends the widget state, or a piece of it, to the frontend.
193 """Sends the widget state, or a piece of it, to the frontend.
190
194
191 Parameters
195 Parameters
192 ----------
196 ----------
193 key : unicode (optional)
197 key : unicode (optional)
194 A single property's name to sync with the frontend.
198 A single property's name to sync with the frontend.
195 """
199 """
196 self._send({"method": "update",
200 self._send({"method": "update",
197 "state": self.get_state()})
201 "state": self.get_state()})
198
202
199 def get_state(self, key=None):
203 def get_state(self, key=None):
200 """Gets the widget state, or a piece of it.
204 """Gets the widget state, or a piece of it.
201
205
202 Parameters
206 Parameters
203 ----------
207 ----------
204 key : unicode (optional)
208 key : unicode (optional)
205 A single property's name to get.
209 A single property's name to get.
206 """
210 """
207 state = {}
211 state = {}
208
212
209 # If a key is provided, just send the state of that key.
213 # If a key is provided, just send the state of that key.
210 if key is None:
214 if key is None:
211 keys = self.keys[:]
215 keys = self.keys[:]
212 else:
216 else:
213 keys = [key]
217 keys = [key]
214 for k in keys:
218 for k in keys:
215 value = getattr(self, k)
219 value = getattr(self, k)
216
220
217 # a more elegant solution to encoding BaseWidgets would be
221 # a more elegant solution to encoding BaseWidgets would be
218 # to tap into the JSON encoder and teach it how to deal
222 # to tap into the JSON encoder and teach it how to deal
219 # with BaseWidget objects, or maybe just teach the JSON
223 # with BaseWidget objects, or maybe just teach the JSON
220 # encoder to look for a _repr_json property before giving
224 # encoder to look for a _repr_json property before giving
221 # up encoding
225 # up encoding
222 if isinstance(value, BaseWidget):
226 if isinstance(value, BaseWidget):
223 value = value.comm.comm_id
227 value = value.model_id
224 elif isinstance(value, list) and len(value)>0 and isinstance(value[0], BaseWidget):
228 elif isinstance(value, list) and len(value)>0 and isinstance(value[0], BaseWidget):
225 # assume all elements of the list are widgets
229 # assume all elements of the list are widgets
226 value = [i.comm.comm_id for i in value]
230 value = [i.model_id for i in value]
227 state[k] = value
231 state[k] = value
228 return state
232 return state
229
233
230
234
231 def send(self, content):
235 def send(self, content):
232 """Sends a custom msg to the widget model in the front-end.
236 """Sends a custom msg to the widget model in the front-end.
233
237
234 Parameters
238 Parameters
235 ----------
239 ----------
236 content : dict
240 content : dict
237 Content of the message to send.
241 Content of the message to send.
238 """
242 """
239 self._send({"method": "custom",
243 self._send({"method": "custom",
240 "custom_content": content})
244 "custom_content": content})
241
245
242
246
243 def on_msg(self, callback, remove=False):
247 def on_msg(self, callback, remove=False):
244 """Register a callback for when a custom msg is recieved from the front-end
248 """Register a callback for when a custom msg is recieved from the front-end
245
249
246 Parameters
250 Parameters
247 ----------
251 ----------
248 callback: method handler
252 callback: method handler
249 Can have a signature of:
253 Can have a signature of:
250 - callback(content)
254 - callback(content)
251 - callback(sender, content)
255 - callback(sender, content)
252 remove: bool
256 remove: bool
253 True if the callback should be unregistered."""
257 True if the callback should be unregistered."""
254 if remove and callback in self._msg_callbacks:
258 if remove and callback in self._msg_callbacks:
255 self._msg_callbacks.remove(callback)
259 self._msg_callbacks.remove(callback)
256 elif not remove and not callback in self._msg_callbacks:
260 elif not remove and not callback in self._msg_callbacks:
257 self._msg_callbacks.append(callback)
261 self._msg_callbacks.append(callback)
258
262
259
263
260 def on_displayed(self, callback, remove=False):
264 def on_displayed(self, callback, remove=False):
261 """Register a callback to be called when the widget has been displayed
265 """Register a callback to be called when the widget has been displayed
262
266
263 Parameters
267 Parameters
264 ----------
268 ----------
265 callback: method handler
269 callback: method handler
266 Can have a signature of:
270 Can have a signature of:
267 - callback()
271 - callback()
268 - callback(sender)
272 - callback(sender)
269 - callback(sender, view_name)
273 - callback(sender, view_name)
270 - callback(sender, **kwargs)
274 - callback(sender, **kwargs)
271 kwargs from display call passed through without modification.
275 kwargs from display call passed through without modification.
272 remove: bool
276 remove: bool
273 True if the callback should be unregistered."""
277 True if the callback should be unregistered."""
274 if remove and callback in self._display_callbacks:
278 if remove and callback in self._display_callbacks:
275 self._display_callbacks.remove(callback)
279 self._display_callbacks.remove(callback)
276 elif not remove and not callback in self._display_callbacks:
280 elif not remove and not callback in self._display_callbacks:
277 self._display_callbacks.append(callback)
281 self._display_callbacks.append(callback)
278
282
279
283
280 # Support methods
284 # Support methods
281 def _repr_widget_(self, **kwargs):
285 def _repr_widget_(self, **kwargs):
282 """Function that is called when `IPython.display.display` is called on
286 """Function that is called when `IPython.display.display` is called on
283 the widget.
287 the widget.
284
288
285 Parameters
289 Parameters
286 ----------
290 ----------
287 view_name: unicode (optional)
291 view_name: unicode (optional)
288 View to display in the frontend. Overrides default_view_name."""
292 View to display in the frontend. Overrides default_view_name."""
289 view_name = kwargs.get('view_name', self.default_view_name)
293 view_name = kwargs.get('view_name', self.default_view_name)
290
294
291 # Create a communication.
295 # Create a communication.
292 self._open_communication()
296 self._open_communication()
293
297
294 # Make sure model is syncronized
298 # Make sure model is syncronized
295 self.send_state()
299 self.send_state()
296
300
297 # Show view.
301 # Show view.
298 self._send({"method": "display", "view_name": view_name})
302 self._send({"method": "display", "view_name": view_name})
299 self._displayed = True
303 self._displayed = True
300 self._handle_displayed(**kwargs)
304 self._handle_displayed(**kwargs)
301
305
302
306
303 def _open_communication(self):
307 def _open_communication(self):
304 """Opens a communication with the front-end."""
308 """Opens a communication with the front-end."""
305 # Create a comm.
309 # Create a comm.
306 if self._comm is None:
310 if self._comm is None:
307 self._comm = Comm(target_name=self.target_name)
311 self._comm = Comm(target_name=self.target_name)
308 self._comm.on_msg(self._handle_msg)
312 self._comm.on_msg(self._handle_msg)
309 self._comm.on_close(self._close_communication)
313 self._comm.on_close(self._close_communication)
310
314
311 # first update
315 # first update
312 self.send_state()
316 self.send_state()
313
317
314
318
315 def _close_communication(self):
319 def _close_communication(self):
316 """Closes a communication with the front-end."""
320 """Closes a communication with the front-end."""
317 if self._comm is not None:
321 if self._comm is not None:
318 try:
322 try:
319 self._comm.close()
323 self._comm.close()
320 finally:
324 finally:
321 self._comm = None
325 self._comm = None
322
326
323
327
324 def _send(self, msg):
328 def _send(self, msg):
325 """Sends a message to the model in the front-end"""
329 """Sends a message to the model in the front-end"""
326 if self._comm is not None:
330 if self._comm is not None:
327 self._comm.send(msg)
331 self._comm.send(msg)
328 return True
332 return True
329 else:
333 else:
330 return False
334 return False
331
335
332 class Widget(BaseWidget):
336 class Widget(BaseWidget):
333 visible = Bool(True, help="Whether or not the widget is visible.")
337 visible = Bool(True, help="Whether or not the widget is visible.")
334
338
335 # Private/protected declarations
339 # Private/protected declarations
336 _css = Dict() # Internal CSS property dict
340 _css = Dict() # Internal CSS property dict
337
341
338 # Properties
342 # Properties
339 @property
343 @property
340 def keys(self):
344 def keys(self):
341 keys = ['visible', '_css']
345 keys = ['visible', '_css']
342 keys.extend(super(Widget, self).keys)
346 keys.extend(super(Widget, self).keys)
343 return keys
347 return keys
344
348
345 def get_css(self, key, selector=""):
349 def get_css(self, key, selector=""):
346 """Get a CSS property of the widget. Note, this function does not
350 """Get a CSS property of the widget. Note, this function does not
347 actually request the CSS from the front-end; Only properties that have
351 actually request the CSS from the front-end; Only properties that have
348 been set with set_css can be read.
352 been set with set_css can be read.
349
353
350 Parameters
354 Parameters
351 ----------
355 ----------
352 key: unicode
356 key: unicode
353 CSS key
357 CSS key
354 selector: unicode (optional)
358 selector: unicode (optional)
355 JQuery selector used when the CSS key/value was set.
359 JQuery selector used when the CSS key/value was set.
356 """
360 """
357 if selector in self._css and key in self._css[selector]:
361 if selector in self._css and key in self._css[selector]:
358 return self._css[selector][key]
362 return self._css[selector][key]
359 else:
363 else:
360 return None
364 return None
361
365
362
366
363 def set_css(self, *args, **kwargs):
367 def set_css(self, *args, **kwargs):
364 """Set one or more CSS properties of the widget (shared among all of the
368 """Set one or more CSS properties of the widget (shared among all of the
365 views). This function has two signatures:
369 views). This function has two signatures:
366 - set_css(css_dict, [selector=''])
370 - set_css(css_dict, [selector=''])
367 - set_css(key, value, [selector=''])
371 - set_css(key, value, [selector=''])
368
372
369 Parameters
373 Parameters
370 ----------
374 ----------
371 css_dict : dict
375 css_dict : dict
372 CSS key/value pairs to apply
376 CSS key/value pairs to apply
373 key: unicode
377 key: unicode
374 CSS key
378 CSS key
375 value
379 value
376 CSS value
380 CSS value
377 selector: unicode (optional)
381 selector: unicode (optional)
378 JQuery selector to use to apply the CSS key/value.
382 JQuery selector to use to apply the CSS key/value.
379 """
383 """
380 selector = kwargs.get('selector', '')
384 selector = kwargs.get('selector', '')
381
385
382 # Signature 1: set_css(css_dict, [selector=''])
386 # Signature 1: set_css(css_dict, [selector=''])
383 if len(args) == 1:
387 if len(args) == 1:
384 if isinstance(args[0], dict):
388 if isinstance(args[0], dict):
385 for (key, value) in args[0].items():
389 for (key, value) in args[0].items():
386 self.set_css(key, value, selector=selector)
390 self.set_css(key, value, selector=selector)
387 else:
391 else:
388 raise Exception('css_dict must be a dict.')
392 raise Exception('css_dict must be a dict.')
389
393
390 # Signature 2: set_css(key, value, [selector=''])
394 # Signature 2: set_css(key, value, [selector=''])
391 elif len(args) == 2 or len(args) == 3:
395 elif len(args) == 2 or len(args) == 3:
392
396
393 # Selector can be a positional arg if it's the 3rd value
397 # Selector can be a positional arg if it's the 3rd value
394 if len(args) == 3:
398 if len(args) == 3:
395 selector = args[2]
399 selector = args[2]
396 if selector not in self._css:
400 if selector not in self._css:
397 self._css[selector] = {}
401 self._css[selector] = {}
398
402
399 # Only update the property if it has changed.
403 # Only update the property if it has changed.
400 key = args[0]
404 key = args[0]
401 value = args[1]
405 value = args[1]
402 if not (key in self._css[selector] and value in self._css[selector][key]):
406 if not (key in self._css[selector] and value in self._css[selector][key]):
403 self._css[selector][key] = value
407 self._css[selector][key] = value
404 self.send_state('_css') # Send new state to client.
408 self.send_state('_css') # Send new state to client.
405 else:
409 else:
406 raise Exception('set_css only accepts 1-3 arguments')
410 raise Exception('set_css only accepts 1-3 arguments')
407
411
408
412
409 def add_class(self, class_name, selector=""):
413 def add_class(self, class_name, selector=""):
410 """Add class[es] to a DOM element
414 """Add class[es] to a DOM element
411
415
412 Parameters
416 Parameters
413 ----------
417 ----------
414 class_name: unicode
418 class_name: unicode
415 Class name(s) to add to the DOM element(s). Multiple class names
419 Class name(s) to add to the DOM element(s). Multiple class names
416 must be space separated.
420 must be space separated.
417 selector: unicode (optional)
421 selector: unicode (optional)
418 JQuery selector to select the DOM element(s) that the class(es) will
422 JQuery selector to select the DOM element(s) that the class(es) will
419 be added to.
423 be added to.
420 """
424 """
421 self.send({"msg_type": "add_class",
425 self.send({"msg_type": "add_class",
422 "class_list": class_name,
426 "class_list": class_name,
423 "selector": selector})
427 "selector": selector})
424
428
425
429
426 def remove_class(self, class_name, selector=""):
430 def remove_class(self, class_name, selector=""):
427 """Remove class[es] from a DOM element
431 """Remove class[es] from a DOM element
428
432
429 Parameters
433 Parameters
430 ----------
434 ----------
431 class_name: unicode
435 class_name: unicode
432 Class name(s) to remove from the DOM element(s). Multiple class
436 Class name(s) to remove from the DOM element(s). Multiple class
433 names must be space separated.
437 names must be space separated.
434 selector: unicode (optional)
438 selector: unicode (optional)
435 JQuery selector to select the DOM element(s) that the class(es) will
439 JQuery selector to select the DOM element(s) that the class(es) will
436 be removed from.
440 be removed from.
437 """
441 """
438 self.send({"msg_type": "remove_class",
442 self.send({"msg_type": "remove_class",
439 "class_list": class_name,
443 "class_list": class_name,
440 "selector": selector})
444 "selector": selector})
441
445
442
446
443 def view(self, view_name=None):
447 def view(self, view_name=None):
444 """Return a widget that can be displayed to display this widget using
448 """Return a widget that can be displayed to display this widget using
445 a non-default view"""
449 a non-default view"""
446 return ViewWidget(self, view_name)
450 return ViewWidget(self, view_name)
General Comments 0
You need to be logged in to leave comments. Login now