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