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