##// END OF EJS Templates
Intermediate changes to javascript side of backbone widgets
Jason Grout -
Show More
@@ -1,129 +1,214 b''
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 (function () {
19 19 "use strict";
20 20
21 21 // Use require.js 'define' method so that require.js is intelligent enough to
22 22 // syncronously load everything within this file when it is being 'required'
23 23 // elsewhere.
24 24 define(["underscore",
25 25 "backbone",
26 26 ], function (underscore, backbone) {
27 27
28 28 //--------------------------------------------------------------------
29 29 // WidgetManager class
30 30 //--------------------------------------------------------------------
31 31 var WidgetManager = function () {
32 32 this.comm_manager = null;
33 33 this.widget_model_types = {};
34 34 this.widget_view_types = {};
35 35 this._model_instances = {};
36 36
37 37 Backbone.sync = function (method, model, options, error) {
38 38 var result = model._handle_sync(method, options);
39 39 if (options.success) {
40 40 options.success(result);
41 41 }
42 42 };
43 43 };
44 44
45 45
46 46 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
47 47 this.comm_manager = comm_manager;
48 48
49 // Register already register widget model types with the comm manager.
49 // Register already-registered widget model types with the comm manager.
50 50 for (var widget_model_name in this.widget_model_types) {
51 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
51 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
52 52 }
53 53 };
54 54
55 55
56 56 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
57 57 // Register the widget with the comm manager. Make sure to pass this object's context
58 58 // in so `this` works in the call back.
59 59 if (this.comm_manager !== null) {
60 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
60 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
61 61 }
62 62 this.widget_model_types[widget_model_name] = widget_model_type;
63 63 };
64 64
65 65
66 66 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
67 67 this.widget_view_types[widget_view_name] = widget_view_type;
68 68 };
69
69 WidgetManager.prototype.handle_msg = function(msg, model) {
70 var method = msg.content.data.method;
71 switch (method) {
72 case 'display':
73 var cell = this.get_msg_cell(msg.parent_header.msg_id);
74 if (cell === null) {
75 console.log("Could not determine where the display" +
76 " message was from. Widget will not be displayed");
77 } else {
78 var view = this.create_view(model,
79 msg.content.data.view_name, cell);
80 if (view !== undefined
81 && cell.widget_subarea !== undefined
82 && cell.widget_subarea !== null) {
83 cell.widget_area.show();
84 cell.widget_subarea.append(view.$el);
85 }
86 }
87 break;
88 case 'set_snapshot':
89 var cell = this.get_msg_cell(msg.parent_header.msg_id);
90 cell.metadata.snapshot = msg.content.data.snapshot;
91 break;
92 }
93 }
94
95 WidgetManager.prototype.create_view = function(model, view_name, cell) {
96 view_name = view_name || model.get('default_view_name');
97 var ViewType = this.widget_view_types[view_name];
98 if (ViewType !== undefined && ViewType !== null) {
99 var view = new ViewType({model: model, widget_manager: this, cell: cell});
100 view.render();
101 //this.views.push(view);
102
103 /*
104 // jng: Handle when the view element is remove from the page.
105 // observe the view destruction event and do this. We may need
106 // to override the view's remove method to trigger this event.
107 var that = this;
108 view.$el.on("remove", function () {
109 var index = that.views.indexOf(view);
110 if (index > -1) {
111 that.views.splice(index, 1);
112 }
113 view.remove(); // Clean-up view
114
115 // Close the comm if there are no views left.
116 if (that.views.length() === 0) {
117 //jng: trigger comm close event
118 }
119
120
121 if (that.comm !== undefined) {
122 that.comm.close();
123 delete that.comm.model; // Delete ref so GC will collect widget model.
124 delete that.comm;
125 }
126 delete that.widget_id; // Delete id from model so widget manager cleans up.
127 });
128 */
129 return view;
130 }
131 },
70 132
71 133 WidgetManager.prototype.get_msg_cell = function (msg_id) {
134 var cell = null;
135 // First, check to see if the msg was triggered by cell execution.
72 136 if (IPython.notebook !== undefined && IPython.notebook !== null) {
73 return IPython.notebook.get_msg_cell(msg_id);
137 cell = IPython.notebook.get_msg_cell(msg_id);
138 }
139 if (cell !== null) {
140 return cell
141 }
142 // Second, check to see if a get_cell callback was defined
143 // for the message. get_cell callbacks are registered for
144 // widget messages, so this block is actually checking to see if the
145 // message was triggered by a widget.
146 var kernel = this.get_kernel();
147 if (kernel !== undefined && kernel !== null) {
148 var callbacks = kernel.get_callbacks_for_msg(msg_id);
149 if (callbacks !== undefined &&
150 callbacks.iopub !== undefined &&
151 callbacks.iopub.get_cell !== undefined) {
152
153 return callbacks.iopub.get_cell();
154 }
74 155 }
156
157 // Not triggered by a cell or widget (no get_cell callback
158 // exists).
159 return null;
75 160 };
76 161
77 162
78 163 WidgetManager.prototype.get_model = function (widget_id) {
79 164 var model = this._model_instances[widget_id];
80 165 if (model !== undefined && model.id == widget_id) {
81 166 return model;
82 167 }
83 168 return null;
84 169 };
85 170
86 171
87 172 WidgetManager.prototype.get_kernel = function () {
88 173 if (this.comm_manager === null) {
89 174 return null;
90 175 } else {
91 176 return this.comm_manager.kernel;
92 177 }
93 178 };
94 179
95 180
96 181 WidgetManager.prototype.on_create_widget = function (callback) {
97 182 this._create_widget_callback = callback;
98 183 };
99 184
100 185
101 186 WidgetManager.prototype._handle_create_widget = function (widget_model) {
102 187 if (this._create_widget_callback) {
103 188 try {
104 189 this._create_widget_callback(widget_model);
105 190 } catch (e) {
106 191 console.log("Exception in WidgetManager callback", e, widget_model);
107 192 }
108 193 }
109 194 };
110 195
111 196
112 WidgetManager.prototype._handle_com_open = function (comm, msg) {
197 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
113 198 var widget_type_name = msg.content.target_name;
114 199 var widget_model = new this.widget_model_types[widget_type_name](this, comm.comm_id, comm);
115 200 this._model_instances[comm.comm_id] = widget_model;
116 201 this._handle_create_widget(widget_model);
117 202 };
118 203
119 204 //--------------------------------------------------------------------
120 205 // Init code
121 206 //--------------------------------------------------------------------
122 207 IPython.WidgetManager = WidgetManager;
123 208 if (IPython.widget_manager === undefined || IPython.widget_manager === null) {
124 209 IPython.widget_manager = new WidgetManager();
125 210 }
126 211
127 212 return IPython.widget_manager;
128 213 });
129 }()); No newline at end of file
214 }());
@@ -1,555 +1,362 b''
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 // Base Widget Model and View classes
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgetmanager",
18 18 "underscore",
19 19 "backbone"],
20 20 function(widget_manager, underscore, backbone){
21 21
22 22 //--------------------------------------------------------------------
23 23 // WidgetModel class
24 24 //--------------------------------------------------------------------
25 25 var WidgetModel = Backbone.Model.extend({
26 26 constructor: function (widget_manager, widget_id, comm) {
27 27 this.widget_manager = widget_manager;
28 28 this.pending_msgs = 0;
29 29 this.msg_throttle = 3;
30 30 this.msg_buffer = null;
31 this.views = [];
32 31 this.id = widget_id;
33 this._custom_msg_callbacks = [];
34 32
35 33 if (comm !== undefined) {
36
37 34 // Remember comm associated with the model.
38 35 this.comm = comm;
39 36 comm.model = this;
40 37
41 38 // Hook comm messages up to model.
39 var that = this;
42 40 comm.on_close($.proxy(this._handle_comm_closed, this));
43 41 comm.on_msg($.proxy(this._handle_comm_msg, this));
44 42 }
45 43
46 44 return Backbone.Model.apply(this);
47 45 },
48 46
49 47
50 send: function (content, cell) {
51 if (this._has_comm()) {
52 // Used the last modified view as the sender of the message. This
53 // will insure that any python code triggered by the sent message
54 // can create and display widgets and output.
55 if (cell === undefined) {
56 if (this.last_modified_view !== undefined &&
57 this.last_modified_view.cell !== undefined) {
58 cell = this.last_modified_view.cell;
59 }
60 }
61 var callbacks = this._make_callbacks(cell);
48 send: function (content, callbacks) {
49 console.log('send',content, callbacks);
50 if (this.comm !== undefined) {
62 51 var data = {method: 'custom', custom_content: content};
63
64 52 this.comm.send(data, callbacks);
65 53 }
66 54 },
67 55
68
69 on_view_created: function (callback) {
70 this._view_created_callback = callback;
71 },
72
73
74 on_close: function (callback) {
75 this._close_callback = callback;
76 },
77
78
79 on_msg: function (callback, remove) {
80 if (remove) {
81 var found_index = -1;
82 for (var index in this._custom_msg_callbacks) {
83 if (callback === this._custom_msg_callbacks[index]) {
84 found_index = index;
85 break;
86 }
87 }
88
89 if (found_index >= 0) {
90 this._custom_msg_callbacks.splice(found_index, 1);
91 }
92 } else {
93 this._custom_msg_callbacks.push(callback);
94 }
95 },
96
97
98 _handle_custom_msg: function (content) {
99 for (var index in this._custom_msg_callbacks) {
100 try {
101 this._custom_msg_callbacks[index](content);
102 } catch (e) {
103 console.log("Exception in widget model msg callback", e, content);
104 }
105 }
106 },
107
108
109 56 // Handle when a widget is closed.
110 57 _handle_comm_closed: function (msg) {
111 this._execute_views_method('remove');
58 // jng: widget manager should observe the comm_close event and delete views when triggered
59 this.trigger('comm:close');
112 60 if (this._has_comm()) {
113 61 delete this.comm.model; // Delete ref so GC will collect widget model.
114 62 delete this.comm;
115 63 }
116 64 delete this.widget_id; // Delete id from model so widget manager cleans up.
117 65 },
118 66
119 67
120 // Handle incomming comm msg.
68 // Handle incoming comm msg.
121 69 _handle_comm_msg: function (msg) {
122 70 var method = msg.content.data.method;
123 71 switch (method) {
124 case 'display':
125
126 // Try to get the cell.
127 var cell = this._get_msg_cell(msg.parent_header.msg_id);
128 if (cell === null) {
129 console.log("Could not determine where the display" +
130 " message was from. Widget will not be displayed");
131 } else {
132 this.create_views(msg.content.data.view_name,
133 msg.content.data.parent,
134 cell);
135 }
136 break;
137 72 case 'update':
138 73 this.apply_update(msg.content.data.state);
139 74 break;
140 case 'add_class':
141 case 'remove_class':
142 var selector = msg.content.data.selector;
143 if (selector === undefined) {
144 selector = '';
145 }
146
147 var class_list = msg.content.data.class_list;
148 this._execute_views_method(method, selector, class_list);
149 break;
150 case 'set_snapshot':
151 var cell = this._get_msg_cell(msg.parent_header.msg_id);
152 cell.metadata.snapshot = msg.content.data.snapshot;
153 break;
154 75 case 'custom':
155 this._handle_custom_msg(msg.content.data.custom_content);
76 this.trigger('msg:custom', msg.content.data.custom_content);
156 77 break;
78 default:
79 // pass on to widget manager
80 this.widget_manager.handle_msg(msg, this);
157 81 }
158 82 },
159 83
160 84
161 85 // Handle when a widget is updated via the python side.
162 86 apply_update: function (state) {
163 87 this.updating = true;
164 88 try {
165 89 for (var key in state) {
166 90 if (state.hasOwnProperty(key)) {
167 if (key == "_css") {
168
169 // Set the css value of the model as an attribute
170 // instead of a backbone trait because we are only
171 // interested in backend css -> frontend css. In
172 // other words, if the css dict changes in the
173 // frontend, we don't need to push the changes to
174 // the backend.
175 this.css = state[key];
176 } else {
177 this.set(key, state[key]);
178 }
91 this.set(key, state[key]);
179 92 }
180 93 }
181 94 this.save();
182 95 } finally {
183 96 this.updating = false;
184 97 }
185 98 },
186 99
187 100
188 _handle_status: function (cell, msg) {
101 _handle_status: function (msg, callbacks) {
189 102 //execution_state : ('busy', 'idle', 'starting')
190 if (this._has_comm()) {
191 if (msg.content.execution_state=='idle') {
103 if (this.comm !== undefined) {
104 if (msg.content.execution_state ==='idle') {
192 105
193 106 // Send buffer if this message caused another message to be
194 107 // throttled.
195 108 if (this.msg_buffer !== null &&
196 this.msg_throttle == this.pending_msgs) {
197
198 var callbacks = this._make_callbacks(cell);
109 this.msg_throttle === this.pending_msgs) {
199 110 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
200 111 this.comm.send(data, callbacks);
201 112 this.msg_buffer = null;
202 113 } else {
203 114
204 115 // Only decrease the pending message count if the buffer
205 116 // doesn't get flushed (sent).
206 117 --this.pending_msgs;
207 118 }
208 119 }
209 120 }
210 121 },
211 122
212 123
213 124 // Custom syncronization logic.
214 125 _handle_sync: function (method, options) {
215 126 var model_json = this.toJSON();
216 127 var attr;
217 128
218 129 // Only send updated state if the state hasn't been changed
219 130 // during an update.
220 if (this._has_comm()) {
131 if (this.comm !== undefined) {
221 132 if (!this.updating) {
222 133 if (this.pending_msgs >= this.msg_throttle) {
223 134 // The throttle has been exceeded, buffer the current msg so
224 135 // it can be sent once the kernel has finished processing
225 136 // some of the existing messages.
226 137 if (method=='patch') {
227 138 if (this.msg_buffer === null) {
228 139 this.msg_buffer = $.extend({}, model_json); // Copy
229 140 }
230 141 for (attr in options.attrs) {
231 142 this.msg_buffer[attr] = options.attrs[attr];
232 143 }
233 144 } else {
234 145 this.msg_buffer = $.extend({}, model_json); // Copy
235 146 }
236 147
237 148 } else {
238 149 // We haven't exceeded the throttle, send the message like
239 150 // normal. If this is a patch operation, just send the
240 151 // changes.
241 152 var send_json = model_json;
242 153 if (method =='patch') {
243 154 send_json = {};
244 155 for (attr in options.attrs) {
245 156 send_json[attr] = options.attrs[attr];
246 157 }
247 158 }
248 159
249 160 var data = {method: 'backbone', sync_method: method, sync_data: send_json};
250
251 var cell = null;
252 if (this.last_modified_view !== undefined && this.last_modified_view !== null) {
253 cell = this.last_modified_view.cell;
254 }
255
256 var callbacks = this._make_callbacks(cell);
257 this.comm.send(data, callbacks);
161 this.comm.send(data, this.cell_callbacks());
258 162 this.pending_msgs++;
259 163 }
260 164 }
261 165 }
262 166
263 167 // Since the comm is a one-way communication, assume the message
264 168 // arrived.
265 169 return model_json;
266 170 },
267 171
268
269 _handle_view_created: function (view) {
270 if (this._view_created_callback) {
271 try {
272 this._view_created_callback(view);
273 } catch (e) {
274 console.log("Exception in widget model view displayed callback", e, view, this);
275 }
276 }
277 },
278
279
280 _execute_views_method: function (/* method_name, [argument0], [argument1], [...] */) {
281 var method_name = arguments[0];
282 var args = null;
283 if (arguments.length > 1) {
284 args = [].splice.call(arguments,1);
285 }
286
287 for (var view_index in this.views) {
288 var view = this.views[view_index];
289 var method = view[method_name];
290 if (args === null) {
291 method.apply(view);
292 } else {
293 method.apply(view, args);
294 }
295 }
296 },
297
298
299 // Create view that represents the model.
300 create_views: function (view_name, parent_id, cell) {
301 var new_views = [];
302 var view;
303
304 // Try creating and adding the view to it's parent.
305 var displayed = false;
306 if (parent_id !== undefined) {
307 var parent_model = this.widget_manager.get_model(parent_id);
308 if (parent_model !== null) {
309 var parent_views = parent_model.views;
310 for (var parent_view_index in parent_views) {
311 var parent_view = parent_views[parent_view_index];
312 if (parent_view.cell === cell) {
313 if (parent_view.display_child !== undefined) {
314 view = this._create_view(view_name, cell);
315 if (view !== null) {
316 new_views.push(view);
317 parent_view.display_child(view);
318 displayed = true;
319 this._handle_view_created(view);
320 }
321 }
322 }
323 }
324 }
325 }
326
327 // If no parent view is defined or exists. Add the view's
328 // element to cell's widget div.
329 if (!displayed) {
330 view = this._create_view(view_name, cell);
331 if (view !== null) {
332 new_views.push(view);
333
334 if (cell.widget_subarea !== undefined && cell.widget_subarea !== null) {
335 cell.widget_area.show();
336 cell.widget_subarea.append(view.$el);
337 this._handle_view_created(view);
338 }
339 }
340 }
341
342 // Force the new view(s) to update their selves
343 for (var view_index in new_views) {
344 view = new_views[view_index];
345 view.update();
346 }
347 },
348
349
350 // Create a view
351 _create_view: function (view_name, cell) {
352 var ViewType = this.widget_manager.widget_view_types[view_name];
353 if (ViewType !== undefined && ViewType !== null) {
354 var view = new ViewType({model: this});
355 view.render();
356 this.views.push(view);
357 view.cell = cell;
358
359 // Handle when the view element is remove from the page.
360 var that = this;
361 view.$el.on("remove", function () {
362 var index = that.views.indexOf(view);
363 if (index > -1) {
364 that.views.splice(index, 1);
365 }
366 view.remove(); // Clean-up view
367
368 // Close the comm if there are no views left.
369 if (that.views.length() === 0) {
370 if (that._close_callback) {
371 try {
372 that._close_callback(that);
373 } catch (e) {
374 console.log("Exception in widget model close callback", e, that);
375 }
376 }
377
378 if (that._has_comm()) {
379 that.comm.close();
380 delete that.comm.model; // Delete ref so GC will collect widget model.
381 delete that.comm;
382 }
383 delete that.widget_id; // Delete id from model so widget manager cleans up.
384 }
385 });
386 return view;
387 }
388 return null;
389 },
390
391
392 172 // Build a callback dict.
393 _make_callbacks: function (cell) {
173 cell_callbacks: function (cell) {
394 174 var callbacks = {};
175 console.log('cell_callbacks A', cell);
176 if (cell === undefined) {
177 // Used the last modified view as the sender of the message. This
178 // will insure that any python code triggered by the sent message
179 // can create and display widgets and output.
180 if (this.last_modified_view !== undefined &&
181 this.last_modified_view.cell !== undefined) {
182 cell = this.last_modified_view.cell;
183 } else {
184 cell = null;
185 }
186 }
187 console.log('cell_callbacks B', cell);
395 188 if (cell !== null) {
396 189
397 190 // Try to get output handlers
398 191 var handle_output = null;
399 192 var handle_clear_output = null;
400 193 if (cell.output_area !== undefined && cell.output_area !== null) {
401 194 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
402 195 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
403 196 }
404 197
405 // Create callback dict usign what is known
198 // Create callback dict using what is known
406 199 var that = this;
407 200 callbacks = {
408 201 iopub : {
409 202 output : handle_output,
410 203 clear_output : handle_clear_output,
411 204
412 205 status : function (msg) {
413 that._handle_status(cell, msg);
206 that._handle_status(msg, that.cell_callbacks(cell));
414 207 },
415 208
416 209 // Special function only registered by widget messages.
417 210 // Allows us to get the cell for a message so we know
418 211 // where to add widgets if the code requires it.
419 212 get_cell : function () {
420 213 return cell;
421 214 },
422 215 },
423 216 };
424 217 }
218 console.log('constructed callbacks for',cell);
425 219 return callbacks;
426 220 },
221 });
427 222
428 223
429 // Get the output area corresponding to the msg_id.
430 // cell is an instance of IPython.Cell
431 _get_msg_cell: function (msg_id) {
432
433 // First, check to see if the msg was triggered by cell execution.
434 var cell = this.widget_manager.get_msg_cell(msg_id);
435 if (cell !== null) {
436 return cell;
437 }
224 //--------------------------------------------------------------------
225 // WidgetView class
226 //--------------------------------------------------------------------
227 var BaseWidgetView = Backbone.View.extend({
228 initialize: function(options) {
229 this.model.on('change',this.update,this);
230 this.widget_manager = options.widget_manager;
231 this.comm_manager = options.widget_manager.comm_manager;
232 this.cell = options.cell
233 this.render();
234 // jng: maybe the following shouldn't be automatic---maybe the render method should take
235 // care of knowing what needs to be added as a child?
236 var children_attr = this.model.get('_children_attr');
237 for (var i in children_attr) {
238 var child_attr = children_attr[i];
239 var child_model = this.comm_manager.comms[this.model.get(child_attr)].model;
240 var child_view_name = this.child_view_name(child_attr, child_model);
241 var child_view = this.widget_manager.create_view(child_model, child_view_name, this.cell);
242 this.add_child_view(child_attr, child_view);
243 }
244 var children_lists_attr = this.model.get('_children_lists_attr')
245 for (var i in children_lists_attr) {
246 var child_attr = children_lists_attr[i];
247 var child_list = this.model.get(child_attr);
248 for (var j in child_list) {
249 var child_model = this.comm_manager.comms[child_list[j]].model;
250 var child_view_name = this.child_view_name(child_attr, child_model);
251 var child_view = this.widget_manager.create_view(child_model, child_view_name, this.cell);
252 this.add_child_view(child_attr, child_view);
253 }
254 }
255 },
256 update: function(){
257 // update thyself to be consistent with this.model
258 },
259
260 child_view: function(attr, viewname) {
261 var child_model = this.comm_manager.comms[this.model.get(attr)].model;
262 var child_view = this.widget_manager.create_view(child_model, view_name, this.cell);
263 return child_view;
264 },
265
266 render: function(){
267 // render thyself
268 },
269 child_view_name: function(attr, model) {
270 // attr is the name of the attribute we are constructing a view for
271 // model is the model stored in that attribute
272 // return a valid view_name to construct a view of that type
273 // or null for the default view for the model
274 return null;
275 },
276 add_child_view: function(attr, view) {
277 //attr is the name of the attribute containing a reference to this child
278 //view is the child view that has been constructed
279 //typically this will just add the child view's view.el attribute to some dom element
280 },
281
282 send: function (content) {
283 this.model.send(content, this.model.cell_callbacks(this.cell));
284 },
438 285
439 // Second, check to see if a get_cell callback was defined
440 // for the message. get_cell callbacks are registered for
441 // widget messages, so this block is actually checking to see if the
442 // message was triggered by a widget.
443 var kernel = this.widget_manager.get_kernel();
444 if (kernel !== undefined && kernel !== null) {
445 var callbacks = kernel.get_callbacks_for_msg(msg_id);
446 if (callbacks !== undefined &&
447 callbacks.iopub !== undefined &&
448 callbacks.iopub.get_cell !== undefined) {
449
450 return callbacks.iopub.get_cell();
451 }
452 }
453
454 // Not triggered by a cell or widget (no get_cell callback
455 // exists).
456 return null;
286 touch: function () {
287 this.model.last_modified_view = this;
288 this.model.save(this.model.changedAttributes(), {patch: true});
457 289 },
458 290
459 291
460 // Function that checks if a comm has been attached to this widget
461 // model. Returns True if a valid comm is attached.
462 _has_comm: function() {
463 return this.comm !== undefined && this.comm !== null;
464 },
465 292 });
466 293
467
468 //--------------------------------------------------------------------
469 // WidgetView class
470 //--------------------------------------------------------------------
471 var WidgetView = Backbone.View.extend({
472
473 initialize: function () {
294 var WidgetView = BaseWidgetView.extend({
295 initialize: function (options) {
474 296 this.visible = true;
475 this.model.on('sync',this.update,this);
297 BaseWidgetView.prototype.initialize.apply(this, arguments);
476 298 },
477 299
478 300 add_class: function (selector, class_list) {
479 301 var elements = this._get_selector_element(selector);
480 302 if (elements.length > 0) {
481 303 elements.addClass(class_list);
482 304 }
483 305 },
484 306
485 307 remove_class: function (selector, class_list) {
486 308 var elements = this._get_selector_element(selector);
487 309 if (elements.length > 0) {
488 310 elements.removeClass(class_list);
489 311 }
490 312 },
491 313
492
493 send: function (content) {
494 this.model.send(content, this.cell);
495 },
496
497
498 touch: function () {
499 this.model.last_modified_view = this;
500 this.model.save(this.model.changedAttributes(), {patch: true});
501 },
502
503 314 update: function () {
504 if (this.model.get('visible') !== undefined) {
505 if (this.visible != this.model.get('visible')) {
506 this.visible = this.model.get('visible');
507 if (this.visible) {
508 this.$el.show();
509 } else {
510 this.$el.hide();
511 }
512 }
315 // jng: hook into change:visible trigger
316 var visible = this.model.get('visible');
317 if (visible !== undefined && this.visible !== visible) {
318 this.visible = visible;
319 this.$el.toggle(visible)
513 320 }
514 321
515 322 if (this.model.css !== undefined) {
516 323 for (var selector in this.model.css) {
517 324 if (this.model.css.hasOwnProperty(selector)) {
518 325
519 326 // Apply the css traits to all elements that match the selector.
520 327 var elements = this._get_selector_element(selector);
521 328 if (elements.length > 0) {
522 329 var css_traits = this.model.css[selector];
523 330 for (var css_key in css_traits) {
524 331 if (css_traits.hasOwnProperty(css_key)) {
525 332 elements.css(css_key, css_traits[css_key]);
526 333 }
527 334 }
528 335 }
529 336 }
530 337 }
531 338 }
532 339 },
533 340
534 341 _get_selector_element: function (selector) {
535 342 // Get the elements via the css selector. If the selector is
536 343 // blank, apply the style to the $el_to_style element. If
537 344 // the $el_to_style element is not defined, use apply the
538 345 // style to the view's element.
539 346 var elements = this.$el.find(selector);
540 347 if (selector === undefined || selector === null || selector === '') {
541 348 if (this.$el_to_style === undefined) {
542 349 elements = this.$el;
543 350 } else {
544 351 elements = this.$el_to_style;
545 352 }
546 353 }
547 354 return elements;
548 355 },
549 356 });
550 357
551 358 IPython.WidgetModel = WidgetModel;
552 359 IPython.WidgetView = WidgetView;
553 360
554 361 return widget_manager;
555 }); No newline at end of file
362 });
@@ -1,259 +1,266 b''
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 // ContainerWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager) {
18 18
19 19 var set_flex_property = function(element, property_name, enabled) {
20 20 if (enabled) {
21 21 element.addClass(property_name);
22 22 } else {
23 23 element.removeClass(property_name);
24 24 }
25 25 };
26 26
27 27 var set_flex_properties = function(context, element) {
28 28
29 29 // Apply flexible box model properties by adding and removing
30 30 // corrosponding CSS classes.
31 31 // Defined in IPython/html/static/base/less/flexbox.less
32 32 set_flex_property(element, 'vbox', context.model.get('_vbox'));
33 33 set_flex_property(element, 'hbox', context.model.get('_hbox'));
34 34 set_flex_property(element, 'start', context.model.get('_pack_start'));
35 35 set_flex_property(element, 'center', context.model.get('_pack_center'));
36 36 set_flex_property(element, 'end', context.model.get('_pack_end'));
37 37 set_flex_property(element, 'align-start', context.model.get('_align_start'));
38 38 set_flex_property(element, 'align-center', context.model.get('_align_center'));
39 39 set_flex_property(element, 'align-end', context.model.get('_align_end'));
40 40 set_flex_property(element, 'box-flex0', context.model.get('_flex0'));
41 41 set_flex_property(element, 'box-flex1', context.model.get('_flex1'));
42 42 set_flex_property(element, 'box-flex2', context.model.get('_flex2'));
43 43 };
44 44
45 45
46 46
47 47 var ContainerModel = IPython.WidgetModel.extend({});
48 48 widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel);
49 49
50 50 var ContainerView = IPython.WidgetView.extend({
51 51
52 52 render: function(){
53 53 this.$el
54 54 .addClass('widget-container');
55 var children = this.model.get('children')
56 for(var i in this.model.get('children')) {
57
58 this.update()
55 59 },
56 60
57 61 update: function(){
58 62 set_flex_properties(this, this.$el);
59 63 return IPython.WidgetView.prototype.update.call(this);
60 64 },
61
62 display_child: function(view) {
63 this.$el.append(view.$el);
64 },
65 add_child_view: function(attr, view) {
66 if (attr==='children') {
67 this.$el.append(view.$el);
68 }
69 }
65 70 });
66 71
67 72 widget_manager.register_widget_view('ContainerView', ContainerView);
68 73
69 74
70 75 var ModalView = IPython.WidgetView.extend({
71 76
72 77 render: function(){
73 78 var that = this;
74 79 this.$el
75 80 .html('')
76 81 .on("remove", function(){
77 82 that.$window.remove();
78 83 });
79 84 this.$window = $('<div />')
80 85 .addClass('modal widget-modal')
81 86 .appendTo($('#notebook-container'))
82 87 .mousedown(function(){
83 88 that.bring_to_front();
84 89 });
85 90 this.$title_bar = $('<div />')
86 91 .addClass('popover-title')
87 92 .appendTo(this.$window)
88 93 .mousedown(function(){
89 94 that.bring_to_front();
90 95 });
91 96 this.$close = $('<button />')
92 97 .addClass('close icon-remove')
93 98 .css('margin-left', '5px')
94 99 .appendTo(this.$title_bar)
95 100 .click(function(){
96 101 that.hide();
97 102 event.stopPropagation();
98 103 });
99 104 this.$minimize = $('<button />')
100 105 .addClass('close icon-arrow-down')
101 106 .appendTo(this.$title_bar)
102 107 .click(function(){
103 108 that.popped_out = !that.popped_out;
104 109 if (!that.popped_out) {
105 110 that.$minimize
106 111 .removeClass('icon-arrow-down')
107 112 .addClass('icon-arrow-up');
108 113
109 114 that.$window
110 115 .draggable('destroy')
111 116 .resizable('destroy')
112 117 .removeClass('widget-modal modal')
113 118 .addClass('docked-widget-modal')
114 119 .detach()
115 120 .insertBefore(that.$show_button);
116 121 that.$show_button.hide();
117 122 that.$close.hide();
118 123 } else {
119 124 that.$minimize
120 125 .addClass('icon-arrow-down')
121 126 .removeClass('icon-arrow-up');
122 127
123 128 that.$window
124 129 .removeClass('docked-widget-modal')
125 130 .addClass('widget-modal modal')
126 131 .detach()
127 132 .appendTo($('#notebook-container'))
128 133 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
129 134 .resizable()
130 135 .children('.ui-resizable-handle').show();
131 136 that.show();
132 137 that.$show_button.show();
133 138 that.$close.show();
134 139 }
135 140 event.stopPropagation();
136 141 });
137 142 this.$title = $('<div />')
138 143 .addClass('widget-modal-title')
139 144 .html('&nbsp;')
140 145 .appendTo(this.$title_bar);
141 146 this.$body = $('<div />')
142 147 .addClass('modal-body')
143 148 .addClass('widget-modal-body')
144 149 .addClass('widget-container')
145 150 .appendTo(this.$window);
146 151
147 152 this.$show_button = $('<button />')
148 153 .html('&nbsp;')
149 154 .addClass('btn btn-info widget-modal-show')
150 155 .appendTo(this.$el)
151 156 .click(function(){
152 157 that.show();
153 158 });
154 159
155 160 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
156 161 this.$window.resizable();
157 162 this.$window.on('resize', function(){
158 163 that.$body.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
159 164 });
160 165
161 166 this.$el_to_style = this.$body;
162 167 this._shown_once = false;
163 168 this.popped_out = true;
164 169 },
165 170
166 171 hide: function() {
167 172 this.$window.hide();
168 173 this.$show_button.removeClass('btn-info');
169 174 },
170 175
171 176 show: function() {
172 177 this.$show_button.addClass('btn-info');
173 178
174 179 this.$window.show();
175 180 if (this.popped_out) {
176 181 this.$window.css("positon", "absolute");
177 182 this.$window.css("top", "0px");
178 183 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
179 184 $(window).scrollLeft()) + "px");
180 185 this.bring_to_front();
181 186 }
182 187 },
183 188
184 189 bring_to_front: function() {
185 190 var $widget_modals = $(".widget-modal");
186 191 var max_zindex = 0;
187 192 $widget_modals.each(function (index, el){
188 193 max_zindex = Math.max(max_zindex, parseInt($(el).css('z-index')));
189 194 });
190 195
191 196 // Start z-index of widget modals at 2000
192 197 max_zindex = Math.max(max_zindex, 2000);
193 198
194 199 $widget_modals.each(function (index, el){
195 200 $el = $(el);
196 201 if (max_zindex == parseInt($el.css('z-index'))) {
197 202 $el.css('z-index', max_zindex - 1);
198 203 }
199 204 });
200 205 this.$window.css('z-index', max_zindex);
201 206 },
202 207
203 208 update: function(){
204 209 set_flex_properties(this, this.$body);
205 210
206 211 var description = this.model.get('description');
207 212 description = description.replace(/ /g, '&nbsp;', 'm');
208 213 description = description.replace(/\n/g, '<br>\n', 'm');
209 214 if (description.length === 0) {
210 215 this.$title.html('&nbsp;'); // Preserve title height
211 216 } else {
212 217 this.$title.html(description);
213 218 }
214 219
215 220 var button_text = this.model.get('button_text');
216 221 button_text = button_text.replace(/ /g, '&nbsp;', 'm');
217 222 button_text = button_text.replace(/\n/g, '<br>\n', 'm');
218 223 if (button_text.length === 0) {
219 224 this.$show_button.html('&nbsp;'); // Preserve button height
220 225 } else {
221 226 this.$show_button.html(button_text);
222 227 }
223 228
224 229 if (!this._shown_once) {
225 230 this._shown_once = true;
226 231 this.show();
227 232 }
228 233
229 234 return IPython.WidgetView.prototype.update.call(this);
230 235 },
231 236
232 display_child: function(view) {
233 this.$body.append(view.$el);
237 add_child_view: function(attr, view) {
238 if (attr==='children') {
239 this.$body.append(view.$el);
240 }
234 241 },
235 242
236 243 _get_selector_element: function(selector) {
237 244
238 245 // Since the modal actually isn't within the $el in the DOM, we need to extend
239 246 // the selector logic to allow the user to set css on the modal if need be.
240 247 // The convention used is:
241 248 // "modal" - select the modal div
242 249 // "modal [selector]" - select element(s) within the modal div.
243 250 // "[selector]" - select elements within $el
244 251 // "" - select the $el_to_style
245 252 if (selector.substring(0, 5) == 'modal') {
246 253 if (selector == 'modal') {
247 254 return this.$window;
248 255 } else {
249 256 return this.$window.find(selector.substring(6));
250 257 }
251 258 } else {
252 259 return IPython.WidgetView.prototype._get_selector_element.call(this, selector);
253 260 }
254 261 },
255 262
256 263 });
257 264
258 265 widget_manager.register_widget_view('ModalView', ModalView);
259 266 });
@@ -1,255 +1,257 b''
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 // FloatRangeWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18 var FloatRangeWidgetModel = IPython.WidgetModel.extend({});
19 19 widget_manager.register_widget_model('FloatRangeWidgetModel', FloatRangeWidgetModel);
20 20
21 21 var FloatSliderView = IPython.WidgetView.extend({
22 22
23 23 // Called when view is rendered.
24 24 render : function(){
25 25 this.$el
26 26 .addClass('widget-hbox-single')
27 27 .html('');
28 28 this.$label = $('<div />')
29 29 .appendTo(this.$el)
30 30 .addClass('widget-hlabel')
31 31 .hide();
32 32 this.$slider = $('<div />')
33 33 .slider({})
34 34 .addClass('slider');
35 35
36 36 // Put the slider in a container
37 37 this.$slider_container = $('<div />')
38 38 .addClass('widget-hslider')
39 39 .append(this.$slider);
40 40 this.$el_to_style = this.$slider_container; // Set default element to style
41 41 this.$el.append(this.$slider_container);
42 42
43 43 // Set defaults.
44 44 this.update();
45 45 },
46 46
47 47 // Handles: Backend -> Frontend Sync
48 48 // Frontent -> Frontend Sync
49 49 update : function(){
50 50 // Slider related keys.
51 51 var _keys = ['step', 'max', 'min', 'disabled'];
52 52 for (var index in _keys) {
53 53 var key = _keys[index];
54 54 if (this.model.get(key) !== undefined) {
55 55 this.$slider.slider("option", key, this.model.get(key));
56 56 }
57 57 }
58 58
59 59 // WORKAROUND FOR JQUERY SLIDER BUG.
60 60 // The horizontal position of the slider handle
61 61 // depends on the value of the slider at the time
62 62 // of orientation change. Before applying the new
63 63 // workaround, we set the value to the minimum to
64 64 // make sure that the horizontal placement of the
65 65 // handle in the vertical slider is always
66 66 // consistent.
67 67 var orientation = this.model.get('orientation');
68 68 var value = this.model.get('min');
69 69 this.$slider.slider('option', 'value', value);
70 70 this.$slider.slider('option', 'orientation', orientation);
71 71 value = this.model.get('value');
72 72 this.$slider.slider('option', 'value', value);
73 console.log('updating value',value)
73 74
74 75 // Use the right CSS classes for vertical & horizontal sliders
75 76 if (orientation=='vertical') {
76 77 this.$slider_container
77 78 .removeClass('widget-hslider')
78 79 .addClass('widget-vslider');
79 80 this.$el
80 81 .removeClass('widget-hbox-single')
81 82 .addClass('widget-vbox-single');
82 83 this.$label
83 84 .removeClass('widget-hlabel')
84 85 .addClass('widget-vlabel');
85 86
86 87 } else {
87 88 this.$slider_container
88 89 .removeClass('widget-vslider')
89 90 .addClass('widget-hslider');
90 91 this.$el
91 92 .removeClass('widget-vbox-single')
92 93 .addClass('widget-hbox-single');
93 94 this.$label
94 95 .removeClass('widget-vlabel')
95 96 .addClass('widget-hlabel');
96 97 }
97 98
98 99 var description = this.model.get('description');
99 100 if (description.length === 0) {
100 101 this.$label.hide();
101 102 } else {
102 103 this.$label.html(description);
103 104 this.$label.show();
104 105 }
105 106 return IPython.WidgetView.prototype.update.call(this);
106 107 },
107 108
108 109 // Handles: User input
109 110 events: { "slide" : "handleSliderChange" },
110 111 handleSliderChange: function(e, ui) {
111 112 this.model.set('value', ui.value);
113 console.log('triggered value change', ui.value, this.model);
112 114 this.touch();
113 115 },
114 116 });
115 117
116 118 widget_manager.register_widget_view('FloatSliderView', FloatSliderView);
117 119
118 120
119 121 var FloatTextView = IPython.WidgetView.extend({
120 122
121 123 // Called when view is rendered.
122 124 render : function(){
123 125 this.$el
124 126 .addClass('widget-hbox-single')
125 127 .html('');
126 128 this.$label = $('<div />')
127 129 .appendTo(this.$el)
128 130 .addClass('widget-hlabel')
129 131 .hide();
130 132 this.$textbox = $('<input type="text" />')
131 133 .addClass('input')
132 134 .addClass('widget-numeric-text')
133 135 .appendTo(this.$el);
134 136 this.$el_to_style = this.$textbox; // Set default element to style
135 137 this.update(); // Set defaults.
136 138 },
137 139
138 140 // Handles: Backend -> Frontend Sync
139 141 // Frontent -> Frontend Sync
140 142 update : function(){
141 143 var value = this.model.get('value');
142 144 if (!this.changing && parseFloat(this.$textbox.val()) != value) {
143 145 this.$textbox.val(value);
144 146 }
145 147
146 148 if (this.model.get('disabled')) {
147 149 this.$textbox.attr('disabled','disabled');
148 150 } else {
149 151 this.$textbox.removeAttr('disabled');
150 152 }
151 153
152 154 var description = this.model.get('description');
153 155 if (description.length === 0) {
154 156 this.$label.hide();
155 157 } else {
156 158 this.$label.html(description);
157 159 this.$label.show();
158 160 }
159 161 return IPython.WidgetView.prototype.update.call(this);
160 162 },
161 163
162 164
163 165 events: {"keyup input" : "handleChanging",
164 166 "paste input" : "handleChanging",
165 167 "cut input" : "handleChanging",
166 168 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
167 169
168 170 // Handles and validates user input.
169 171 handleChanging: function(e) {
170 172
171 173 // Try to parse value as a float.
172 174 var numericalValue = 0.0;
173 175 if (e.target.value !== '') {
174 176 numericalValue = parseFloat(e.target.value);
175 177 }
176 178
177 179 // If parse failed, reset value to value stored in model.
178 180 if (isNaN(numericalValue)) {
179 181 e.target.value = this.model.get('value');
180 182 } else if (!isNaN(numericalValue)) {
181 183 if (this.model.get('max') !== undefined) {
182 184 numericalValue = Math.min(this.model.get('max'), numericalValue);
183 185 }
184 186 if (this.model.get('min') !== undefined) {
185 187 numericalValue = Math.max(this.model.get('min'), numericalValue);
186 188 }
187 189
188 190 // Apply the value if it has changed.
189 191 if (numericalValue != this.model.get('value')) {
190 192 this.changing = true;
191 193 this.model.set('value', numericalValue);
192 194 this.touch();
193 195 this.changing = false;
194 196 }
195 197 }
196 198 },
197 199
198 200 // Applies validated input.
199 201 handleChanged: function(e) {
200 202 // Update the textbox
201 203 if (this.model.get('value') != e.target.value) {
202 204 e.target.value = this.model.get('value');
203 205 }
204 206 }
205 207 });
206 208
207 209 widget_manager.register_widget_view('FloatTextView', FloatTextView);
208 210
209 211
210 212 var ProgressView = IPython.WidgetView.extend({
211 213
212 214 // Called when view is rendered.
213 215 render : function(){
214 216 this.$el
215 217 .addClass('widget-hbox-single')
216 218 .html('');
217 219 this.$label = $('<div />')
218 220 .appendTo(this.$el)
219 221 .addClass('widget-hlabel')
220 222 .hide();
221 223 this.$progress = $('<div />')
222 224 .addClass('progress')
223 225 .addClass('widget-progress')
224 226 .appendTo(this.$el);
225 227 this.$el_to_style = this.$progress; // Set default element to style
226 228 this.$bar = $('<div />')
227 229 .addClass('bar')
228 230 .css('width', '50%')
229 231 .appendTo(this.$progress);
230 232 this.update(); // Set defaults.
231 233 },
232 234
233 235 // Handles: Backend -> Frontend Sync
234 236 // Frontent -> Frontend Sync
235 237 update : function(){
236 238 var value = this.model.get('value');
237 239 var max = this.model.get('max');
238 240 var min = this.model.get('min');
239 241 var percent = 100.0 * (value - min) / (max - min);
240 242 this.$bar.css('width', percent + '%');
241 243
242 244 var description = this.model.get('description');
243 245 if (description.length === 0) {
244 246 this.$label.hide();
245 247 } else {
246 248 this.$label.html(description);
247 249 this.$label.show();
248 250 }
249 251 return IPython.WidgetView.prototype.update.call(this);
250 252 },
251 253
252 254 });
253 255
254 256 widget_manager.register_widget_view('ProgressView', ProgressView);
255 257 });
@@ -1,179 +1,183 b''
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 // MultiContainerWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/base"], function(widget_manager){
18 18 var MulticontainerModel = IPython.WidgetModel.extend({});
19 19 widget_manager.register_widget_model('MulticontainerWidgetModel', MulticontainerModel);
20 20
21 21 var AccordionView = IPython.WidgetView.extend({
22 22
23 23 render: function(){
24 24 var guid = 'accordion' + IPython.utils.uuid();
25 25 this.$el
26 26 .attr('id', guid)
27 27 .addClass('accordion');
28 28 this.containers = [];
29 29 },
30
31 30 update: function() {
32 31 // Set tab titles
33 32 var titles = this.model.get('_titles');
34 33 for (var page_index in titles) {
35 34
36 35 var accordian = this.containers[page_index];
37 36 if (accordian !== undefined) {
38 37 accordian
39 38 .find('.accordion-heading')
40 39 .find('.accordion-toggle')
41 40 .html(titles[page_index]);
42 41 }
43 42 }
44 43
45 44 // Set selected page
46 45 var selected_index = this.model.get("selected_index");
47 46 if (0 <= selected_index && selected_index < this.containers.length) {
48 47 for (var index in this.containers) {
49 48 if (index==selected_index) {
50 49 this.containers[index].find('.accordion-body').collapse('show');
51 50 } else {
52 51 this.containers[index].find('.accordion-body').collapse('hide');
53 52 }
54 53
55 54 }
56 55 }
57 56
58 57 return IPython.WidgetView.prototype.update.call(this);
59 58 },
60 59
61 display_child: function(view) {
60 add_child_view: function(attr, view) {
62 61
63 62 var index = this.containers.length;
64 63 var uuid = IPython.utils.uuid();
65 64 var accordion_group = $('<div />')
66 65 .addClass('accordion-group')
67 66 .appendTo(this.$el);
68 67 var accordion_heading = $('<div />')
69 68 .addClass('accordion-heading')
70 69 .appendTo(accordion_group);
71 70 var that = this;
72 71 var accordion_toggle = $('<a />')
73 72 .addClass('accordion-toggle')
74 73 .attr('data-toggle', 'collapse')
75 74 .attr('data-parent', '#' + this.$el.attr('id'))
76 75 .attr('href', '#' + uuid)
77 76 .click(function(evt){
78 77 that.model.set("selected_index", index);
79 78 that.touch();
80 79 })
81 80 .html('Page ' + index)
82 81 .appendTo(accordion_heading);
83 82 var accordion_body = $('<div />', {id: uuid})
84 83 .addClass('accordion-body collapse')
85 84 .appendTo(accordion_group);
86 85 var accordion_inner = $('<div />')
87 86 .addClass('accordion-inner')
88 87 .appendTo(accordion_body);
89 88 this.containers.push(accordion_group);
90 89 accordion_inner.append(view.$el);
91 90
92 91 this.update();
93 92
94 93 // Stupid workaround to close the bootstrap accordion tabs which
95 94 // open by default even though they don't have the `in` class
96 95 // attached to them. For some reason a delay is required.
97 96 // TODO: Better fix.
98 97 setTimeout(function(){ that.update(); }, 500);
99 98 },
100 99 });
101 100
102 101 widget_manager.register_widget_view('AccordionView', AccordionView);
103 102
104 103 var TabView = IPython.WidgetView.extend({
105 104
105 initialize: function() {
106 this.containers = [];
107 IPython.WidgetView.prototype.initialize.apply(this, arguments);
108 },
109
106 110 render: function(){
111 console.log('rendering tabs', this);
107 112 var uuid = 'tabs'+IPython.utils.uuid();
108 113 var that = this;
109 114 this.$tabs = $('<div />', {id: uuid})
110 115 .addClass('nav')
111 116 .addClass('nav-tabs')
112 117 .appendTo(this.$el);
113 118 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
114 119 .addClass('tab-content')
115 120 .appendTo(this.$el);
116
117 this.containers = [];
121 this.update();
118 122 },
119 123
120 124 update: function() {
121 125 // Set tab titles
122 126 var titles = this.model.get('_titles');
123 127 for (var page_index in titles) {
124 128 var tab_text = this.containers[page_index];
125 129 if (tab_text !== undefined) {
126 130 tab_text.html(titles[page_index]);
127 131 }
128 132 }
129 133
130 134 var selected_index = this.model.get('selected_index');
131 135 if (0 <= selected_index && selected_index < this.containers.length) {
132 136 this.select_page(selected_index);
133 137 }
134 138
135 139 return IPython.WidgetView.prototype.update.call(this);
136 140 },
137 141
138 display_child: function(view) {
139
142 add_child_view: function(attr, view) {
143 console.log('adding child view', attr, view);
140 144 var index = this.containers.length;
141 145 var uuid = IPython.utils.uuid();
142 146
143 147 var that = this;
144 148 var tab = $('<li />')
145 149 .css('list-style-type', 'none')
146 150 .appendTo(this.$tabs);
147 151 var tab_text = $('<a />')
148 152 .attr('href', '#' + uuid)
149 153 .attr('data-toggle', 'tab')
150 154 .html('Page ' + index)
151 155 .appendTo(tab)
152 156 .click(function (e) {
153 157 that.model.set("selected_index", index);
154 158 that.touch();
155 159 that.select_page(index);
156 160 });
157 161 this.containers.push(tab_text);
158 162
159 163 var contents_div = $('<div />', {id: uuid})
160 164 .addClass('tab-pane')
161 165 .addClass('fade')
162 166 .append(view.$el)
163 167 .appendTo(this.$tab_contents);
164 168
165 169 if (index === 0) {
166 170 tab_text.tab('show');
167 171 }
168 172 this.update();
169 173 },
170 174
171 175 select_page: function(index) {
172 176 this.$tabs.find('li')
173 177 .removeClass('active');
174 178 this.containers[index].tab('show');
175 179 },
176 180 });
177 181
178 182 widget_manager.register_widget_view('TabView', TabView);
179 183 });
@@ -1,198 +1,198 b''
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 // Comm and CommManager bases
10 10 //============================================================================
11 11 /**
12 12 * Base Comm classes
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule comm
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19 "use strict";
20 20
21 21 //-----------------------------------------------------------------------
22 22 // CommManager class
23 23 //-----------------------------------------------------------------------
24 24
25 25 var CommManager = function (kernel) {
26 26 this.comms = {};
27 27 this.targets = {};
28 28 if (kernel !== undefined) {
29 29 this.init_kernel(kernel);
30 30 }
31 31 };
32 32
33 33 CommManager.prototype.init_kernel = function (kernel) {
34 34 // connect the kernel, and register message handlers
35 35 this.kernel = kernel;
36 36 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
37 37 for (var i = 0; i < msg_types.length; i++) {
38 38 var msg_type = msg_types[i];
39 39 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
40 40 }
41 41 };
42 42
43 43 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
44 44 // Create a new Comm, register it, and open its Kernel-side counterpart
45 45 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
46 46 var comm = new Comm(target_name);
47 47 this.register_comm(comm);
48 48 comm.open(data, callbacks, metadata);
49 49 return comm;
50 50 };
51 51
52 52 CommManager.prototype.register_target = function (target_name, f) {
53 53 // Register a target function for a given target name
54 54 this.targets[target_name] = f;
55 55 };
56 56
57 57 CommManager.prototype.unregister_target = function (target_name, f) {
58 58 // Unregister a target function for a given target name
59 59 delete this.targets[target_name];
60 60 };
61 61
62 62 CommManager.prototype.register_comm = function (comm) {
63 63 // Register a comm in the mapping
64 64 this.comms[comm.comm_id] = comm;
65 65 comm.kernel = this.kernel;
66 66 return comm.comm_id;
67 67 };
68 68
69 69 CommManager.prototype.unregister_comm = function (comm_id) {
70 70 // Remove a comm from the mapping
71 71 delete this.comms[comm_id];
72 72 };
73 73
74 74 // comm message handlers
75 75
76 76 CommManager.prototype.comm_open = function (msg) {
77 77 var content = msg.content;
78 78 var f = this.targets[content.target_name];
79 79 if (f === undefined) {
80 80 console.log("No such target registered: ", content.target_name);
81 81 console.log("Available targets are: ", this.targets);
82 82 return;
83 83 }
84 84 var comm = new Comm(content.target_name, content.comm_id);
85 85 this.register_comm(comm);
86 86 try {
87 87 f(comm, msg);
88 88 } catch (e) {
89 console.log("Exception opening new comm:", e, msg);
89 console.log("Exception opening new comm:", e, e.stack, msg);
90 90 comm.close();
91 91 this.unregister_comm(comm);
92 92 }
93 93 };
94 94
95 95 CommManager.prototype.comm_close = function (msg) {
96 96 var content = msg.content;
97 97 var comm = this.comms[content.comm_id];
98 98 if (comm === undefined) {
99 99 return;
100 100 }
101 101 delete this.comms[content.comm_id];
102 102 try {
103 103 comm.handle_close(msg);
104 104 } catch (e) {
105 console.log("Exception closing comm: ", e, msg);
105 console.log("Exception closing comm: ", e, e.stack, msg);
106 106 }
107 107 };
108 108
109 109 CommManager.prototype.comm_msg = function (msg) {
110 110 var content = msg.content;
111 111 var comm = this.comms[content.comm_id];
112 112 if (comm === undefined) {
113 113 return;
114 114 }
115 115 try {
116 116 comm.handle_msg(msg);
117 117 } catch (e) {
118 console.log("Exception handling comm msg: ", e, msg);
118 console.log("Exception handling comm msg: ", e, e.stack, msg);
119 119 }
120 120 };
121 121
122 122 //-----------------------------------------------------------------------
123 123 // Comm base class
124 124 //-----------------------------------------------------------------------
125 125
126 126 var Comm = function (target_name, comm_id) {
127 127 this.target_name = target_name;
128 128 this.comm_id = comm_id || IPython.utils.uuid();
129 129 this._msg_callback = this._close_callback = null;
130 130 };
131 131
132 132 // methods for sending messages
133 133 Comm.prototype.open = function (data, callbacks, metadata) {
134 134 var content = {
135 135 comm_id : this.comm_id,
136 136 target_name : this.target_name,
137 137 data : data || {},
138 138 };
139 139 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
140 140 };
141 141
142 142 Comm.prototype.send = function (data, callbacks, metadata) {
143 143 var content = {
144 144 comm_id : this.comm_id,
145 145 data : data || {},
146 146 };
147 147 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata);
148 148 };
149 149
150 150 Comm.prototype.close = function (data, callbacks, metadata) {
151 151 var content = {
152 152 comm_id : this.comm_id,
153 153 data : data || {},
154 154 };
155 155 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
156 156 };
157 157
158 158 // methods for registering callbacks for incoming messages
159 159 Comm.prototype._register_callback = function (key, callback) {
160 160 this['_' + key + '_callback'] = callback;
161 161 };
162 162
163 163 Comm.prototype.on_msg = function (callback) {
164 164 this._register_callback('msg', callback);
165 165 };
166 166
167 167 Comm.prototype.on_close = function (callback) {
168 168 this._register_callback('close', callback);
169 169 };
170 170
171 171 // methods for handling incoming messages
172 172
173 173 Comm.prototype._maybe_callback = function (key, msg) {
174 174 var callback = this['_' + key + '_callback'];
175 175 if (callback) {
176 176 try {
177 177 callback(msg);
178 178 } catch (e) {
179 console.log("Exception in Comm callback", e, msg);
179 console.log("Exception in Comm callback", e, e.stack, msg);
180 180 }
181 181 }
182 182 };
183 183
184 184 Comm.prototype.handle_msg = function (msg) {
185 185 this._maybe_callback('msg', msg);
186 186 };
187 187
188 188 Comm.prototype.handle_close = function (msg) {
189 189 this._maybe_callback('close', msg);
190 190 };
191 191
192 192 IPython.CommManager = CommManager;
193 193 IPython.Comm = Comm;
194 194
195 195 return IPython;
196 196
197 197 }(IPython));
198 198
@@ -1,445 +1,450 b''
1 1 """Base Widget class. Allows user to create widgets in the backend that render
2 2 in the IPython notebook frontend.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from copy import copy
16 16 from glob import glob
17 17 import uuid
18 18 import sys
19 19 import os
20 20 import inspect
21 21 import types
22 22
23 23 import IPython
24 24 from IPython.kernel.comm import Comm
25 25 from IPython.config import LoggingConfigurable
26 26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
27 27 from IPython.display import Javascript, display
28 28 from IPython.utils.py3compat import string_types
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Classes
32 32 #-----------------------------------------------------------------------------
33 33
34 34 class BaseWidget(LoggingConfigurable):
35 35
36 36 # Shared declarations (Class level)
37 _keys = List(Unicode, help="List of keys comprising the state of the model.")
38 _children_attr = List(Unicode, help="List of keys of children objects of the model.")
39 _children_lists_attr = List(Unicode, help="List of keys containing lists of children objects of the model.")
37 _keys = List(Unicode, default_value = [],
38 help="List of keys comprising the state of the model.", allow_none=False)
39 _children_attr = List(Unicode, default_value = [],
40 help="List of keys of children objects of the model.", allow_none=False)
41 _children_lists_attr = List(Unicode, default_value = [],
42 help="List of keys containing lists of children objects of the model.",
43 allow_none=False)
40 44 widget_construction_callback = None
41 45
42 46 def on_widget_constructed(callback):
43 47 """Class method, registers a callback to be called when a widget is
44 48 constructed. The callback must have the following signature:
45 49 callback(widget)"""
46 50 Widget.widget_construction_callback = callback
47 51
48 52 def _handle_widget_constructed(widget):
49 53 """Class method, called when a widget is constructed."""
50 54 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
51 55 Widget.widget_construction_callback(widget)
52 56
53 57
54 58
55 59 # Public declarations (Instance level)
56 60 target_name = Unicode('widget', help="""Name of the backbone model
57 61 registered in the frontend to create and sync this widget with.""")
58 62 default_view_name = Unicode(help="""Default view registered in the frontend
59 63 to use to represent the widget.""")
60 64
61 65 # Private/protected declarations
66 # todo: change this to a context manager
62 67 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
63 68 _displayed = False
64 _comm = None
69 _comm = Instance('IPython.kernel.comm.Comm')
65 70
66 71 def __init__(self, **kwargs):
67 72 """Public constructor
68 73 """
69 74 self._display_callbacks = []
70 75 self._msg_callbacks = []
71 76 super(BaseWidget, self).__init__(**kwargs)
72 77
73 78 # Register after init to allow default values to be specified
74 79 # TODO: register three different handlers, one for each list, and abstract out the common parts
80 #print self.keys, self._children_attr, self._children_lists_attr
75 81 self.on_trait_change(self._handle_property_changed, self.keys+self._children_attr+self._children_lists_attr)
76 82 Widget._handle_widget_constructed(self)
77 83
78 84 def __del__(self):
79 85 """Object disposal"""
80 86 self.close()
81 87
82 88
83 89 def close(self):
84 90 """Close method. Closes the widget which closes the underlying comm.
85 91 When the comm is closed, all of the widget views are automatically
86 92 removed from the frontend."""
87 93 self._close_communication()
88 94
89 95
90 96 # Properties
91 97 @property
92 98 def keys(self):
93 keys = ['_children_attr', '_children_lists_attr']
99 keys = ['_children_attr', '_children_lists_attr', 'default_view_name']
94 100 keys.extend(self._keys)
95 101 return keys
96 102
97 103 @property
98 104 def comm(self):
99 105 if self._comm is None:
100 106 self._open_communication()
101 107 return self._comm
102 108
103 109 # Event handlers
104 110 def _handle_msg(self, msg):
105 111 """Called when a msg is recieved from the frontend"""
106 112 data = msg['content']['data']
107 113 method = data['method']
108 114
109 115 # Handle backbone sync methods CREATE, PATCH, and UPDATE
110 116 if method == 'backbone':
111 117 if 'sync_method' in data and 'sync_data' in data:
112 118 sync_method = data['sync_method']
113 119 sync_data = data['sync_data']
114 120 self._handle_recieve_state(sync_data) # handles all methods
115 121
116 122 # Handle a custom msg from the front-end
117 123 elif method == 'custom':
118 124 if 'custom_content' in data:
119 125 self._handle_custom_msg(data['custom_content'])
120 126
121
122 127 def _handle_custom_msg(self, content):
123 128 """Called when a custom msg is recieved."""
124 129 for handler in self._msg_callbacks:
125 130 if callable(handler):
126 131 argspec = inspect.getargspec(handler)
127 132 nargs = len(argspec[0])
128 133
129 134 # Bound methods have an additional 'self' argument
130 135 if isinstance(handler, types.MethodType):
131 136 nargs -= 1
132 137
133 138 # Call the callback
134 139 if nargs == 1:
135 140 handler(content)
136 141 elif nargs == 2:
137 142 handler(self, content)
138 143 else:
139 144 raise TypeError('Widget msg callback must ' \
140 145 'accept 1 or 2 arguments, not %d.' % nargs)
141 146
142 147
143 148 def _handle_recieve_state(self, sync_data):
144 149 """Called when a state is recieved from the frontend."""
145 150 # Use _keys instead of keys - Don't get retrieve the css from the client side.
146 151 for name in self._keys:
147 152 if name in sync_data:
148 153 try:
149 154 self._property_lock = (name, sync_data[name])
150 155 setattr(self, name, sync_data[name])
151 156 finally:
152 157 self._property_lock = (None, None)
153 158
154 159
155 160 def _handle_property_changed(self, name, old, new):
156 """Called when a proeprty has been changed."""
161 """Called when a property has been changed."""
157 162 # Make sure this isn't information that the front-end just sent us.
158 163 if self._property_lock[0] != name and self._property_lock[1] != new:
159 164 # Send new state to frontend
160 165 self.send_state(key=name)
161 166
162 167 def _handle_displayed(self, **kwargs):
163 168 """Called when a view has been displayed for this widget instance
164 169
165 170 Parameters
166 171 ----------
167 172 [view_name]: unicode (optional kwarg)
168 173 Name of the view that was displayed."""
169 174 for handler in self._display_callbacks:
170 175 if callable(handler):
171 176 argspec = inspect.getargspec(handler)
172 177 nargs = len(argspec[0])
173 178
174 179 # Bound methods have an additional 'self' argument
175 180 if isinstance(handler, types.MethodType):
176 181 nargs -= 1
177 182
178 183 # Call the callback
179 184 if nargs == 0:
180 185 handler()
181 186 elif nargs == 1:
182 187 handler(self)
183 188 elif nargs == 2:
184 189 handler(self, kwargs.get('view_name', None))
185 190 else:
186 191 handler(self, **kwargs)
187 192
188 193 # Public methods
189 194 def send_state(self, key=None):
190 195 """Sends the widget state, or a piece of it, to the frontend.
191 196
192 197 Parameters
193 198 ----------
194 199 key : unicode (optional)
195 200 A single property's name to sync with the frontend.
196 201 """
197 202 self._send({"method": "update",
198 203 "state": self.get_state()})
199 204
200 def get_state(self, key=None)
205 def get_state(self, key=None):
201 206 """Gets the widget state, or a piece of it.
202 207
203 208 Parameters
204 209 ----------
205 210 key : unicode (optional)
206 211 A single property's name to get.
207 212 """
208 213 state = {}
209 214
210 215 # If a key is provided, just send the state of that key.
211 216 if key is None:
212 217 keys = self.keys[:]
213 218 children_attr = self._children_attr[:]
214 219 children_lists_attr = self._children_lists_attr[:]
215 220 else:
216 221 keys = []
217 222 children_attr = []
218 223 children_lists_attr = []
219 224 if key in self._children_attr:
220 225 children_attr.append(key)
221 226 elif key in self._children_lists_attr:
222 227 children_lists_attr.append(key)
223 228 else:
224 229 keys.append(key)
225 230 for k in keys:
226 231 state[k] = getattr(self, k)
227 232 for k in children_attr:
228 233 # automatically create models on the browser side if they aren't already created
229 234 state[k] = getattr(self, k).comm.comm_id
230 235 for k in children_lists_attr:
231 236 # automatically create models on the browser side if they aren't already created
232 237 state[k] = [i.comm.comm_id for i in getattr(self, k)]
233 238 return state
234 239
235 240
236 241 def send(self, content):
237 242 """Sends a custom msg to the widget model in the front-end.
238 243
239 244 Parameters
240 245 ----------
241 246 content : dict
242 247 Content of the message to send.
243 248 """
244 249 self._send({"method": "custom",
245 250 "custom_content": content})
246 251
247 252
248 253 def on_msg(self, callback, remove=False):
249 254 """Register a callback for when a custom msg is recieved from the front-end
250 255
251 256 Parameters
252 257 ----------
253 258 callback: method handler
254 259 Can have a signature of:
255 260 - callback(content)
256 261 - callback(sender, content)
257 262 remove: bool
258 263 True if the callback should be unregistered."""
259 264 if remove and callback in self._msg_callbacks:
260 265 self._msg_callbacks.remove(callback)
261 266 elif not remove and not callback in self._msg_callbacks:
262 267 self._msg_callbacks.append(callback)
263 268
264 269
265 270 def on_displayed(self, callback, remove=False):
266 271 """Register a callback to be called when the widget has been displayed
267 272
268 273 Parameters
269 274 ----------
270 275 callback: method handler
271 276 Can have a signature of:
272 277 - callback()
273 278 - callback(sender)
274 279 - callback(sender, view_name)
275 280 - callback(sender, **kwargs)
276 281 kwargs from display call passed through without modification.
277 282 remove: bool
278 283 True if the callback should be unregistered."""
279 284 if remove and callback in self._display_callbacks:
280 285 self._display_callbacks.remove(callback)
281 286 elif not remove and not callback in self._display_callbacks:
282 287 self._display_callbacks.append(callback)
283 288
284 289
285 290 # Support methods
286 291 def _repr_widget_(self, **kwargs):
287 292 """Function that is called when `IPython.display.display` is called on
288 293 the widget.
289 294
290 295 Parameters
291 296 ----------
292 297 view_name: unicode (optional)
293 298 View to display in the frontend. Overrides default_view_name."""
294 299 view_name = kwargs.get('view_name', self.default_view_name)
295 300
296 301 # Create a communication.
297 302 self._open_communication()
298 303
299 304 # Make sure model is syncronized
300 305 self.send_state()
301 306
302 307 # Show view.
303 308 self._send({"method": "display", "view_name": view_name})
304 309 self._displayed = True
305 310 self._handle_displayed(**kwargs)
306 311
307 312
308 313 def _open_communication(self):
309 314 """Opens a communication with the front-end."""
310 315 # Create a comm.
311 if not hasattr(self, '_comm') or self._comm is None:
316 if self._comm is None:
312 317 self._comm = Comm(target_name=self.target_name)
313 318 self._comm.on_msg(self._handle_msg)
314 319 self._comm.on_close(self._close_communication)
315 320
321 # first update
322 self.send_state()
323
316 324
317 325 def _close_communication(self):
318 326 """Closes a communication with the front-end."""
319 if hasattr(self, '_comm') and self._comm is not None:
327 if self._comm is not None:
320 328 try:
321 329 self._comm.close()
322 330 finally:
323 331 self._comm = None
324 332
325 333
326 334 def _send(self, msg):
327 335 """Sends a message to the model in the front-end"""
328 336 if self._comm is not None:
329 337 self._comm.send(msg)
330 338 return True
331 339 else:
332 340 return False
333 341
334 342 class Widget(BaseWidget):
335
336 _children = List(Instance('IPython.html.widgets.widget.Widget'))
337 _children_lists_attr = List(Unicode, ['_children'])
338 343 visible = Bool(True, help="Whether or not the widget is visible.")
339 344
340 345 # Private/protected declarations
341 346 _css = Dict() # Internal CSS property dict
342 347
343 348 # Properties
344 349 @property
345 350 def keys(self):
346 351 keys = ['visible', '_css']
347 352 keys.extend(super(Widget, self).keys)
348 353 return keys
349 354
350 355 def get_css(self, key, selector=""):
351 356 """Get a CSS property of the widget. Note, this function does not
352 357 actually request the CSS from the front-end; Only properties that have
353 358 been set with set_css can be read.
354 359
355 360 Parameters
356 361 ----------
357 362 key: unicode
358 363 CSS key
359 364 selector: unicode (optional)
360 365 JQuery selector used when the CSS key/value was set.
361 366 """
362 367 if selector in self._css and key in self._css[selector]:
363 368 return self._css[selector][key]
364 369 else:
365 370 return None
366 371
367 372
368 373 def set_css(self, *args, **kwargs):
369 374 """Set one or more CSS properties of the widget (shared among all of the
370 375 views). This function has two signatures:
371 376 - set_css(css_dict, [selector=''])
372 377 - set_css(key, value, [selector=''])
373 378
374 379 Parameters
375 380 ----------
376 381 css_dict : dict
377 382 CSS key/value pairs to apply
378 383 key: unicode
379 384 CSS key
380 385 value
381 386 CSS value
382 387 selector: unicode (optional)
383 388 JQuery selector to use to apply the CSS key/value.
384 389 """
385 390 selector = kwargs.get('selector', '')
386 391
387 392 # Signature 1: set_css(css_dict, [selector=''])
388 393 if len(args) == 1:
389 394 if isinstance(args[0], dict):
390 395 for (key, value) in args[0].items():
391 396 self.set_css(key, value, selector=selector)
392 397 else:
393 398 raise Exception('css_dict must be a dict.')
394 399
395 400 # Signature 2: set_css(key, value, [selector=''])
396 401 elif len(args) == 2 or len(args) == 3:
397 402
398 403 # Selector can be a positional arg if it's the 3rd value
399 404 if len(args) == 3:
400 405 selector = args[2]
401 406 if selector not in self._css:
402 407 self._css[selector] = {}
403 408
404 409 # Only update the property if it has changed.
405 410 key = args[0]
406 411 value = args[1]
407 412 if not (key in self._css[selector] and value in self._css[selector][key]):
408 413 self._css[selector][key] = value
409 414 self.send_state('_css') # Send new state to client.
410 415 else:
411 416 raise Exception('set_css only accepts 1-3 arguments')
412 417
413 418
414 419 def add_class(self, class_name, selector=""):
415 420 """Add class[es] to a DOM element
416 421
417 422 Parameters
418 423 ----------
419 424 class_name: unicode
420 425 Class name(s) to add to the DOM element(s). Multiple class names
421 426 must be space separated.
422 427 selector: unicode (optional)
423 428 JQuery selector to select the DOM element(s) that the class(es) will
424 429 be added to.
425 430 """
426 431 self._send({"method": "add_class",
427 432 "class_list": class_name,
428 433 "selector": selector})
429 434
430 435
431 436 def remove_class(self, class_name, selector=""):
432 437 """Remove class[es] from a DOM element
433 438
434 439 Parameters
435 440 ----------
436 441 class_name: unicode
437 442 Class name(s) to remove from the DOM element(s). Multiple class
438 443 names must be space separated.
439 444 selector: unicode (optional)
440 445 JQuery selector to select the DOM element(s) that the class(es) will
441 446 be removed from.
442 447 """
443 448 self._send({"method": "remove_class",
444 449 "class_list": class_name,
445 450 "selector": selector})
@@ -1,93 +1,93 b''
1 1 """ButtonWidget class.
2 2
3 3 Represents a button in the frontend using a widget. Allows user to listen for
4 4 click events on the button and trigger backend code when the clicks are fired.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2013, the IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the Modified BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 import inspect
18 18 import types
19 19
20 20 from .widget import Widget
21 21 from IPython.utils.traitlets import Unicode, Bool, Int
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Classes
25 25 #-----------------------------------------------------------------------------
26 26 class ButtonWidget(Widget):
27 27 target_name = Unicode('ButtonWidgetModel')
28 28 default_view_name = Unicode('ButtonView')
29 29
30 30 # Keys
31 31 _keys = ['description', 'disabled']
32 32 description = Unicode('', help="Description of the button (label).")
33 33 disabled = Bool(False, help="Enable or disable user changes.")
34 34
35 35
36 36 def __init__(self, **kwargs):
37 37 super(ButtonWidget, self).__init__(**kwargs)
38 38
39 39 self._click_handlers = []
40 40 self.on_msg(self._handle_button_msg)
41 41
42 42
43 43 def on_click(self, callback, remove=False):
44 44 """Register a callback to execute when the button is clicked. The
45 45 callback can either accept no parameters or one sender parameter:
46 46 - callback()
47 47 - callback(sender)
48 48 If the callback has a sender parameter, the ButtonWidget instance that
49 49 called the callback will be passed into the method as the sender.
50 50
51 51 Parameters
52 52 ----------
53 53 remove : bool (optional)
54 54 Set to true to remove the callback from the list of callbacks."""
55 55 if remove:
56 56 self._click_handlers.remove(callback)
57 57 elif not callback in self._click_handlers:
58 58 self._click_handlers.append(callback)
59 59
60 60
61 61 def _handle_button_msg(self, content):
62 """Hanlde a msg from the front-end
62 """Handle a msg from the front-end
63 63
64 64 Parameters
65 65 ----------
66 66 content: dict
67 67 Content of the msg."""
68 68 if 'event' in content and content['event'] == 'click':
69 69 self._handle_click()
70 70
71 71
72 72 def _handle_click(self):
73 73 """Handles when the button has been clicked. Fires on_click
74 74 callbacks when appropriate."""
75 75
76 76 for handler in self._click_handlers:
77 77 if callable(handler):
78 78 argspec = inspect.getargspec(handler)
79 79 nargs = len(argspec[0])
80 80
81 81 # Bound methods have an additional 'self' argument
82 82 if isinstance(handler, types.MethodType):
83 83 nargs -= 1
84 84
85 85 # Call the callback
86 86 if nargs == 0:
87 87 handler()
88 88 elif nargs == 1:
89 89 handler(self)
90 90 else:
91 91 raise TypeError('ButtonWidget click callback must ' \
92 92 'accept 0 or 1 arguments.')
93 93
@@ -1,200 +1,203 b''
1 1 """ContainerWidget class.
2 2
3 3 Represents a container that can be used to group other widgets.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import Widget
17 from IPython.utils.traitlets import Unicode, Bool
17 from IPython.utils.traitlets import Unicode, Bool, List, Instance
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Classes
21 21 #-----------------------------------------------------------------------------
22 22 class ContainerWidget(Widget):
23 23 target_name = Unicode('ContainerWidgetModel')
24 24 default_view_name = Unicode('ContainerView')
25 25
26 children = []#List(Instance('IPython.html.widgets.widget.Widget'))
27 _children_lists_attr = List(Unicode, ['children'])
28
26 29 # Keys, all private and managed by helper methods. Flexible box model
27 30 # classes...
28 31 _keys = ['_vbox', '_hbox', '_align_start', '_align_end', '_align_center',
29 32 '_pack_start', '_pack_end', '_pack_center', '_flex0', '_flex1',
30 33 '_flex2', 'description', 'button_text']
31 34 description = Unicode()
32 35 button_text = Unicode()
33 36 _hbox = Bool(False)
34 37 _vbox = Bool(False)
35 38 _align_start = Bool(False)
36 39 _align_end = Bool(False)
37 40 _align_center = Bool(False)
38 41 _pack_start = Bool(False)
39 42 _pack_end = Bool(False)
40 43 _pack_center = Bool(False)
41 44 _flex0 = Bool(False)
42 45 _flex1 = Bool(False)
43 46 _flex2 = Bool(False)
44 47
45 48 def hbox(self, enabled=True):
46 49 """Make this container an hbox. Automatically disables conflicting
47 50 features.
48 51
49 52 Parameters
50 53 ----------
51 54 enabled: bool (optional)
52 55 Enabled or disable the hbox feature of the container, defaults to
53 56 True."""
54 57 self._hbox = enabled
55 58 if enabled:
56 59 self._vbox = False
57 60
58 61 def vbox(self, enabled=True):
59 62 """Make this container an vbox. Automatically disables conflicting
60 63 features.
61 64
62 65 Parameters
63 66 ----------
64 67 enabled: bool (optional)
65 68 Enabled or disable the vbox feature of the container, defaults to
66 69 True."""
67 70 self._vbox = enabled
68 71 if enabled:
69 72 self._hbox = False
70 73
71 74 def align_start(self, enabled=True):
72 75 """Make the contents of this container align to the start of the axis.
73 76 Automatically disables conflicting alignments.
74 77
75 78 Parameters
76 79 ----------
77 80 enabled: bool (optional)
78 81 Enabled or disable the start alignment of the container, defaults to
79 82 True."""
80 83 self._align_start = enabled
81 84 if enabled:
82 85 self._align_end = False
83 86 self._align_center = False
84 87
85 88 def align_end(self, enabled=True):
86 89 """Make the contents of this container align to the end of the axis.
87 90 Automatically disables conflicting alignments.
88 91
89 92 Parameters
90 93 ----------
91 94 enabled: bool (optional)
92 95 Enabled or disable the end alignment of the container, defaults to
93 96 True."""
94 97 self._align_end = enabled
95 98 if enabled:
96 99 self._align_start = False
97 100 self._align_center = False
98 101
99 102 def align_center(self, enabled=True):
100 103 """Make the contents of this container align to the center of the axis.
101 104 Automatically disables conflicting alignments.
102 105
103 106 Parameters
104 107 ----------
105 108 enabled: bool (optional)
106 109 Enabled or disable the center alignment of the container, defaults to
107 110 True."""
108 111 self._align_center = enabled
109 112 if enabled:
110 113 self._align_start = False
111 114 self._align_end = False
112 115
113 116
114 117 def pack_start(self, enabled=True):
115 118 """Make the contents of this container pack to the start of the axis.
116 119 Automatically disables conflicting packings.
117 120
118 121 Parameters
119 122 ----------
120 123 enabled: bool (optional)
121 124 Enabled or disable the start packing of the container, defaults to
122 125 True."""
123 126 self._pack_start = enabled
124 127 if enabled:
125 128 self._pack_end = False
126 129 self._pack_center = False
127 130
128 131 def pack_end(self, enabled=True):
129 132 """Make the contents of this container pack to the end of the axis.
130 133 Automatically disables conflicting packings.
131 134
132 135 Parameters
133 136 ----------
134 137 enabled: bool (optional)
135 138 Enabled or disable the end packing of the container, defaults to
136 139 True."""
137 140 self._pack_end = enabled
138 141 if enabled:
139 142 self._pack_start = False
140 143 self._pack_center = False
141 144
142 145 def pack_center(self, enabled=True):
143 146 """Make the contents of this container pack to the center of the axis.
144 147 Automatically disables conflicting packings.
145 148
146 149 Parameters
147 150 ----------
148 151 enabled: bool (optional)
149 152 Enabled or disable the center packing of the container, defaults to
150 153 True."""
151 154 self._pack_center = enabled
152 155 if enabled:
153 156 self._pack_start = False
154 157 self._pack_end = False
155 158
156 159
157 160 def flex0(self, enabled=True):
158 161 """Put this container in flex0 mode. Automatically disables conflicting
159 162 flex modes. See the widget tutorial part 5 example notebook for more
160 163 information.
161 164
162 165 Parameters
163 166 ----------
164 167 enabled: bool (optional)
165 168 Enabled or disable the flex0 attribute of the container, defaults to
166 169 True."""
167 170 self._flex0 = enabled
168 171 if enabled:
169 172 self._flex1 = False
170 173 self._flex2 = False
171 174
172 175 def flex1(self, enabled=True):
173 176 """Put this container in flex1 mode. Automatically disables conflicting
174 177 flex modes. See the widget tutorial part 5 example notebook for more
175 178 information.
176 179
177 180 Parameters
178 181 ----------
179 182 enabled: bool (optional)
180 183 Enabled or disable the flex1 attribute of the container, defaults to
181 184 True."""
182 185 self._flex1 = enabled
183 186 if enabled:
184 187 self._flex0 = False
185 188 self._flex2 = False
186 189
187 190 def flex2(self, enabled=True):
188 191 """Put this container in flex2 mode. Automatically disables conflicting
189 192 flex modes. See the widget tutorial part 5 example notebook for more
190 193 information.
191 194
192 195 Parameters
193 196 ----------
194 197 enabled: bool (optional)
195 198 Enabled or disable the flex2 attribute of the container, defaults to
196 199 True."""
197 200 self._flex2 = enabled
198 201 if enabled:
199 202 self._flex0 = False
200 203 self._flex1 = False
@@ -1,56 +1,59 b''
1 1 """MulticontainerWidget class.
2 2
3 3 Represents a multipage container that can be used to group other widgets into
4 4 pages.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2013, the IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the Modified BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 from .widget import Widget
18 from IPython.utils.traitlets import Unicode, Dict, Int
18 from IPython.utils.traitlets import Unicode, Dict, Int, List, Instance
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class MulticontainerWidget(Widget):
24 24 target_name = Unicode('MulticontainerWidgetModel')
25 25 default_view_name = Unicode('TabView')
26 26
27 27 # Keys
28 28 _keys = ['_titles', 'selected_index']
29 29 _titles = Dict(help="Titles of the pages")
30 30 selected_index = Int(0)
31 31
32 children = []#List(Instance('IPython.html.widgets.widget.Widget'))
33 _children_lists_attr = List(Unicode, ['children'])
34
32 35 # Public methods
33 36 def set_title(self, index, title):
34 """Sets the title of a container pages
37 """Sets the title of a container page
35 38
36 39 Parameters
37 40 ----------
38 41 index : int
39 42 Index of the container page
40 43 title : unicode
41 44 New title"""
42 45 self._titles[index] = title
43 46 self.send_state('_titles')
44 47
45 48
46 49 def get_title(self, index):
47 50 """Gets the title of a container pages
48 51
49 52 Parameters
50 53 ----------
51 54 index : int
52 55 Index of the container page"""
53 56 if index in self._titles:
54 57 return self._titles[index]
55 58 else:
56 59 return None
@@ -1,1442 +1,1445 b''
1 1 # encoding: utf-8
2 2 """
3 3 A lightweight Traits like module.
4 4
5 5 This is designed to provide a lightweight, simple, pure Python version of
6 6 many of the capabilities of enthought.traits. This includes:
7 7
8 8 * Validation
9 9 * Type specification with defaults
10 10 * Static and dynamic notification
11 11 * Basic predefined types
12 12 * An API that is similar to enthought.traits
13 13
14 14 We don't support:
15 15
16 16 * Delegation
17 17 * Automatic GUI generation
18 18 * A full set of trait types. Most importantly, we don't provide container
19 19 traits (list, dict, tuple) that can trigger notifications if their
20 20 contents change.
21 21 * API compatibility with enthought.traits
22 22
23 23 There are also some important difference in our design:
24 24
25 25 * enthought.traits does not validate default values. We do.
26 26
27 27 We choose to create this module because we need these capabilities, but
28 28 we need them to be pure Python so they work in all Python implementations,
29 29 including Jython and IronPython.
30 30
31 31 Inheritance diagram:
32 32
33 33 .. inheritance-diagram:: IPython.utils.traitlets
34 34 :parts: 3
35 35
36 36 Authors:
37 37
38 38 * Brian Granger
39 39 * Enthought, Inc. Some of the code in this file comes from enthought.traits
40 40 and is licensed under the BSD license. Also, many of the ideas also come
41 41 from enthought.traits even though our implementation is very different.
42 42 """
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Copyright (C) 2008-2011 The IPython Development Team
46 46 #
47 47 # Distributed under the terms of the BSD License. The full license is in
48 48 # the file COPYING, distributed as part of this software.
49 49 #-----------------------------------------------------------------------------
50 50
51 51 #-----------------------------------------------------------------------------
52 52 # Imports
53 53 #-----------------------------------------------------------------------------
54 54
55 55
56 56 import inspect
57 57 import re
58 58 import sys
59 59 import types
60 60 from types import FunctionType
61 61 try:
62 62 from types import ClassType, InstanceType
63 63 ClassTypes = (ClassType, type)
64 64 except:
65 65 ClassTypes = (type,)
66 66
67 67 from .importstring import import_item
68 68 from IPython.utils import py3compat
69 69 from IPython.utils.py3compat import iteritems
70 70
71 71 SequenceTypes = (list, tuple, set, frozenset)
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Basic classes
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class NoDefaultSpecified ( object ): pass
79 79 NoDefaultSpecified = NoDefaultSpecified()
80 80
81 81
82 82 class Undefined ( object ): pass
83 83 Undefined = Undefined()
84 84
85 85 class TraitError(Exception):
86 86 pass
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Utilities
90 90 #-----------------------------------------------------------------------------
91 91
92 92
93 93 def class_of ( object ):
94 94 """ Returns a string containing the class name of an object with the
95 95 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
96 96 'a PlotValue').
97 97 """
98 98 if isinstance( object, py3compat.string_types ):
99 99 return add_article( object )
100 100
101 101 return add_article( object.__class__.__name__ )
102 102
103 103
104 104 def add_article ( name ):
105 105 """ Returns a string containing the correct indefinite article ('a' or 'an')
106 106 prefixed to the specified string.
107 107 """
108 108 if name[:1].lower() in 'aeiou':
109 109 return 'an ' + name
110 110
111 111 return 'a ' + name
112 112
113 113
114 114 def repr_type(obj):
115 115 """ Return a string representation of a value and its type for readable
116 116 error messages.
117 117 """
118 118 the_type = type(obj)
119 119 if (not py3compat.PY3) and the_type is InstanceType:
120 120 # Old-style class.
121 121 the_type = obj.__class__
122 122 msg = '%r %r' % (obj, the_type)
123 123 return msg
124 124
125 125
126 126 def is_trait(t):
127 127 """ Returns whether the given value is an instance or subclass of TraitType.
128 128 """
129 129 return (isinstance(t, TraitType) or
130 130 (isinstance(t, type) and issubclass(t, TraitType)))
131 131
132 132
133 133 def parse_notifier_name(name):
134 134 """Convert the name argument to a list of names.
135 135
136 136 Examples
137 137 --------
138 138
139 139 >>> parse_notifier_name('a')
140 140 ['a']
141 141 >>> parse_notifier_name(['a','b'])
142 142 ['a', 'b']
143 143 >>> parse_notifier_name(None)
144 144 ['anytrait']
145 145 """
146 146 if isinstance(name, str):
147 147 return [name]
148 148 elif name is None:
149 149 return ['anytrait']
150 150 elif isinstance(name, (list, tuple)):
151 151 for n in name:
152 assert isinstance(n, str), "names must be strings"
152 assert isinstance(n, basestring), "names must be strings: %s, %r"%(type(n), n)
153 153 return name
154 154
155 155
156 156 class _SimpleTest:
157 157 def __init__ ( self, value ): self.value = value
158 158 def __call__ ( self, test ):
159 159 return test == self.value
160 160 def __repr__(self):
161 161 return "<SimpleTest(%r)" % self.value
162 162 def __str__(self):
163 163 return self.__repr__()
164 164
165 165
166 166 def getmembers(object, predicate=None):
167 167 """A safe version of inspect.getmembers that handles missing attributes.
168 168
169 169 This is useful when there are descriptor based attributes that for
170 170 some reason raise AttributeError even though they exist. This happens
171 171 in zope.inteface with the __provides__ attribute.
172 172 """
173 173 results = []
174 174 for key in dir(object):
175 175 try:
176 176 value = getattr(object, key)
177 177 except AttributeError:
178 178 pass
179 179 else:
180 180 if not predicate or predicate(value):
181 181 results.append((key, value))
182 182 results.sort()
183 183 return results
184 184
185 185
186 186 #-----------------------------------------------------------------------------
187 187 # Base TraitType for all traits
188 188 #-----------------------------------------------------------------------------
189 189
190 190
191 191 class TraitType(object):
192 192 """A base class for all trait descriptors.
193 193
194 194 Notes
195 195 -----
196 196 Our implementation of traits is based on Python's descriptor
197 197 prototol. This class is the base class for all such descriptors. The
198 198 only magic we use is a custom metaclass for the main :class:`HasTraits`
199 199 class that does the following:
200 200
201 201 1. Sets the :attr:`name` attribute of every :class:`TraitType`
202 202 instance in the class dict to the name of the attribute.
203 203 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
204 204 instance in the class dict to the *class* that declared the trait.
205 205 This is used by the :class:`This` trait to allow subclasses to
206 206 accept superclasses for :class:`This` values.
207 207 """
208 208
209 209
210 210 metadata = {}
211 211 default_value = Undefined
212 212 info_text = 'any value'
213 213
214 214 def __init__(self, default_value=NoDefaultSpecified, **metadata):
215 215 """Create a TraitType.
216 216 """
217 217 if default_value is not NoDefaultSpecified:
218 218 self.default_value = default_value
219 219
220 220 if len(metadata) > 0:
221 221 if len(self.metadata) > 0:
222 222 self._metadata = self.metadata.copy()
223 223 self._metadata.update(metadata)
224 224 else:
225 225 self._metadata = metadata
226 226 else:
227 227 self._metadata = self.metadata
228 228
229 229 self.init()
230 230
231 231 def init(self):
232 232 pass
233 233
234 234 def get_default_value(self):
235 235 """Create a new instance of the default value."""
236 236 return self.default_value
237 237
238 238 def instance_init(self, obj):
239 239 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
240 240
241 241 Some stages of initialization must be delayed until the parent
242 242 :class:`HasTraits` instance has been created. This method is
243 243 called in :meth:`HasTraits.__new__` after the instance has been
244 244 created.
245 245
246 246 This method trigger the creation and validation of default values
247 247 and also things like the resolution of str given class names in
248 248 :class:`Type` and :class`Instance`.
249 249
250 250 Parameters
251 251 ----------
252 252 obj : :class:`HasTraits` instance
253 253 The parent :class:`HasTraits` instance that has just been
254 254 created.
255 255 """
256 256 self.set_default_value(obj)
257 257
258 258 def set_default_value(self, obj):
259 259 """Set the default value on a per instance basis.
260 260
261 261 This method is called by :meth:`instance_init` to create and
262 262 validate the default value. The creation and validation of
263 263 default values must be delayed until the parent :class:`HasTraits`
264 264 class has been instantiated.
265 265 """
266 266 # Check for a deferred initializer defined in the same class as the
267 267 # trait declaration or above.
268 268 mro = type(obj).mro()
269 269 meth_name = '_%s_default' % self.name
270 270 for cls in mro[:mro.index(self.this_class)+1]:
271 271 if meth_name in cls.__dict__:
272 272 break
273 273 else:
274 274 # We didn't find one. Do static initialization.
275 275 dv = self.get_default_value()
276 276 newdv = self._validate(obj, dv)
277 277 obj._trait_values[self.name] = newdv
278 278 return
279 279 # Complete the dynamic initialization.
280 280 obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name]
281 281
282 282 def __get__(self, obj, cls=None):
283 283 """Get the value of the trait by self.name for the instance.
284 284
285 285 Default values are instantiated when :meth:`HasTraits.__new__`
286 286 is called. Thus by the time this method gets called either the
287 287 default value or a user defined value (they called :meth:`__set__`)
288 288 is in the :class:`HasTraits` instance.
289 289 """
290 290 if obj is None:
291 291 return self
292 292 else:
293 293 try:
294 294 value = obj._trait_values[self.name]
295 295 except KeyError:
296 296 # Check for a dynamic initializer.
297 297 if self.name in obj._trait_dyn_inits:
298 298 value = obj._trait_dyn_inits[self.name](obj)
299 299 # FIXME: Do we really validate here?
300 300 value = self._validate(obj, value)
301 301 obj._trait_values[self.name] = value
302 302 return value
303 303 else:
304 304 raise TraitError('Unexpected error in TraitType: '
305 305 'both default value and dynamic initializer are '
306 306 'absent.')
307 307 except Exception:
308 308 # HasTraits should call set_default_value to populate
309 309 # this. So this should never be reached.
310 310 raise TraitError('Unexpected error in TraitType: '
311 311 'default value not set properly')
312 312 else:
313 313 return value
314 314
315 315 def __set__(self, obj, value):
316 316 new_value = self._validate(obj, value)
317 317 old_value = self.__get__(obj)
318 318 obj._trait_values[self.name] = new_value
319 319 if old_value != new_value:
320 320 obj._notify_trait(self.name, old_value, new_value)
321 321
322 322 def _validate(self, obj, value):
323 323 if hasattr(self, 'validate'):
324 324 return self.validate(obj, value)
325 325 elif hasattr(self, 'is_valid_for'):
326 326 valid = self.is_valid_for(value)
327 327 if valid:
328 328 return value
329 329 else:
330 330 raise TraitError('invalid value for type: %r' % value)
331 331 elif hasattr(self, 'value_for'):
332 332 return self.value_for(value)
333 333 else:
334 334 return value
335 335
336 336 def info(self):
337 337 return self.info_text
338 338
339 339 def error(self, obj, value):
340 340 if obj is not None:
341 341 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
342 342 % (self.name, class_of(obj),
343 343 self.info(), repr_type(value))
344 344 else:
345 345 e = "The '%s' trait must be %s, but a value of %r was specified." \
346 346 % (self.name, self.info(), repr_type(value))
347 347 raise TraitError(e)
348 348
349 349 def get_metadata(self, key):
350 350 return getattr(self, '_metadata', {}).get(key, None)
351 351
352 352 def set_metadata(self, key, value):
353 353 getattr(self, '_metadata', {})[key] = value
354 354
355 355
356 356 #-----------------------------------------------------------------------------
357 357 # The HasTraits implementation
358 358 #-----------------------------------------------------------------------------
359 359
360 360
361 361 class MetaHasTraits(type):
362 362 """A metaclass for HasTraits.
363 363
364 364 This metaclass makes sure that any TraitType class attributes are
365 365 instantiated and sets their name attribute.
366 366 """
367 367
368 368 def __new__(mcls, name, bases, classdict):
369 369 """Create the HasTraits class.
370 370
371 371 This instantiates all TraitTypes in the class dict and sets their
372 372 :attr:`name` attribute.
373 373 """
374 374 # print "MetaHasTraitlets (mcls, name): ", mcls, name
375 375 # print "MetaHasTraitlets (bases): ", bases
376 376 # print "MetaHasTraitlets (classdict): ", classdict
377 377 for k,v in iteritems(classdict):
378 378 if isinstance(v, TraitType):
379 379 v.name = k
380 380 elif inspect.isclass(v):
381 381 if issubclass(v, TraitType):
382 382 vinst = v()
383 383 vinst.name = k
384 384 classdict[k] = vinst
385 385 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
386 386
387 387 def __init__(cls, name, bases, classdict):
388 388 """Finish initializing the HasTraits class.
389 389
390 390 This sets the :attr:`this_class` attribute of each TraitType in the
391 391 class dict to the newly created class ``cls``.
392 392 """
393 393 for k, v in iteritems(classdict):
394 394 if isinstance(v, TraitType):
395 395 v.this_class = cls
396 396 super(MetaHasTraits, cls).__init__(name, bases, classdict)
397 397
398 398 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
399 399
400 400 def __new__(cls, *args, **kw):
401 401 # This is needed because in Python 2.6 object.__new__ only accepts
402 402 # the cls argument.
403 403 new_meth = super(HasTraits, cls).__new__
404 404 if new_meth is object.__new__:
405 405 inst = new_meth(cls)
406 406 else:
407 407 inst = new_meth(cls, **kw)
408 408 inst._trait_values = {}
409 409 inst._trait_notifiers = {}
410 410 inst._trait_dyn_inits = {}
411 411 # Here we tell all the TraitType instances to set their default
412 412 # values on the instance.
413 413 for key in dir(cls):
414 414 # Some descriptors raise AttributeError like zope.interface's
415 415 # __provides__ attributes even though they exist. This causes
416 416 # AttributeErrors even though they are listed in dir(cls).
417 417 try:
418 418 value = getattr(cls, key)
419 419 except AttributeError:
420 420 pass
421 421 else:
422 422 if isinstance(value, TraitType):
423 423 value.instance_init(inst)
424 424
425 425 return inst
426 426
427 427 def __init__(self, *args, **kw):
428 428 # Allow trait values to be set using keyword arguments.
429 429 # We need to use setattr for this to trigger validation and
430 430 # notifications.
431 431 for key, value in iteritems(kw):
432 432 setattr(self, key, value)
433 433
434 434 def _notify_trait(self, name, old_value, new_value):
435 435
436 436 # First dynamic ones
437 437 callables = []
438 438 callables.extend(self._trait_notifiers.get(name,[]))
439 439 callables.extend(self._trait_notifiers.get('anytrait',[]))
440 440
441 441 # Now static ones
442 442 try:
443 443 cb = getattr(self, '_%s_changed' % name)
444 444 except:
445 445 pass
446 446 else:
447 447 callables.append(cb)
448 448
449 449 # Call them all now
450 450 for c in callables:
451 451 # Traits catches and logs errors here. I allow them to raise
452 452 if callable(c):
453 453 argspec = inspect.getargspec(c)
454 454 nargs = len(argspec[0])
455 455 # Bound methods have an additional 'self' argument
456 456 # I don't know how to treat unbound methods, but they
457 457 # can't really be used for callbacks.
458 458 if isinstance(c, types.MethodType):
459 459 offset = -1
460 460 else:
461 461 offset = 0
462 462 if nargs + offset == 0:
463 463 c()
464 464 elif nargs + offset == 1:
465 465 c(name)
466 466 elif nargs + offset == 2:
467 467 c(name, new_value)
468 468 elif nargs + offset == 3:
469 469 c(name, old_value, new_value)
470 470 else:
471 471 raise TraitError('a trait changed callback '
472 472 'must have 0-3 arguments.')
473 473 else:
474 474 raise TraitError('a trait changed callback '
475 475 'must be callable.')
476 476
477 477
478 478 def _add_notifiers(self, handler, name):
479 479 if name not in self._trait_notifiers:
480 480 nlist = []
481 481 self._trait_notifiers[name] = nlist
482 482 else:
483 483 nlist = self._trait_notifiers[name]
484 484 if handler not in nlist:
485 485 nlist.append(handler)
486 486
487 487 def _remove_notifiers(self, handler, name):
488 488 if name in self._trait_notifiers:
489 489 nlist = self._trait_notifiers[name]
490 490 try:
491 491 index = nlist.index(handler)
492 492 except ValueError:
493 493 pass
494 494 else:
495 495 del nlist[index]
496 496
497 497 def on_trait_change(self, handler, name=None, remove=False):
498 498 """Setup a handler to be called when a trait changes.
499 499
500 500 This is used to setup dynamic notifications of trait changes.
501 501
502 502 Static handlers can be created by creating methods on a HasTraits
503 503 subclass with the naming convention '_[traitname]_changed'. Thus,
504 504 to create static handler for the trait 'a', create the method
505 505 _a_changed(self, name, old, new) (fewer arguments can be used, see
506 506 below).
507 507
508 508 Parameters
509 509 ----------
510 510 handler : callable
511 511 A callable that is called when a trait changes. Its
512 512 signature can be handler(), handler(name), handler(name, new)
513 513 or handler(name, old, new).
514 514 name : list, str, None
515 515 If None, the handler will apply to all traits. If a list
516 516 of str, handler will apply to all names in the list. If a
517 517 str, the handler will apply just to that name.
518 518 remove : bool
519 519 If False (the default), then install the handler. If True
520 520 then unintall it.
521 521 """
522 522 if remove:
523 523 names = parse_notifier_name(name)
524 524 for n in names:
525 525 self._remove_notifiers(handler, n)
526 526 else:
527 527 names = parse_notifier_name(name)
528 528 for n in names:
529 529 self._add_notifiers(handler, n)
530 530
531 531 @classmethod
532 532 def class_trait_names(cls, **metadata):
533 533 """Get a list of all the names of this classes traits.
534 534
535 535 This method is just like the :meth:`trait_names` method, but is unbound.
536 536 """
537 537 return cls.class_traits(**metadata).keys()
538 538
539 539 @classmethod
540 540 def class_traits(cls, **metadata):
541 541 """Get a list of all the traits of this class.
542 542
543 543 This method is just like the :meth:`traits` method, but is unbound.
544 544
545 545 The TraitTypes returned don't know anything about the values
546 546 that the various HasTrait's instances are holding.
547 547
548 548 This follows the same algorithm as traits does and does not allow
549 549 for any simple way of specifying merely that a metadata name
550 550 exists, but has any value. This is because get_metadata returns
551 551 None if a metadata key doesn't exist.
552 552 """
553 553 traits = dict([memb for memb in getmembers(cls) if \
554 554 isinstance(memb[1], TraitType)])
555 555
556 556 if len(metadata) == 0:
557 557 return traits
558 558
559 559 for meta_name, meta_eval in metadata.items():
560 560 if type(meta_eval) is not FunctionType:
561 561 metadata[meta_name] = _SimpleTest(meta_eval)
562 562
563 563 result = {}
564 564 for name, trait in traits.items():
565 565 for meta_name, meta_eval in metadata.items():
566 566 if not meta_eval(trait.get_metadata(meta_name)):
567 567 break
568 568 else:
569 569 result[name] = trait
570 570
571 571 return result
572 572
573 573 def trait_names(self, **metadata):
574 574 """Get a list of all the names of this classes traits."""
575 575 return self.traits(**metadata).keys()
576 576
577 577 def traits(self, **metadata):
578 578 """Get a list of all the traits of this class.
579 579
580 580 The TraitTypes returned don't know anything about the values
581 581 that the various HasTrait's instances are holding.
582 582
583 583 This follows the same algorithm as traits does and does not allow
584 584 for any simple way of specifying merely that a metadata name
585 585 exists, but has any value. This is because get_metadata returns
586 586 None if a metadata key doesn't exist.
587 587 """
588 588 traits = dict([memb for memb in getmembers(self.__class__) if \
589 589 isinstance(memb[1], TraitType)])
590 590
591 591 if len(metadata) == 0:
592 592 return traits
593 593
594 594 for meta_name, meta_eval in metadata.items():
595 595 if type(meta_eval) is not FunctionType:
596 596 metadata[meta_name] = _SimpleTest(meta_eval)
597 597
598 598 result = {}
599 599 for name, trait in traits.items():
600 600 for meta_name, meta_eval in metadata.items():
601 601 if not meta_eval(trait.get_metadata(meta_name)):
602 602 break
603 603 else:
604 604 result[name] = trait
605 605
606 606 return result
607 607
608 608 def trait_metadata(self, traitname, key):
609 609 """Get metadata values for trait by key."""
610 610 try:
611 611 trait = getattr(self.__class__, traitname)
612 612 except AttributeError:
613 613 raise TraitError("Class %s does not have a trait named %s" %
614 614 (self.__class__.__name__, traitname))
615 615 else:
616 616 return trait.get_metadata(key)
617 617
618 618 #-----------------------------------------------------------------------------
619 619 # Actual TraitTypes implementations/subclasses
620 620 #-----------------------------------------------------------------------------
621 621
622 622 #-----------------------------------------------------------------------------
623 623 # TraitTypes subclasses for handling classes and instances of classes
624 624 #-----------------------------------------------------------------------------
625 625
626 626
627 627 class ClassBasedTraitType(TraitType):
628 628 """A trait with error reporting for Type, Instance and This."""
629 629
630 630 def error(self, obj, value):
631 631 kind = type(value)
632 632 if (not py3compat.PY3) and kind is InstanceType:
633 633 msg = 'class %s' % value.__class__.__name__
634 634 else:
635 635 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
636 636
637 637 if obj is not None:
638 638 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
639 639 % (self.name, class_of(obj),
640 640 self.info(), msg)
641 641 else:
642 642 e = "The '%s' trait must be %s, but a value of %r was specified." \
643 643 % (self.name, self.info(), msg)
644 644
645 645 raise TraitError(e)
646 646
647 647
648 648 class Type(ClassBasedTraitType):
649 649 """A trait whose value must be a subclass of a specified class."""
650 650
651 651 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
652 652 """Construct a Type trait
653 653
654 654 A Type trait specifies that its values must be subclasses of
655 655 a particular class.
656 656
657 657 If only ``default_value`` is given, it is used for the ``klass`` as
658 658 well.
659 659
660 660 Parameters
661 661 ----------
662 662 default_value : class, str or None
663 663 The default value must be a subclass of klass. If an str,
664 664 the str must be a fully specified class name, like 'foo.bar.Bah'.
665 665 The string is resolved into real class, when the parent
666 666 :class:`HasTraits` class is instantiated.
667 667 klass : class, str, None
668 668 Values of this trait must be a subclass of klass. The klass
669 669 may be specified in a string like: 'foo.bar.MyClass'.
670 670 The string is resolved into real class, when the parent
671 671 :class:`HasTraits` class is instantiated.
672 672 allow_none : boolean
673 673 Indicates whether None is allowed as an assignable value. Even if
674 674 ``False``, the default value may be ``None``.
675 675 """
676 676 if default_value is None:
677 677 if klass is None:
678 678 klass = object
679 679 elif klass is None:
680 680 klass = default_value
681 681
682 682 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
683 683 raise TraitError("A Type trait must specify a class.")
684 684
685 685 self.klass = klass
686 686 self._allow_none = allow_none
687 687
688 688 super(Type, self).__init__(default_value, **metadata)
689 689
690 690 def validate(self, obj, value):
691 691 """Validates that the value is a valid object instance."""
692 692 try:
693 693 if issubclass(value, self.klass):
694 694 return value
695 695 except:
696 696 if (value is None) and (self._allow_none):
697 697 return value
698 698
699 699 self.error(obj, value)
700 700
701 701 def info(self):
702 702 """ Returns a description of the trait."""
703 703 if isinstance(self.klass, py3compat.string_types):
704 704 klass = self.klass
705 705 else:
706 706 klass = self.klass.__name__
707 707 result = 'a subclass of ' + klass
708 708 if self._allow_none:
709 709 return result + ' or None'
710 710 return result
711 711
712 712 def instance_init(self, obj):
713 713 self._resolve_classes()
714 714 super(Type, self).instance_init(obj)
715 715
716 716 def _resolve_classes(self):
717 717 if isinstance(self.klass, py3compat.string_types):
718 718 self.klass = import_item(self.klass)
719 719 if isinstance(self.default_value, py3compat.string_types):
720 720 self.default_value = import_item(self.default_value)
721 721
722 722 def get_default_value(self):
723 723 return self.default_value
724 724
725 725
726 726 class DefaultValueGenerator(object):
727 727 """A class for generating new default value instances."""
728 728
729 729 def __init__(self, *args, **kw):
730 730 self.args = args
731 731 self.kw = kw
732 732
733 733 def generate(self, klass):
734 734 return klass(*self.args, **self.kw)
735 735
736 736
737 737 class Instance(ClassBasedTraitType):
738 738 """A trait whose value must be an instance of a specified class.
739 739
740 740 The value can also be an instance of a subclass of the specified class.
741 741 """
742 742
743 743 def __init__(self, klass=None, args=None, kw=None,
744 744 allow_none=True, **metadata ):
745 745 """Construct an Instance trait.
746 746
747 747 This trait allows values that are instances of a particular
748 748 class or its sublclasses. Our implementation is quite different
749 749 from that of enthough.traits as we don't allow instances to be used
750 750 for klass and we handle the ``args`` and ``kw`` arguments differently.
751 751
752 752 Parameters
753 753 ----------
754 754 klass : class, str
755 755 The class that forms the basis for the trait. Class names
756 756 can also be specified as strings, like 'foo.bar.Bar'.
757 757 args : tuple
758 758 Positional arguments for generating the default value.
759 759 kw : dict
760 760 Keyword arguments for generating the default value.
761 761 allow_none : bool
762 762 Indicates whether None is allowed as a value.
763 763
764 764 Notes
765 765 -----
766 766 If both ``args`` and ``kw`` are None, then the default value is None.
767 767 If ``args`` is a tuple and ``kw`` is a dict, then the default is
768 768 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
769 769 not (but not both), None is replace by ``()`` or ``{}``.
770 770 """
771 771
772 772 self._allow_none = allow_none
773 773
774 774 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))):
775 775 raise TraitError('The klass argument must be a class'
776 776 ' you gave: %r' % klass)
777 777 self.klass = klass
778 778
779 779 # self.klass is a class, so handle default_value
780 780 if args is None and kw is None:
781 781 default_value = None
782 782 else:
783 783 if args is None:
784 784 # kw is not None
785 785 args = ()
786 786 elif kw is None:
787 787 # args is not None
788 788 kw = {}
789 789
790 790 if not isinstance(kw, dict):
791 791 raise TraitError("The 'kw' argument must be a dict or None.")
792 792 if not isinstance(args, tuple):
793 793 raise TraitError("The 'args' argument must be a tuple or None.")
794 794
795 795 default_value = DefaultValueGenerator(*args, **kw)
796 796
797 797 super(Instance, self).__init__(default_value, **metadata)
798 798
799 799 def validate(self, obj, value):
800 800 if value is None:
801 801 if self._allow_none:
802 802 return value
803 803 self.error(obj, value)
804
805 if isinstance(value, self.klass):
806 return value
807 else:
808 self.error(obj, value)
804 try:
805 if isinstance(value, self.klass):
806 return value
807 else:
808 self.error(obj, value)
809 except TypeError as e:
810 print self.klass, type(self.klass)
811 raise TypeError("validate, %s, %s"%(self.klass, type(self.klass)))
809 812
810 813 def info(self):
811 814 if isinstance(self.klass, py3compat.string_types):
812 815 klass = self.klass
813 816 else:
814 817 klass = self.klass.__name__
815 818 result = class_of(klass)
816 819 if self._allow_none:
817 820 return result + ' or None'
818 821
819 822 return result
820 823
821 824 def instance_init(self, obj):
822 825 self._resolve_classes()
823 826 super(Instance, self).instance_init(obj)
824 827
825 828 def _resolve_classes(self):
826 829 if isinstance(self.klass, py3compat.string_types):
827 830 self.klass = import_item(self.klass)
828 831
829 832 def get_default_value(self):
830 833 """Instantiate a default value instance.
831 834
832 835 This is called when the containing HasTraits classes'
833 836 :meth:`__new__` method is called to ensure that a unique instance
834 837 is created for each HasTraits instance.
835 838 """
836 839 dv = self.default_value
837 840 if isinstance(dv, DefaultValueGenerator):
838 841 return dv.generate(self.klass)
839 842 else:
840 843 return dv
841 844
842 845
843 846 class This(ClassBasedTraitType):
844 847 """A trait for instances of the class containing this trait.
845 848
846 849 Because how how and when class bodies are executed, the ``This``
847 850 trait can only have a default value of None. This, and because we
848 851 always validate default values, ``allow_none`` is *always* true.
849 852 """
850 853
851 854 info_text = 'an instance of the same type as the receiver or None'
852 855
853 856 def __init__(self, **metadata):
854 857 super(This, self).__init__(None, **metadata)
855 858
856 859 def validate(self, obj, value):
857 860 # What if value is a superclass of obj.__class__? This is
858 861 # complicated if it was the superclass that defined the This
859 862 # trait.
860 863 if isinstance(value, self.this_class) or (value is None):
861 864 return value
862 865 else:
863 866 self.error(obj, value)
864 867
865 868
866 869 #-----------------------------------------------------------------------------
867 870 # Basic TraitTypes implementations/subclasses
868 871 #-----------------------------------------------------------------------------
869 872
870 873
871 874 class Any(TraitType):
872 875 default_value = None
873 876 info_text = 'any value'
874 877
875 878
876 879 class Int(TraitType):
877 880 """An int trait."""
878 881
879 882 default_value = 0
880 883 info_text = 'an int'
881 884
882 885 def validate(self, obj, value):
883 886 if isinstance(value, int):
884 887 return value
885 888 self.error(obj, value)
886 889
887 890 class CInt(Int):
888 891 """A casting version of the int trait."""
889 892
890 893 def validate(self, obj, value):
891 894 try:
892 895 return int(value)
893 896 except:
894 897 self.error(obj, value)
895 898
896 899 if py3compat.PY3:
897 900 Long, CLong = Int, CInt
898 901 Integer = Int
899 902 else:
900 903 class Long(TraitType):
901 904 """A long integer trait."""
902 905
903 906 default_value = 0
904 907 info_text = 'a long'
905 908
906 909 def validate(self, obj, value):
907 910 if isinstance(value, long):
908 911 return value
909 912 if isinstance(value, int):
910 913 return long(value)
911 914 self.error(obj, value)
912 915
913 916
914 917 class CLong(Long):
915 918 """A casting version of the long integer trait."""
916 919
917 920 def validate(self, obj, value):
918 921 try:
919 922 return long(value)
920 923 except:
921 924 self.error(obj, value)
922 925
923 926 class Integer(TraitType):
924 927 """An integer trait.
925 928
926 929 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
927 930
928 931 default_value = 0
929 932 info_text = 'an integer'
930 933
931 934 def validate(self, obj, value):
932 935 if isinstance(value, int):
933 936 return value
934 937 if isinstance(value, long):
935 938 # downcast longs that fit in int:
936 939 # note that int(n > sys.maxint) returns a long, so
937 940 # we don't need a condition on this cast
938 941 return int(value)
939 942 if sys.platform == "cli":
940 943 from System import Int64
941 944 if isinstance(value, Int64):
942 945 return int(value)
943 946 self.error(obj, value)
944 947
945 948
946 949 class Float(TraitType):
947 950 """A float trait."""
948 951
949 952 default_value = 0.0
950 953 info_text = 'a float'
951 954
952 955 def validate(self, obj, value):
953 956 if isinstance(value, float):
954 957 return value
955 958 if isinstance(value, int):
956 959 return float(value)
957 960 self.error(obj, value)
958 961
959 962
960 963 class CFloat(Float):
961 964 """A casting version of the float trait."""
962 965
963 966 def validate(self, obj, value):
964 967 try:
965 968 return float(value)
966 969 except:
967 970 self.error(obj, value)
968 971
969 972 class Complex(TraitType):
970 973 """A trait for complex numbers."""
971 974
972 975 default_value = 0.0 + 0.0j
973 976 info_text = 'a complex number'
974 977
975 978 def validate(self, obj, value):
976 979 if isinstance(value, complex):
977 980 return value
978 981 if isinstance(value, (float, int)):
979 982 return complex(value)
980 983 self.error(obj, value)
981 984
982 985
983 986 class CComplex(Complex):
984 987 """A casting version of the complex number trait."""
985 988
986 989 def validate (self, obj, value):
987 990 try:
988 991 return complex(value)
989 992 except:
990 993 self.error(obj, value)
991 994
992 995 # We should always be explicit about whether we're using bytes or unicode, both
993 996 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
994 997 # we don't have a Str type.
995 998 class Bytes(TraitType):
996 999 """A trait for byte strings."""
997 1000
998 1001 default_value = b''
999 1002 info_text = 'a bytes object'
1000 1003
1001 1004 def validate(self, obj, value):
1002 1005 if isinstance(value, bytes):
1003 1006 return value
1004 1007 self.error(obj, value)
1005 1008
1006 1009
1007 1010 class CBytes(Bytes):
1008 1011 """A casting version of the byte string trait."""
1009 1012
1010 1013 def validate(self, obj, value):
1011 1014 try:
1012 1015 return bytes(value)
1013 1016 except:
1014 1017 self.error(obj, value)
1015 1018
1016 1019
1017 1020 class Unicode(TraitType):
1018 1021 """A trait for unicode strings."""
1019 1022
1020 1023 default_value = u''
1021 1024 info_text = 'a unicode string'
1022 1025
1023 1026 def validate(self, obj, value):
1024 1027 if isinstance(value, py3compat.unicode_type):
1025 1028 return value
1026 1029 if isinstance(value, bytes):
1027 1030 try:
1028 1031 return value.decode('ascii', 'strict')
1029 1032 except UnicodeDecodeError:
1030 1033 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1031 1034 raise TraitError(msg.format(value, self.name, class_of(obj)))
1032 1035 self.error(obj, value)
1033 1036
1034 1037
1035 1038 class CUnicode(Unicode):
1036 1039 """A casting version of the unicode trait."""
1037 1040
1038 1041 def validate(self, obj, value):
1039 1042 try:
1040 1043 return py3compat.unicode_type(value)
1041 1044 except:
1042 1045 self.error(obj, value)
1043 1046
1044 1047
1045 1048 class ObjectName(TraitType):
1046 1049 """A string holding a valid object name in this version of Python.
1047 1050
1048 1051 This does not check that the name exists in any scope."""
1049 1052 info_text = "a valid object identifier in Python"
1050 1053
1051 1054 if py3compat.PY3:
1052 1055 # Python 3:
1053 1056 coerce_str = staticmethod(lambda _,s: s)
1054 1057
1055 1058 else:
1056 1059 # Python 2:
1057 1060 def coerce_str(self, obj, value):
1058 1061 "In Python 2, coerce ascii-only unicode to str"
1059 1062 if isinstance(value, unicode):
1060 1063 try:
1061 1064 return str(value)
1062 1065 except UnicodeEncodeError:
1063 1066 self.error(obj, value)
1064 1067 return value
1065 1068
1066 1069 def validate(self, obj, value):
1067 1070 value = self.coerce_str(obj, value)
1068 1071
1069 1072 if isinstance(value, str) and py3compat.isidentifier(value):
1070 1073 return value
1071 1074 self.error(obj, value)
1072 1075
1073 1076 class DottedObjectName(ObjectName):
1074 1077 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1075 1078 def validate(self, obj, value):
1076 1079 value = self.coerce_str(obj, value)
1077 1080
1078 1081 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1079 1082 return value
1080 1083 self.error(obj, value)
1081 1084
1082 1085
1083 1086 class Bool(TraitType):
1084 1087 """A boolean (True, False) trait."""
1085 1088
1086 1089 default_value = False
1087 1090 info_text = 'a boolean'
1088 1091
1089 1092 def validate(self, obj, value):
1090 1093 if isinstance(value, bool):
1091 1094 return value
1092 1095 self.error(obj, value)
1093 1096
1094 1097
1095 1098 class CBool(Bool):
1096 1099 """A casting version of the boolean trait."""
1097 1100
1098 1101 def validate(self, obj, value):
1099 1102 try:
1100 1103 return bool(value)
1101 1104 except:
1102 1105 self.error(obj, value)
1103 1106
1104 1107
1105 1108 class Enum(TraitType):
1106 1109 """An enum that whose value must be in a given sequence."""
1107 1110
1108 1111 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1109 1112 self.values = values
1110 1113 self._allow_none = allow_none
1111 1114 super(Enum, self).__init__(default_value, **metadata)
1112 1115
1113 1116 def validate(self, obj, value):
1114 1117 if value is None:
1115 1118 if self._allow_none:
1116 1119 return value
1117 1120
1118 1121 if value in self.values:
1119 1122 return value
1120 1123 self.error(obj, value)
1121 1124
1122 1125 def info(self):
1123 1126 """ Returns a description of the trait."""
1124 1127 result = 'any of ' + repr(self.values)
1125 1128 if self._allow_none:
1126 1129 return result + ' or None'
1127 1130 return result
1128 1131
1129 1132 class CaselessStrEnum(Enum):
1130 1133 """An enum of strings that are caseless in validate."""
1131 1134
1132 1135 def validate(self, obj, value):
1133 1136 if value is None:
1134 1137 if self._allow_none:
1135 1138 return value
1136 1139
1137 1140 if not isinstance(value, py3compat.string_types):
1138 1141 self.error(obj, value)
1139 1142
1140 1143 for v in self.values:
1141 1144 if v.lower() == value.lower():
1142 1145 return v
1143 1146 self.error(obj, value)
1144 1147
1145 1148 class Container(Instance):
1146 1149 """An instance of a container (list, set, etc.)
1147 1150
1148 1151 To be subclassed by overriding klass.
1149 1152 """
1150 1153 klass = None
1151 1154 _valid_defaults = SequenceTypes
1152 1155 _trait = None
1153 1156
1154 1157 def __init__(self, trait=None, default_value=None, allow_none=True,
1155 1158 **metadata):
1156 1159 """Create a container trait type from a list, set, or tuple.
1157 1160
1158 1161 The default value is created by doing ``List(default_value)``,
1159 1162 which creates a copy of the ``default_value``.
1160 1163
1161 1164 ``trait`` can be specified, which restricts the type of elements
1162 1165 in the container to that TraitType.
1163 1166
1164 1167 If only one arg is given and it is not a Trait, it is taken as
1165 1168 ``default_value``:
1166 1169
1167 1170 ``c = List([1,2,3])``
1168 1171
1169 1172 Parameters
1170 1173 ----------
1171 1174
1172 1175 trait : TraitType [ optional ]
1173 1176 the type for restricting the contents of the Container. If unspecified,
1174 1177 types are not checked.
1175 1178
1176 1179 default_value : SequenceType [ optional ]
1177 1180 The default value for the Trait. Must be list/tuple/set, and
1178 1181 will be cast to the container type.
1179 1182
1180 1183 allow_none : Bool [ default True ]
1181 1184 Whether to allow the value to be None
1182 1185
1183 1186 **metadata : any
1184 1187 further keys for extensions to the Trait (e.g. config)
1185 1188
1186 1189 """
1187 1190 # allow List([values]):
1188 1191 if default_value is None and not is_trait(trait):
1189 1192 default_value = trait
1190 1193 trait = None
1191 1194
1192 1195 if default_value is None:
1193 1196 args = ()
1194 1197 elif isinstance(default_value, self._valid_defaults):
1195 1198 args = (default_value,)
1196 1199 else:
1197 1200 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1198 1201
1199 1202 if is_trait(trait):
1200 1203 self._trait = trait() if isinstance(trait, type) else trait
1201 1204 self._trait.name = 'element'
1202 1205 elif trait is not None:
1203 1206 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1204 1207
1205 1208 super(Container,self).__init__(klass=self.klass, args=args,
1206 1209 allow_none=allow_none, **metadata)
1207 1210
1208 1211 def element_error(self, obj, element, validator):
1209 1212 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1210 1213 % (self.name, class_of(obj), validator.info(), repr_type(element))
1211 1214 raise TraitError(e)
1212 1215
1213 1216 def validate(self, obj, value):
1214 1217 value = super(Container, self).validate(obj, value)
1215 1218 if value is None:
1216 1219 return value
1217 1220
1218 1221 value = self.validate_elements(obj, value)
1219 1222
1220 1223 return value
1221 1224
1222 1225 def validate_elements(self, obj, value):
1223 1226 validated = []
1224 1227 if self._trait is None or isinstance(self._trait, Any):
1225 1228 return value
1226 1229 for v in value:
1227 1230 try:
1228 1231 v = self._trait.validate(obj, v)
1229 1232 except TraitError:
1230 1233 self.element_error(obj, v, self._trait)
1231 1234 else:
1232 1235 validated.append(v)
1233 1236 return self.klass(validated)
1234 1237
1235 1238
1236 1239 class List(Container):
1237 1240 """An instance of a Python list."""
1238 1241 klass = list
1239 1242
1240 1243 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
1241 1244 allow_none=True, **metadata):
1242 1245 """Create a List trait type from a list, set, or tuple.
1243 1246
1244 1247 The default value is created by doing ``List(default_value)``,
1245 1248 which creates a copy of the ``default_value``.
1246 1249
1247 1250 ``trait`` can be specified, which restricts the type of elements
1248 1251 in the container to that TraitType.
1249 1252
1250 1253 If only one arg is given and it is not a Trait, it is taken as
1251 1254 ``default_value``:
1252 1255
1253 1256 ``c = List([1,2,3])``
1254 1257
1255 1258 Parameters
1256 1259 ----------
1257 1260
1258 1261 trait : TraitType [ optional ]
1259 1262 the type for restricting the contents of the Container. If unspecified,
1260 1263 types are not checked.
1261 1264
1262 1265 default_value : SequenceType [ optional ]
1263 1266 The default value for the Trait. Must be list/tuple/set, and
1264 1267 will be cast to the container type.
1265 1268
1266 1269 minlen : Int [ default 0 ]
1267 1270 The minimum length of the input list
1268 1271
1269 1272 maxlen : Int [ default sys.maxsize ]
1270 1273 The maximum length of the input list
1271 1274
1272 1275 allow_none : Bool [ default True ]
1273 1276 Whether to allow the value to be None
1274 1277
1275 1278 **metadata : any
1276 1279 further keys for extensions to the Trait (e.g. config)
1277 1280
1278 1281 """
1279 1282 self._minlen = minlen
1280 1283 self._maxlen = maxlen
1281 1284 super(List, self).__init__(trait=trait, default_value=default_value,
1282 1285 allow_none=allow_none, **metadata)
1283 1286
1284 1287 def length_error(self, obj, value):
1285 1288 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1286 1289 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1287 1290 raise TraitError(e)
1288 1291
1289 1292 def validate_elements(self, obj, value):
1290 1293 length = len(value)
1291 1294 if length < self._minlen or length > self._maxlen:
1292 1295 self.length_error(obj, value)
1293 1296
1294 1297 return super(List, self).validate_elements(obj, value)
1295 1298
1296 1299
1297 1300 class Set(Container):
1298 1301 """An instance of a Python set."""
1299 1302 klass = set
1300 1303
1301 1304 class Tuple(Container):
1302 1305 """An instance of a Python tuple."""
1303 1306 klass = tuple
1304 1307
1305 1308 def __init__(self, *traits, **metadata):
1306 1309 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1307 1310
1308 1311 Create a tuple from a list, set, or tuple.
1309 1312
1310 1313 Create a fixed-type tuple with Traits:
1311 1314
1312 1315 ``t = Tuple(Int, Str, CStr)``
1313 1316
1314 1317 would be length 3, with Int,Str,CStr for each element.
1315 1318
1316 1319 If only one arg is given and it is not a Trait, it is taken as
1317 1320 default_value:
1318 1321
1319 1322 ``t = Tuple((1,2,3))``
1320 1323
1321 1324 Otherwise, ``default_value`` *must* be specified by keyword.
1322 1325
1323 1326 Parameters
1324 1327 ----------
1325 1328
1326 1329 *traits : TraitTypes [ optional ]
1327 1330 the tsype for restricting the contents of the Tuple. If unspecified,
1328 1331 types are not checked. If specified, then each positional argument
1329 1332 corresponds to an element of the tuple. Tuples defined with traits
1330 1333 are of fixed length.
1331 1334
1332 1335 default_value : SequenceType [ optional ]
1333 1336 The default value for the Tuple. Must be list/tuple/set, and
1334 1337 will be cast to a tuple. If `traits` are specified, the
1335 1338 `default_value` must conform to the shape and type they specify.
1336 1339
1337 1340 allow_none : Bool [ default True ]
1338 1341 Whether to allow the value to be None
1339 1342
1340 1343 **metadata : any
1341 1344 further keys for extensions to the Trait (e.g. config)
1342 1345
1343 1346 """
1344 1347 default_value = metadata.pop('default_value', None)
1345 1348 allow_none = metadata.pop('allow_none', True)
1346 1349
1347 1350 # allow Tuple((values,)):
1348 1351 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1349 1352 default_value = traits[0]
1350 1353 traits = ()
1351 1354
1352 1355 if default_value is None:
1353 1356 args = ()
1354 1357 elif isinstance(default_value, self._valid_defaults):
1355 1358 args = (default_value,)
1356 1359 else:
1357 1360 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1358 1361
1359 1362 self._traits = []
1360 1363 for trait in traits:
1361 1364 t = trait() if isinstance(trait, type) else trait
1362 1365 t.name = 'element'
1363 1366 self._traits.append(t)
1364 1367
1365 1368 if self._traits and default_value is None:
1366 1369 # don't allow default to be an empty container if length is specified
1367 1370 args = None
1368 1371 super(Container,self).__init__(klass=self.klass, args=args,
1369 1372 allow_none=allow_none, **metadata)
1370 1373
1371 1374 def validate_elements(self, obj, value):
1372 1375 if not self._traits:
1373 1376 # nothing to validate
1374 1377 return value
1375 1378 if len(value) != len(self._traits):
1376 1379 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1377 1380 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1378 1381 raise TraitError(e)
1379 1382
1380 1383 validated = []
1381 1384 for t,v in zip(self._traits, value):
1382 1385 try:
1383 1386 v = t.validate(obj, v)
1384 1387 except TraitError:
1385 1388 self.element_error(obj, v, t)
1386 1389 else:
1387 1390 validated.append(v)
1388 1391 return tuple(validated)
1389 1392
1390 1393
1391 1394 class Dict(Instance):
1392 1395 """An instance of a Python dict."""
1393 1396
1394 1397 def __init__(self, default_value=None, allow_none=True, **metadata):
1395 1398 """Create a dict trait type from a dict.
1396 1399
1397 1400 The default value is created by doing ``dict(default_value)``,
1398 1401 which creates a copy of the ``default_value``.
1399 1402 """
1400 1403 if default_value is None:
1401 1404 args = ((),)
1402 1405 elif isinstance(default_value, dict):
1403 1406 args = (default_value,)
1404 1407 elif isinstance(default_value, SequenceTypes):
1405 1408 args = (default_value,)
1406 1409 else:
1407 1410 raise TypeError('default value of Dict was %s' % default_value)
1408 1411
1409 1412 super(Dict,self).__init__(klass=dict, args=args,
1410 1413 allow_none=allow_none, **metadata)
1411 1414
1412 1415 class TCPAddress(TraitType):
1413 1416 """A trait for an (ip, port) tuple.
1414 1417
1415 1418 This allows for both IPv4 IP addresses as well as hostnames.
1416 1419 """
1417 1420
1418 1421 default_value = ('127.0.0.1', 0)
1419 1422 info_text = 'an (ip, port) tuple'
1420 1423
1421 1424 def validate(self, obj, value):
1422 1425 if isinstance(value, tuple):
1423 1426 if len(value) == 2:
1424 1427 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1425 1428 port = value[1]
1426 1429 if port >= 0 and port <= 65535:
1427 1430 return value
1428 1431 self.error(obj, value)
1429 1432
1430 1433 class CRegExp(TraitType):
1431 1434 """A casting compiled regular expression trait.
1432 1435
1433 1436 Accepts both strings and compiled regular expressions. The resulting
1434 1437 attribute will be a compiled regular expression."""
1435 1438
1436 1439 info_text = 'a regular expression'
1437 1440
1438 1441 def validate(self, obj, value):
1439 1442 try:
1440 1443 return re.compile(value)
1441 1444 except:
1442 1445 self.error(obj, value)
@@ -1,327 +1,390 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "To use IPython widgets in the notebook, the widget namespace and display function need to be imported."
21 21 ]
22 22 },
23 23 {
24 24 "cell_type": "code",
25 25 "collapsed": false,
26 26 "input": [
27 27 "from IPython.html import widgets # Widget definitions\n",
28 28 "from IPython.display import display # Used to display widgets in the notebook"
29 29 ],
30 30 "language": "python",
31 31 "metadata": {},
32 32 "outputs": [],
33 33 "prompt_number": 1
34 34 },
35 35 {
36 36 "cell_type": "heading",
37 37 "level": 1,
38 38 "metadata": {},
39 39 "source": [
40 40 "Basic Widgets"
41 41 ]
42 42 },
43 43 {
44 44 "cell_type": "markdown",
45 45 "metadata": {},
46 46 "source": [
47 47 "The IPython notebook comes preloaded with basic widgets that represent common data types. These widgets are\n",
48 48 "\n",
49 49 "- BoolWidget : boolean \n",
50 50 "- FloatRangeWidget : bounded float \n",
51 51 "- FloatWidget : unbounded float \n",
52 52 "- ImageWidget : image\n",
53 53 "- IntRangeWidget : bounded integer \n",
54 54 "- IntWidget : unbounded integer \n",
55 55 "- SelectionWidget : enumeration \n",
56 56 "- StringWidget : string \n",
57 57 "\n",
58 58 "A few special widgets are also included, that can be used to capture events and change how other widgets are displayed. These widgets are\n",
59 59 "\n",
60 60 "- ButtonWidget \n",
61 61 "- ContainerWidget \n",
62 62 "- MulticontainerWidget \n",
63 63 "\n",
64 64 "To see the complete list of widgets, one can execute the following"
65 65 ]
66 66 },
67 67 {
68 68 "cell_type": "code",
69 69 "collapsed": false,
70 70 "input": [
71 71 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
72 72 ],
73 73 "language": "python",
74 74 "metadata": {},
75 75 "outputs": [
76 76 {
77 77 "metadata": {},
78 78 "output_type": "pyout",
79 79 "prompt_number": 2,
80 80 "text": [
81 81 "['BoolWidget',\n",
82 82 " 'ButtonWidget',\n",
83 83 " 'ContainerWidget',\n",
84 84 " 'FloatRangeWidget',\n",
85 85 " 'FloatWidget',\n",
86 86 " 'ImageWidget',\n",
87 87 " 'IntRangeWidget',\n",
88 88 " 'IntWidget',\n",
89 89 " 'MulticontainerWidget',\n",
90 90 " 'SelectionWidget',\n",
91 91 " 'StringWidget',\n",
92 92 " 'Widget']"
93 93 ]
94 94 }
95 95 ],
96 96 "prompt_number": 2
97 97 },
98 98 {
99 99 "cell_type": "markdown",
100 100 "metadata": {},
101 101 "source": [
102 102 "The basic widgets can all be constructed without arguments. The following creates a FloatRangeWidget without displaying it"
103 103 ]
104 104 },
105 105 {
106 106 "cell_type": "code",
107 107 "collapsed": false,
108 108 "input": [
109 109 "mywidget = widgets.FloatRangeWidget()"
110 110 ],
111 111 "language": "python",
112 112 "metadata": {},
113 "outputs": [],
113 "outputs": [
114 {
115 "output_type": "stream",
116 "stream": "stdout",
117 "text": [
118 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'step', 'max', 'min', 'disabled', 'orientation', 'description']\n",
119 "[]\n",
120 "[]\n"
121 ]
122 }
123 ],
114 124 "prompt_number": 3
115 125 },
116 126 {
117 127 "cell_type": "markdown",
118 128 "metadata": {},
119 129 "source": [
120 130 "Constructing a widget does not display it on the page. To display a widget, the widget must be passed to the IPython `display(object)` method. `mywidget` is displayed by"
121 131 ]
122 132 },
123 133 {
124 134 "cell_type": "code",
125 135 "collapsed": false,
126 136 "input": [
127 137 "display(mywidget)"
128 138 ],
129 139 "language": "python",
130 140 "metadata": {},
131 141 "outputs": [],
132 142 "prompt_number": 4
133 143 },
134 144 {
145 "cell_type": "code",
146 "collapsed": false,
147 "input": [
148 "mywidget.value"
149 ],
150 "language": "python",
151 "metadata": {},
152 "outputs": [
153 {
154 "metadata": {},
155 "output_type": "pyout",
156 "prompt_number": 13,
157 "text": [
158 "55.1"
159 ]
160 }
161 ],
162 "prompt_number": 13
163 },
164 {
135 165 "cell_type": "markdown",
136 166 "metadata": {},
137 167 "source": [
138 168 "It's important to realize that widgets are not the same as output, even though they are displayed with `display`. Widgets are drawn in a special widget area. That area is marked with a close button which allows you to collapse the widgets. Widgets cannot be interleaved with output. Doing so would break the ability to make simple animations using `clear_output`.\n",
139 169 "\n",
140 170 "Widgets are manipulated via special instance properties (traitlets). The names of these instance properties are listed in the widget's `keys` property (as seen below). A few of these properties are common to most, if not all, widgets. The common properties are `value`, `description`, `visible`, and `disabled`. `_css`, `_add_class`, and `_remove_class` are internal properties that exist in all widgets and should not be modified."
141 171 ]
142 172 },
143 173 {
144 174 "cell_type": "code",
145 175 "collapsed": false,
146 176 "input": [
147 177 "mywidget.keys"
148 178 ],
149 179 "language": "python",
150 180 "metadata": {},
151 181 "outputs": [
152 182 {
153 183 "metadata": {},
154 184 "output_type": "pyout",
155 "prompt_number": 5,
185 "prompt_number": 11,
156 186 "text": [
157 187 "['visible',\n",
158 188 " '_css',\n",
189 " '_children_attr',\n",
190 " '_children_lists_attr',\n",
191 " 'default_view_name',\n",
159 192 " 'value',\n",
160 193 " 'step',\n",
161 194 " 'max',\n",
162 195 " 'min',\n",
163 196 " 'disabled',\n",
164 197 " 'orientation',\n",
165 198 " 'description']"
166 199 ]
167 200 }
168 201 ],
169 "prompt_number": 5
202 "prompt_number": 11
170 203 },
171 204 {
172 205 "cell_type": "markdown",
173 206 "metadata": {},
174 207 "source": [
175 208 "Changing a widget's property value will automatically update that widget everywhere it is displayed in the notebook. Here the value of `mywidget` is set. The slider shown above (after input 4) updates automatically to the new value. In reverse, changing the value of the displayed widget will update the property's value."
176 209 ]
177 210 },
178 211 {
179 212 "cell_type": "code",
180 213 "collapsed": false,
181 214 "input": [
182 215 "mywidget.value = 25.0"
183 216 ],
184 217 "language": "python",
185 218 "metadata": {},
186 219 "outputs": [],
187 "prompt_number": 6
220 "prompt_number": 12
188 221 },
189 222 {
190 223 "cell_type": "markdown",
191 224 "metadata": {},
192 225 "source": [
193 226 "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)."
194 227 ]
195 228 },
196 229 {
197 230 "cell_type": "code",
198 231 "collapsed": false,
199 232 "input": [
200 233 "mywidget.value"
201 234 ],
202 235 "language": "python",
203 236 "metadata": {},
204 237 "outputs": [
205 238 {
206 239 "metadata": {},
207 240 "output_type": "pyout",
208 "prompt_number": 7,
241 "prompt_number": 30,
209 242 "text": [
210 "0.0"
243 "25.0"
211 244 ]
212 245 }
213 246 ],
214 "prompt_number": 7
247 "prompt_number": 30
215 248 },
216 249 {
217 250 "cell_type": "markdown",
218 251 "metadata": {},
219 252 "source": [
220 253 "Widget property values can also be set with kwargs during the construction of the widget (as seen below)."
221 254 ]
222 255 },
223 256 {
224 257 "cell_type": "code",
225 258 "collapsed": false,
226 259 "input": [
227 260 "mysecondwidget = widgets.SelectionWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Nothing Selected\")\n",
228 261 "display(mysecondwidget)"
229 262 ],
230 263 "language": "python",
231 264 "metadata": {},
232 "outputs": [],
233 "prompt_number": 8
265 "outputs": [
266 {
267 "output_type": "stream",
268 "stream": "stdout",
269 "text": [
270 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'values', 'disabled', 'description']\n",
271 "[]\n",
272 "[]\n"
273 ]
274 }
275 ],
276 "prompt_number": 14
277 },
278 {
279 "cell_type": "code",
280 "collapsed": false,
281 "input": [
282 "mysecondwidget.value"
283 ],
284 "language": "python",
285 "metadata": {},
286 "outputs": [
287 {
288 "metadata": {},
289 "output_type": "pyout",
290 "prompt_number": 15,
291 "text": [
292 "u'Item C'"
293 ]
294 }
295 ],
296 "prompt_number": 15
234 297 },
235 298 {
236 299 "cell_type": "heading",
237 300 "level": 1,
238 301 "metadata": {},
239 302 "source": [
240 303 "Views"
241 304 ]
242 305 },
243 306 {
244 307 "cell_type": "markdown",
245 308 "metadata": {},
246 309 "source": [
247 310 "The data types that most of the widgets represent can be displayed more than one way. A `view` is a visual representation of a widget in the notebook. In the example in the section above, the default `view` for the `FloatRangeWidget` is used. The default view is set in the widgets `default_view_name` instance property (as seen below)."
248 311 ]
249 312 },
250 313 {
251 314 "cell_type": "code",
252 315 "collapsed": false,
253 316 "input": [
254 317 "mywidget.default_view_name"
255 318 ],
256 319 "language": "python",
257 320 "metadata": {},
258 321 "outputs": [
259 322 {
260 323 "metadata": {},
261 324 "output_type": "pyout",
262 "prompt_number": 9,
325 "prompt_number": 16,
263 326 "text": [
264 327 "u'FloatSliderView'"
265 328 ]
266 329 }
267 330 ],
268 "prompt_number": 9
331 "prompt_number": 16
269 332 },
270 333 {
271 334 "cell_type": "markdown",
272 335 "metadata": {},
273 336 "source": [
274 337 "When a widget is displayed using `display(...)`, the `default_view_name` is used to determine what view type should be used to display the widget. View names are case sensitive. Sometimes the default view isn't the best view to represent a piece of data. To change what view is used, either the `default_view_name` can be changed or the `view_name` kwarg of `display` can be set. This also can be used to display one widget multiple ways in one output (as seen below)."
275 338 ]
276 339 },
277 340 {
278 341 "cell_type": "code",
279 342 "collapsed": false,
280 343 "input": [
281 344 "display(mywidget)\n",
282 345 "display(mywidget, view_name=\"FloatTextView\")"
283 346 ],
284 347 "language": "python",
285 348 "metadata": {},
286 349 "outputs": [],
287 "prompt_number": 10
350 "prompt_number": 17
288 351 },
289 352 {
290 353 "cell_type": "markdown",
291 354 "metadata": {},
292 355 "source": [
293 356 "Some views work with multiple different widget types and some views only work with one. The complete list of views and supported widgets is below. The default views are italicized.\n",
294 357 "\n",
295 358 "| Widget Name | View Names |\n",
296 359 "|:-----------------------|:--------------------|\n",
297 360 "| BoolWidget | *CheckboxView* |\n",
298 361 "| | ToggleButtonView |\n",
299 362 "| ButtonWidget | *ButtonView* |\n",
300 363 "| ContainerWidget | *ContainerView* |\n",
301 364 "| | ModalView |\n",
302 365 "| FloatRangeWidget | *FloatSliderView* |\n",
303 366 "| | FloatTextView |\n",
304 367 "| | ProgressView |\n",
305 368 "| FloatWidget | *FloatTextView* |\n",
306 369 "| ImageWidget | *ImageView* |\n",
307 370 "| IntRangeWidget | *IntSliderView* |\n",
308 371 "| | IntTextView |\n",
309 372 "| | ProgressView |\n",
310 373 "| IntWidget | *IntTextView* |\n",
311 374 "| MulticontainerWidget | AccordionView |\n",
312 375 "| | *TabView* |\n",
313 376 "| SelectionWidget | ToggleButtonsView |\n",
314 377 "| | RadioButtonsView |\n",
315 378 "| | *DropdownView* |\n",
316 379 "| | ListBoxView |\n",
317 380 "| StringWidget | HTMLView |\n",
318 381 "| | LatexView |\n",
319 382 "| | TextAreaView |\n",
320 383 "| | *TextBoxView* |\n"
321 384 ]
322 385 }
323 386 ],
324 387 "metadata": {}
325 388 }
326 389 ]
327 390 } No newline at end of file
@@ -1,271 +1,479 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "code",
18 18 "collapsed": false,
19 19 "input": [
20 20 "from __future__ import print_function # 2.7 compatability\n",
21 21 "\n",
22 22 "from IPython.html import widgets # Widget definitions\n",
23 23 "from IPython.display import display # Used to display widgets in the notebook"
24 24 ],
25 25 "language": "python",
26 26 "metadata": {},
27 27 "outputs": [],
28 28 "prompt_number": 1
29 29 },
30 30 {
31 31 "cell_type": "heading",
32 32 "level": 1,
33 33 "metadata": {},
34 34 "source": [
35 35 "Traitlet Events"
36 36 ]
37 37 },
38 38 {
39 39 "cell_type": "markdown",
40 40 "metadata": {},
41 41 "source": [
42 42 "The widget properties are IPython traitlets. Traitlets are eventful. To handle property value changes, the `on_trait_change` method of the widget can be used to register an event handling callback. The doc string for `on_trait_change` can be seen below. Both the `name` and `remove` properties are optional."
43 43 ]
44 44 },
45 45 {
46 46 "cell_type": "code",
47 47 "collapsed": false,
48 48 "input": [
49 49 "print(widgets.Widget.on_trait_change.__doc__)"
50 50 ],
51 51 "language": "python",
52 52 "metadata": {},
53 53 "outputs": [
54 54 {
55 55 "output_type": "stream",
56 56 "stream": "stdout",
57 57 "text": [
58 58 "Setup a handler to be called when a trait changes.\n",
59 59 "\n",
60 60 " This is used to setup dynamic notifications of trait changes.\n",
61 61 "\n",
62 62 " Static handlers can be created by creating methods on a HasTraits\n",
63 63 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
64 64 " to create static handler for the trait 'a', create the method\n",
65 65 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
66 66 " below).\n",
67 67 "\n",
68 68 " Parameters\n",
69 69 " ----------\n",
70 70 " handler : callable\n",
71 71 " A callable that is called when a trait changes. Its\n",
72 72 " signature can be handler(), handler(name), handler(name, new)\n",
73 73 " or handler(name, old, new).\n",
74 74 " name : list, str, None\n",
75 75 " If None, the handler will apply to all traits. If a list\n",
76 76 " of str, handler will apply to all names in the list. If a\n",
77 77 " str, the handler will apply just to that name.\n",
78 78 " remove : bool\n",
79 79 " If False (the default), then install the handler. If True\n",
80 80 " then unintall it.\n",
81 81 " \n"
82 82 ]
83 83 }
84 84 ],
85 85 "prompt_number": 2
86 86 },
87 87 {
88 88 "cell_type": "markdown",
89 89 "metadata": {},
90 90 "source": [
91 91 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
92 92 "\n",
93 93 "- callback()\n",
94 94 "- callback(trait_name)\n",
95 95 "- callback(trait_name, new_value)\n",
96 96 "- callback(trait_name, old_value, new_value)\n",
97 97 "\n",
98 98 "An example of how to output an IntRangeWiget's value as it is changed can be seen below."
99 99 ]
100 100 },
101 101 {
102 102 "cell_type": "code",
103 103 "collapsed": false,
104 104 "input": [
105 105 "intrange = widgets.IntRangeWidget()\n",
106 106 "display(intrange)\n",
107 107 "\n",
108 108 "def on_value_change(name, value):\n",
109 109 " print(value)\n",
110 110 "\n",
111 "intrange.on_trait_change(on_value_change, 'value')"
111 "#intrange.on_trait_change(on_value_change, 'value')"
112 112 ],
113 113 "language": "python",
114 114 "metadata": {},
115 115 "outputs": [
116 116 {
117 117 "output_type": "stream",
118 118 "stream": "stdout",
119 119 "text": [
120 "28\n"
121 ]
122 },
123 {
124 "output_type": "stream",
125 "stream": "stdout",
126 "text": [
127 "55\n"
128 ]
129 },
130 {
131 "output_type": "stream",
132 "stream": "stdout",
133 "text": [
134 "94\n"
120 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'step', 'max', 'min', 'disabled', 'orientation', 'description']\n",
121 "[]\n",
122 "[]\n"
135 123 ]
136 124 }
137 125 ],
138 "prompt_number": 3
126 "prompt_number": 9
139 127 },
140 128 {
141 129 "cell_type": "heading",
142 130 "level": 1,
143 131 "metadata": {},
144 132 "source": [
145 133 "Specialized Events"
146 134 ]
147 135 },
148 136 {
149 137 "cell_type": "heading",
150 138 "level": 2,
151 139 "metadata": {},
152 140 "source": [
153 141 "Button On Click Event"
154 142 ]
155 143 },
156 144 {
157 145 "cell_type": "markdown",
158 146 "metadata": {},
159 147 "source": [
160 148 "The `ButtonWidget` is a special widget, like the `ContainerWidget` and `MulticontainerWidget`, that isn't used to represent a data type. Instead the button widget is used to handle mouse clicks. The `on_click` method of the `ButtonWidget` can be used to register a click even handler. The doc string of the `on_click` can be seen below."
161 149 ]
162 150 },
163 151 {
164 152 "cell_type": "code",
165 153 "collapsed": false,
166 154 "input": [
167 155 "print(widgets.ButtonWidget.on_click.__doc__)"
168 156 ],
169 157 "language": "python",
170 158 "metadata": {},
171 159 "outputs": [
172 160 {
173 161 "output_type": "stream",
174 162 "stream": "stdout",
175 163 "text": [
176 164 "Register a callback to execute when the button is clicked. The\n",
177 165 " callback can either accept no parameters or one sender parameter:\n",
178 166 " - callback()\n",
179 167 " - callback(sender)\n",
180 168 " If the callback has a sender parameter, the ButtonWidget instance that\n",
181 169 " called the callback will be passed into the method as the sender.\n",
182 170 "\n",
183 171 " Parameters\n",
184 172 " ----------\n",
185 173 " remove : bool (optional)\n",
186 174 " Set to true to remove the callback from the list of callbacks.\n"
187 175 ]
188 176 }
189 177 ],
190 178 "prompt_number": 4
191 179 },
192 180 {
193 181 "cell_type": "markdown",
194 182 "metadata": {},
195 183 "source": [
196 184 "Button clicks are transmitted from the front-end to the back-end using custom messages. By using the `on_click` method, a button that prints a message when it has been clicked is shown below."
197 185 ]
198 186 },
199 187 {
200 188 "cell_type": "code",
201 189 "collapsed": false,
202 190 "input": [
191 "display(intrange)\n",
192 "print('hi')"
193 ],
194 "language": "python",
195 "metadata": {},
196 "outputs": [
197 {
198 "output_type": "stream",
199 "stream": "stdout",
200 "text": [
201 "hi\n"
202 ]
203 }
204 ],
205 "prompt_number": 5
206 },
207 {
208 "cell_type": "code",
209 "collapsed": false,
210 "input": [
203 211 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
204 212 "display(button)\n",
205 213 "\n",
206 214 "def on_button_clicked(sender):\n",
207 215 " print(\"Button clicked.\")\n",
216 " intrange.value +=1\n",
208 217 "\n",
209 218 "button.on_click(on_button_clicked)"
210 219 ],
211 220 "language": "python",
212 221 "metadata": {},
213 222 "outputs": [
214 223 {
215 224 "output_type": "stream",
216 225 "stream": "stdout",
217 226 "text": [
227 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
228 "[]\n",
229 "[]\n"
230 ]
231 },
232 {
233 "output_type": "stream",
234 "stream": "stdout",
235 "text": [
236 "Button clicked.\n"
237 ]
238 },
239 {
240 "output_type": "stream",
241 "stream": "stdout",
242 "text": [
218 243 "Button clicked.\n"
219 244 ]
220 245 },
221 246 {
222 247 "output_type": "stream",
223 248 "stream": "stdout",
224 249 "text": [
225 250 "Button clicked.\n"
226 251 ]
227 252 },
228 253 {
229 254 "output_type": "stream",
230 255 "stream": "stdout",
231 256 "text": [
232 257 "Button clicked.\n"
233 258 ]
234 259 }
235 260 ],
236 "prompt_number": 5
261 "prompt_number": 12
237 262 },
238 263 {
239 264 "cell_type": "markdown",
240 265 "metadata": {},
241 266 "source": [
242 267 "Event handlers can also be used to create widgets. In the example below, clicking a button spawns another button with a description equal to how many times the parent button had been clicked at the time."
243 268 ]
244 269 },
245 270 {
246 271 "cell_type": "code",
247 272 "collapsed": false,
273 "input": [],
274 "language": "python",
275 "metadata": {},
276 "outputs": [
277 {
278 "metadata": {},
279 "output_type": "pyout",
280 "prompt_number": 11,
281 "text": [
282 "{'content': {'data': \"{'parent_header': {}, 'msg_type': u'comm_msg', 'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', 'content': {u'data': {u'method': u'custom', u'custom_content': {u'event': u'click'}}, u'comm_id': u'eea5f11ae7aa473993dd0c81d6016648'}, 'header': {u'username': u'username', u'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', u'msg_type': u'comm_msg', u'session': u'0F6D6BE728DA47A38CFC4BDEACF34FC4'}, 'buffers': [], 'metadata': {}}\\ncustom message {'parent_header': {}, 'msg_type': u'comm_msg', 'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', 'content': {u'data': {u'method': u'custom', u'custom_content': {u'event': u'click'}}, u'comm_id': u'eea5f11ae7aa473993dd0c81d6016648'}, 'header': {u'username': u'username', u'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', u'msg_type': u'comm_msg', u'session': u'0F6D6BE728DA47A38CFC4BDEACF34FC4'}, 'buffers': [], 'metadata': {}}\\nhandling click\\n{u'event': u'click'}\\nButton clicked.\\n2\\n\",\n",
283 " 'name': 'stdout'},\n",
284 " 'header': {'msg_id': 'd9dc144a-d86c-42c1-8bab-f8a6bc525723',\n",
285 " 'msg_type': 'stream',\n",
286 " 'session': '9b9408d8-7420-4e0c-976d-cdda9f8d2564',\n",
287 " 'username': 'kernel'},\n",
288 " 'metadata': {},\n",
289 " 'msg_id': 'd9dc144a-d86c-42c1-8bab-f8a6bc525723',\n",
290 " 'msg_type': 'stream',\n",
291 " 'parent_header': {'msg_id': '3DBB06AD83C942DD85DC6477B08F1FBF',\n",
292 " 'msg_type': 'comm_msg',\n",
293 " 'session': '0F6D6BE728DA47A38CFC4BDEACF34FC4',\n",
294 " 'username': 'username'}}"
295 ]
296 }
297 ],
298 "prompt_number": 11
299 },
300 {
301 "cell_type": "code",
302 "collapsed": false,
248 303 "input": [
249 304 "def show_button(sender=None):\n",
250 305 " button = widgets.ButtonWidget()\n",
251 306 " button.clicks = 0\n",
252 307 " if sender is None:\n",
253 308 " button.description = \"0\"\n",
254 309 " else:\n",
255 310 " sender.clicks += 1\n",
256 311 " button.description = \"%d\" % sender.clicks\n",
257 312 " display(button)\n",
258 313 " button.on_click(show_button)\n",
259 314 "show_button()\n",
260 315 " "
261 316 ],
262 317 "language": "python",
263 318 "metadata": {},
264 "outputs": [],
265 "prompt_number": 6
319 "outputs": [
320 {
321 "output_type": "stream",
322 "stream": "stdout",
323 "text": [
324 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
325 "[]\n",
326 "[]\n"
327 ]
328 },
329 {
330 "output_type": "stream",
331 "stream": "stdout",
332 "text": [
333 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
334 "[]\n",
335 "[]\n"
336 ]
337 },
338 {
339 "output_type": "stream",
340 "stream": "stdout",
341 "text": [
342 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
343 "[]\n",
344 "[]\n"
345 ]
346 },
347 {
348 "output_type": "stream",
349 "stream": "stdout",
350 "text": [
351 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
352 "[]\n",
353 "[]\n"
354 ]
355 },
356 {
357 "output_type": "stream",
358 "stream": "stdout",
359 "text": [
360 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
361 "[]\n",
362 "[]\n"
363 ]
364 },
365 {
366 "output_type": "stream",
367 "stream": "stdout",
368 "text": [
369 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
370 "[]\n",
371 "[]\n"
372 ]
373 },
374 {
375 "output_type": "stream",
376 "stream": "stdout",
377 "text": [
378 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
379 "[]\n",
380 "[]\n"
381 ]
382 },
383 {
384 "output_type": "stream",
385 "stream": "stdout",
386 "text": [
387 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
388 "[]\n",
389 "[]\n"
390 ]
391 },
392 {
393 "output_type": "stream",
394 "stream": "stdout",
395 "text": [
396 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
397 "[]\n",
398 "[]\n"
399 ]
400 },
401 {
402 "output_type": "stream",
403 "stream": "stdout",
404 "text": [
405 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
406 "[]\n",
407 "[]\n"
408 ]
409 },
410 {
411 "output_type": "stream",
412 "stream": "stdout",
413 "text": [
414 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
415 "[]\n",
416 "[]\n"
417 ]
418 },
419 {
420 "output_type": "stream",
421 "stream": "stdout",
422 "text": [
423 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
424 "[]\n",
425 "[]\n"
426 ]
427 },
428 {
429 "output_type": "stream",
430 "stream": "stdout",
431 "text": [
432 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
433 "[]\n",
434 "[]\n"
435 ]
436 },
437 {
438 "output_type": "stream",
439 "stream": "stdout",
440 "text": [
441 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
442 "[]\n",
443 "[]\n"
444 ]
445 },
446 {
447 "output_type": "stream",
448 "stream": "stdout",
449 "text": [
450 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
451 "[]\n",
452 "[]\n"
453 ]
454 },
455 {
456 "output_type": "stream",
457 "stream": "stdout",
458 "text": [
459 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
460 "[]\n",
461 "[]\n"
462 ]
463 }
464 ],
465 "prompt_number": 7
466 },
467 {
468 "cell_type": "code",
469 "collapsed": false,
470 "input": [],
471 "language": "python",
472 "metadata": {},
473 "outputs": []
266 474 }
267 475 ],
268 476 "metadata": {}
269 477 }
270 478 ]
271 479 } No newline at end of file
@@ -1,258 +1,345 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "code",
18 18 "collapsed": false,
19 19 "input": [
20 20 "from IPython.html import widgets # Widget definitions\n",
21 21 "from IPython.display import display # Used to display widgets in the notebook"
22 22 ],
23 23 "language": "python",
24 24 "metadata": {},
25 25 "outputs": [],
26 26 "prompt_number": 1
27 27 },
28 28 {
29 29 "cell_type": "heading",
30 30 "level": 1,
31 31 "metadata": {},
32 32 "source": [
33 33 "Parent/Child Relationships"
34 34 ]
35 35 },
36 36 {
37 37 "cell_type": "markdown",
38 38 "metadata": {},
39 39 "source": [
40 40 "To display widget A inside widget B, widget A must be a child of widget B. With IPython widgets, the widgets are instances that live in the back-end (usally Python). There can be multiple views displayed in the front-end that represent one widget in the backend. Each view can be displayed at a different time, or even displayed two or more times in the same output. Because of this, the parent of a widget can only be set before the widget has been displayed.\n",
41 41 "\n",
42 42 "Every widget has a `parent` property. This property can be set via a kwarg in the widget's constructor or after construction, but before display. Calling display on an object with children automatically displays those children too (as seen below)."
43 43 ]
44 44 },
45 45 {
46 46 "cell_type": "code",
47 47 "collapsed": false,
48 48 "input": [
49 "container = widgets.MulticontainerWidget()\n",
50 49 "\n",
51 "floatrange = widgets.FloatRangeWidget(parent=container) # You can set the parent in the constructor,\n",
50 "floatrange = widgets.FloatRangeWidget() # You can set the parent in the constructor,\n",
52 51 "\n",
53 "string = widgets.StringWidget()\n",
54 "string.parent = container # or after the widget has been created.\n",
52 "string = widgets.StringWidget(value='hi')\n",
53 "container = widgets.MulticontainerWidget(children=[floatrange, string])\n",
55 54 "\n",
56 55 "display(container) # Displays the `container` and all of it's children."
57 56 ],
58 57 "language": "python",
59 58 "metadata": {},
60 59 "outputs": [],
61 60 "prompt_number": 2
62 61 },
63 62 {
64 63 "cell_type": "markdown",
65 64 "metadata": {},
66 65 "source": [
67 66 "Children can also be added to parents after the parent has been displayed. If the children are added after the parent has already been displayed, the children must be displayed themselves.\n",
68 67 "\n",
69 68 "In the example below, the IntRangeWidget is never rendered since display was called on the parent before the parent/child relationship was established."
70 69 ]
71 70 },
72 71 {
73 72 "cell_type": "code",
74 73 "collapsed": false,
75 74 "input": [
76 75 "container = widgets.MulticontainerWidget()\n",
77 76 "display(container)\n",
78 77 "\n",
79 78 "intrange = widgets.IntRangeWidget(parent=container) # Never gets displayed."
80 79 ],
81 80 "language": "python",
82 81 "metadata": {},
83 82 "outputs": [],
84 83 "prompt_number": 3
85 84 },
86 85 {
87 86 "cell_type": "markdown",
88 87 "metadata": {},
89 88 "source": [
90 89 "Calling display on the child fixes the problem."
91 90 ]
92 91 },
93 92 {
94 93 "cell_type": "code",
95 94 "collapsed": false,
96 95 "input": [
97 96 "container = widgets.MulticontainerWidget()\n",
98 97 "display(container)\n",
99 98 "\n",
100 99 "intrange = widgets.IntRangeWidget(parent=container)\n",
101 100 "display(intrange) # This line is needed since the `container` has already been displayed."
102 101 ],
103 102 "language": "python",
104 103 "metadata": {},
105 104 "outputs": [],
106 105 "prompt_number": 4
107 106 },
108 107 {
109 108 "cell_type": "heading",
110 109 "level": 1,
111 110 "metadata": {},
112 111 "source": [
113 112 "Changing Child Views"
114 113 ]
115 114 },
116 115 {
117 116 "cell_type": "markdown",
118 117 "metadata": {},
119 118 "source": [
120 119 "The view used to display a widget must defined by the time the widget is displayed. If children widgets are to be displayed along with the parent in one call, their `view_name`s can't be set since all of the widgets are sharing the same display call. Instead, their `default_view_name`s must be set (as seen below)."
121 120 ]
122 121 },
123 122 {
124 123 "cell_type": "code",
125 124 "collapsed": false,
126 125 "input": [
127 126 "container = widgets.MulticontainerWidget()\n",
128 127 "\n",
129 128 "floatrange = widgets.FloatRangeWidget(parent=container)\n",
130 129 "floatrange.default_view_name = \"FloatTextView\" # It can be set as a property.\n",
131 130 "\n",
132 131 "string = widgets.StringWidget(default_view_name = \"TextAreaView\") # It can also be set in the constructor.\n",
133 132 "string.parent = container\n",
134 133 "\n",
135 134 "display(container)"
136 135 ],
137 136 "language": "python",
138 137 "metadata": {},
139 138 "outputs": [],
140 139 "prompt_number": 5
141 140 },
142 141 {
143 142 "cell_type": "markdown",
144 143 "metadata": {},
145 144 "source": [
146 145 "However, if the children are displayed after the parent, their `view_name` can also be set like normal. Both methods will work. The code below produces the same output as the code above."
147 146 ]
148 147 },
149 148 {
150 149 "cell_type": "code",
151 150 "collapsed": false,
152 151 "input": [
153 152 "container = widgets.MulticontainerWidget()\n",
154 153 "display(container)\n",
155 154 "\n",
156 155 "floatrange = widgets.FloatRangeWidget()\n",
157 156 "floatrange.parent=container\n",
158 157 "display(floatrange, view_name = \"FloatTextView\") # view_name can be set during display.\n",
159 158 "\n",
160 159 "string = widgets.StringWidget()\n",
161 160 "string.parent = container\n",
162 161 "string.default_view_name = \"TextAreaView\" # Setting default_view_name still works.\n",
163 162 "display(string)\n"
164 163 ],
165 164 "language": "python",
166 165 "metadata": {},
167 166 "outputs": [],
168 167 "prompt_number": 6
169 168 },
170 169 {
171 170 "cell_type": "heading",
172 171 "level": 1,
173 172 "metadata": {},
174 173 "source": [
175 174 "Visibility"
176 175 ]
177 176 },
178 177 {
179 178 "cell_type": "markdown",
180 179 "metadata": {},
181 180 "source": [
182 181 "Sometimes it's necessary to hide/show widget views in place, without ruining the order that they have been displayed on the page. Using the `display` method, the views are always added to the end of their respective containers. Instead the `visibility` property of widgets can be used to hide/show widgets that have already been displayed (as seen below)."
183 182 ]
184 183 },
185 184 {
186 185 "cell_type": "code",
187 186 "collapsed": false,
188 187 "input": [
189 188 "string = widgets.StringWidget(value=\"Hello World!\")\n",
190 "display(string, view_name=\"LabelView\") "
189 "display(string, view_name=\"HTMLView\") "
191 190 ],
192 191 "language": "python",
193 192 "metadata": {},
194 "outputs": [],
195 "prompt_number": 7
193 "outputs": [
194 {
195 "output_type": "stream",
196 "stream": "stdout",
197 "text": [
198 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'disabled', 'description']\n",
199 "[]\n",
200 "[]\n"
201 ]
202 }
203 ],
204 "prompt_number": 11
196 205 },
197 206 {
198 207 "cell_type": "code",
199 208 "collapsed": false,
200 209 "input": [
201 210 "string.visible=False"
202 211 ],
203 212 "language": "python",
204 213 "metadata": {},
205 214 "outputs": [],
206 "prompt_number": 8
215 "prompt_number": 12
207 216 },
208 217 {
209 218 "cell_type": "code",
210 219 "collapsed": false,
211 220 "input": [
212 221 "string.visible=True"
213 222 ],
214 223 "language": "python",
215 224 "metadata": {},
216 225 "outputs": [],
217 "prompt_number": 9
226 "prompt_number": 13
218 227 },
219 228 {
220 229 "cell_type": "markdown",
221 230 "metadata": {},
222 231 "source": [
223 232 "In the example below, a form is rendered which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox."
224 233 ]
225 234 },
226 235 {
227 236 "cell_type": "code",
228 237 "collapsed": false,
229 238 "input": [
230 239 "form = widgets.ContainerWidget()\n",
231 "first = widgets.StringWidget(description=\"First Name:\", parent=form)\n",
232 "last = widgets.StringWidget(description=\"Last Name:\", parent=form)\n",
240 "first = widgets.StringWidget(description=\"First Name:\")\n",
241 "last = widgets.StringWidget(description=\"Last Name:\")\n",
233 242 "\n",
234 "student = widgets.BoolWidget(description=\"Student:\", value=False, parent=form)\n",
235 "school_info = widgets.ContainerWidget(visible=False, parent=form)\n",
236 "school = widgets.StringWidget(description=\"School:\", parent=school_info)\n",
237 "grade = widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView', parent=school_info)\n",
243 "student = widgets.BoolWidget(description=\"Student:\", value=False)\n",
244 "form.children=[first, last, student]\n",
245 "display(form)"
246 ],
247 "language": "python",
248 "metadata": {},
249 "outputs": [],
250 "prompt_number": 2
251 },
252 {
253 "cell_type": "code",
254 "collapsed": false,
255 "input": [
256 "form = widgets.ContainerWidget()\n",
257 "first = widgets.StringWidget(description=\"First Name:\")\n",
258 "last = widgets.StringWidget(description=\"Last Name:\")\n",
259 "\n",
260 "student = widgets.BoolWidget(description=\"Student:\", value=False)\n",
261 "school_info = widgets.ContainerWidget(visible=False, children=[\n",
262 " widgets.StringWidget(description=\"School:\"),\n",
263 " widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView')\n",
264 " ])\n",
238 265 "\n",
239 "pet = widgets.StringWidget(description=\"Pet's Name:\", parent=form)\n",
266 "pet = widgets.StringWidget(description=\"Pet's Name:\")\n",
267 "form.children = [first, last, student, school_info, pet]\n",
240 268 "display(form)\n",
241 269 "\n",
242 270 "def on_student_toggle(name, value):\n",
271 " print value\n",
243 272 " if value:\n",
244 273 " school_info.visible = True\n",
245 274 " else:\n",
246 275 " school_info.visible = False\n",
247 276 "student.on_trait_change(on_student_toggle, 'value')\n"
248 277 ],
249 278 "language": "python",
250 279 "metadata": {},
251 "outputs": [],
252 "prompt_number": 10
280 "outputs": [
281 {
282 "output_type": "stream",
283 "stream": "stdout",
284 "text": [
285 "True\n"
286 ]
287 },
288 {
289 "output_type": "stream",
290 "stream": "stdout",
291 "text": [
292 "False\n"
293 ]
294 },
295 {
296 "output_type": "stream",
297 "stream": "stdout",
298 "text": [
299 "True\n"
300 ]
301 },
302 {
303 "output_type": "stream",
304 "stream": "stdout",
305 "text": [
306 "False\n"
307 ]
308 },
309 {
310 "output_type": "stream",
311 "stream": "stdout",
312 "text": [
313 "True\n"
314 ]
315 },
316 {
317 "output_type": "stream",
318 "stream": "stdout",
319 "text": [
320 "False\n"
321 ]
322 },
323 {
324 "output_type": "stream",
325 "stream": "stdout",
326 "text": [
327 "True\n"
328 ]
329 }
330 ],
331 "prompt_number": 2
332 },
333 {
334 "cell_type": "code",
335 "collapsed": false,
336 "input": [],
337 "language": "python",
338 "metadata": {},
339 "outputs": []
253 340 }
254 341 ],
255 342 "metadata": {}
256 343 }
257 344 ]
258 345 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now