##// END OF EJS Templates
Added ability to specify the element that gets style in a view...
Jonathan Frederic -
Show More
@@ -1,388 +1,394
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",
23 define(["components/underscore/underscore-min",
24 "components/backbone/backbone-min",
24 "components/backbone/backbone-min",
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 var cell_index = this._get_cell_index(msg.parent_header.msg_id);
144 var cell_index = this._get_cell_index(msg.parent_header.msg_id);
145 this.display_view(msg.content.data.view_name,
145 this.display_view(msg.content.data.view_name,
146 msg.content.data.parent,
146 msg.content.data.parent,
147 cell_index);
147 cell_index);
148 break;
148 break;
149 case 'update':
149 case 'update':
150 this.handle_update(msg.content.data.state);
150 this.handle_update(msg.content.data.state);
151 break;
151 break;
152 }
152 }
153 },
153 },
154
154
155
155
156 // Handle when a widget is updated via the python side.
156 // Handle when a widget is updated via the python side.
157 handle_update: function (state) {
157 handle_update: function (state) {
158 this.updating = true;
158 this.updating = true;
159 try {
159 try {
160 for (var key in state) {
160 for (var key in state) {
161 if (state.hasOwnProperty(key)) {
161 if (state.hasOwnProperty(key)) {
162 if (key == "_css"){
162 if (key == "_css"){
163 this.css = state[key];
163 this.css = state[key];
164 } else {
164 } else {
165 this.set(key, state[key]);
165 this.set(key, state[key]);
166 }
166 }
167 }
167 }
168 }
168 }
169 this.id = this.comm.comm_id;
169 this.id = this.comm.comm_id;
170 this.save();
170 this.save();
171 } finally {
171 } finally {
172 this.updating = false;
172 this.updating = false;
173 }
173 }
174 },
174 },
175
175
176
176
177 // Handle when a widget is closed.
177 // Handle when a widget is closed.
178 handle_comm_closed: function (msg) {
178 handle_comm_closed: function (msg) {
179 for (var cell_index in this.views) {
179 for (var cell_index in this.views) {
180 var views = this.views[cell_index];
180 var views = this.views[cell_index];
181 for (var view_index in views) {
181 for (var view_index in views) {
182 var view = views[view_index];
182 var view = views[view_index];
183 view.remove();
183 view.remove();
184 }
184 }
185 }
185 }
186 },
186 },
187
187
188
188
189 // Create view that represents the model.
189 // Create view that represents the model.
190 display_view: function (view_name, parent_comm_id, cell_index) {
190 display_view: function (view_name, parent_comm_id, cell_index) {
191 var new_views = [];
191 var new_views = [];
192
192
193 var displayed = false;
193 var displayed = false;
194 if (parent_comm_id != undefined) {
194 if (parent_comm_id != undefined) {
195 var parent_comm = this.comm_manager.comms[parent_comm_id];
195 var parent_comm = this.comm_manager.comms[parent_comm_id];
196 var parent_model = parent_comm.model;
196 var parent_model = parent_comm.model;
197 var parent_views = parent_model.views[cell_index];
197 var parent_views = parent_model.views[cell_index];
198 for (var parent_view_index in parent_views) {
198 for (var parent_view_index in parent_views) {
199 var parent_view = parent_views[parent_view_index];
199 var parent_view = parent_views[parent_view_index];
200 if (parent_view.display_child != undefined) {
200 if (parent_view.display_child != undefined) {
201 var view = this._create_view(view_name, cell_index);
201 var view = this._create_view(view_name, cell_index);
202 new_views.push(view);
202 new_views.push(view);
203 parent_view.display_child(view);
203 parent_view.display_child(view);
204 displayed = true;
204 displayed = true;
205 }
205 }
206 }
206 }
207 }
207 }
208
208
209 if (!displayed) {
209 if (!displayed) {
210 // No parent view is defined or exists. Add the view's
210 // No parent view is defined or exists. Add the view's
211 // element to cell's widget div.
211 // element to cell's widget div.
212 var view = this._create_view(view_name, cell_index);
212 var view = this._create_view(view_name, cell_index);
213 new_views.push(view);
213 new_views.push(view);
214 var cell = IPython.notebook.get_cell(cell_index);
214 var cell = IPython.notebook.get_cell(cell_index);
215 cell.element.find('.widget-area').find('.widget-subarea')
215 cell.element.find('.widget-area').find('.widget-subarea')
216 .append(view.$el)
216 .append(view.$el)
217 .parent().show(); // Show the widget_area (parent of widget_subarea)
217 .parent().show(); // Show the widget_area (parent of widget_subarea)
218
218
219 }
219 }
220
220
221 for (var view_index in new_views) {
221 for (var view_index in new_views) {
222 var view = new_views[view_index];
222 var view = new_views[view_index];
223 view.update();
223 view.update();
224 }
224 }
225 },
225 },
226
226
227
227
228 // Create a view
228 // Create a view
229 _create_view: function (view_name, cell_index) {
229 _create_view: function (view_name, cell_index) {
230 var view = new this.widget_view_types[view_name]({model: this});
230 var view = new this.widget_view_types[view_name]({model: this});
231 view.render();
231 view.render();
232 if (this.views[cell_index]==undefined) {
232 if (this.views[cell_index]==undefined) {
233 this.views[cell_index] = []
233 this.views[cell_index] = []
234 }
234 }
235 this.views[cell_index].push(view);
235 this.views[cell_index].push(view);
236 view.cell_index = cell_index;
236 view.cell_index = cell_index;
237
237
238 // Handle when the view element is remove from the page.
238 // Handle when the view element is remove from the page.
239 var that = this;
239 var that = this;
240 view.$el.on("remove", function(){
240 view.$el.on("remove", function(){
241 var index = that.views[cell_index].indexOf(view);
241 var index = that.views[cell_index].indexOf(view);
242 if (index > -1) {
242 if (index > -1) {
243 that.views[cell_index].splice(index, 1);
243 that.views[cell_index].splice(index, 1);
244 }
244 }
245 view.remove(); // Clean-up view
245 view.remove(); // Clean-up view
246 if (that.views[cell_index].length()==0) {
246 if (that.views[cell_index].length()==0) {
247 delete that.views[cell_index];
247 delete that.views[cell_index];
248 }
248 }
249
249
250 // Close the comm if there are no views left.
250 // Close the comm if there are no views left.
251 if (that.views.length()==0) {
251 if (that.views.length()==0) {
252 that.comm.close();
252 that.comm.close();
253 }
253 }
254 });
254 });
255 return view;
255 return view;
256 },
256 },
257
257
258
258
259 // Build a callback dict.
259 // Build a callback dict.
260 _make_callbacks: function (output_area) {
260 _make_callbacks: function (output_area) {
261 var callbacks = {};
261 var callbacks = {};
262 if (output_area != null) {
262 if (output_area != null) {
263 var that = this;
263 var that = this;
264 callbacks = {
264 callbacks = {
265 iopub : {
265 iopub : {
266 output : $.proxy(output_area.handle_output, output_area),
266 output : $.proxy(output_area.handle_output, output_area),
267 clear_output : $.proxy(output_area.handle_clear_output, output_area),
267 clear_output : $.proxy(output_area.handle_clear_output, output_area),
268 status : function(msg){
268 status : function(msg){
269 that.handle_status(output_area, msg);
269 that.handle_status(output_area, msg);
270 },
270 },
271 },
271 },
272 };
272 };
273 }
273 }
274 return callbacks;
274 return callbacks;
275 },
275 },
276
276
277
277
278 // Get the cell index corresponding to the msg_id.
278 // Get the cell index corresponding to the msg_id.
279 _get_cell_index: function (msg_id) {
279 _get_cell_index: function (msg_id) {
280 var cells = IPython.notebook.get_cells();
280 var cells = IPython.notebook.get_cells();
281 for (var cell_index in cells) {
281 for (var cell_index in cells) {
282 if (cells[cell_index].last_msg_id == msg_id) {
282 if (cells[cell_index].last_msg_id == msg_id) {
283 return cell_index;
283 return cell_index;
284 }
284 }
285 }
285 }
286 return -1;
286 return -1;
287 },
287 },
288
288
289
289
290 // Get the cell output area corresponding to the view.
290 // Get the cell output area corresponding to the view.
291 _get_view_output_area: function (view) {
291 _get_view_output_area: function (view) {
292 return this._get_cell_output_area(view.cell_index);
292 return this._get_cell_output_area(view.cell_index);
293 },
293 },
294
294
295
295
296 // Get the cell output area corresponding to the cell index.
296 // Get the cell output area corresponding to the cell index.
297 _get_cell_output_area: function (cell_index) {
297 _get_cell_output_area: function (cell_index) {
298 var cell = IPython.notebook.get_cell(cell_index)
298 var cell = IPython.notebook.get_cell(cell_index)
299 return cell.output_area;
299 return cell.output_area;
300 },
300 },
301 });
301 });
302
302
303
303
304 //--------------------------------------------------------------------
304 //--------------------------------------------------------------------
305 // WidgetView class
305 // WidgetView class
306 //--------------------------------------------------------------------
306 //--------------------------------------------------------------------
307 var WidgetView = Backbone.View.extend({
307 var WidgetView = Backbone.View.extend({
308
308
309 initialize: function() {
309 initialize: function() {
310 this.model.on('change',this.update,this);
310 this.model.on('change',this.update,this);
311 },
311 },
312
312
313 update: function() {
313 update: function() {
314 if (this.model.css != undefined) {
314 if (this.model.css != undefined) {
315 for (var selector in this.model.css) {
315 for (var selector in this.model.css) {
316 if (this.model.css.hasOwnProperty(selector)) {
316 if (this.model.css.hasOwnProperty(selector)) {
317
317
318 // Get the elements via the css selector. If the selector is
318 // Get the elements via the css selector. If the selector is
319 // blank, assume the current element is the target.
319 // blank, apply the style to the $el_to_style element. If
320 // the $el_to_style element is not defined, use apply the
321 // style to the view's element.
320 var elements = this.$el.find(selector);
322 var elements = this.$el.find(selector);
321 if (selector=='') {
323 if (selector=='') {
322 elements = this.$el;
324 if (this.$el_to_style == undefined) {
325 elements = this.$el;
326 } else {
327 elements = this.$el_to_style;
328 }
323 }
329 }
324
330
325 // Apply the css traits to all elements that match the selector.
331 // Apply the css traits to all elements that match the selector.
326 if (elements.length>0){
332 if (elements.length>0){
327 var css_traits = this.model.css[selector];
333 var css_traits = this.model.css[selector];
328 for (var css_key in css_traits) {
334 for (var css_key in css_traits) {
329 if (css_traits.hasOwnProperty(css_key)) {
335 if (css_traits.hasOwnProperty(css_key)) {
330 elements.css(css_key, css_traits[css_key]);
336 elements.css(css_key, css_traits[css_key]);
331 }
337 }
332 }
338 }
333 }
339 }
334 }
340 }
335 }
341 }
336 }
342 }
337 },
343 },
338 });
344 });
339
345
340
346
341 //--------------------------------------------------------------------
347 //--------------------------------------------------------------------
342 // WidgetManager class
348 // WidgetManager class
343 //--------------------------------------------------------------------
349 //--------------------------------------------------------------------
344 var WidgetManager = function(comm_manager){
350 var WidgetManager = function(comm_manager){
345 this.comm_manager = comm_manager;
351 this.comm_manager = comm_manager;
346 this.widget_model_types = {};
352 this.widget_model_types = {};
347 this.widget_view_types = {};
353 this.widget_view_types = {};
348
354
349 var that = this;
355 var that = this;
350 Backbone.sync = function(method, model, options, error) {
356 Backbone.sync = function(method, model, options, error) {
351 var result = model.handle_sync(method, options);
357 var result = model.handle_sync(method, options);
352 if (options.success) {
358 if (options.success) {
353 options.success(result);
359 options.success(result);
354 }
360 }
355 };
361 };
356 }
362 }
357
363
358
364
359 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
365 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
360 // Register the widget with the comm manager. Make sure to pass this object's context
366 // Register the widget with the comm manager. Make sure to pass this object's context
361 // in so `this` works in the call back.
367 // in so `this` works in the call back.
362 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
368 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
363 this.widget_model_types[widget_model_name] = widget_model_type;
369 this.widget_model_types[widget_model_name] = widget_model_type;
364 }
370 }
365
371
366
372
367 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
373 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
368 this.widget_view_types[widget_view_name] = widget_view_type;
374 this.widget_view_types[widget_view_name] = widget_view_type;
369 }
375 }
370
376
371
377
372 WidgetManager.prototype.handle_com_open = function (comm, msg) {
378 WidgetManager.prototype.handle_com_open = function (comm, msg) {
373 var widget_type_name = msg.content.target_name;
379 var widget_type_name = msg.content.target_name;
374 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
380 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
375 }
381 }
376
382
377
383
378 //--------------------------------------------------------------------
384 //--------------------------------------------------------------------
379 // Init code
385 // Init code
380 //--------------------------------------------------------------------
386 //--------------------------------------------------------------------
381 IPython.WidgetManager = WidgetManager;
387 IPython.WidgetManager = WidgetManager;
382 IPython.WidgetModel = WidgetModel;
388 IPython.WidgetModel = WidgetModel;
383 IPython.WidgetView = WidgetView;
389 IPython.WidgetView = WidgetView;
384
390
385 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
391 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
386
392
387 };
393 };
388 });
394 });
General Comments 0
You need to be logged in to leave comments. Login now