##// END OF EJS Templates
Added msg_id - cell mapping.
Jonathan Frederic -
Show More
@@ -1,363 +1,372 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.js",
23 define(["../../components/underscore/underscore-min.js",
24 "../../components/backbone/backbone-min.js",
24 "../../components/backbone/backbone-min.js",
25 ], function(){
25 ], function(){
26
26
27 // Only run once on a notebook.
27 // Only run once on a notebook.
28 if (IPython.notebook.widget_manager == undefined) {
28 if (IPython.notebook.widget_manager == undefined) {
29
29
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 // WidgetModel class
31 // WidgetModel class
32 //--------------------------------------------------------------------
32 //--------------------------------------------------------------------
33 var WidgetModel = Backbone.Model.extend({
33 var WidgetModel = Backbone.Model.extend({
34 constructor: function(comm_manager, comm, widget_view_types) {
34 constructor: function(comm_manager, comm, widget_view_types) {
35 this.comm_manager = comm_manager;
35 this.comm_manager = comm_manager;
36 this.widget_view_types = widget_view_types;
36 this.widget_view_types = widget_view_types;
37 this.pending_msgs = 0;
37 this.pending_msgs = 0;
38 this.msg_throttle = 3;
38 this.msg_throttle = 3;
39 this.msg_buffer = {};
39 this.msg_buffer = {};
40 this.views = {};
40 this.views = {};
41
41
42 // Remember comm associated with the model.
42 // Remember comm associated with the model.
43 this.comm = comm;
43 this.comm = comm;
44 comm.model = this;
44 comm.model = this;
45
45
46 // Hook comm messages up to model.
46 // Hook comm messages up to model.
47 comm.on_close($.proxy(this.handle_comm_closed, this));
47 comm.on_close($.proxy(this.handle_comm_closed, this));
48 comm.on_msg($.proxy(this.handle_comm_msg, this));
48 comm.on_msg($.proxy(this.handle_comm_msg, this));
49
49
50 return Backbone.Model.apply(this);
50 return Backbone.Model.apply(this);
51 },
51 },
52
52
53
53
54 update_other_views: function(caller) {
54 update_other_views: function(caller) {
55 this.last_modified_view = caller;
55 this.last_modified_view = caller;
56 this.save(this.changedAttributes(), {patch: true});
56 this.save(this.changedAttributes(), {patch: true});
57
57
58 for (var cell_index in this.views) {
58 for (var cell_index in this.views) {
59 var views = this.views[cell_index];
59 var views = this.views[cell_index];
60 for (var view_index in views) {
60 for (var view_index in views) {
61 var view = views[view_index];
61 var view = views[view_index];
62 if (view !== caller) {
62 if (view !== caller) {
63 view.update();
63 view.update();
64 }
64 }
65 }
65 }
66 }
66 }
67 },
67 },
68
68
69
69
70 handle_status: function (output_area, msg) {
70 handle_status: function (output_area, msg) {
71 //execution_state : ('busy', 'idle', 'starting')
71 //execution_state : ('busy', 'idle', 'starting')
72 if (msg.content.execution_state=='idle') {
72 if (msg.content.execution_state=='idle') {
73
73
74 // Send buffer if this message caused another message to be
74 // Send buffer if this message caused another message to be
75 // throttled.
75 // throttled.
76 if (this.msg_throttle == this.pending_msgs &&
76 if (this.msg_throttle == this.pending_msgs &&
77 this.msg_buffer.length > 0) {
77 this.msg_buffer.length > 0) {
78
78
79 var output_area = this._get_msg_output_area(msg);
79 var output_area = this._get_msg_output_area(msg);
80 var callbacks = this._make_callbacks(output_area);
80 var callbacks = this._make_callbacks(output_area);
81 var data = {sync_method: 'patch', sync_data: this.msg_buffer};
81 var data = {sync_method: 'patch', sync_data: this.msg_buffer};
82 comm.send(data, callbacks);
82 comm.send(data, callbacks);
83 this.msg_buffer = {};
83 this.msg_buffer = {};
84 } else {
84 } else {
85
85
86 // Only decrease the pending message count if the buffer
86 // Only decrease the pending message count if the buffer
87 // doesn't get flushed (sent).
87 // doesn't get flushed (sent).
88 --this.pending_msgs;
88 --this.pending_msgs;
89 }
89 }
90 }
90 }
91 },
91 },
92
92
93
93
94 // Custom syncronization logic.
94 // Custom syncronization logic.
95 handle_sync: function (method, options) {
95 handle_sync: function (method, options) {
96 var model_json = this.toJSON();
96 var model_json = this.toJSON();
97
97
98 // Only send updated state if the state hasn't been changed
98 // Only send updated state if the state hasn't been changed
99 // during an update.
99 // during an update.
100 if (!this.updating) {
100 if (!this.updating) {
101 if (this.pending_msgs >= this.msg_throttle) {
101 if (this.pending_msgs >= this.msg_throttle) {
102 // The throttle has been exceeded, buffer the current msg so
102 // The throttle has been exceeded, buffer the current msg so
103 // it can be sent once the kernel has finished processing
103 // it can be sent once the kernel has finished processing
104 // some of the existing messages.
104 // some of the existing messages.
105 if (method=='patch') {
105 if (method=='patch') {
106 for (var attr in options.attrs) {
106 for (var attr in options.attrs) {
107 this.msg_buffer[attr] = options.attrs[attr];
107 this.msg_buffer[attr] = options.attrs[attr];
108 }
108 }
109 } else {
109 } else {
110 this.msg_buffer = $.extend({}, model_json); // Copy
110 this.msg_buffer = $.extend({}, model_json); // Copy
111 }
111 }
112
112
113 } else {
113 } else {
114 // We haven't exceeded the throttle, send the message like
114 // We haven't exceeded the throttle, send the message like
115 // normal. If this is a patch operation, just send the
115 // normal. If this is a patch operation, just send the
116 // changes.
116 // changes.
117 var send_json = model_json;
117 var send_json = model_json;
118 if (method=='patch') {
118 if (method=='patch') {
119 send_json = {};
119 send_json = {};
120 for (var attr in options.attrs) {
120 for (var attr in options.attrs) {
121 send_json[attr] = options.attrs[attr];
121 send_json[attr] = options.attrs[attr];
122 }
122 }
123 }
123 }
124
124
125 var data = {sync_method: method, sync_data: send_json};
125 var data = {sync_method: method, sync_data: send_json};
126 var output_area = this._get_view_output_area(this.last_modified_view);
126 var output_area = this._get_view_output_area(this.last_modified_view);
127 var callbacks = this._make_callbacks(output_area);
127 var callbacks = this._make_callbacks(output_area);
128 this.comm.send(data, callbacks);
128 this.comm.send(data, callbacks);
129 this.pending_msgs++;
129 this.pending_msgs++;
130 }
130 }
131 }
131 }
132
132
133 // Since the comm is a one-way communication, assume the message
133 // Since the comm is a one-way communication, assume the message
134 // arrived.
134 // arrived.
135 return model_json;
135 return model_json;
136 },
136 },
137
137
138
138
139 // Handle incomming comm msg.
139 // Handle incomming comm msg.
140 handle_comm_msg: function (msg) {
140 handle_comm_msg: function (msg) {
141 var method = msg.content.data.method;
141 var method = msg.content.data.method;
142 switch (method){
142 switch (method){
143 case 'display':
143 case 'display':
144
144 var cell_index = this._get_cell_index(msg.parent_header.msg_id);
145 ////////////////////////// TODO: Get cell index via currently executing cell.
146 var cell_index = IPython.notebook.get_selected_index()-1;
147
148 this.display_view(msg.content.data.view_name,
145 this.display_view(msg.content.data.view_name,
149 msg.content.data.parent,
146 msg.content.data.parent,
150 cell_index);
147 cell_index);
151 break;
148 break;
152 case 'update':
149 case 'update':
153 this.handle_update(msg.content.data.state);
150 this.handle_update(msg.content.data.state);
154 break;
151 break;
155 }
152 }
156 },
153 },
157
154
158
155
159 // Handle when a widget is updated via the python side.
156 // Handle when a widget is updated via the python side.
160 handle_update: function (state) {
157 handle_update: function (state) {
161 this.updating = true;
158 this.updating = true;
162 try {
159 try {
163 for (var key in state) {
160 for (var key in state) {
164 if (state.hasOwnProperty(key)) {
161 if (state.hasOwnProperty(key)) {
165 if (key == "_css"){
162 if (key == "_css"){
166 this.css = state[key];
163 this.css = state[key];
167 } else {
164 } else {
168 this.set(key, state[key]);
165 this.set(key, state[key]);
169 }
166 }
170 }
167 }
171 }
168 }
172 this.id = this.comm.comm_id;
169 this.id = this.comm.comm_id;
173 this.save();
170 this.save();
174 } finally {
171 } finally {
175 this.updating = false;
172 this.updating = false;
176 }
173 }
177 },
174 },
178
175
179
176
180 // Handle when a widget is closed.
177 // Handle when a widget is closed.
181 handle_comm_closed: function (msg) {
178 handle_comm_closed: function (msg) {
182 for (var cell_index in this.views) {
179 for (var cell_index in this.views) {
183 var views = this.views[cell_index];
180 var views = this.views[cell_index];
184 for (var view_index in views) {
181 for (var view_index in views) {
185 var view = views[view_index];
182 var view = views[view_index];
186 view.remove();
183 view.remove();
187 }
184 }
188 }
185 }
189 },
186 },
190
187
191
188
192 // Create view that represents the model.
189 // Create view that represents the model.
193 display_view: function (view_name, parent_comm_id, cell_index) {
190 display_view: function (view_name, parent_comm_id, cell_index) {
194 var view = new this.widget_view_types[view_name]({model: this});
191 var view = new this.widget_view_types[view_name]({model: this});
195 view.render();
192 view.render();
196 if (this.views[cell_index]==undefined) {
193 if (this.views[cell_index]==undefined) {
197 this.views[cell_index] = []
194 this.views[cell_index] = []
198 }
195 }
199 this.views[cell_index].push(view);
196 this.views[cell_index].push(view);
200 view.cell_index = cell_index;
197 view.cell_index = cell_index;
201
198
202 // Handle when the view element is remove from the page.
199 // Handle when the view element is remove from the page.
203 var that = this;
200 var that = this;
204 view.$el.on("remove", function(){
201 view.$el.on("remove", function(){
205 var index = that.views[cell_index].indexOf(view);
202 var index = that.views[cell_index].indexOf(view);
206 if (index > -1) {
203 if (index > -1) {
207 that.views[cell_index].splice(index, 1);
204 that.views[cell_index].splice(index, 1);
208 }
205 }
209 view.remove(); // Clean-up view
206 view.remove(); // Clean-up view
210 if (that.views[cell_index].length()==0) {
207 if (that.views[cell_index].length()==0) {
211 delete that.views[cell_index];
208 delete that.views[cell_index];
212 }
209 }
213
210
214 // Close the comm if there are no views left.
211 // Close the comm if there are no views left.
215 if (that.views.length()==0) {
212 if (that.views.length()==0) {
216 that.comm.close();
213 that.comm.close();
217 }
214 }
218 });
215 });
219
216
220 var displayed = false;
217 var displayed = false;
221 if (parent_comm_id != undefined) {
218 if (parent_comm_id != undefined) {
222 var parent_comm = this.comm_manager.comms[parent_comm_id];
219 var parent_comm = this.comm_manager.comms[parent_comm_id];
223 var parent_model = parent_comm.model;
220 var parent_model = parent_comm.model;
224 var parent_view = parent_model.views[cell_index];
221 var parent_view = parent_model.views[cell_index];
225 if (parent_view.display_child != undefined) {
222 if (parent_view.display_child != undefined) {
226 parent_view.display_child(view);
223 parent_view.display_child(view);
227 displayed = true;
224 displayed = true;
228 }
225 }
229 }
226 }
230
227
231 if (!displayed) {
228 if (!displayed) {
232 // No parent view is defined or exists. Add the view's
229 // No parent view is defined or exists. Add the view's
233 // element to cell's widget div.
230 // element to cell's widget div.
234 var cell = IPython.notebook.get_cell(cell_index);
231 var cell = IPython.notebook.get_cell(cell_index);
235 cell.element.find('.widget_area').find('.widget_subarea')
232 cell.element.find('.widget_area').find('.widget_subarea')
236 .append(view.$el)
233 .append(view.$el)
237 .parent().show(); // Show the widget_area (parent of widget_subarea)
234 .parent().show(); // Show the widget_area (parent of widget_subarea)
238
235
239 }
236 }
240
237
241 // Update the view based on the model contents.
238 // Update the view based on the model contents.
242 view.update();
239 view.update();
243 },
240 },
244
241
245
242
246 // Build a callback dict.
243 // Build a callback dict.
247 _make_callbacks: function (output_area) {
244 _make_callbacks: function (output_area) {
248 var callbacks = {};
245 var callbacks = {};
249 if (output_area != null) {
246 if (output_area != null) {
250 var that = this;
247 var that = this;
251 callbacks = {
248 callbacks = {
252 iopub : {
249 iopub : {
253 output : $.proxy(output_area.handle_output, output_area),
250 output : $.proxy(output_area.handle_output, output_area),
254 clear_output : $.proxy(output_area.handle_clear_output, output_area),
251 clear_output : $.proxy(output_area.handle_clear_output, output_area),
255 status : function(msg){
252 status : function(msg){
256 that.handle_status(output_area, msg);
253 that.handle_status(output_area, msg);
257 },
254 },
258 },
255 },
259 };
256 };
260 }
257 }
261 return callbacks;
258 return callbacks;
262 },
259 },
263
260
264
261
262 // Get the cell index corresponding to the msg_id.
263 _get_cell_index: function (msg_id) {
264 var cells = IPython.notebook.get_cells();
265 for (cell_index in cells) {
266 if (cells[cell_index].last_msg_id == msg_id) {
267 return cell_index;
268 }
269 }
270 return -1;
271 },
272
273
265 // Get the cell output area corresponding to the view.
274 // Get the cell output area corresponding to the view.
266 _get_view_output_area: function (view) {
275 _get_view_output_area: function (view) {
267 return this._get_cell_output_area(view.cell_index);
276 return this._get_cell_output_area(view.cell_index);
268 },
277 },
269
278
270
279
271 // Get the cell output area corresponding to the cell id.
280 // Get the cell output area corresponding to the cell index.
272 _get_cell_output_area: function (cell_id) {
281 _get_cell_output_area: function (cell_index) {
273 var cell = IPython.notebook.get_cell(cell_id)
282 var cell = IPython.notebook.get_cell(cell_index)
274 return cell.output_area;
283 return cell.output_area;
275 },
284 },
276 });
285 });
277
286
278
287
279 //--------------------------------------------------------------------
288 //--------------------------------------------------------------------
280 // WidgetView class
289 // WidgetView class
281 //--------------------------------------------------------------------
290 //--------------------------------------------------------------------
282 var WidgetView = Backbone.View.extend({
291 var WidgetView = Backbone.View.extend({
283
292
284 initialize: function() {
293 initialize: function() {
285 this.model.on('change',this.update,this);
294 this.model.on('change',this.update,this);
286 },
295 },
287
296
288 update: function() {
297 update: function() {
289 if (this.model.css != undefined) {
298 if (this.model.css != undefined) {
290 for (var selector in this.model.css) {
299 for (var selector in this.model.css) {
291 if (this.model.css.hasOwnProperty(selector)) {
300 if (this.model.css.hasOwnProperty(selector)) {
292
301
293 // Get the elements via the css selector. If the selector is
302 // Get the elements via the css selector. If the selector is
294 // blank, assume the current element is the target.
303 // blank, assume the current element is the target.
295 var elements = this.$el.find(selector);
304 var elements = this.$el.find(selector);
296 if (selector=='') {
305 if (selector=='') {
297 elements = this.$el;
306 elements = this.$el;
298 }
307 }
299
308
300 // Apply the css traits to all elements that match the selector.
309 // Apply the css traits to all elements that match the selector.
301 if (elements.length>0){
310 if (elements.length>0){
302 var css_traits = this.model.css[selector];
311 var css_traits = this.model.css[selector];
303 for (var css_key in css_traits) {
312 for (var css_key in css_traits) {
304 if (css_traits.hasOwnProperty(css_key)) {
313 if (css_traits.hasOwnProperty(css_key)) {
305 elements.css(css_key, css_traits[css_key]);
314 elements.css(css_key, css_traits[css_key]);
306 }
315 }
307 }
316 }
308 }
317 }
309 }
318 }
310 }
319 }
311 }
320 }
312 },
321 },
313 });
322 });
314
323
315
324
316 //--------------------------------------------------------------------
325 //--------------------------------------------------------------------
317 // WidgetManager class
326 // WidgetManager class
318 //--------------------------------------------------------------------
327 //--------------------------------------------------------------------
319 var WidgetManager = function(comm_manager){
328 var WidgetManager = function(comm_manager){
320 this.comm_manager = comm_manager;
329 this.comm_manager = comm_manager;
321 this.widget_model_types = {};
330 this.widget_model_types = {};
322 this.widget_view_types = {};
331 this.widget_view_types = {};
323
332
324 var that = this;
333 var that = this;
325 Backbone.sync = function(method, model, options, error) {
334 Backbone.sync = function(method, model, options, error) {
326 var result = model.handle_sync(method, options);
335 var result = model.handle_sync(method, options);
327 if (options.success) {
336 if (options.success) {
328 options.success(result);
337 options.success(result);
329 }
338 }
330 };
339 };
331 }
340 }
332
341
333
342
334 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
343 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
335 // Register the widget with the comm manager. Make sure to pass this object's context
344 // Register the widget with the comm manager. Make sure to pass this object's context
336 // in so `this` works in the call back.
345 // in so `this` works in the call back.
337 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
346 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
338 this.widget_model_types[widget_model_name] = widget_model_type;
347 this.widget_model_types[widget_model_name] = widget_model_type;
339 }
348 }
340
349
341
350
342 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
351 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
343 this.widget_view_types[widget_view_name] = widget_view_type;
352 this.widget_view_types[widget_view_name] = widget_view_type;
344 }
353 }
345
354
346
355
347 WidgetManager.prototype.handle_com_open = function (comm, msg) {
356 WidgetManager.prototype.handle_com_open = function (comm, msg) {
348 var widget_type_name = msg.content.target_name;
357 var widget_type_name = msg.content.target_name;
349 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
358 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
350 }
359 }
351
360
352
361
353 //--------------------------------------------------------------------
362 //--------------------------------------------------------------------
354 // Init code
363 // Init code
355 //--------------------------------------------------------------------
364 //--------------------------------------------------------------------
356 IPython.WidgetManager = WidgetManager;
365 IPython.WidgetManager = WidgetManager;
357 IPython.WidgetModel = WidgetModel;
366 IPython.WidgetModel = WidgetModel;
358 IPython.WidgetView = WidgetView;
367 IPython.WidgetView = WidgetView;
359
368
360 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
369 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
361
370
362 };
371 };
363 });
372 });
General Comments 0
You need to be logged in to leave comments. Login now