##// END OF EJS Templates
Moved get_msg_cell which depends on notebook specific logic...
Jonathan Frederic -
Show More
@@ -1,488 +1,483 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // WidgetModel, WidgetView, and WidgetManager
10 10 //============================================================================
11 11 /**
12 12 * Base Widget classes
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule widget
16 16 */
17 17
18 18 "use strict";
19 19
20 20 // Use require.js 'define' method so that require.js is intelligent enough to
21 21 // syncronously load everything within this file when it is being 'required'
22 22 // elsewhere.
23 23 define(["components/underscore/underscore-min",
24 24 "components/backbone/backbone-min",
25 25 ], function(underscore, backbone){
26 26
27 27
28 28 //--------------------------------------------------------------------
29 29 // WidgetModel class
30 30 //--------------------------------------------------------------------
31 31 var WidgetModel = Backbone.Model.extend({
32 constructor: function(comm_manager, comm, widget_view_types) {
32 constructor: function(comm_manager, comm, widget_manager) {
33 33 this.comm_manager = comm_manager;
34 this.widget_view_types = widget_view_types;
34 this.widget_manager = widget_manager;
35 35 this.pending_msgs = 0;
36 36 this.msg_throttle = 3;
37 37 this.msg_buffer = null;
38 38 this.views = {};
39 39
40 40 // Remember comm associated with the model.
41 41 this.comm = comm;
42 42 comm.model = this;
43 43
44 44 // Hook comm messages up to model.
45 45 comm.on_close($.proxy(this.handle_comm_closed, this));
46 46 comm.on_msg($.proxy(this.handle_comm_msg, this));
47 47
48 48 return Backbone.Model.apply(this);
49 49 },
50 50
51 51
52 52 update_other_views: function(caller) {
53 53 this.last_modified_view = caller;
54 54 this.save(this.changedAttributes(), {patch: true});
55 55
56 for (var output_area in this.views) {
57 var views = this.views[output_area];
56 for (var cell in this.views) {
57 var views = this.views[cell];
58 58 for (var view_index in views) {
59 59 var view = views[view_index];
60 60 if (view !== caller) {
61 61 view.update();
62 62 }
63 63 }
64 64 }
65 65 },
66 66
67 67
68 handle_status: function (output_area, msg) {
68 handle_status: function (cell, msg) {
69 69 //execution_state : ('busy', 'idle', 'starting')
70 70 if (msg.content.execution_state=='idle') {
71 71
72 72 // Send buffer if this message caused another message to be
73 73 // throttled.
74 74 if (this.msg_buffer != null &&
75 75 this.msg_throttle == this.pending_msgs) {
76 76
77 var output_area = this._get_output_area(msg.parent_header.msg_id);
78 var callbacks = this._make_callbacks(output_area);
77 var cell = this._get_msg_cell(msg.parent_header.msg_id);
78 var callbacks = this._make_callbacks(cell);
79 79 var data = {sync_method: 'update', sync_data: this.msg_buffer};
80 80 this.comm.send(data, callbacks);
81 81 this.msg_buffer = null;
82 82 } else {
83 83
84 84 // Only decrease the pending message count if the buffer
85 85 // doesn't get flushed (sent).
86 86 --this.pending_msgs;
87 87 }
88 88 }
89 89 },
90 90
91 91
92 92 // Custom syncronization logic.
93 93 handle_sync: function (method, options) {
94 94 var model_json = this.toJSON();
95 95
96 96 // Only send updated state if the state hasn't been changed
97 97 // during an update.
98 98 if (!this.updating) {
99 99 if (this.pending_msgs >= this.msg_throttle) {
100 100 // The throttle has been exceeded, buffer the current msg so
101 101 // it can be sent once the kernel has finished processing
102 102 // some of the existing messages.
103 103 if (method=='patch') {
104 104 if (this.msg_buffer == null) {
105 105 this.msg_buffer = $.extend({}, model_json); // Copy
106 106 }
107 107 for (var attr in options.attrs) {
108 108 this.msg_buffer[attr] = options.attrs[attr];
109 109 }
110 110 } else {
111 111 this.msg_buffer = $.extend({}, model_json); // Copy
112 112 }
113 113
114 114 } else {
115 115 // We haven't exceeded the throttle, send the message like
116 116 // normal. If this is a patch operation, just send the
117 117 // changes.
118 118 var send_json = model_json;
119 119 if (method=='patch') {
120 120 send_json = {};
121 121 for (var attr in options.attrs) {
122 122 send_json[attr] = options.attrs[attr];
123 123 }
124 124 }
125 125
126 126 var data = {sync_method: method, sync_data: send_json};
127 127
128 var output_area = null;
128 var cell = null;
129 129 if (this.last_modified_view != undefined && this.last_modified_view != null) {
130 output_area = this.last_modified_view.output_area;
130 cell = this.last_modified_view.cell;
131 131 }
132 132
133 var callbacks = this._make_callbacks(output_area);
133 var callbacks = this._make_callbacks(cell);
134 134 this.comm.send(data, callbacks);
135 135 this.pending_msgs++;
136 136 }
137 137 }
138 138
139 139 // Since the comm is a one-way communication, assume the message
140 140 // arrived.
141 141 return model_json;
142 142 },
143 143
144 144
145 145 // Handle incomming comm msg.
146 146 handle_comm_msg: function (msg) {
147 147 var method = msg.content.data.method;
148 148 switch (method){
149 149 case 'display':
150 150
151 151 // Try to get the cell index.
152 var output_area = this._get_output_area(msg.parent_header.msg_id);
153 if (output_area == null) {
152 var cell = this._get_msg_cell(msg.parent_header.msg_id);
153 if (cell == null) {
154 154 console.log("Could not determine where the display" +
155 155 " message was from. Widget will not be displayed")
156 156 } else {
157 157 this.display_view(msg.content.data.view_name,
158 158 msg.content.data.parent,
159 output_area);
159 cell);
160 160 }
161 161 break;
162 162 case 'update':
163 163 this.handle_update(msg.content.data.state);
164 164 break;
165 165 }
166 166 },
167 167
168 168
169 169 // Handle when a widget is updated via the python side.
170 170 handle_update: function (state) {
171 171 this.updating = true;
172 172 try {
173 173 for (var key in state) {
174 174 if (state.hasOwnProperty(key)) {
175 175 if (key == "_css"){
176 176 this.css = state[key];
177 177 } else {
178 178 this.set(key, state[key]);
179 179 }
180 180 }
181 181 }
182 182 this.id = this.comm.comm_id;
183 183 this.save();
184 184 } finally {
185 185 this.updating = false;
186 186 }
187 187 },
188 188
189 189
190 190 // Handle when a widget is closed.
191 191 handle_comm_closed: function (msg) {
192 for (var output_area in this.views) {
193 var views = this.views[output_area];
192 for (var cell in this.views) {
193 var views = this.views[cell];
194 194 for (var view_index in views) {
195 195 var view = views[view_index];
196 196 view.remove();
197 197 }
198 198 }
199 199 },
200 200
201 201
202 202 // Create view that represents the model.
203 display_view: function (view_name, parent_comm_id, output_area) {
203 display_view: function (view_name, parent_comm_id, cell) {
204 204 var new_views = [];
205 205
206 206 var displayed = false;
207 207 if (parent_comm_id != undefined) {
208 208 var parent_comm = this.comm_manager.comms[parent_comm_id];
209 209 var parent_model = parent_comm.model;
210 var parent_views = parent_model.views[output_area];
210 var parent_views = parent_model.views[cell];
211 211 for (var parent_view_index in parent_views) {
212 212 var parent_view = parent_views[parent_view_index];
213 213 if (parent_view.display_child != undefined) {
214 var view = this._create_view(view_name, output_area);
214 var view = this._create_view(view_name, cell);
215 215 new_views.push(view);
216 216 parent_view.display_child(view);
217 217 displayed = true;
218 218 }
219 219 }
220 220 }
221 221
222 222 if (!displayed) {
223 223 // No parent view is defined or exists. Add the view's
224 224 // element to cell's widget div.
225 var view = this._create_view(view_name, output_area);
225 var view = this._create_view(view_name, cell);
226 226 new_views.push(view);
227 this._get_widget_area_element(output_area, true)
228 .append(view.$el);
229
227
228 if (cell.widget_subarea != undefined && cell.widget_subarea != null) {
229 cell.widget_area.show();
230 cell.widget_subarea.append(view.$el);
231 }
230 232 }
231 233
232 234 for (var view_index in new_views) {
233 235 var view = new_views[view_index];
234 236 view.update();
235 237 }
236 238 },
237 239
238 240
239 241 // Create a view
240 _create_view: function (view_name, output_area) {
241 var view = new this.widget_view_types[view_name]({model: this});
242 _create_view: function (view_name, cell) {
243 var view = new this.widget_manager.widget_view_types[view_name]({model: this});
242 244 view.render();
243 if (this.views[output_area]==undefined) {
244 this.views[output_area] = []
245 if (this.views[cell]==undefined) {
246 this.views[cell] = []
245 247 }
246 this.views[output_area].push(view);
247 view.output_area = output_area;
248 this.views[cell].push(view);
249 view.cell = cell;
248 250
249 251 // Handle when the view element is remove from the page.
250 252 var that = this;
251 253 view.$el.on("remove", function(){
252 var index = that.views[output_area].indexOf(view);
254 var index = that.views[cell].indexOf(view);
253 255 if (index > -1) {
254 that.views[output_area].splice(index, 1);
256 that.views[cell].splice(index, 1);
255 257 }
256 258 view.remove(); // Clean-up view
257 if (that.views[output_area].length()==0) {
258 delete that.views[output_area];
259 if (that.views[cell].length()==0) {
260 delete that.views[cell];
259 261 }
260 262
261 263 // Close the comm if there are no views left.
262 264 if (that.views.length()==0) {
263 265 that.comm.close();
264 266 }
265 267 });
266 268 return view;
267 269 },
268 270
269 271
270 272 // Build a callback dict.
271 _make_callbacks: function (output_area) {
273 _make_callbacks: function (cell) {
272 274 var callbacks = {};
273 if (output_area != null) {
275 if (cell != null && cell.output_area != undefined && cell.output_area != null) {
274 276 var that = this;
275 277 callbacks = {
276 278 iopub : {
277 output : $.proxy(output_area.handle_output, output_area),
278 clear_output : $.proxy(output_area.handle_clear_output, output_area),
279 output : $.proxy(cell.output_area.handle_output, cell.output_area),
280 clear_output : $.proxy(cell.output_area.handle_clear_output, cell.output_area),
279 281 status : function(msg){
280 that.handle_status(output_area, msg);
282 that.handle_status(cell, msg);
281 283 },
282 get_output_area : function() {
284 get_cell : function() {
283 285 if (that.last_modified_view != undefined &&
284 that.last_modified_view.output_area != undefined) {
285 return that.last_modified_view.output_area;
286 that.last_modified_view.cell != undefined) {
287 return that.last_modified_view.cell;
286 288 } else {
287 289 return null
288 290 }
289 291 },
290 292 },
291 293 };
292 294 }
293 295 return callbacks;
294 296 },
295 297
296 298
297 299 // Get the output area corresponding to the msg_id.
298 // output_area is an instance of Ipython.OutputArea
299 _get_output_area: function (msg_id) {
300 // cell is an instance of IPython.Cell
301 _get_msg_cell: function (msg_id) {
300 302
301 303 // First, check to see if the msg was triggered by cell execution.
302 var cell = IPython.notebook.get_msg_cell(msg_id);
304 var cell = this.widget_manager.get_msg_cell(msg_id);
303 305 if (cell != null) {
304 return cell.output_area;
306 return cell;
305 307 }
306 308
307 // Second, check to see if a get_output_area callback was defined
308 // for the message. get_output_area callbacks are registered for
309 // Second, check to see if a get_cell callback was defined
310 // for the message. get_cell callbacks are registered for
309 311 // widget messages, so this block is actually checking to see if the
310 312 // message was triggered by a widget.
311 313 var kernel = this.comm_manager.kernel;
312 314 var callbacks = kernel.get_callbacks_for_msg(msg_id);
313 315 if (callbacks != undefined &&
314 316 callbacks.iopub != undefined &&
315 callbacks.iopub.get_output_area != undefined) {
317 callbacks.iopub.get_cell != undefined) {
316 318
317 var output_area = callbacks.iopub.get_output_area();
318 if (output_area != null) {
319 return output_area;
320 }
319 return callbacks.iopub.get_cell();
321 320 }
322 321
323 // Not triggered by a cell or widget (no get_output_area callback
322 // Not triggered by a cell or widget (no get_cell callback
324 323 // exists).
325 324 return null;
326 325 },
327 326
328 // Gets widget output area (as a JQuery element) from the
329 // output_area (Ipython.OutputArea instance)
330 _get_widget_area_element: function (output_area, show) {
331 var widget_area = output_area.element
332 .parent() // output_wrapper
333 .parent() // cell
334 .find('.widget-area');
335 if (show) { widget_area.show(); }
336 return widget_area.find('.widget-subarea');
337 },
338
339 327 });
340 328
341 329
342 330 //--------------------------------------------------------------------
343 331 // WidgetView class
344 332 //--------------------------------------------------------------------
345 333 var WidgetView = Backbone.View.extend({
346 334
347 335 initialize: function() {
348 336 this.visible = true;
349 337 this.model.on('change',this.update,this);
350 338 this._add_class_calls = this.model.get('_add_class')[0];
351 339 this._remove_class_calls = this.model.get('_remove_class')[0];
352 340 },
353 341
354 342 update: function() {
355 343 if (this.model.get('visible') != undefined) {
356 344 if (this.visible != this.model.get('visible')) {
357 345 this.visible = this.model.get('visible');
358 346 if (this.visible) {
359 347 this.$el.show();
360 348 } else {
361 349 this.$el.hide();
362 350 }
363 351 }
364 352 }
365 353
366 354 if (this.model.css != undefined) {
367 355 for (var selector in this.model.css) {
368 356 if (this.model.css.hasOwnProperty(selector)) {
369 357
370 358 // Apply the css traits to all elements that match the selector.
371 359 var elements = this.get_selector_element(selector);
372 360 if (elements.length > 0) {
373 361 var css_traits = this.model.css[selector];
374 362 for (var css_key in css_traits) {
375 363 if (css_traits.hasOwnProperty(css_key)) {
376 364 elements.css(css_key, css_traits[css_key]);
377 365 }
378 366 }
379 367 }
380 368 }
381 369 }
382 370 }
383 371
384 372 var add_class = this.model.get('_add_class');
385 373 if (add_class != undefined){
386 374 var add_class_calls = add_class[0];
387 375 if (add_class_calls > this._add_class_calls) {
388 376 this._add_class_calls = add_class_calls;
389 377 var elements = this.get_selector_element(add_class[1]);
390 378 if (elements.length > 0) {
391 379 elements.addClass(add_class[2]);
392 380 }
393 381 }
394 382 }
395 383
396 384 var remove_class = this.model.get('_remove_class');
397 385 if (remove_class != undefined){
398 386 var remove_class_calls = remove_class[0];
399 387 if (remove_class_calls > this._remove_class_calls) {
400 388 this._remove_class_calls = remove_class_calls;
401 389 var elements = this.get_selector_element(remove_class[1]);
402 390 if (elements.length > 0) {
403 391 elements.removeClass(remove_class[2]);
404 392 }
405 393 }
406 394 }
407 395 },
408 396
409 397 get_selector_element: function(selector) {
410 398 // Get the elements via the css selector. If the selector is
411 399 // blank, apply the style to the $el_to_style element. If
412 400 // the $el_to_style element is not defined, use apply the
413 401 // style to the view's element.
414 402 var elements = this.$el.find(selector);
415 403 if (selector=='') {
416 404 if (this.$el_to_style == undefined) {
417 405 elements = this.$el;
418 406 } else {
419 407 elements = this.$el_to_style;
420 408 }
421 409 }
422 410 return elements;
423 411 },
424 412 });
425 413
426 414
427 415 //--------------------------------------------------------------------
428 416 // WidgetManager class
429 417 //--------------------------------------------------------------------
430 418 var WidgetManager = function(){
431 419 this.comm_manager = null;
432 420 this.widget_model_types = {};
433 421 this.widget_view_types = {};
434 422
435 423 var that = this;
436 424 Backbone.sync = function(method, model, options, error) {
437 425 var result = model.handle_sync(method, options);
438 426 if (options.success) {
439 427 options.success(result);
440 428 }
441 429 };
442 430 }
443 431
444 432
445 433 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
446 434 this.comm_manager = comm_manager;
447 435
448 436 // Register already register widget model types with the comm manager.
449 437 for (var widget_model_name in this.widget_model_types) {
450 438 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
451 439 }
452 440 }
453 441
454 442
455 443 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
456 444 // Register the widget with the comm manager. Make sure to pass this object's context
457 445 // in so `this` works in the call back.
458 446 if (this.comm_manager!=null) {
459 447 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
460 448 }
461 449 this.widget_model_types[widget_model_name] = widget_model_type;
462 450 }
463 451
464 452
465 453 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
466 454 this.widget_view_types[widget_view_name] = widget_view_type;
467 455 }
468 456
469 457
470 458 WidgetManager.prototype.handle_com_open = function (comm, msg) {
471 459 var widget_type_name = msg.content.target_name;
472 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
460 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this);
461 }
462
463
464 WidgetManager.prototype.get_msg_cell = function (msg_id) {
465 if (IPython.notebook != undefined && IPython.notebook != null) {
466 return IPython.notebook.get_msg_cell(msg_id);
467 }
473 468 }
474 469
475 470
476 471 //--------------------------------------------------------------------
477 472 // Init code
478 473 //--------------------------------------------------------------------
479 474 IPython.WidgetManager = WidgetManager;
480 475 IPython.WidgetModel = WidgetModel;
481 476 IPython.WidgetView = WidgetView;
482 477
483 478 if (IPython.widget_manager==undefined || IPython.widget_manager==null) {
484 479 IPython.widget_manager = new WidgetManager();
485 480 }
486 481
487 482 return IPython.widget_manager;
488 483 });
General Comments 0
You need to be logged in to leave comments. Login now