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