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