##// END OF EJS Templates
Persistence API,...
Jonathan Frederic -
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('&times;')
197 .html('&times;')
197 .click(function() {
198 .click(function() {
198 widget_area.slideUp('', function(){ widget_subarea.html(''); });
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('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 * 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 return view;
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.trigger('comm:close');
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' and 'sync_data' in data:
346 if method == 'backbone':
349 sync_data = data['sync_data']
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