Show More
@@ -101,7 +101,7 b' define([' | |||||
101 |
|
101 | |||
102 | this.last_msg_id = null; |
|
102 | this.last_msg_id = null; | |
103 | this.completer = null; |
|
103 | this.completer = null; | |
104 |
|
104 | this.widget_views = []; | ||
105 |
|
105 | |||
106 | var config = utils.mergeopt(CodeCell, this.config); |
|
106 | var config = utils.mergeopt(CodeCell, this.config); | |
107 | Cell.apply(this,[{ |
|
107 | Cell.apply(this,[{ | |
@@ -191,12 +191,19 b' define([' | |||||
191 | .addClass('widget-subarea') |
|
191 | .addClass('widget-subarea') | |
192 | .appendTo(widget_area); |
|
192 | .appendTo(widget_area); | |
193 | this.widget_subarea = widget_subarea; |
|
193 | this.widget_subarea = widget_subarea; | |
|
194 | var that = this; | |||
194 | var widget_clear_buton = $('<button />') |
|
195 | var widget_clear_buton = $('<button />') | |
195 | .addClass('close') |
|
196 | .addClass('close') | |
196 | .html('×') |
|
197 | .html('×') | |
197 | .click(function() { |
|
198 | .click(function() { | |
198 |
widget_area.slideUp('', function(){ |
|
199 | widget_area.slideUp('', function(){ | |
199 | }) |
|
200 | for (var i = 0; i < that.widget_views.length; i++) { | |
|
201 | that.widget_views[i].remove(); | |||
|
202 | } | |||
|
203 | that.widget_views = []; | |||
|
204 | widget_subarea.html(''); | |||
|
205 | }); | |||
|
206 | }) | |||
200 | .appendTo(widget_prompt); |
|
207 | .appendTo(widget_prompt); | |
201 |
|
208 | |||
202 | var output = $('<div></div>'); |
|
209 | var output = $('<div></div>'); | |
@@ -210,6 +217,24 b' define([' | |||||
210 | this.completer = new completer.Completer(this, this.events); |
|
217 | this.completer = new completer.Completer(this, this.events); | |
211 | }; |
|
218 | }; | |
212 |
|
219 | |||
|
220 | /** | |||
|
221 | * Display a widget view in the cell. | |||
|
222 | */ | |||
|
223 | CodeCell.prototype.display_widget_view = function(view_promise) { | |||
|
224 | ||||
|
225 | // Display a dummy element | |||
|
226 | var dummy = $('<div/>'); | |||
|
227 | this.widget_subarea.append(dummy); | |||
|
228 | ||||
|
229 | // Display the view. | |||
|
230 | var that = this; | |||
|
231 | return view_promise.then(function(view) { | |||
|
232 | dummy.replaceWith(view.$el); | |||
|
233 | this.widget_views.push(view); | |||
|
234 | return view; | |||
|
235 | }); | |||
|
236 | }; | |||
|
237 | ||||
213 | /** @method bind_events */ |
|
238 | /** @method bind_events */ | |
214 | CodeCell.prototype.bind_events = function () { |
|
239 | CodeCell.prototype.bind_events = function () { | |
215 | Cell.prototype.bind_events.apply(this); |
|
240 | Cell.prototype.bind_events.apply(this); | |
@@ -322,6 +347,10 b' define([' | |||||
322 | this.active_output_area.clear_output(); |
|
347 | this.active_output_area.clear_output(); | |
323 |
|
348 | |||
324 | // Clear widget area |
|
349 | // Clear widget area | |
|
350 | for (var i = 0; i < this.widget_views.length; i++) { | |||
|
351 | this.widget_views[i].remove(); | |||
|
352 | } | |||
|
353 | this.widget_views = []; | |||
325 | this.widget_subarea.html(''); |
|
354 | this.widget_subarea.html(''); | |
326 | this.widget_subarea.height(''); |
|
355 | this.widget_subarea.height(''); | |
327 | this.widget_area.height(''); |
|
356 | this.widget_area.height(''); |
@@ -291,6 +291,13 b' define([' | |||||
291 | // Firefox 22 broke $(window).on("beforeunload") |
|
291 | // Firefox 22 broke $(window).on("beforeunload") | |
292 | // I'm not sure why or how. |
|
292 | // I'm not sure why or how. | |
293 | window.onbeforeunload = function (e) { |
|
293 | window.onbeforeunload = function (e) { | |
|
294 | // Raise an event that allows the user to execute custom code on unload | |||
|
295 | try { | |||
|
296 | that.events.trigger('beforeunload.Notebook', {notebook: that}); | |||
|
297 | } catch(e) { | |||
|
298 | console.err('Error in "beforeunload.Notebook" event handler.', e); | |||
|
299 | } | |||
|
300 | ||||
294 | // TODO: Make killing the kernel configurable. |
|
301 | // TODO: Make killing the kernel configurable. | |
295 | var kill_kernel = false; |
|
302 | var kill_kernel = false; | |
296 | if (kill_kernel) { |
|
303 | if (kill_kernel) { |
@@ -7,7 +7,8 b' define([' | |||||
7 | "jquery", |
|
7 | "jquery", | |
8 | "base/js/utils", |
|
8 | "base/js/utils", | |
9 | "base/js/namespace", |
|
9 | "base/js/namespace", | |
10 | ], function (_, Backbone, $, utils, IPython) { |
|
10 | "services/kernels/comm" | |
|
11 | ], function (_, Backbone, $, utils, IPython, comm) { | |||
11 | "use strict"; |
|
12 | "use strict"; | |
12 | //-------------------------------------------------------------------- |
|
13 | //-------------------------------------------------------------------- | |
13 | // WidgetManager class |
|
14 | // WidgetManager class | |
@@ -22,10 +23,11 b' define([' | |||||
22 | this.keyboard_manager = notebook.keyboard_manager; |
|
23 | this.keyboard_manager = notebook.keyboard_manager; | |
23 | this.notebook = notebook; |
|
24 | this.notebook = notebook; | |
24 | this.comm_manager = comm_manager; |
|
25 | this.comm_manager = comm_manager; | |
|
26 | this.comm_target_name = 'ipython.widget'; | |||
25 | this._models = {}; /* Dictionary of model ids and model instances */ |
|
27 | this._models = {}; /* Dictionary of model ids and model instances */ | |
26 |
|
28 | |||
27 | // Register with the comm manager. |
|
29 | // Register with the comm manager. | |
28 |
this.comm_manager.register_target( |
|
30 | this.comm_manager.register_target(this.comm_target_name, $.proxy(this._handle_comm_open, this)); | |
29 | }; |
|
31 | }; | |
30 |
|
32 | |||
31 | //-------------------------------------------------------------------- |
|
33 | //-------------------------------------------------------------------- | |
@@ -53,21 +55,34 b' define([' | |||||
53 | * Displays a view for a particular model. |
|
55 | * Displays a view for a particular model. | |
54 | */ |
|
56 | */ | |
55 | var that = this; |
|
57 | var that = this; | |
56 | var cell = this.get_msg_cell(msg.parent_header.msg_id); |
|
58 | return new Promise(function(resolve, reject) { | |
57 | if (cell === null) { |
|
59 | var cell = that.get_msg_cell(msg.parent_header.msg_id); | |
58 | return Promise.reject(new Error("Could not determine where the display" + |
|
60 | if (cell === null) { | |
59 | " message was from. Widget will not be displayed")); |
|
61 | reject(new Error("Could not determine where the display" + | |
60 | } else if (cell.widget_subarea) { |
|
62 | " message was from. Widget will not be displayed")); | |
61 | var dummy = $('<div />'); |
|
63 | } else { | |
62 | cell.widget_subarea.append(dummy); |
|
64 | return that.display_view_in_cell(cell, model); | |
63 | return this.create_view(model, {cell: cell}).then( |
|
65 | } | |
64 | function(view) { |
|
66 | }); | |
|
67 | }; | |||
|
68 | ||||
|
69 | WidgetManager.prototype.display_view_in_cell = function(cell, model) { | |||
|
70 | // Displays a view in a cell. | |||
|
71 | return new Promise(function(resolve, reject) { | |||
|
72 | if (cell.display_widget_view) { | |||
|
73 | cell.display_widget_view(that.create_view(model, {cell: cell})) | |||
|
74 | .then(function(view) { | |||
|
75 | ||||
65 | that._handle_display_view(view); |
|
76 | that._handle_display_view(view); | |
66 | dummy.replaceWith(view.$el); |
|
|||
67 | view.trigger('displayed'); |
|
77 | view.trigger('displayed'); | |
68 |
re |
|
78 | resolve(view); | |
69 | }).catch(utils.reject('Could not display view', true)); |
|
79 | }, function(error) { | |
70 | } |
|
80 | reject(new utils.WrappedError('Could not display view', error)); | |
|
81 | }); | |||
|
82 | } else { | |||
|
83 | reject(new Error('Cell does not have a `display_widget_view` method.')); | |||
|
84 | } | |||
|
85 | }); | |||
71 | }; |
|
86 | }; | |
72 |
|
87 | |||
73 | WidgetManager.prototype._handle_display_view = function (view) { |
|
88 | WidgetManager.prototype._handle_display_view = function (view) { | |
@@ -238,6 +253,8 b' define([' | |||||
238 | widget_model.once('comm:close', function () { |
|
253 | widget_model.once('comm:close', function () { | |
239 | delete that._models[model_id]; |
|
254 | delete that._models[model_id]; | |
240 | }); |
|
255 | }); | |
|
256 | widget_model.name = options.model_name; | |||
|
257 | widget_model.module = options.model_module; | |||
241 | return widget_model; |
|
258 | return widget_model; | |
242 |
|
259 | |||
243 | }, function(error) { |
|
260 | }, function(error) { | |
@@ -249,6 +266,100 b' define([' | |||||
249 | return model_promise; |
|
266 | return model_promise; | |
250 | }; |
|
267 | }; | |
251 |
|
268 | |||
|
269 | WidgetManager.prototype.get_state = function(options) { | |||
|
270 | // Get the state of the widget manager. | |||
|
271 | // | |||
|
272 | // This includes all of the widget models and the cells that they are | |||
|
273 | // displayed in. | |||
|
274 | // | |||
|
275 | // Parameters | |||
|
276 | // ---------- | |||
|
277 | // options: dictionary | |||
|
278 | // Dictionary of options with the following contents: | |||
|
279 | // only_displayed: (optional) boolean=false | |||
|
280 | // Only return models with one or more displayed views. | |||
|
281 | // not_alive: (optional) boolean=false | |||
|
282 | // Include models that have comms with severed connections. | |||
|
283 | return utils.resolve_promise_dict(function(models) { | |||
|
284 | var state = {}; | |||
|
285 | for (var model_id in models) { | |||
|
286 | if (models.hasOwnProperty(model_id)) { | |||
|
287 | var model = models[model_id]; | |||
|
288 | ||||
|
289 | // If the model has one or more views defined for it, | |||
|
290 | // consider it displayed. | |||
|
291 | var displayed_flag = !(options && options.only_displayed) || Object.keys(model.views).length > 0; | |||
|
292 | var alive_flag = (options && options.not_alive) || model.comm_alive; | |||
|
293 | if (displayed_flag && alive_flag) { | |||
|
294 | state[model.model_id] = { | |||
|
295 | model_name: model.name, | |||
|
296 | model_module: model.module, | |||
|
297 | views: [], | |||
|
298 | }; | |||
|
299 | ||||
|
300 | // Get the views that are displayed *now*. | |||
|
301 | for (var id in model.views) { | |||
|
302 | if (model.views.hasOwnProperty(id)) { | |||
|
303 | var view = model.views[id]; | |||
|
304 | var cell_index = this.notebook.find_cell_index(view.options.cell); | |||
|
305 | state[model.model_id].views.push(cell_index); | |||
|
306 | } | |||
|
307 | } | |||
|
308 | } | |||
|
309 | } | |||
|
310 | } | |||
|
311 | return state; | |||
|
312 | }); | |||
|
313 | }; | |||
|
314 | ||||
|
315 | WidgetManager.prototype.set_state = function(state) { | |||
|
316 | // Set the notebook's state. | |||
|
317 | // | |||
|
318 | // Reconstructs all of the widget models and attempts to redisplay the | |||
|
319 | // widgets in the appropriate cells by cell index. | |||
|
320 | ||||
|
321 | // Get the kernel when it's available. | |||
|
322 | var that = this; | |||
|
323 | return (new Promise(function(resolve, reject) { | |||
|
324 | if (that.kernel) { | |||
|
325 | resolve(that.kernel); | |||
|
326 | } else { | |||
|
327 | that.events.on('kernel_created.Session', function(event, data) { | |||
|
328 | resolve(data.kernel); | |||
|
329 | }); | |||
|
330 | } | |||
|
331 | })).then(function(kernel) { | |||
|
332 | ||||
|
333 | // Recreate all the widget models for the given state. | |||
|
334 | that.widget_models = []; | |||
|
335 | for (var i = 0; i < state.length; i++) { | |||
|
336 | // Recreate a comm using the widget's model id (model_id == comm_id). | |||
|
337 | var new_comm = new comm.Comm(kernel.widget_manager.comm_target_name, state[i].model_id); | |||
|
338 | kernel.comm_manager.register_comm(new_comm); | |||
|
339 | ||||
|
340 | // Create the model using the recreated comm. When the model is | |||
|
341 | // created we don't know yet if the comm is valid so set_comm_alive | |||
|
342 | // false. Once we receive the first state push from the back-end | |||
|
343 | // we know the comm is alive. | |||
|
344 | var model = kernel.widget_manager.create_model({ | |||
|
345 | comm: new_comm, | |||
|
346 | model_name: state[i].model_name, | |||
|
347 | model_module: state[i].model_module}).then(function(model) { | |||
|
348 | model.set_comm_alive(false); | |||
|
349 | model.request_state(); | |||
|
350 | model.received_state.then(function() { | |||
|
351 | model.set_comm_alive(true); | |||
|
352 | }); | |||
|
353 | return model; | |||
|
354 | }); | |||
|
355 | that.widget_models.push(model); | |||
|
356 | } | |||
|
357 | return Promise.all(that.widget_models); | |||
|
358 | ||||
|
359 | }); | |||
|
360 | ||||
|
361 | }; | |||
|
362 | ||||
252 | // Backwards compatibility. |
|
363 | // Backwards compatibility. | |
253 | IPython.WidgetManager = WidgetManager; |
|
364 | IPython.WidgetManager = WidgetManager; | |
254 |
|
365 |
@@ -32,6 +32,13 b' define(["widgets/js/manager",' | |||||
32 | this.id = model_id; |
|
32 | this.id = model_id; | |
33 | this.views = {}; |
|
33 | this.views = {}; | |
34 |
|
34 | |||
|
35 | // Promise that is resolved when a state is received | |||
|
36 | // from the back-end. | |||
|
37 | var that = this; | |||
|
38 | this.received_state = new Promise(function(resolve) { | |||
|
39 | that._resolve_received_state = resolve; | |||
|
40 | }); | |||
|
41 | ||||
35 | if (comm !== undefined) { |
|
42 | if (comm !== undefined) { | |
36 | // Remember comm associated with the model. |
|
43 | // Remember comm associated with the model. | |
37 | this.comm = comm; |
|
44 | this.comm = comm; | |
@@ -40,6 +47,11 b' define(["widgets/js/manager",' | |||||
40 | // Hook comm messages up to model. |
|
47 | // Hook comm messages up to model. | |
41 | comm.on_close($.proxy(this._handle_comm_closed, this)); |
|
48 | comm.on_close($.proxy(this._handle_comm_closed, this)); | |
42 | comm.on_msg($.proxy(this._handle_comm_msg, this)); |
|
49 | comm.on_msg($.proxy(this._handle_comm_msg, this)); | |
|
50 | ||||
|
51 | // Assume the comm is alive. | |||
|
52 | this.set_comm_alive(true); | |||
|
53 | } else { | |||
|
54 | this.set_comm_alive(false); | |||
43 | } |
|
55 | } | |
44 | return Backbone.Model.apply(this); |
|
56 | return Backbone.Model.apply(this); | |
45 | }, |
|
57 | }, | |
@@ -55,11 +67,34 b' define(["widgets/js/manager",' | |||||
55 | } |
|
67 | } | |
56 | }, |
|
68 | }, | |
57 |
|
69 | |||
58 | _handle_comm_closed: function (msg) { |
|
70 | request_state: function(callbacks) { | |
|
71 | /** | |||
|
72 | * Request a state push from the back-end. | |||
|
73 | */ | |||
|
74 | if (!this.comm) { | |||
|
75 | console.error("Could not request_state because comm doesn't exist!"); | |||
|
76 | return; | |||
|
77 | } | |||
|
78 | this.comm.send({method: 'request_state'}, callbacks || this.widget_manager.callbacks()); | |||
|
79 | }, | |||
|
80 | ||||
|
81 | set_comm_alive: function(alive) { | |||
|
82 | /** | |||
|
83 | * Change the comm_alive state of the model. | |||
|
84 | */ | |||
|
85 | if (this.comm_alive === undefined || this.comm_alive != alive) { | |||
|
86 | this.comm_alive = alive; | |||
|
87 | this.trigger(alive ? 'comm_is_live' : 'comm_is_dead', {model: this}); | |||
|
88 | } | |||
|
89 | }, | |||
|
90 | ||||
|
91 | close: function(comm_closed) { | |||
59 | /** |
|
92 | /** | |
60 | * Handle when a widget is closed. |
|
93 | * Close model | |
61 | */ |
|
94 | */ | |
62 |
this. |
|
95 | if (this.comm && !comm_closed) { | |
|
96 | this.comm.close(); | |||
|
97 | } | |||
63 | this.stopListening(); |
|
98 | this.stopListening(); | |
64 | this.trigger('destroy', this); |
|
99 | this.trigger('destroy', this); | |
65 | delete this.comm.model; // Delete ref so GC will collect widget model. |
|
100 | delete this.comm.model; // Delete ref so GC will collect widget model. | |
@@ -73,6 +108,14 b' define(["widgets/js/manager",' | |||||
73 | }); |
|
108 | }); | |
74 | }, |
|
109 | }, | |
75 |
|
110 | |||
|
111 | _handle_comm_closed: function (msg) { | |||
|
112 | /** | |||
|
113 | * Handle when a widget is closed. | |||
|
114 | */ | |||
|
115 | this.trigger('comm:close'); | |||
|
116 | this.close(true); | |||
|
117 | }, | |||
|
118 | ||||
76 | _handle_comm_msg: function (msg) { |
|
119 | _handle_comm_msg: function (msg) { | |
77 | /** |
|
120 | /** | |
78 | * Handle incoming comm msg. |
|
121 | * Handle incoming comm msg. | |
@@ -104,7 +147,20 b' define(["widgets/js/manager",' | |||||
104 | } finally { |
|
147 | } finally { | |
105 | that.state_lock = null; |
|
148 | that.state_lock = null; | |
106 | } |
|
149 | } | |
107 | }).catch(utils.reject("Couldn't set model state", true)); |
|
150 | that._resolve_received_state(); | |
|
151 | return Promise.resolve(); | |||
|
152 | }, utils.reject("Couldn't set model state", true)); | |||
|
153 | }, | |||
|
154 | ||||
|
155 | get_state: function() { | |||
|
156 | // Get the serializable state of the model. | |||
|
157 | state = this.toJSON(); | |||
|
158 | for (var key in state) { | |||
|
159 | if (state.hasOwnProperty(key)) { | |||
|
160 | state[key] = this._pack_models(state[key]); | |||
|
161 | } | |||
|
162 | } | |||
|
163 | return state; | |||
108 | }, |
|
164 | }, | |
109 |
|
165 | |||
110 | _handle_status: function (msg, callbacks) { |
|
166 | _handle_status: function (msg, callbacks) { | |
@@ -322,6 +378,9 b' define(["widgets/js/manager",' | |||||
322 | this.on('displayed', function() { |
|
378 | this.on('displayed', function() { | |
323 | this.is_displayed = true; |
|
379 | this.is_displayed = true; | |
324 | }, this); |
|
380 | }, this); | |
|
381 | this.on('remove', function() { | |||
|
382 | delete this.model.views[this.id]; | |||
|
383 | }, this); | |||
325 | }, |
|
384 | }, | |
326 |
|
385 | |||
327 | update: function(){ |
|
386 | update: function(){ | |
@@ -387,6 +446,12 b' define(["widgets/js/manager",' | |||||
387 | } else { |
|
446 | } else { | |
388 | this.on('displayed', callback, context); |
|
447 | this.on('displayed', callback, context); | |
389 | } |
|
448 | } | |
|
449 | }, | |||
|
450 | ||||
|
451 | remove: function () { | |||
|
452 | // Raise a remove event when the view is removed. | |||
|
453 | WidgetView.__super__.remove.apply(this, arguments); | |||
|
454 | this.trigger('remove'); | |||
390 | } |
|
455 | } | |
391 | }); |
|
456 | }); | |
392 |
|
457 |
@@ -341,19 +341,26 b' class Widget(LoggingConfigurable):' | |||||
341 | """Called when a msg is received from the front-end""" |
|
341 | """Called when a msg is received from the front-end""" | |
342 | data = msg['content']['data'] |
|
342 | data = msg['content']['data'] | |
343 | method = data['method'] |
|
343 | method = data['method'] | |
344 | if not method in ['backbone', 'custom']: |
|
|||
345 | self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method) |
|
|||
346 |
|
344 | |||
347 | # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one. |
|
345 | # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one. | |
348 |
if method == 'backbone' |
|
346 | if method == 'backbone': | |
349 |
|
|
347 | if 'sync_data' in data: | |
350 | self.set_state(sync_data) # handles all methods |
|
348 | sync_data = data['sync_data'] | |
|
349 | self.set_state(sync_data) # handles all methods | |||
|
350 | ||||
|
351 | # Handle a state request. | |||
|
352 | elif method == 'request_state': | |||
|
353 | self.send_state() | |||
351 |
|
354 | |||
352 | # Handle a custom msg from the front-end |
|
355 | # Handle a custom msg from the front-end. | |
353 | elif method == 'custom': |
|
356 | elif method == 'custom': | |
354 | if 'content' in data: |
|
357 | if 'content' in data: | |
355 | self._handle_custom_msg(data['content']) |
|
358 | self._handle_custom_msg(data['content']) | |
356 |
|
359 | |||
|
360 | # Catch remainder. | |||
|
361 | else: | |||
|
362 | self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method) | |||
|
363 | ||||
357 | def _handle_custom_msg(self, content): |
|
364 | def _handle_custom_msg(self, content): | |
358 | """Called when a custom msg is received.""" |
|
365 | """Called when a custom msg is received.""" | |
359 | self._msg_callbacks(self, content) |
|
366 | self._msg_callbacks(self, content) | |
@@ -368,7 +375,7 b' class Widget(LoggingConfigurable):' | |||||
368 | # Send the state after the user registered callbacks for trait changes |
|
375 | # Send the state after the user registered callbacks for trait changes | |
369 | # have all fired (allows for user to validate values). |
|
376 | # have all fired (allows for user to validate values). | |
370 | if self.comm is not None and name in self.keys: |
|
377 | if self.comm is not None and name in self.keys: | |
371 | # Make sure this isn't information that the front-end just sent us. |
|
378 | # Make sure this isn't information that the front-end just sent us. | |
372 | if self._should_send_property(name, new_value): |
|
379 | if self._should_send_property(name, new_value): | |
373 | # Send new state to front-end |
|
380 | # Send new state to front-end | |
374 | self.send_state(key=name) |
|
381 | self.send_state(key=name) |
General Comments 0
You need to be logged in to leave comments.
Login now