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