##// END OF EJS Templates
Persistence API,...
Jonathan Frederic -
Show More
@@ -101,7 +101,7 b' define(['
101 101
102 102 this.last_msg_id = null;
103 103 this.completer = null;
104
104 this.widget_views = [];
105 105
106 106 var config = utils.mergeopt(CodeCell, this.config);
107 107 Cell.apply(this,[{
@@ -191,11 +191,18 b' define(['
191 191 .addClass('widget-subarea')
192 192 .appendTo(widget_area);
193 193 this.widget_subarea = widget_subarea;
194 var that = this;
194 195 var widget_clear_buton = $('<button />')
195 196 .addClass('close')
196 197 .html('&times;')
197 198 .click(function() {
198 widget_area.slideUp('', function(){ widget_subarea.html(''); });
199 widget_area.slideUp('', function(){
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 });
199 206 })
200 207 .appendTo(widget_prompt);
201 208
@@ -210,6 +217,24 b' define(['
210 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 238 /** @method bind_events */
214 239 CodeCell.prototype.bind_events = function () {
215 240 Cell.prototype.bind_events.apply(this);
@@ -322,6 +347,10 b' define(['
322 347 this.active_output_area.clear_output();
323 348
324 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 354 this.widget_subarea.html('');
326 355 this.widget_subarea.height('');
327 356 this.widget_area.height('');
@@ -291,6 +291,13 b' define(['
291 291 // Firefox 22 broke $(window).on("beforeunload")
292 292 // I'm not sure why or how.
293 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 301 // TODO: Make killing the kernel configurable.
295 302 var kill_kernel = false;
296 303 if (kill_kernel) {
@@ -7,7 +7,8 b' define(['
7 7 "jquery",
8 8 "base/js/utils",
9 9 "base/js/namespace",
10 ], function (_, Backbone, $, utils, IPython) {
10 "services/kernels/comm"
11 ], function (_, Backbone, $, utils, IPython, comm) {
11 12 "use strict";
12 13 //--------------------------------------------------------------------
13 14 // WidgetManager class
@@ -22,10 +23,11 b' define(['
22 23 this.keyboard_manager = notebook.keyboard_manager;
23 24 this.notebook = notebook;
24 25 this.comm_manager = comm_manager;
26 this.comm_target_name = 'ipython.widget';
25 27 this._models = {}; /* Dictionary of model ids and model instances */
26 28
27 29 // Register with the comm manager.
28 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
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 55 * Displays a view for a particular model.
54 56 */
55 57 var that = this;
56 var cell = this.get_msg_cell(msg.parent_header.msg_id);
58 return new Promise(function(resolve, reject) {
59 var cell = that.get_msg_cell(msg.parent_header.msg_id);
57 60 if (cell === null) {
58 return Promise.reject(new Error("Could not determine where the display" +
61 reject(new Error("Could not determine where the display" +
59 62 " message was from. Widget will not be displayed"));
60 } else if (cell.widget_subarea) {
61 var dummy = $('<div />');
62 cell.widget_subarea.append(dummy);
63 return this.create_view(model, {cell: cell}).then(
64 function(view) {
63 } else {
64 return that.display_view_in_cell(cell, model);
65 }
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 76 that._handle_display_view(view);
66 dummy.replaceWith(view.$el);
67 77 view.trigger('displayed');
68 return view;
69 }).catch(utils.reject('Could not display view', true));
78 resolve(view);
79 }, function(error) {
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.'));
70 84 }
85 });
71 86 };
72 87
73 88 WidgetManager.prototype._handle_display_view = function (view) {
@@ -238,6 +253,8 b' define(['
238 253 widget_model.once('comm:close', function () {
239 254 delete that._models[model_id];
240 255 });
256 widget_model.name = options.model_name;
257 widget_model.module = options.model_module;
241 258 return widget_model;
242 259
243 260 }, function(error) {
@@ -249,6 +266,100 b' define(['
249 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 363 // Backwards compatibility.
253 364 IPython.WidgetManager = WidgetManager;
254 365
@@ -32,6 +32,13 b' define(["widgets/js/manager",'
32 32 this.id = model_id;
33 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 42 if (comm !== undefined) {
36 43 // Remember comm associated with the model.
37 44 this.comm = comm;
@@ -40,6 +47,11 b' define(["widgets/js/manager",'
40 47 // Hook comm messages up to model.
41 48 comm.on_close($.proxy(this._handle_comm_closed, this));
42 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 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) {
59 71 /**
60 * Handle when a widget is closed.
72 * Request a state push from the back-end.
61 73 */
62 this.trigger('comm:close');
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) {
92 /**
93 * Close model
94 */
95 if (this.comm && !comm_closed) {
96 this.comm.close();
97 }
63 98 this.stopListening();
64 99 this.trigger('destroy', this);
65 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 119 _handle_comm_msg: function (msg) {
77 120 /**
78 121 * Handle incoming comm msg.
@@ -104,7 +147,20 b' define(["widgets/js/manager",'
104 147 } finally {
105 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 166 _handle_status: function (msg, callbacks) {
@@ -322,6 +378,9 b' define(["widgets/js/manager",'
322 378 this.on('displayed', function() {
323 379 this.is_displayed = true;
324 380 }, this);
381 this.on('remove', function() {
382 delete this.model.views[this.id];
383 }, this);
325 384 },
326 385
327 386 update: function(){
@@ -387,6 +446,12 b' define(["widgets/js/manager",'
387 446 } else {
388 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 341 """Called when a msg is received from the front-end"""
342 342 data = msg['content']['data']
343 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 345 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
348 if method == 'backbone' and 'sync_data' in data:
346 if method == 'backbone':
347 if 'sync_data' in data:
349 348 sync_data = data['sync_data']
350 349 self.set_state(sync_data) # handles all methods
351 350
352 # Handle a custom msg from the front-end
351 # Handle a state request.
352 elif method == 'request_state':
353 self.send_state()
354
355 # Handle a custom msg from the front-end.
353 356 elif method == 'custom':
354 357 if 'content' in data:
355 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 364 def _handle_custom_msg(self, content):
358 365 """Called when a custom msg is received."""
359 366 self._msg_callbacks(self, content)
General Comments 0
You need to be logged in to leave comments. Login now