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