##// END OF EJS Templates
Added `visible` property to all widgets
Jonathan Frederic -
Show More
@@ -1,394 +1,405 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",
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.visible = true;
310 this.model.on('change',this.update,this);
311 this.model.on('change',this.update,this);
311 },
312 },
312
313
313 update: function() {
314 update: function() {
315 if (this.model.get('visible') != undefined) {
316 if (this.visible != this.model.get('visible')) {
317 this.visible = this.model.get('visible');
318 if (this.visible) {
319 this.$el.show();
320 } else {
321 this.$el.hide();
322 }
323 }
324 }
314 if (this.model.css != undefined) {
325 if (this.model.css != undefined) {
315 for (var selector in this.model.css) {
326 for (var selector in this.model.css) {
316 if (this.model.css.hasOwnProperty(selector)) {
327 if (this.model.css.hasOwnProperty(selector)) {
317
328
318 // Get the elements via the css selector. If the selector is
329 // Get the elements via the css selector. If the selector is
319 // blank, apply the style to the $el_to_style element. If
330 // blank, apply the style to the $el_to_style element. If
320 // the $el_to_style element is not defined, use apply the
331 // the $el_to_style element is not defined, use apply the
321 // style to the view's element.
332 // style to the view's element.
322 var elements = this.$el.find(selector);
333 var elements = this.$el.find(selector);
323 if (selector=='') {
334 if (selector=='') {
324 if (this.$el_to_style == undefined) {
335 if (this.$el_to_style == undefined) {
325 elements = this.$el;
336 elements = this.$el;
326 } else {
337 } else {
327 elements = this.$el_to_style;
338 elements = this.$el_to_style;
328 }
339 }
329 }
340 }
330
341
331 // Apply the css traits to all elements that match the selector.
342 // Apply the css traits to all elements that match the selector.
332 if (elements.length>0){
343 if (elements.length>0){
333 var css_traits = this.model.css[selector];
344 var css_traits = this.model.css[selector];
334 for (var css_key in css_traits) {
345 for (var css_key in css_traits) {
335 if (css_traits.hasOwnProperty(css_key)) {
346 if (css_traits.hasOwnProperty(css_key)) {
336 elements.css(css_key, css_traits[css_key]);
347 elements.css(css_key, css_traits[css_key]);
337 }
348 }
338 }
349 }
339 }
350 }
340 }
351 }
341 }
352 }
342 }
353 }
343 },
354 },
344 });
355 });
345
356
346
357
347 //--------------------------------------------------------------------
358 //--------------------------------------------------------------------
348 // WidgetManager class
359 // WidgetManager class
349 //--------------------------------------------------------------------
360 //--------------------------------------------------------------------
350 var WidgetManager = function(comm_manager){
361 var WidgetManager = function(comm_manager){
351 this.comm_manager = comm_manager;
362 this.comm_manager = comm_manager;
352 this.widget_model_types = {};
363 this.widget_model_types = {};
353 this.widget_view_types = {};
364 this.widget_view_types = {};
354
365
355 var that = this;
366 var that = this;
356 Backbone.sync = function(method, model, options, error) {
367 Backbone.sync = function(method, model, options, error) {
357 var result = model.handle_sync(method, options);
368 var result = model.handle_sync(method, options);
358 if (options.success) {
369 if (options.success) {
359 options.success(result);
370 options.success(result);
360 }
371 }
361 };
372 };
362 }
373 }
363
374
364
375
365 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
376 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
366 // Register the widget with the comm manager. Make sure to pass this object's context
377 // Register the widget with the comm manager. Make sure to pass this object's context
367 // in so `this` works in the call back.
378 // in so `this` works in the call back.
368 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
379 this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
369 this.widget_model_types[widget_model_name] = widget_model_type;
380 this.widget_model_types[widget_model_name] = widget_model_type;
370 }
381 }
371
382
372
383
373 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
384 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
374 this.widget_view_types[widget_view_name] = widget_view_type;
385 this.widget_view_types[widget_view_name] = widget_view_type;
375 }
386 }
376
387
377
388
378 WidgetManager.prototype.handle_com_open = function (comm, msg) {
389 WidgetManager.prototype.handle_com_open = function (comm, msg) {
379 var widget_type_name = msg.content.target_name;
390 var widget_type_name = msg.content.target_name;
380 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
391 var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this.widget_view_types);
381 }
392 }
382
393
383
394
384 //--------------------------------------------------------------------
395 //--------------------------------------------------------------------
385 // Init code
396 // Init code
386 //--------------------------------------------------------------------
397 //--------------------------------------------------------------------
387 IPython.WidgetManager = WidgetManager;
398 IPython.WidgetManager = WidgetManager;
388 IPython.WidgetModel = WidgetModel;
399 IPython.WidgetModel = WidgetModel;
389 IPython.WidgetView = WidgetView;
400 IPython.WidgetView = WidgetView;
390
401
391 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
402 IPython.notebook.widget_manager = new WidgetManager(IPython.notebook.kernel.comm_manager);
392
403
393 };
404 };
394 });
405 });
@@ -1,252 +1,253 b''
1 """Base Widget class. Allows user to create widgets in the backend that render
1 """Base Widget class. Allows user to create widgets in the backend that render
2 in the IPython notebook frontend.
2 in the IPython notebook frontend.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from copy import copy
15 from copy import copy
16 from glob import glob
16 from glob import glob
17 import uuid
17 import uuid
18 import sys
18 import sys
19 import os
19 import os
20
20
21 import IPython
21 import IPython
22 from IPython.kernel.comm import Comm
22 from IPython.kernel.comm import Comm
23 from IPython.config import LoggingConfigurable
23 from IPython.config import LoggingConfigurable
24 from IPython.utils.traitlets import Unicode, Dict, List, Instance
24 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
25 from IPython.display import Javascript, display
25 from IPython.display import Javascript, display
26 from IPython.utils.py3compat import string_types
26 from IPython.utils.py3compat import string_types
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Shared
29 # Shared
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 def init_widget_js():
31 def init_widget_js():
32 path = os.path.split(os.path.abspath( __file__ ))[0]
32 path = os.path.split(os.path.abspath( __file__ ))[0]
33 for filepath in glob(os.path.join(path, "*.py")):
33 for filepath in glob(os.path.join(path, "*.py")):
34 filename = os.path.split(filepath)[1]
34 filename = os.path.split(filepath)[1]
35 name = filename.rsplit('.', 1)[0]
35 name = filename.rsplit('.', 1)[0]
36 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
36 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
37 # Remove 'widget_' from the start of the name before compiling the path.
37 # Remove 'widget_' from the start of the name before compiling the path.
38 js_path = '/static/notebook/js/widgets/%s.js' % name[7:]
38 js_path = '/static/notebook/js/widgets/%s.js' % name[7:]
39 display(Javascript(data='$.getScript("%s");' % js_path))
39 display(Javascript(data='$.getScript("%s");' % js_path))
40
40
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Classes
43 # Classes
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 class Widget(LoggingConfigurable):
45 class Widget(LoggingConfigurable):
46
46
47 # Public declarations
47 # Public declarations
48 target_name = Unicode('widget', help="""Name of the backbone model
48 target_name = Unicode('widget', help="""Name of the backbone model
49 registered in the frontend to create and sync this widget with.""")
49 registered in the frontend to create and sync this widget with.""")
50 default_view_name = Unicode(help="""Default view registered in the frontend
50 default_view_name = Unicode(help="""Default view registered in the frontend
51 to use to represent the widget.""")
51 to use to represent the widget.""")
52 parent = Instance('IPython.html.widgets.widget.Widget')
52 parent = Instance('IPython.html.widgets.widget.Widget')
53 visible = Bool(True, help="Whether or not the widget is visible.")
53
54
54 def _parent_changed(self, name, old, new):
55 def _parent_changed(self, name, old, new):
55 if self._displayed:
56 if self._displayed:
56 raise Exception('Parent cannot be set because widget has been displayed.')
57 raise Exception('Parent cannot be set because widget has been displayed.')
57 elif new == self:
58 elif new == self:
58 raise Exception('Parent cannot be set to self.')
59 raise Exception('Parent cannot be set to self.')
59 else:
60 else:
60
61
61 # Parent/child association
62 # Parent/child association
62 if new is not None and not self in new._children:
63 if new is not None and not self in new._children:
63 new._children.append(self)
64 new._children.append(self)
64 if old is not None and self in old._children:
65 if old is not None and self in old._children:
65 old._children.remove(self)
66 old._children.remove(self)
66
67
67 # Private/protected declarations
68 # Private/protected declarations
68 _keys = []
69 _keys = []
69 _property_lock = False
70 _property_lock = False
70 _css = Dict()
71 _css = Dict()
71 _displayed = False
72 _displayed = False
72 _comm = None
73 _comm = None
73
74
74
75
75 def __init__(self, **kwargs):
76 def __init__(self, **kwargs):
76 """Public constructor
77 """Public constructor
77
78
78 Parameters
79 Parameters
79 ----------
80 ----------
80 parent : Widget instance (optional)
81 parent : Widget instance (optional)
81 Widget that this widget instance is child of. When the widget is
82 Widget that this widget instance is child of. When the widget is
82 displayed in the frontend, it's corresponding view will be made
83 displayed in the frontend, it's corresponding view will be made
83 child of the parent's view if the parent's view exists already. If
84 child of the parent's view if the parent's view exists already. If
84 the parent's view is displayed, it will automatically display this
85 the parent's view is displayed, it will automatically display this
85 widget's default view as it's child. The default view can be set
86 widget's default view as it's child. The default view can be set
86 via the default_view_name property.
87 via the default_view_name property.
87 """
88 """
88 self._children = []
89 self._children = []
89 super(Widget, self).__init__(**kwargs)
90 super(Widget, self).__init__(**kwargs)
90
91
91 # Register after init to allow default values to be specified
92 # Register after init to allow default values to be specified
92 self.on_trait_change(self._handle_property_changed, self.keys)
93 self.on_trait_change(self._handle_property_changed, self.keys)
93
94
94
95
95 def __del__(self):
96 def __del__(self):
96 """Object disposal"""
97 """Object disposal"""
97 self.close()
98 self.close()
98
99
99
100
100 def close(self):
101 def close(self):
101 """Close method. Closes the widget which closes the underlying comm.
102 """Close method. Closes the widget which closes the underlying comm.
102 When the comm is closed, all of the widget views are automatically
103 When the comm is closed, all of the widget views are automatically
103 removed from the frontend."""
104 removed from the frontend."""
104 self._comm.close()
105 self._comm.close()
105 del self._comm
106 del self._comm
106
107
107
108
108 # Properties
109 # Properties
109 def _get_keys(self):
110 def _get_keys(self):
110 keys = ['_css']
111 keys = ['_css', 'visible']
111 keys.extend(self._keys)
112 keys.extend(self._keys)
112 return keys
113 return keys
113 keys = property(_get_keys)
114 keys = property(_get_keys)
114
115
115
116
116 # Event handlers
117 # Event handlers
117 def _handle_msg(self, msg):
118 def _handle_msg(self, msg):
118 """Called when a msg is recieved from the frontend"""
119 """Called when a msg is recieved from the frontend"""
119 # Handle backbone sync methods CREATE, PATCH, and UPDATE
120 # Handle backbone sync methods CREATE, PATCH, and UPDATE
120 sync_method = msg['content']['data']['sync_method']
121 sync_method = msg['content']['data']['sync_method']
121 sync_data = msg['content']['data']['sync_data']
122 sync_data = msg['content']['data']['sync_data']
122 self._handle_recieve_state(sync_data) # handles all methods
123 self._handle_recieve_state(sync_data) # handles all methods
123
124
124
125
125 def _handle_recieve_state(self, sync_data):
126 def _handle_recieve_state(self, sync_data):
126 """Called when a state is recieved from the frontend."""
127 """Called when a state is recieved from the frontend."""
127 self._property_lock = True
128 self._property_lock = True
128 try:
129 try:
129
130
130 # Use _keys instead of keys - Don't get retrieve the css from the client side.
131 # Use _keys instead of keys - Don't get retrieve the css from the client side.
131 for name in self._keys:
132 for name in self._keys:
132 if name in sync_data:
133 if name in sync_data:
133 setattr(self, name, sync_data[name])
134 setattr(self, name, sync_data[name])
134 finally:
135 finally:
135 self._property_lock = False
136 self._property_lock = False
136
137
137
138
138 def _handle_property_changed(self, name, old, new):
139 def _handle_property_changed(self, name, old, new):
139 """Called when a proeprty has been changed."""
140 """Called when a proeprty has been changed."""
140 if not self._property_lock and self._comm is not None:
141 if not self._property_lock and self._comm is not None:
141 # TODO: Validate properties.
142 # TODO: Validate properties.
142 # Send new state to frontend
143 # Send new state to frontend
143 self.send_state(key=name)
144 self.send_state(key=name)
144
145
145
146
146 def _handle_close(self):
147 def _handle_close(self):
147 """Called when the comm is closed by the frontend."""
148 """Called when the comm is closed by the frontend."""
148 self._comm = None
149 self._comm = None
149
150
150
151
151 # Public methods
152 # Public methods
152 def send_state(self, key=None):
153 def send_state(self, key=None):
153 """Sends the widget state, or a piece of it, to the frontend.
154 """Sends the widget state, or a piece of it, to the frontend.
154
155
155 Parameters
156 Parameters
156 ----------
157 ----------
157 key : unicode (optional)
158 key : unicode (optional)
158 A single property's name to sync with the frontend.
159 A single property's name to sync with the frontend.
159 """
160 """
160 if self._comm is not None:
161 if self._comm is not None:
161 state = {}
162 state = {}
162
163
163 # If a key is provided, just send the state of that key.
164 # If a key is provided, just send the state of that key.
164 keys = []
165 keys = []
165 if key is None:
166 if key is None:
166 keys.extend(self.keys)
167 keys.extend(self.keys)
167 else:
168 else:
168 keys.append(key)
169 keys.append(key)
169 for key in self.keys:
170 for key in self.keys:
170 try:
171 try:
171 state[key] = getattr(self, key)
172 state[key] = getattr(self, key)
172 except Exception as e:
173 except Exception as e:
173 pass # Eat errors, nom nom nom
174 pass # Eat errors, nom nom nom
174 self._comm.send({"method": "update",
175 self._comm.send({"method": "update",
175 "state": state})
176 "state": state})
176
177
177
178
178 def get_css(self, key, selector=""):
179 def get_css(self, key, selector=""):
179 """Get a CSS property of the widget views (shared among all of the
180 """Get a CSS property of the widget views (shared among all of the
180 views)
181 views)
181
182
182 Parameters
183 Parameters
183 ----------
184 ----------
184 key: unicode
185 key: unicode
185 CSS key
186 CSS key
186 selector: unicode (optional)
187 selector: unicode (optional)
187 JQuery selector used when the CSS key/value was set.
188 JQuery selector used when the CSS key/value was set.
188 """
189 """
189 if selector in self._css and key in self._css[selector]:
190 if selector in self._css and key in self._css[selector]:
190 return self._css[selector][key]
191 return self._css[selector][key]
191 else:
192 else:
192 return None
193 return None
193
194
194
195
195 def set_css(self, key, value, selector=""):
196 def set_css(self, key, value, selector=""):
196 """Set a CSS property of the widget views (shared among all of the
197 """Set a CSS property of the widget views (shared among all of the
197 views)
198 views)
198
199
199 Parameters
200 Parameters
200 ----------
201 ----------
201 key: unicode
202 key: unicode
202 CSS key
203 CSS key
203 value
204 value
204 CSS value
205 CSS value
205 selector: unicode (optional)
206 selector: unicode (optional)
206 JQuery selector to use to apply the CSS key/value.
207 JQuery selector to use to apply the CSS key/value.
207 """
208 """
208 if selector not in self._css:
209 if selector not in self._css:
209 self._css[selector] = {}
210 self._css[selector] = {}
210
211
211 # Only update the property if it has changed.
212 # Only update the property if it has changed.
212 if not (key in self._css[selector] and value in self._css[selector][key]):
213 if not (key in self._css[selector] and value in self._css[selector][key]):
213 self._css[selector][key] = value
214 self._css[selector][key] = value
214 self.send_state() # Send new state to client.
215 self.send_state() # Send new state to client.
215
216
216
217
217 # Support methods
218 # Support methods
218 def _repr_widget_(self, view_name=None):
219 def _repr_widget_(self, view_name=None):
219 """Function that is called when `IPython.display.display` is called on
220 """Function that is called when `IPython.display.display` is called on
220 the widget.
221 the widget.
221
222
222 Parameters
223 Parameters
223 ----------
224 ----------
224 view_name: unicode (optional)
225 view_name: unicode (optional)
225 View to display in the frontend. Overrides default_view_name."""
226 View to display in the frontend. Overrides default_view_name."""
226
227
227 if not view_name:
228 if not view_name:
228 view_name = self.default_view_name
229 view_name = self.default_view_name
229
230
230 # Create a comm.
231 # Create a comm.
231 if self._comm is None:
232 if self._comm is None:
232 self._comm = Comm(target_name=self.target_name)
233 self._comm = Comm(target_name=self.target_name)
233 self._comm.on_msg(self._handle_msg)
234 self._comm.on_msg(self._handle_msg)
234 self._comm.on_close(self._handle_close)
235 self._comm.on_close(self._handle_close)
235
236
236 # Make sure model is syncronized
237 # Make sure model is syncronized
237 self.send_state()
238 self.send_state()
238
239
239 # Show view.
240 # Show view.
240 if self.parent is None or self.parent._comm is None:
241 if self.parent is None or self.parent._comm is None:
241 self._comm.send({"method": "display", "view_name": view_name})
242 self._comm.send({"method": "display", "view_name": view_name})
242 else:
243 else:
243 self._comm.send({"method": "display",
244 self._comm.send({"method": "display",
244 "view_name": view_name,
245 "view_name": view_name,
245 "parent": self.parent._comm.comm_id})
246 "parent": self.parent._comm.comm_id})
246 self._displayed = True
247 self._displayed = True
247
248
248 # Now display children if any.
249 # Now display children if any.
249 for child in self._children:
250 for child in self._children:
250 if child != self:
251 if child != self:
251 child._repr_widget_()
252 child._repr_widget_()
252 return None
253 return None
General Comments 0
You need to be logged in to leave comments. Login now