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