##// END OF EJS Templates
Updated comments to new style.
Jonathan Frederic -
Show More
@@ -1,446 +1,458 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/utils",
9 9 "base/js/namespace",
10 10 "services/kernels/comm"
11 11 ], function (_, Backbone, $, utils, IPython, comm) {
12 12 "use strict";
13 13 //--------------------------------------------------------------------
14 14 // WidgetManager class
15 15 //--------------------------------------------------------------------
16 16 var WidgetManager = function (comm_manager, notebook) {
17 17 /**
18 18 * Public constructor
19 19 */
20 20 WidgetManager._managers.push(this);
21 21
22 22 // Attach a comm manager to the
23 23 this.keyboard_manager = notebook.keyboard_manager;
24 24 this.notebook = notebook;
25 25 this.comm_manager = comm_manager;
26 26 this.comm_target_name = 'ipython.widget';
27 27 this._models = {}; /* Dictionary of model ids and model instance promises */
28 28
29 29 // Register with the comm manager.
30 30 this.comm_manager.register_target(this.comm_target_name, $.proxy(this._handle_comm_open, this));
31 31
32 32 // Load the initial state of the widget manager if a load callback was
33 33 // registered.
34 34 var that = this;
35 35 if (WidgetManager._load_callback) {
36 36 Promise.resolve(WidgetManager._load_callback.call(this)).then(function(state) {
37 37 that.set_state(state);
38 38 }).catch(utils.reject('Error loading widget manager state', true));
39 39 }
40 40
41 41 // Setup state saving code.
42 42 this.notebook.events.on('before_save.Notebook', function() {
43 43 var save_callback = WidgetManager._save_callback;
44 44 var options = WidgetManager._get_state_options;
45 45 if (save_callback) {
46 46 that.get_state(options).then(function(state) {
47 47 save_callback.call(that, state);
48 48 }).catch(utils.reject('Could not call widget save state callback.', true));
49 49 }
50 50 });
51 51 };
52 52
53 53 //--------------------------------------------------------------------
54 54 // Class level
55 55 //--------------------------------------------------------------------
56 56 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
57 57 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
58 58 WidgetManager._managers = []; /* List of widget managers */
59 59 WidgetManager._load_callback = null;
60 60 WidgetManager._save_callback = null;
61 61
62 62 WidgetManager.register_widget_model = function (model_name, model_type) {
63 // Registers a widget model by name.
63 /**
64 * Registers a widget model by name.
65 */
64 66 WidgetManager._model_types[model_name] = model_type;
65 67 };
66 68
67 69 WidgetManager.register_widget_view = function (view_name, view_type) {
68 // Registers a widget view by name.
70 /**
71 * Registers a widget view by name.
72 */
69 73 WidgetManager._view_types[view_name] = view_type;
70 74 };
71 75
72 76 WidgetManager.set_state_callbacks = function (load_callback, save_callback, options) {
73 // Registers callbacks for widget state persistence.
74 //
75 // Parameters
76 // ----------
77 // load_callback: function()
78 // function that is called when the widget manager state should be
79 // loaded. This function should return a promise for the widget
80 // manager state. An empty state is an empty dictionary `{}`.
81 // save_callback: function(state as dictionary)
82 // function that is called when the notebook is saved or autosaved.
83 // The current state of the widget manager is passed in as the first
84 // argument.
77 /**
78 * Registers callbacks for widget state persistence.
79 *
80 * Parameters
81 * ----------
82 * load_callback: function()
83 * function that is called when the widget manager state should be
84 * loaded. This function should return a promise for the widget
85 * manager state. An empty state is an empty dictionary `{}`.
86 * save_callback: function(state as dictionary)
87 * function that is called when the notebook is saved or autosaved.
88 * The current state of the widget manager is passed in as the first
89 * argument.
90 */
85 91 WidgetManager._load_callback = load_callback;
86 92 WidgetManager._save_callback = save_callback;
87 93 WidgetManager._get_state_options = options;
88 94
89 95 // Use the load callback to immediately load widget states.
90 96 WidgetManager._managers.forEach(function(manager) {
91 97 if (load_callback) {
92 98 Promise.resolve(load_callback.call(manager)).then(function(state) {
93 99 manager.set_state(state);
94 100 }).catch(utils.reject('Error loading widget manager state', true));
95 101 }
96 102 });
97 103 };
98 104
99 105 //--------------------------------------------------------------------
100 106 // Instance level
101 107 //--------------------------------------------------------------------
102 108 WidgetManager.prototype.display_view = function(msg, model) {
103 109 /**
104 110 * Displays a view for a particular model.
105 111 */
106 112 var cell = this.get_msg_cell(msg.parent_header.msg_id);
107 113 if (cell === null) {
108 114 return Promise.reject(new Error("Could not determine where the display" +
109 115 " message was from. Widget will not be displayed"));
110 116 } else {
111 117 return this.display_view_in_cell(cell, model)
112 118 .catch(utils.reject('Could not display view', true));
113 119 }
114 120 };
115 121
116 122 WidgetManager.prototype.display_view_in_cell = function(cell, model) {
117 123 // Displays a view in a cell.
118 124 if (cell.display_widget_view) {
119 125 var that = this;
120 126 return cell.display_widget_view(this.create_view(model, {
121 127 cell: cell,
122 128 // Only set cell_index when view is displayed as directly.
123 129 cell_index: that.notebook.find_cell_index(cell),
124 130 })).then(function(view) {
125 131 that._handle_display_view(view);
126 132 view.trigger('displayed');
127 133 resolve(view);
128 134 }).catch(utils.reject('Could not create or display view', true));
129 135 } else {
130 136 return Promise.reject(new Error('Cell does not have a `display_widget_view` method'));
131 137 }
132 138 };
133 139
134 140 WidgetManager.prototype._handle_display_view = function (view) {
135 141 /**
136 142 * Have the IPython keyboard manager disable its event
137 143 * handling so the widget can capture keyboard input.
138 144 * Note, this is only done on the outer most widgets.
139 145 */
140 146 if (this.keyboard_manager) {
141 147 this.keyboard_manager.register_events(view.$el);
142 148
143 149 if (view.additional_elements) {
144 150 for (var i = 0; i < view.additional_elements.length; i++) {
145 151 this.keyboard_manager.register_events(view.additional_elements[i]);
146 152 }
147 153 }
148 154 }
149 155 };
150 156
151 157 WidgetManager.prototype.create_view = function(model, options) {
152 158 /**
153 159 * Creates a promise for a view of a given model
154 160 *
155 161 * Make sure the view creation is not out of order with
156 162 * any state updates.
157 163 */
158 164 model.state_change = model.state_change.then(function() {
159 165
160 166 return utils.load_class(model.get('_view_name'), model.get('_view_module'),
161 167 WidgetManager._view_types).then(function(ViewType) {
162 168
163 169 // If a view is passed into the method, use that view's cell as
164 170 // the cell for the view that is created.
165 171 options = options || {};
166 172 if (options.parent !== undefined) {
167 173 options.cell = options.parent.options.cell;
168 174 }
169 175 // Create and render the view...
170 176 var parameters = {model: model, options: options};
171 177 var view = new ViewType(parameters);
172 178 view.listenTo(model, 'destroy', view.remove);
173 179 return Promise.resolve(view.render()).then(function() {return view;});
174 180 }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true));
175 181 });
176 182 model.views[utils.uuid()] = model.state_change;
177 183 return model.state_change;
178 184 };
179 185
180 186 WidgetManager.prototype.get_msg_cell = function (msg_id) {
181 187 var cell = null;
182 188 // First, check to see if the msg was triggered by cell execution.
183 189 if (this.notebook) {
184 190 cell = this.notebook.get_msg_cell(msg_id);
185 191 }
186 192 if (cell !== null) {
187 193 return cell;
188 194 }
189 195 // Second, check to see if a get_cell callback was defined
190 196 // for the message. get_cell callbacks are registered for
191 197 // widget messages, so this block is actually checking to see if the
192 198 // message was triggered by a widget.
193 199 var kernel = this.comm_manager.kernel;
194 200 if (kernel) {
195 201 var callbacks = kernel.get_callbacks_for_msg(msg_id);
196 202 if (callbacks && callbacks.iopub &&
197 203 callbacks.iopub.get_cell !== undefined) {
198 204 return callbacks.iopub.get_cell();
199 205 }
200 206 }
201 207
202 208 // Not triggered by a cell or widget (no get_cell callback
203 209 // exists).
204 210 return null;
205 211 };
206 212
207 213 WidgetManager.prototype.callbacks = function (view) {
208 214 /**
209 215 * callback handlers specific a view
210 216 */
211 217 var callbacks = {};
212 218 if (view && view.options.cell) {
213 219
214 220 // Try to get output handlers
215 221 var cell = view.options.cell;
216 222 var handle_output = null;
217 223 var handle_clear_output = null;
218 224 if (cell.output_area) {
219 225 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
220 226 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
221 227 }
222 228
223 229 // Create callback dictionary using what is known
224 230 var that = this;
225 231 callbacks = {
226 232 iopub : {
227 233 output : handle_output,
228 234 clear_output : handle_clear_output,
229 235
230 236 // Special function only registered by widget messages.
231 237 // Allows us to get the cell for a message so we know
232 238 // where to add widgets if the code requires it.
233 239 get_cell : function () {
234 240 return cell;
235 241 },
236 242 },
237 243 };
238 244 }
239 245 return callbacks;
240 246 };
241 247
242 248 WidgetManager.prototype.get_model = function (model_id) {
243 249 /**
244 250 * Get a promise for a model by model id.
245 251 */
246 252 return this._models[model_id];
247 253 };
248 254
249 255 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
250 256 /**
251 257 * Handle when a comm is opened.
252 258 */
253 259 return this.create_model({
254 260 model_name: msg.content.data.model_name,
255 261 model_module: msg.content.data.model_module,
256 262 comm: comm}).catch(utils.reject("Couldn't create a model.", true));
257 263 };
258 264
259 265 WidgetManager.prototype.create_model = function (options) {
260 266 /**
261 267 * Create and return a promise for a new widget model
262 268 *
263 269 * Minimally, one must provide the model_name and widget_class
264 270 * parameters to create a model from Javascript.
265 271 *
266 272 * Example
267 273 * --------
268 274 * JS:
269 275 * IPython.notebook.kernel.widget_manager.create_model({
270 276 * model_name: 'WidgetModel',
271 277 * widget_class: 'IPython.html.widgets.widget_int.IntSlider'})
272 278 * .then(function(model) { console.log('Create success!', model); },
273 279 * $.proxy(console.error, console));
274 280 *
275 281 * Parameters
276 282 * ----------
277 283 * options: dictionary
278 284 * Dictionary of options with the following contents:
279 285 * model_name: string
280 286 * Target name of the widget model to create.
281 287 * model_module: (optional) string
282 288 * Module name of the widget model to create.
283 289 * widget_class: (optional) string
284 290 * Target name of the widget in the back-end.
285 291 * comm: (optional) Comm
286 292 *
287 293 * Create a comm if it wasn't provided.
288 294 */
289 295 var comm = options.comm;
290 296 if (!comm) {
291 297 comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
292 298 }
293 299
294 300 var that = this;
295 301 var model_id = comm.comm_id;
296 302 var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types)
297 303 .then(function(ModelType) {
298 304 var widget_model = new ModelType(that, model_id, comm);
299 305 widget_model.once('comm:close', function () {
300 306 delete that._models[model_id];
301 307 });
302 308 widget_model.name = options.model_name;
303 309 widget_model.module = options.model_module;
304 310 return widget_model;
305 311
306 312 }, function(error) {
307 313 delete that._models[model_id];
308 314 var wrapped_error = new utils.WrappedError("Couldn't create model", error);
309 315 return Promise.reject(wrapped_error);
310 316 });
311 317 this._models[model_id] = model_promise;
312 318 return model_promise;
313 319 };
314 320
315 321 WidgetManager.prototype.get_state = function(options) {
316 // Asynchronously get the state of the widget manager.
317 //
318 // This includes all of the widget models and the cells that they are
319 // displayed in.
320 //
321 // Parameters
322 // ----------
323 // options: dictionary
324 // Dictionary of options with the following contents:
325 // only_displayed: (optional) boolean=false
326 // Only return models with one or more displayed views.
327 // not_live: (optional) boolean=false
328 // Include models that have comms with severed connections.
329 //
330 // Returns
331 // -------
332 // Promise for a state dictionary
322 /**
323 * Asynchronously get the state of the widget manager.
324 *
325 * This includes all of the widget models and the cells that they are
326 * displayed in.
327 *
328 * Parameters
329 * ----------
330 * options: dictionary
331 * Dictionary of options with the following contents:
332 * only_displayed: (optional) boolean=false
333 * Only return models with one or more displayed views.
334 * not_live: (optional) boolean=false
335 * Include models that have comms with severed connections.
336 *
337 * Returns
338 * -------
339 * Promise for a state dictionary
340 */
333 341 var that = this;
334 342 return utils.resolve_promises_dict(this._models).then(function(models) {
335 343 var state = {};
336 344 for (var model_id in models) {
337 345 if (models.hasOwnProperty(model_id)) {
338 346 var model = models[model_id];
339 347
340 348 // If the model has one or more views defined for it,
341 349 // consider it displayed.
342 350 var displayed_flag = !(options && options.only_displayed) || Object.keys(model.views).length > 0;
343 351 var live_flag = (options && options.not_live) || model.comm_live;
344 352 if (displayed_flag && live_flag) {
345 353 state[model_id] = {
346 354 model_name: model.name,
347 355 model_module: model.module,
348 356 state: model.get_state(),
349 357 views: [],
350 358 };
351 359
352 360 // Get the views that are displayed *now*.
353 361 for (var id in model.views) {
354 362 if (model.views.hasOwnProperty(id)) {
355 363 var view = model.views[id];
356 364 if (view.options.cell_index) {
357 365 state[model_id].views.push(view.options.cell_index);
358 366 }
359 367 }
360 368 }
361 369 }
362 370 }
363 371 }
364 372 return state;
365 373 }).catch(utils.reject('Could not get state of widget manager', true));
366 374 };
367 375
368 376 WidgetManager.prototype.set_state = function(state) {
369 // Set the notebook's state.
370 //
371 // Reconstructs all of the widget models and attempts to redisplay the
372 // widgets in the appropriate cells by cell index.
377 /**
378 * Set the notebook's state.
379 *
380 * Reconstructs all of the widget models and attempts to redisplay the
381 * widgets in the appropriate cells by cell index.
382 */
373 383
374 384 // Get the kernel when it's available.
375 385 var that = this;
376 386 return this._get_connected_kernel().then(function(kernel) {
377 387
378 388 // Recreate all the widget models for the given state and
379 389 // display the views.
380 390 that.all_views = [];
381 391 var model_ids = Object.keys(state);
382 392 for (var i = 0; i < model_ids.length; i++) {
383 393 var model_id = model_ids[i];
384 394
385 395 // Recreate a comm using the widget's model id (model_id == comm_id).
386 396 var new_comm = new comm.Comm(kernel.widget_manager.comm_target_name, model_id);
387 397 kernel.comm_manager.register_comm(new_comm);
388 398
389 399 // Create the model using the recreated comm. When the model is
390 400 // created we don't know yet if the comm is valid so set_comm_live
391 401 // false. Once we receive the first state push from the back-end
392 402 // we know the comm is alive.
393 403 var views = kernel.widget_manager.create_model({
394 404 comm: new_comm,
395 405 model_name: state[model_id].model_name,
396 406 model_module: state[model_id].model_module})
397 407 .then(function(model) {
398 408
399 409 model.set_comm_live(false);
400 410 var view_promise = Promise.resolve().then(function() {
401 411 return model.set_state(state[model.id].state);
402 412 }).then(function() {
403 413 model.request_state().then(function() {
404 414 model.set_comm_live(true);
405 415 });
406 416
407 417 // Display the views of the model.
408 418 var views = [];
409 419 var model_views = state[model.id].views;
410 420 for (var j=0; j<model_views.length; j++) {
411 421 var cell_index = model_views[j];
412 422 var cell = that.notebook.get_cell(cell_index);
413 423 views.push(that.display_view_in_cell(cell, model));
414 424 }
415 425 return Promise.all(views);
416 426 });
417 427 return view_promise;
418 428 });
419 429 that.all_views.push(views);
420 430 }
421 431 return Promise.all(that.all_views);
422 432 }).catch(utils.reject('Could not set widget manager state.', true));
423 433 };
424 434
425 435 WidgetManager.prototype._get_connected_kernel = function() {
426 // Gets a promise for a connected kernel.
436 /**
437 * Gets a promise for a connected kernel
438 */
427 439 var that = this;
428 440 return new Promise(function(resolve, reject) {
429 441 if (that.comm_manager &&
430 442 that.comm_manager.kernel &&
431 443 that.comm_manager.kernel.is_connected()) {
432 444
433 445 resolve(that.comm_manager.kernel);
434 446 } else {
435 447 that.notebook.events.on('kernel_connected.Kernel', function(event, data) {
436 448 resolve(data.kernel);
437 449 });
438 450 }
439 451 });
440 452 };
441 453
442 454 // Backwards compatibility.
443 455 IPython.WidgetManager = WidgetManager;
444 456
445 457 return {'WidgetManager': WidgetManager};
446 458 });
General Comments 0
You need to be logged in to leave comments. Login now