##// END OF EJS Templates
Added support for custom widget msgs
Jonathan Frederic -
Show More
@@ -1,553 +1,587 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 "use strict";
18 "use strict";
19
19
20 // Use require.js 'define' method so that require.js is intelligent enough to
20 // Use require.js 'define' method so that require.js is intelligent enough to
21 // syncronously load everything within this file when it is being 'required'
21 // syncronously load everything within this file when it is being 'required'
22 // elsewhere.
22 // elsewhere.
23 define(["components/underscore/underscore-min",
23 define(["components/underscore/underscore-min",
24 "components/backbone/backbone-min",
24 "components/backbone/backbone-min",
25 ], function(underscore, backbone){
25 ], function(underscore, backbone){
26
26
27
27
28 //--------------------------------------------------------------------
28 //--------------------------------------------------------------------
29 // WidgetModel class
29 // WidgetModel class
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 var WidgetModel = Backbone.Model.extend({
31 var WidgetModel = Backbone.Model.extend({
32 constructor: function(comm_manager, comm, widget_manager) {
32 constructor: function(comm_manager, comm, widget_manager) {
33 this.comm_manager = comm_manager;
33 this.comm_manager = comm_manager;
34 this.widget_manager = widget_manager;
34 this.widget_manager = widget_manager;
35 this.pending_msgs = 0;
35 this.pending_msgs = 0;
36 this.msg_throttle = 3;
36 this.msg_throttle = 3;
37 this.msg_buffer = null;
37 this.msg_buffer = null;
38 this.views = {};
38 this.views = {};
39
39
40 // Remember comm associated with the model.
40 // Remember comm associated with the model.
41 this.comm = comm;
41 this.comm = comm;
42 comm.model = this;
42 comm.model = this;
43
43
44 // Hook comm messages up to model.
44 // Hook comm messages up to model.
45 comm.on_close($.proxy(this._handle_comm_closed, this));
45 comm.on_close($.proxy(this._handle_comm_closed, this));
46 comm.on_msg($.proxy(this._handle_comm_msg, this));
46 comm.on_msg($.proxy(this._handle_comm_msg, this));
47
47
48 return Backbone.Model.apply(this);
48 return Backbone.Model.apply(this);
49 },
49 },
50
50
51
51
52 update_other_views: function(caller) {
52 update_other_views: function(caller) {
53 this.last_modified_view = caller;
53 this.last_modified_view = caller;
54 this.save(this.changedAttributes(), {patch: true});
54 this.save(this.changedAttributes(), {patch: true});
55
55
56 for (var cell in this.views) {
56 for (var cell in this.views) {
57 var views = this.views[cell];
57 var views = this.views[cell];
58 for (var view_index in views) {
58 for (var view_index in views) {
59 var view = views[view_index];
59 var view = views[view_index];
60 if (view !== caller) {
60 if (view !== caller) {
61 view.update();
61 view.update();
62 }
62 }
63 }
63 }
64 }
64 }
65 },
65 },
66
67 send = function (content) {
68
69 // Used the last modified view as the sender of the message. This
70 // will insure that any python code triggered by the sent message
71 // can create and display widgets and output.
72 var cell = null;
73 if (this.last_modified_view != undefined &&
74 this.last_modified_view.cell != undefined) {
75 cell = this.last_modified_view.cell;
76 }
77 var callbacks = this._make_callbacks(cell);
78 var data = {custom_content: content};
79 this.comm.send(data, callbacks);
80 },
66
81
67
82
68 on_view_displayed: function (callback) {
83 on_view_displayed: function (callback) {
69 this._view_displayed_callback = callback;
84 this._view_displayed_callback = callback;
70 }
85 },
71
86
72
87
73 on_close: function (callback) {
88 on_close: function (callback) {
74 this._close_callback = callback;
89 this._close_callback = callback;
75 }
90 },
91
92
93 on_msg: function (callback) {
94 this._msg_callback = callback;
95 },
96
97
98 _handle_custom_msg: function (content) {
99 if (this._msg_callback) {
100 try {
101 this._msg_callback(content);
102 } catch (e) {
103 console.log("Exception in widget model msg callback", e, content);
104 }
105 }
106 },
76
107
77
108
78 // Handle when a widget is closed.
109 // Handle when a widget is closed.
79 _handle_comm_closed: function (msg) {
110 _handle_comm_closed: function (msg) {
80 for (var cell in this.views) {
111 for (var cell in this.views) {
81 var views = this.views[cell];
112 var views = this.views[cell];
82 for (var view_index in views) {
113 for (var view_index in views) {
83 var view = views[view_index];
114 var view = views[view_index];
84 view.remove();
115 view.remove();
85 }
116 }
86 }
117 }
87 delete this.comm.model; // Delete ref so GC will collect widget model.
118 delete this.comm.model; // Delete ref so GC will collect widget model.
88 },
119 },
89
120
90
121
91 // Handle incomming comm msg.
122 // Handle incomming comm msg.
92 _handle_comm_msg: function (msg) {
123 _handle_comm_msg: function (msg) {
93 var method = msg.content.data.method;
124 var method = msg.content.data.method;
94 switch (method){
125 switch (method){
95 case 'display':
126 case 'display':
96
127
97 // Try to get the cell.
128 // Try to get the cell.
98 var cell = this._get_msg_cell(msg.parent_header.msg_id);
129 var cell = this._get_msg_cell(msg.parent_header.msg_id);
99 if (cell == null) {
130 if (cell == null) {
100 console.log("Could not determine where the display" +
131 console.log("Could not determine where the display" +
101 " message was from. Widget will not be displayed")
132 " message was from. Widget will not be displayed")
102 } else {
133 } else {
103 this._display_view(msg.content.data.view_name,
134 this._display_view(msg.content.data.view_name,
104 msg.content.data.parent,
135 msg.content.data.parent,
105 cell);
136 cell);
106 }
137 }
107 break;
138 break;
108 case 'update':
139 case 'update':
109 this._handle_update(msg.content.data.state);
140 this._handle_update(msg.content.data.state);
110 break;
141 break;
142 case 'custom':
143 this._handle_custom_msg(msg.content.data.custom_content);
144 break;
111 }
145 }
112 },
146 },
113
147
114
148
115 // Handle when a widget is updated via the python side.
149 // Handle when a widget is updated via the python side.
116 _handle_update: function (state) {
150 _handle_update: function (state) {
117 this.updating = true;
151 this.updating = true;
118 try {
152 try {
119 for (var key in state) {
153 for (var key in state) {
120 if (state.hasOwnProperty(key)) {
154 if (state.hasOwnProperty(key)) {
121 if (key == "_css"){
155 if (key == "_css"){
122
156
123 // Set the css value of the model as an attribute
157 // Set the css value of the model as an attribute
124 // instead of a backbone trait because we are only
158 // instead of a backbone trait because we are only
125 // interested in backend css -> frontend css. In
159 // interested in backend css -> frontend css. In
126 // other words, if the css dict changes in the
160 // other words, if the css dict changes in the
127 // frontend, we don't need to push the changes to
161 // frontend, we don't need to push the changes to
128 // the backend.
162 // the backend.
129 this.css = state[key];
163 this.css = state[key];
130 } else {
164 } else {
131 this.set(key, state[key]);
165 this.set(key, state[key]);
132 }
166 }
133 }
167 }
134 }
168 }
135 this.id = this.comm.comm_id;
169 this.id = this.comm.comm_id;
136 this.save();
170 this.save();
137 } finally {
171 } finally {
138 this.updating = false;
172 this.updating = false;
139 }
173 }
140 },
174 },
141
175
142
176
143 _handle_status: function (cell, msg) {
177 _handle_status: function (cell, msg) {
144 //execution_state : ('busy', 'idle', 'starting')
178 //execution_state : ('busy', 'idle', 'starting')
145 if (msg.content.execution_state=='idle') {
179 if (msg.content.execution_state=='idle') {
146
180
147 // Send buffer if this message caused another message to be
181 // Send buffer if this message caused another message to be
148 // throttled.
182 // throttled.
149 if (this.msg_buffer != null &&
183 if (this.msg_buffer != null &&
150 this.msg_throttle == this.pending_msgs) {
184 this.msg_throttle == this.pending_msgs) {
151
185
152 var cell = this._get_msg_cell(msg.parent_header.msg_id);
186 var cell = this._get_msg_cell(msg.parent_header.msg_id);
153 var callbacks = this._make_callbacks(cell);
187 var callbacks = this._make_callbacks(cell);
154 var data = {sync_method: 'update', sync_data: this.msg_buffer};
188 var data = {sync_method: 'update', sync_data: this.msg_buffer};
155 this.comm.send(data, callbacks);
189 this.comm.send(data, callbacks);
156 this.msg_buffer = null;
190 this.msg_buffer = null;
157 } else {
191 } else {
158
192
159 // Only decrease the pending message count if the buffer
193 // Only decrease the pending message count if the buffer
160 // doesn't get flushed (sent).
194 // doesn't get flushed (sent).
161 --this.pending_msgs;
195 --this.pending_msgs;
162 }
196 }
163 }
197 }
164 },
198 },
165
199
166
200
167 // Custom syncronization logic.
201 // Custom syncronization logic.
168 _handle_sync: function (method, options) {
202 _handle_sync: function (method, options) {
169 var model_json = this.toJSON();
203 var model_json = this.toJSON();
170
204
171 // Only send updated state if the state hasn't been changed
205 // Only send updated state if the state hasn't been changed
172 // during an update.
206 // during an update.
173 if (!this.updating) {
207 if (!this.updating) {
174 if (this.pending_msgs >= this.msg_throttle) {
208 if (this.pending_msgs >= this.msg_throttle) {
175 // The throttle has been exceeded, buffer the current msg so
209 // The throttle has been exceeded, buffer the current msg so
176 // it can be sent once the kernel has finished processing
210 // it can be sent once the kernel has finished processing
177 // some of the existing messages.
211 // some of the existing messages.
178 if (method=='patch') {
212 if (method=='patch') {
179 if (this.msg_buffer == null) {
213 if (this.msg_buffer == null) {
180 this.msg_buffer = $.extend({}, model_json); // Copy
214 this.msg_buffer = $.extend({}, model_json); // Copy
181 }
215 }
182 for (var attr in options.attrs) {
216 for (var attr in options.attrs) {
183 this.msg_buffer[attr] = options.attrs[attr];
217 this.msg_buffer[attr] = options.attrs[attr];
184 }
218 }
185 } else {
219 } else {
186 this.msg_buffer = $.extend({}, model_json); // Copy
220 this.msg_buffer = $.extend({}, model_json); // Copy
187 }
221 }
188
222
189 } else {
223 } else {
190 // We haven't exceeded the throttle, send the message like
224 // We haven't exceeded the throttle, send the message like
191 // normal. If this is a patch operation, just send the
225 // normal. If this is a patch operation, just send the
192 // changes.
226 // changes.
193 var send_json = model_json;
227 var send_json = model_json;
194 if (method=='patch') {
228 if (method=='patch') {
195 send_json = {};
229 send_json = {};
196 for (var attr in options.attrs) {
230 for (var attr in options.attrs) {
197 send_json[attr] = options.attrs[attr];
231 send_json[attr] = options.attrs[attr];
198 }
232 }
199 }
233 }
200
234
201 var data = {sync_method: method, sync_data: send_json};
235 var data = {sync_method: method, sync_data: send_json};
202
236
203 var cell = null;
237 var cell = null;
204 if (this.last_modified_view != undefined && this.last_modified_view != null) {
238 if (this.last_modified_view != undefined && this.last_modified_view != null) {
205 cell = this.last_modified_view.cell;
239 cell = this.last_modified_view.cell;
206 }
240 }
207
241
208 var callbacks = this._make_callbacks(cell);
242 var callbacks = this._make_callbacks(cell);
209 this.comm.send(data, callbacks);
243 this.comm.send(data, callbacks);
210 this.pending_msgs++;
244 this.pending_msgs++;
211 }
245 }
212 }
246 }
213
247
214 // Since the comm is a one-way communication, assume the message
248 // Since the comm is a one-way communication, assume the message
215 // arrived.
249 // arrived.
216 return model_json;
250 return model_json;
217 },
251 },
218
252
219
253
220 _handle_view_displayed: function(view) {
254 _handle_view_displayed: function(view) {
221 if (this._view_displayed_callback) {
255 if (this._view_displayed_callback) {
222 try {
256 try {
223 this._view_displayed_callback(view)
257 this._view_displayed_callback(view)
224 } catch (e) {
258 } catch (e) {
225 console.log("Exception in widget model view displayed callback", e, view, this);
259 console.log("Exception in widget model view displayed callback", e, view, this);
226 }
260 }
227 }
261 }
228 }
262 },
229
263
230
264
231 // Create view that represents the model.
265 // Create view that represents the model.
232 _display_view: function (view_name, parent_comm_id, cell) {
266 _display_view: function (view_name, parent_comm_id, cell) {
233 var new_views = [];
267 var new_views = [];
234
268
235 // Try creating and adding the view to it's parent.
269 // Try creating and adding the view to it's parent.
236 var displayed = false;
270 var displayed = false;
237 if (parent_comm_id != undefined) {
271 if (parent_comm_id != undefined) {
238 var parent_comm = this.comm_manager.comms[parent_comm_id];
272 var parent_comm = this.comm_manager.comms[parent_comm_id];
239 var parent_model = parent_comm.model;
273 var parent_model = parent_comm.model;
240 var parent_views = parent_model.views[cell];
274 var parent_views = parent_model.views[cell];
241 for (var parent_view_index in parent_views) {
275 for (var parent_view_index in parent_views) {
242 var parent_view = parent_views[parent_view_index];
276 var parent_view = parent_views[parent_view_index];
243 if (parent_view.display_child != undefined) {
277 if (parent_view.display_child != undefined) {
244 var view = this._create_view(view_name, cell);
278 var view = this._create_view(view_name, cell);
245 if (view != null) {
279 if (view != null) {
246 new_views.push(view);
280 new_views.push(view);
247 parent_view.display_child(view);
281 parent_view.display_child(view);
248 displayed = true;
282 displayed = true;
249 this._handle_view_displayed(view);
283 this._handle_view_displayed(view);
250 }
284 }
251 }
285 }
252 }
286 }
253 }
287 }
254
288
255 // If no parent view is defined or exists. Add the view's
289 // If no parent view is defined or exists. Add the view's
256 // element to cell's widget div.
290 // element to cell's widget div.
257 if (!displayed) {
291 if (!displayed) {
258 var view = this._create_view(view_name, cell);
292 var view = this._create_view(view_name, cell);
259 if (view != null) {
293 if (view != null) {
260 new_views.push(view);
294 new_views.push(view);
261
295
262 if (cell.widget_subarea != undefined && cell.widget_subarea != null) {
296 if (cell.widget_subarea != undefined && cell.widget_subarea != null) {
263 cell.widget_area.show();
297 cell.widget_area.show();
264 cell.widget_subarea.append(view.$el);
298 cell.widget_subarea.append(view.$el);
265 this._handle_view_displayed(view);
299 this._handle_view_displayed(view);
266 }
300 }
267 }
301 }
268 }
302 }
269
303
270 // Force the new view(s) to update their selves
304 // Force the new view(s) to update their selves
271 for (var view_index in new_views) {
305 for (var view_index in new_views) {
272 var view = new_views[view_index];
306 var view = new_views[view_index];
273 view.update();
307 view.update();
274 }
308 }
275 },
309 },
276
310
277
311
278 // Create a view
312 // Create a view
279 _create_view: function (view_name, cell) {
313 _create_view: function (view_name, cell) {
280 var view_type = this.widget_manager.widget_view_types[view_name];
314 var view_type = this.widget_manager.widget_view_types[view_name];
281 if (view_type != undefined && view_type != null) {
315 if (view_type != undefined && view_type != null) {
282 var view = new view_type({model: this});
316 var view = new view_type({model: this});
283 view.render();
317 view.render();
284 if (this.views[cell]==undefined) {
318 if (this.views[cell]==undefined) {
285 this.views[cell] = []
319 this.views[cell] = []
286 }
320 }
287 this.views[cell].push(view);
321 this.views[cell].push(view);
288 view.cell = cell;
322 view.cell = cell;
289
323
290 // Handle when the view element is remove from the page.
324 // Handle when the view element is remove from the page.
291 var that = this;
325 var that = this;
292 view.$el.on("remove", function(){
326 view.$el.on("remove", function(){
293 var index = that.views[cell].indexOf(view);
327 var index = that.views[cell].indexOf(view);
294 if (index > -1) {
328 if (index > -1) {
295 that.views[cell].splice(index, 1);
329 that.views[cell].splice(index, 1);
296 }
330 }
297 view.remove(); // Clean-up view
331 view.remove(); // Clean-up view
298 if (that.views[cell].length()==0) {
332 if (that.views[cell].length()==0) {
299 delete that.views[cell];
333 delete that.views[cell];
300 }
334 }
301
335
302 // Close the comm if there are no views left.
336 // Close the comm if there are no views left.
303 if (that.views.length()==0) {
337 if (that.views.length()==0) {
304 if (that._close_callback) {
338 if (that._close_callback) {
305 try {
339 try {
306 that._close_callback(that)
340 that._close_callback(that)
307 } catch (e) {
341 } catch (e) {
308 console.log("Exception in widget model close callback", e, that);
342 console.log("Exception in widget model close callback", e, that);
309 }
343 }
310 }
344 }
311 that.comm.close();
345 that.comm.close();
312 delete that.comm.model; // Delete ref so GC will collect widget model.
346 delete that.comm.model; // Delete ref so GC will collect widget model.
313 }
347 }
314 });
348 });
315 return view;
349 return view;
316 }
350 }
317 return null;
351 return null;
318 },
352 },
319
353
320
354
321 // Build a callback dict.
355 // Build a callback dict.
322 _make_callbacks: function (cell) {
356 _make_callbacks: function (cell) {
323 var callbacks = {};
357 var callbacks = {};
324 if (cell != null && cell.output_area != undefined && cell.output_area != null) {
358 if (cell != null && cell.output_area != undefined && cell.output_area != null) {
325 var that = this;
359 var that = this;
326 callbacks = {
360 callbacks = {
327 iopub : {
361 iopub : {
328 output : $.proxy(cell.output_area.handle_output, cell.output_area),
362 output : $.proxy(cell.output_area.handle_output, cell.output_area),
329 clear_output : $.proxy(cell.output_area.handle_clear_output, cell.output_area),
363 clear_output : $.proxy(cell.output_area.handle_clear_output, cell.output_area),
330 status : function(msg){
364 status : function(msg){
331 that._handle_status(cell, msg);
365 that._handle_status(cell, msg);
332 },
366 },
333
367
334 // Special function only registered by widget messages.
368 // Special function only registered by widget messages.
335 // Allows us to get the cell for a message so we know
369 // Allows us to get the cell for a message so we know
336 // where to add widgets if the code requires it.
370 // where to add widgets if the code requires it.
337 get_cell : function() {
371 get_cell : function() {
338 if (that.last_modified_view != undefined &&
372 if (that.last_modified_view != undefined &&
339 that.last_modified_view.cell != undefined) {
373 that.last_modified_view.cell != undefined) {
340 return that.last_modified_view.cell;
374 return that.last_modified_view.cell;
341 } else {
375 } else {
342 return null
376 return null
343 }
377 }
344 },
378 },
345 },
379 },
346 };
380 };
347 }
381 }
348 return callbacks;
382 return callbacks;
349 },
383 },
350
384
351
385
352 // Get the output area corresponding to the msg_id.
386 // Get the output area corresponding to the msg_id.
353 // cell is an instance of IPython.Cell
387 // cell is an instance of IPython.Cell
354 _get_msg_cell: function (msg_id) {
388 _get_msg_cell: function (msg_id) {
355
389
356 // First, check to see if the msg was triggered by cell execution.
390 // First, check to see if the msg was triggered by cell execution.
357 var cell = this.widget_manager.get_msg_cell(msg_id);
391 var cell = this.widget_manager.get_msg_cell(msg_id);
358 if (cell != null) {
392 if (cell != null) {
359 return cell;
393 return cell;
360 }
394 }
361
395
362 // Second, check to see if a get_cell callback was defined
396 // Second, check to see if a get_cell callback was defined
363 // for the message. get_cell callbacks are registered for
397 // for the message. get_cell callbacks are registered for
364 // widget messages, so this block is actually checking to see if the
398 // widget messages, so this block is actually checking to see if the
365 // message was triggered by a widget.
399 // message was triggered by a widget.
366 var kernel = this.comm_manager.kernel;
400 var kernel = this.comm_manager.kernel;
367 var callbacks = kernel.get_callbacks_for_msg(msg_id);
401 var callbacks = kernel.get_callbacks_for_msg(msg_id);
368 if (callbacks != undefined &&
402 if (callbacks != undefined &&
369 callbacks.iopub != undefined &&
403 callbacks.iopub != undefined &&
370 callbacks.iopub.get_cell != undefined) {
404 callbacks.iopub.get_cell != undefined) {
371
405
372 return callbacks.iopub.get_cell();
406 return callbacks.iopub.get_cell();
373 }
407 }
374
408
375 // Not triggered by a cell or widget (no get_cell callback
409 // Not triggered by a cell or widget (no get_cell callback
376 // exists).
410 // exists).
377 return null;
411 return null;
378 },
412 },
379
413
380 });
414 });
381
415
382
416
383 //--------------------------------------------------------------------
417 //--------------------------------------------------------------------
384 // WidgetView class
418 // WidgetView class
385 //--------------------------------------------------------------------
419 //--------------------------------------------------------------------
386 var WidgetView = Backbone.View.extend({
420 var WidgetView = Backbone.View.extend({
387
421
388 initialize: function() {
422 initialize: function() {
389 this.visible = true;
423 this.visible = true;
390 this.model.on('change',this.update,this);
424 this.model.on('change',this.update,this);
391 this._add_class_calls = this.model.get('_add_class')[0];
425 this._add_class_calls = this.model.get('_add_class')[0];
392 this._remove_class_calls = this.model.get('_remove_class')[0];
426 this._remove_class_calls = this.model.get('_remove_class')[0];
393 },
427 },
394
428
395 update: function() {
429 update: function() {
396 if (this.model.get('visible') != undefined) {
430 if (this.model.get('visible') != undefined) {
397 if (this.visible != this.model.get('visible')) {
431 if (this.visible != this.model.get('visible')) {
398 this.visible = this.model.get('visible');
432 this.visible = this.model.get('visible');
399 if (this.visible) {
433 if (this.visible) {
400 this.$el.show();
434 this.$el.show();
401 } else {
435 } else {
402 this.$el.hide();
436 this.$el.hide();
403 }
437 }
404 }
438 }
405 }
439 }
406
440
407 if (this.model.css != undefined) {
441 if (this.model.css != undefined) {
408 for (var selector in this.model.css) {
442 for (var selector in this.model.css) {
409 if (this.model.css.hasOwnProperty(selector)) {
443 if (this.model.css.hasOwnProperty(selector)) {
410
444
411 // Apply the css traits to all elements that match the selector.
445 // Apply the css traits to all elements that match the selector.
412 var elements = this._get_selector_element(selector);
446 var elements = this._get_selector_element(selector);
413 if (elements.length > 0) {
447 if (elements.length > 0) {
414 var css_traits = this.model.css[selector];
448 var css_traits = this.model.css[selector];
415 for (var css_key in css_traits) {
449 for (var css_key in css_traits) {
416 if (css_traits.hasOwnProperty(css_key)) {
450 if (css_traits.hasOwnProperty(css_key)) {
417 elements.css(css_key, css_traits[css_key]);
451 elements.css(css_key, css_traits[css_key]);
418 }
452 }
419 }
453 }
420 }
454 }
421 }
455 }
422 }
456 }
423 }
457 }
424
458
425 var add_class = this.model.get('_add_class');
459 var add_class = this.model.get('_add_class');
426 if (add_class != undefined){
460 if (add_class != undefined){
427 var add_class_calls = add_class[0];
461 var add_class_calls = add_class[0];
428 if (add_class_calls > this._add_class_calls) {
462 if (add_class_calls > this._add_class_calls) {
429 this._add_class_calls = add_class_calls;
463 this._add_class_calls = add_class_calls;
430 var elements = this._get_selector_element(add_class[1]);
464 var elements = this._get_selector_element(add_class[1]);
431 if (elements.length > 0) {
465 if (elements.length > 0) {
432 elements.addClass(add_class[2]);
466 elements.addClass(add_class[2]);
433 }
467 }
434 }
468 }
435 }
469 }
436
470
437 var remove_class = this.model.get('_remove_class');
471 var remove_class = this.model.get('_remove_class');
438 if (remove_class != undefined){
472 if (remove_class != undefined){
439 var remove_class_calls = remove_class[0];
473 var remove_class_calls = remove_class[0];
440 if (remove_class_calls > this._remove_class_calls) {
474 if (remove_class_calls > this._remove_class_calls) {
441 this._remove_class_calls = remove_class_calls;
475 this._remove_class_calls = remove_class_calls;
442 var elements = this._get_selector_element(remove_class[1]);
476 var elements = this._get_selector_element(remove_class[1]);
443 if (elements.length > 0) {
477 if (elements.length > 0) {
444 elements.removeClass(remove_class[2]);
478 elements.removeClass(remove_class[2]);
445 }
479 }
446 }
480 }
447 }
481 }
448 },
482 },
449
483
450 _get_selector_element: function(selector) {
484 _get_selector_element: function(selector) {
451 // Get the elements via the css selector. If the selector is
485 // Get the elements via the css selector. If the selector is
452 // blank, apply the style to the $el_to_style element. If
486 // blank, apply the style to the $el_to_style element. If
453 // the $el_to_style element is not defined, use apply the
487 // the $el_to_style element is not defined, use apply the
454 // style to the view's element.
488 // style to the view's element.
455 var elements = this.$el.find(selector);
489 var elements = this.$el.find(selector);
456 if (selector=='') {
490 if (selector=='') {
457 if (this.$el_to_style == undefined) {
491 if (this.$el_to_style == undefined) {
458 elements = this.$el;
492 elements = this.$el;
459 } else {
493 } else {
460 elements = this.$el_to_style;
494 elements = this.$el_to_style;
461 }
495 }
462 }
496 }
463 return elements;
497 return elements;
464 },
498 },
465 });
499 });
466
500
467
501
468 //--------------------------------------------------------------------
502 //--------------------------------------------------------------------
469 // WidgetManager class
503 // WidgetManager class
470 //--------------------------------------------------------------------
504 //--------------------------------------------------------------------
471 var WidgetManager = function(){
505 var WidgetManager = function(){
472 this.comm_manager = null;
506 this.comm_manager = null;
473 this.widget_model_types = {};
507 this.widget_model_types = {};
474 this.widget_view_types = {};
508 this.widget_view_types = {};
475
509
476 var that = this;
510 var that = this;
477 Backbone.sync = function(method, model, options, error) {
511 Backbone.sync = function(method, model, options, error) {
478 var result = model._handle_sync(method, options);
512 var result = model._handle_sync(method, options);
479 if (options.success) {
513 if (options.success) {
480 options.success(result);
514 options.success(result);
481 }
515 }
482 };
516 };
483 }
517 }
484
518
485
519
486 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
520 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
487 this.comm_manager = comm_manager;
521 this.comm_manager = comm_manager;
488
522
489 // Register already register widget model types with the comm manager.
523 // Register already register widget model types with the comm manager.
490 for (var widget_model_name in this.widget_model_types) {
524 for (var widget_model_name in this.widget_model_types) {
491 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
525 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
492 }
526 }
493 }
527 }
494
528
495
529
496 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
530 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
497 // Register the widget with the comm manager. Make sure to pass this object's context
531 // Register the widget with the comm manager. Make sure to pass this object's context
498 // in so `this` works in the call back.
532 // in so `this` works in the call back.
499 if (this.comm_manager!=null) {
533 if (this.comm_manager!=null) {
500 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
534 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
501 }
535 }
502 this.widget_model_types[widget_model_name] = widget_model_type;
536 this.widget_model_types[widget_model_name] = widget_model_type;
503 }
537 }
504
538
505
539
506 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
540 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
507 this.widget_view_types[widget_view_name] = widget_view_type;
541 this.widget_view_types[widget_view_name] = widget_view_type;
508 }
542 }
509
543
510
544
511 WidgetManager.prototype.get_msg_cell = function (msg_id) {
545 WidgetManager.prototype.get_msg_cell = function (msg_id) {
512 if (IPython.notebook != undefined && IPython.notebook != null) {
546 if (IPython.notebook != undefined && IPython.notebook != null) {
513 return IPython.notebook.get_msg_cell(msg_id);
547 return IPython.notebook.get_msg_cell(msg_id);
514 }
548 }
515 }
549 }
516
550
517
551
518 WidgetManager.prototype.on_create_widget = function (callback) {
552 WidgetManager.prototype.on_create_widget = function (callback) {
519 this._create_widget_callback = callback;
553 this._create_widget_callback = callback;
520 }
554 }
521
555
522
556
523 WidgetManager.prototype._handle_create_widget = function (widget_model) {
557 WidgetManager.prototype._handle_create_widget = function (widget_model) {
524 if (this._create_widget_callback) {
558 if (this._create_widget_callback) {
525 try {
559 try {
526 this._create_widget_callback(widget_model);
560 this._create_widget_callback(widget_model);
527 } catch (e) {
561 } catch (e) {
528 console.log("Exception in WidgetManager callback", e, widget_model);
562 console.log("Exception in WidgetManager callback", e, widget_model);
529 }
563 }
530 }
564 }
531 }
565 }
532
566
533
567
534 WidgetManager.prototype._handle_com_open = function (comm, msg) {
568 WidgetManager.prototype._handle_com_open = function (comm, msg) {
535 var widget_type_name = msg.content.target_name;
569 var widget_type_name = msg.content.target_name;
536 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this);
570 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this);
537 this._handle_create_widget(widget_model);
571 this._handle_create_widget(widget_model);
538 }
572 }
539
573
540
574
541 //--------------------------------------------------------------------
575 //--------------------------------------------------------------------
542 // Init code
576 // Init code
543 //--------------------------------------------------------------------
577 //--------------------------------------------------------------------
544 IPython.WidgetManager = WidgetManager;
578 IPython.WidgetManager = WidgetManager;
545 IPython.WidgetModel = WidgetModel;
579 IPython.WidgetModel = WidgetModel;
546 IPython.WidgetView = WidgetView;
580 IPython.WidgetView = WidgetView;
547
581
548 if (IPython.widget_manager==undefined || IPython.widget_manager==null) {
582 if (IPython.widget_manager==undefined || IPython.widget_manager==null) {
549 IPython.widget_manager = new WidgetManager();
583 IPython.widget_manager = new WidgetManager();
550 }
584 }
551
585
552 return IPython.widget_manager;
586 return IPython.widget_manager;
553 });
587 });
@@ -1,356 +1,415 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
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Classes
32 # Classes
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 class Widget(LoggingConfigurable):
34 class Widget(LoggingConfigurable):
35
35
36 # Shared declarations
36 # Shared declarations
37 _keys = []
37 _keys = []
38
38
39 # Public declarations
39 # Public declarations
40 target_name = Unicode('widget', help="""Name of the backbone model
40 target_name = Unicode('widget', help="""Name of the backbone model
41 registered in the frontend to create and sync this widget with.""")
41 registered in the frontend to create and sync this widget with.""")
42 default_view_name = Unicode(help="""Default view registered in the frontend
42 default_view_name = Unicode(help="""Default view registered in the frontend
43 to use to represent the widget.""")
43 to use to represent the widget.""")
44 parent = Instance('IPython.html.widgets.widget.Widget')
44 parent = Instance('IPython.html.widgets.widget.Widget')
45 visible = Bool(True, help="Whether or not the widget is visible.")
45 visible = Bool(True, help="Whether or not the widget is visible.")
46
46
47 def _parent_changed(self, name, old, new):
47 def _parent_changed(self, name, old, new):
48 if self._displayed:
48 if self._displayed:
49 raise Exception('Parent cannot be set because widget has been displayed.')
49 raise Exception('Parent cannot be set because widget has been displayed.')
50 elif new == self:
50 elif new == self:
51 raise Exception('Parent cannot be set to self.')
51 raise Exception('Parent cannot be set to self.')
52 else:
52 else:
53
53
54 # Parent/child association
54 # Parent/child association
55 if new is not None and not self in new._children:
55 if new is not None and not self in new._children:
56 new._children.append(self)
56 new._children.append(self)
57 if old is not None and self in old._children:
57 if old is not None and self in old._children:
58 old._children.remove(self)
58 old._children.remove(self)
59
59
60 # Private/protected declarations
60 # Private/protected declarations
61 _property_lock = False
61 _property_lock = False
62 _css = Dict() # Internal CSS property dict
62 _css = Dict() # Internal CSS property dict
63 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
63 _add_class = List() # Used to add a js class to a DOM element (call#, selector, class_name)
64 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
64 _remove_class = List() # Used to remove a js class from a DOM element (call#, selector, class_name)
65 _displayed = False
65 _displayed = False
66 _comm = None
66 _comm = None
67
67
68
68
69 def __init__(self, **kwargs):
69 def __init__(self, **kwargs):
70 """Public constructor
70 """Public constructor
71
71
72 Parameters
72 Parameters
73 ----------
73 ----------
74 parent : Widget instance (optional)
74 parent : Widget instance (optional)
75 Widget that this widget instance is child of. When the widget is
75 Widget that this widget instance is child of. When the widget is
76 displayed in the frontend, it's corresponding view will be made
76 displayed in the frontend, it's corresponding view will be made
77 child of the parent's view if the parent's view exists already. If
77 child of the parent's view if the parent's view exists already. If
78 the parent's view is displayed, it will automatically display this
78 the parent's view is displayed, it will automatically display this
79 widget's default view as it's child. The default view can be set
79 widget's default view as it's child. The default view can be set
80 via the default_view_name property.
80 via the default_view_name property.
81 """
81 """
82 self._children = []
82 self._children = []
83 self._add_class = [0]
83 self._add_class = [0]
84 self._remove_class = [0]
84 self._remove_class = [0]
85 self._display_callbacks = []
85 self._display_callbacks = []
86 self._msg_callbacks = []
86 super(Widget, self).__init__(**kwargs)
87 super(Widget, self).__init__(**kwargs)
87
88
88 # Register after init to allow default values to be specified
89 # Register after init to allow default values to be specified
89 self.on_trait_change(self._handle_property_changed, self.keys)
90 self.on_trait_change(self._handle_property_changed, self.keys)
90
91
91
92
92 def __del__(self):
93 def __del__(self):
93 """Object disposal"""
94 """Object disposal"""
94 self.close()
95 self.close()
95
96
96
97
97 def close(self):
98 def close(self):
98 """Close method. Closes the widget which closes the underlying comm.
99 """Close method. Closes the widget which closes the underlying comm.
99 When the comm is closed, all of the widget views are automatically
100 When the comm is closed, all of the widget views are automatically
100 removed from the frontend."""
101 removed from the frontend."""
101 try:
102 try:
102 self._comm.close()
103 self._comm.close()
103 del self._comm
104 del self._comm
104 except:
105 except:
105 pass # Comm doesn't exist and/or is already closed.
106 pass # Comm doesn't exist and/or is already closed.
106
107
107
108
108 # Properties
109 # Properties
109 def _get_keys(self):
110 def _get_keys(self):
110 keys = ['visible', '_css', '_add_class', '_remove_class']
111 keys = ['visible', '_css', '_add_class', '_remove_class']
111 keys.extend(self._keys)
112 keys.extend(self._keys)
112 return keys
113 return keys
113 keys = property(_get_keys)
114 keys = property(_get_keys)
114
115
115
116
116 # Event handlers
117 # Event handlers
117 def _handle_msg(self, msg):
118 def _handle_msg(self, msg):
118 """Called when a msg is recieved from the frontend"""
119 """Called when a msg is recieved from the frontend"""
120 data = msg['content']['data']
121
119 # Handle backbone sync methods CREATE, PATCH, and UPDATE
122 # Handle backbone sync methods CREATE, PATCH, and UPDATE
120 sync_method = msg['content']['data']['sync_method']
123 if 'sync_method' in data and 'sync_data' in data:
121 sync_data = msg['content']['data']['sync_data']
124 sync_method = data['sync_method']
122 self._handle_recieve_state(sync_data) # handles all methods
125 sync_data = data['sync_data']
126 self._handle_recieve_state(sync_data) # handles all methods
127
128 # Handle a custom msg from the front-end
129 if 'custom_content' in data:
130 self._handle_custom_msg(data['custom_content'])
131
132
133 def _handle_custom_msg(self, content):
134 """Called when a custom msg is recieved."""
135 for handler in self._msg_callbacks:
136 if callable(handler):
137 argspec = inspect.getargspec(handler)
138 nargs = len(argspec[0])
139
140 # Bound methods have an additional 'self' argument
141 if isinstance(handler, types.MethodType):
142 nargs -= 1
143
144 # Call the callback
145 if nargs == 1:
146 handler(content)
147 elif nargs == 2:
148 handler(self, content)
149 else:
150 raise TypeError('Widget msg callback must ' \
151 'accept 1 or 2 arguments, not %d.' % nargs)
123
152
124
153
125 def _handle_recieve_state(self, sync_data):
154 def _handle_recieve_state(self, sync_data):
126 """Called when a state is recieved from the frontend."""
155 """Called when a state is recieved from the frontend."""
127 self._property_lock = True
156 self._property_lock = True
128 try:
157 try:
129
158
130 # Use _keys instead of keys - Don't get retrieve the css from the client side.
159 # Use _keys instead of keys - Don't get retrieve the css from the client side.
131 for name in self._keys:
160 for name in self._keys:
132 if name in sync_data:
161 if name in sync_data:
133 setattr(self, name, sync_data[name])
162 setattr(self, name, sync_data[name])
134 finally:
163 finally:
135 self._property_lock = False
164 self._property_lock = False
136
165
137
166
138 def _handle_property_changed(self, name, old, new):
167 def _handle_property_changed(self, name, old, new):
139 """Called when a proeprty has been changed."""
168 """Called when a proeprty has been changed."""
140 if not self._property_lock and self._comm is not None:
169 if not self._property_lock and self._comm is not None:
141 # TODO: Validate properties.
170 # TODO: Validate properties.
142 # Send new state to frontend
171 # Send new state to frontend
143 self.send_state(key=name)
172 self.send_state(key=name)
144
173
145
174
146 def _handle_close(self):
175 def _handle_close(self):
147 """Called when the comm is closed by the frontend."""
176 """Called when the comm is closed by the frontend."""
148 self._comm = None
177 self._comm = None
178
179
180 def _handle_displayed(self, view_name):
181 """Called when a view has been displayed for this widget instance
182
183 Parameters
184 ----------
185 view_name: unicode
186 Name of the view that was displayed."""
187 for handler in self._display_callbacks:
188 if callable(handler):
189 argspec = inspect.getargspec(handler)
190 nargs = len(argspec[0])
191
192 # Bound methods have an additional 'self' argument
193 if isinstance(handler, types.MethodType):
194 nargs -= 1
195
196 # Call the callback
197 if nargs == 0:
198 handler()
199 elif nargs == 1:
200 handler(self)
201 elif nargs == 2:
202 handler(self, view_name)
203 else:
204 raise TypeError('Widget display callback must ' \
205 'accept 0-2 arguments, not %d.' % nargs)
149
206
150
207
151 # Public methods
208 # Public methods
152 def send_state(self, key=None):
209 def send_state(self, key=None):
153 """Sends the widget state, or a piece of it, to the frontend.
210 """Sends the widget state, or a piece of it, to the frontend.
154
211
155 Parameters
212 Parameters
156 ----------
213 ----------
157 key : unicode (optional)
214 key : unicode (optional)
158 A single property's name to sync with the frontend.
215 A single property's name to sync with the frontend.
159 """
216 """
160 if self._comm is not None:
217 if self._comm is not None:
161 state = {}
218 state = {}
162
219
163 # If a key is provided, just send the state of that key.
220 # If a key is provided, just send the state of that key.
164 keys = []
221 keys = []
165 if key is None:
222 if key is None:
166 keys.extend(self.keys)
223 keys.extend(self.keys)
167 else:
224 else:
168 keys.append(key)
225 keys.append(key)
169 for key in self.keys:
226 for key in self.keys:
170 try:
227 try:
171 state[key] = getattr(self, key)
228 state[key] = getattr(self, key)
172 except Exception as e:
229 except Exception as e:
173 pass # Eat errors, nom nom nom
230 pass # Eat errors, nom nom nom
174 self._comm.send({"method": "update",
231 self._comm.send({"method": "update",
175 "state": state})
232 "state": state})
176
233
177
234
178 def get_css(self, key, selector=""):
235 def get_css(self, key, selector=""):
179 """Get a CSS property of the widget. Note, this function does not
236 """Get a CSS property of the widget. Note, this function does not
180 actually request the CSS from the front-end; Only properties that have
237 actually request the CSS from the front-end; Only properties that have
181 been set with set_css can be read.
238 been set with set_css can be read.
182
239
183 Parameters
240 Parameters
184 ----------
241 ----------
185 key: unicode
242 key: unicode
186 CSS key
243 CSS key
187 selector: unicode (optional)
244 selector: unicode (optional)
188 JQuery selector used when the CSS key/value was set.
245 JQuery selector used when the CSS key/value was set.
189 """
246 """
190 if selector in self._css and key in self._css[selector]:
247 if selector in self._css and key in self._css[selector]:
191 return self._css[selector][key]
248 return self._css[selector][key]
192 else:
249 else:
193 return None
250 return None
194
251
195
252
196 def set_css(self, *args, **kwargs):
253 def set_css(self, *args, **kwargs):
197 """Set one or more CSS properties of the widget (shared among all of the
254 """Set one or more CSS properties of the widget (shared among all of the
198 views). This function has two signatures:
255 views). This function has two signatures:
199 - set_css(css_dict, [selector=''])
256 - set_css(css_dict, [selector=''])
200 - set_css(key, value, [selector=''])
257 - set_css(key, value, [selector=''])
201
258
202 Parameters
259 Parameters
203 ----------
260 ----------
204 css_dict : dict
261 css_dict : dict
205 CSS key/value pairs to apply
262 CSS key/value pairs to apply
206 key: unicode
263 key: unicode
207 CSS key
264 CSS key
208 value
265 value
209 CSS value
266 CSS value
210 selector: unicode (optional)
267 selector: unicode (optional)
211 JQuery selector to use to apply the CSS key/value.
268 JQuery selector to use to apply the CSS key/value.
212 """
269 """
213 selector = kwargs.get('selector', '')
270 selector = kwargs.get('selector', '')
214
271
215 # Signature 1: set_css(css_dict, [selector=''])
272 # Signature 1: set_css(css_dict, [selector=''])
216 if len(args) == 1:
273 if len(args) == 1:
217 if isinstance(args[0], dict):
274 if isinstance(args[0], dict):
218 for (key, value) in args[0].items():
275 for (key, value) in args[0].items():
219 self.set_css(key, value, selector=selector)
276 self.set_css(key, value, selector=selector)
220 else:
277 else:
221 raise Exception('css_dict must be a dict.')
278 raise Exception('css_dict must be a dict.')
222
279
223 # Signature 2: set_css(key, value, [selector=''])
280 # Signature 2: set_css(key, value, [selector=''])
224 elif len(args) == 2 or len(args) == 3:
281 elif len(args) == 2 or len(args) == 3:
225
282
226 # Selector can be a positional arg if it's the 3rd value
283 # Selector can be a positional arg if it's the 3rd value
227 if len(args) == 3:
284 if len(args) == 3:
228 selector = args[2]
285 selector = args[2]
229 if selector not in self._css:
286 if selector not in self._css:
230 self._css[selector] = {}
287 self._css[selector] = {}
231
288
232 # Only update the property if it has changed.
289 # Only update the property if it has changed.
233 key = args[0]
290 key = args[0]
234 value = args[1]
291 value = args[1]
235 if not (key in self._css[selector] and value in self._css[selector][key]):
292 if not (key in self._css[selector] and value in self._css[selector][key]):
236 self._css[selector][key] = value
293 self._css[selector][key] = value
237 self.send_state('_css') # Send new state to client.
294 self.send_state('_css') # Send new state to client.
238 else:
295 else:
239 raise Exception('set_css only accepts 1-3 arguments')
296 raise Exception('set_css only accepts 1-3 arguments')
240
297
241
298
242 def add_class(self, class_name, selector=""):
299 def add_class(self, class_name, selector=""):
243 """Add class[es] to a DOM element
300 """Add class[es] to a DOM element
244
301
245 Parameters
302 Parameters
246 ----------
303 ----------
247 class_name: unicode
304 class_name: unicode
248 Class name(s) to add to the DOM element(s). Multiple class names
305 Class name(s) to add to the DOM element(s). Multiple class names
249 must be space separated.
306 must be space separated.
250 selector: unicode (optional)
307 selector: unicode (optional)
251 JQuery selector to select the DOM element(s) that the class(es) will
308 JQuery selector to select the DOM element(s) that the class(es) will
252 be added to.
309 be added to.
253 """
310 """
254 self._add_class = [self._add_class[0] + 1, selector, class_name]
311 self._add_class = [self._add_class[0] + 1, selector, class_name]
255 self.send_state(key='_add_class')
312 self.send_state(key='_add_class')
256
313
257
314
258 def remove_class(self, class_name, selector=""):
315 def remove_class(self, class_name, selector=""):
259 """Remove class[es] from a DOM element
316 """Remove class[es] from a DOM element
260
317
261 Parameters
318 Parameters
262 ----------
319 ----------
263 class_name: unicode
320 class_name: unicode
264 Class name(s) to remove from the DOM element(s). Multiple class
321 Class name(s) to remove from the DOM element(s). Multiple class
265 names must be space separated.
322 names must be space separated.
266 selector: unicode (optional)
323 selector: unicode (optional)
267 JQuery selector to select the DOM element(s) that the class(es) will
324 JQuery selector to select the DOM element(s) that the class(es) will
268 be removed from.
325 be removed from.
269 """
326 """
270 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
327 self._remove_class = [self._remove_class[0] + 1, selector, class_name]
271 self.send_state(key='_remove_class')
328 self.send_state(key='_remove_class')
272
329
273
330
331 def send(self, content):
332 """Sends a custom msg to the widget model in the front-end.
333
334 Parameters
335 ----------
336 content : dict
337 Content of the message to send.
338 """
339 if self._comm is not None:
340 self._comm.send({"method": "custom",
341 "custom_content": content})
342
343
344 def on_msg(self, callback, remove=False):
345 """Register a callback for when a custom msg is recieved from the front-end
346
347 Parameters
348 ----------
349 callback: method handler
350 Can have a signature of:
351 - callback(content)
352 - callback(sender, content)
353 remove: bool
354 True if the callback should be unregistered."""
355 if remove and callback in self._msg_callbacks:
356 self._msg_callbacks.remove(callback)
357 elif not remove and not callback in self._msg_callbacks:
358 self._msg_callbacks.append(callback)
359
360
274 def on_displayed(self, callback, remove=False):
361 def on_displayed(self, callback, remove=False):
275 """Register a callback to be called when the widget has been displayed
362 """Register a callback to be called when the widget has been displayed
276
363
277 Parameters
364 Parameters
278 ----------
365 ----------
279 callback: method handler
366 callback: method handler
280 Can have a signature of:
367 Can have a signature of:
281 - callback()
368 - callback()
282 - callback(sender)
369 - callback(sender)
283 - callback(sender, view_name)
370 - callback(sender, view_name)
284 remove: bool
371 remove: bool
285 True if the callback should be unregistered."""
372 True if the callback should be unregistered."""
286 if remove:
373 if remove and callback in self._display_callbacks:
287 self._display_callbacks.remove(callback)
374 self._display_callbacks.remove(callback)
288 elif not callback in self._display_callbacks:
375 elif not remove and not callback in self._display_callbacks:
289 self._display_callbacks.append(callback)
376 self._display_callbacks.append(callback)
290
377
291
378
292 def handle_displayed(self, view_name):
293 """Called when a view has been displayed for this widget instance
294
295 Parameters
296 ----------
297 view_name: unicode
298 Name of the view that was displayed."""
299 for handler in self._display_callbacks:
300 if callable(handler):
301 argspec = inspect.getargspec(handler)
302 nargs = len(argspec[0])
303
304 # Bound methods have an additional 'self' argument
305 if isinstance(handler, types.MethodType):
306 nargs -= 1
307
308 # Call the callback
309 if nargs == 0:
310 handler()
311 elif nargs == 1:
312 handler(self)
313 elif nargs == 2:
314 handler(self, view_name)
315 else:
316 raise TypeError('Widget display callback must ' \
317 'accept 0-2 arguments, not %d.' % nargs)
318
319
320 # Support methods
379 # Support methods
321 def _repr_widget_(self, view_name=None):
380 def _repr_widget_(self, view_name=None):
322 """Function that is called when `IPython.display.display` is called on
381 """Function that is called when `IPython.display.display` is called on
323 the widget.
382 the widget.
324
383
325 Parameters
384 Parameters
326 ----------
385 ----------
327 view_name: unicode (optional)
386 view_name: unicode (optional)
328 View to display in the frontend. Overrides default_view_name."""
387 View to display in the frontend. Overrides default_view_name."""
329
388
330 if not view_name:
389 if not view_name:
331 view_name = self.default_view_name
390 view_name = self.default_view_name
332
391
333 # Create a comm.
392 # Create a comm.
334 if self._comm is None:
393 if self._comm is None:
335 self._comm = Comm(target_name=self.target_name)
394 self._comm = Comm(target_name=self.target_name)
336 self._comm.on_msg(self._handle_msg)
395 self._comm.on_msg(self._handle_msg)
337 self._comm.on_close(self._handle_close)
396 self._comm.on_close(self._handle_close)
338
397
339 # Make sure model is syncronized
398 # Make sure model is syncronized
340 self.send_state()
399 self.send_state()
341
400
342 # Show view.
401 # Show view.
343 if self.parent is None or self.parent._comm is None:
402 if self.parent is None or self.parent._comm is None:
344 self._comm.send({"method": "display", "view_name": view_name})
403 self._comm.send({"method": "display", "view_name": view_name})
345 else:
404 else:
346 self._comm.send({"method": "display",
405 self._comm.send({"method": "display",
347 "view_name": view_name,
406 "view_name": view_name,
348 "parent": self.parent._comm.comm_id})
407 "parent": self.parent._comm.comm_id})
349 self._displayed = True
408 self._displayed = True
350 self.handle_displayed(view_name)
409 self._handle_displayed(view_name)
351
410
352 # Now display children if any.
411 # Now display children if any.
353 for child in self._children:
412 for child in self._children:
354 if child != self:
413 if child != self:
355 child._repr_widget_()
414 child._repr_widget_()
356 return None
415 return None
General Comments 0
You need to be logged in to leave comments. Login now