##// END OF EJS Templates
Merge pull request #4374 from jdfreder/widget-msg...
Brian E. Granger -
r14837:7eac82e1 merge
parent child Browse files
Show More
@@ -0,0 +1,210 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // WidgetModel, WidgetView, and WidgetManager
10 //============================================================================
11 /**
12 * Base Widget classes
13 * @module IPython
14 * @namespace IPython
15 * @submodule widget
16 */
17
18 (function () {
19 "use strict";
20
21 // Use require.js 'define' method so that require.js is intelligent enough to
22 // syncronously load everything within this file when it is being 'required'
23 // elsewhere.
24 define(["underscore",
25 "backbone",
26 ], function (Underscore, Backbone) {
27
28 //--------------------------------------------------------------------
29 // WidgetManager class
30 //--------------------------------------------------------------------
31 var WidgetManager = function (comm_manager) {
32 // Public constructor
33 WidgetManager._managers.push(this);
34
35 // Attach a comm manager to the
36 this.comm_manager = comm_manager;
37 this._models = {}; /* Dictionary of model ids and model instances */
38
39 // Register already-registered widget model types with the comm manager.
40 var that = this;
41 _.each(WidgetManager._model_types, function(model_type, model_name) {
42 that.comm_manager.register_target(model_name, $.proxy(that._handle_comm_open, that));
43 });
44 };
45
46 //--------------------------------------------------------------------
47 // Class level
48 //--------------------------------------------------------------------
49 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
50 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
51 WidgetManager._managers = []; /* List of widget managers */
52
53 WidgetManager.register_widget_model = function (model_name, model_type) {
54 // Registers a widget model by name.
55 WidgetManager._model_types[model_name] = model_type;
56
57 // Register the widget with the comm manager. Make sure to pass this object's context
58 // in so `this` works in the call back.
59 _.each(WidgetManager._managers, function(instance, i) {
60 if (instance.comm_manager !== null) {
61 instance.comm_manager.register_target(model_name, $.proxy(instance._handle_comm_open, instance));
62 }
63 });
64 };
65
66 WidgetManager.register_widget_view = function (view_name, view_type) {
67 // Registers a widget view by name.
68 WidgetManager._view_types[view_name] = view_type;
69 };
70
71 //--------------------------------------------------------------------
72 // Instance level
73 //--------------------------------------------------------------------
74 WidgetManager.prototype.display_view = function(msg, model) {
75 // Displays a view for a particular model.
76 var cell = this.get_msg_cell(msg.parent_header.msg_id);
77 if (cell === null) {
78 console.log("Could not determine where the display" +
79 " message was from. Widget will not be displayed");
80 } else {
81 var view = this.create_view(model, {cell: cell});
82 if (view === null) {
83 console.error("View creation failed", model);
84 }
85 if (cell.widget_subarea) {
86
87 cell.widget_area.show();
88 cell.widget_subarea.append(view.$el);
89 }
90 }
91 };
92
93 WidgetManager.prototype.create_view = function(model, options, view) {
94 // Creates a view for a particular model.
95 var view_name = model.get('_view_name');
96 var ViewType = WidgetManager._view_types[view_name];
97 if (ViewType) {
98
99 // If a view is passed into the method, use that view's cell as
100 // the cell for the view that is created.
101 options = options || {};
102 if (view !== undefined) {
103 options.cell = view.options.cell;
104 }
105
106 // Create and render the view...
107 var parameters = {model: model, options: options};
108 view = new ViewType(parameters);
109 view.render();
110 model.views.push(view);
111 model.on('destroy', view.remove, view);
112
113 this._handle_new_view(view);
114 return view;
115 }
116 return null;
117 };
118
119 WidgetManager.prototype._handle_new_view = function (view) {
120 // Called when a view has been created and rendered.
121
122 // If the view has a well defined element, inform the keyboard
123 // manager about the view's element, so as the element can
124 // escape the dreaded command mode.
125 if (view.$el) {
126 IPython.keyboard_manager.register_events(view.$el);
127 }
128 };
129
130 WidgetManager.prototype.get_msg_cell = function (msg_id) {
131 var cell = null;
132 // First, check to see if the msg was triggered by cell execution.
133 if (IPython.notebook) {
134 cell = IPython.notebook.get_msg_cell(msg_id);
135 }
136 if (cell !== null) {
137 return cell;
138 }
139 // Second, check to see if a get_cell callback was defined
140 // for the message. get_cell callbacks are registered for
141 // widget messages, so this block is actually checking to see if the
142 // message was triggered by a widget.
143 var kernel = this.comm_manager.kernel;
144 if (kernel) {
145 var callbacks = kernel.get_callbacks_for_msg(msg_id);
146 if (callbacks && callbacks.iopub &&
147 callbacks.iopub.get_cell !== undefined) {
148 return callbacks.iopub.get_cell();
149 }
150 }
151
152 // Not triggered by a cell or widget (no get_cell callback
153 // exists).
154 return null;
155 };
156
157 WidgetManager.prototype.callbacks = function (view) {
158 // callback handlers specific a view
159 var callbacks = {};
160 if (view && view.options.cell) {
161
162 // Try to get output handlers
163 var cell = view.options.cell;
164 var handle_output = null;
165 var handle_clear_output = null;
166 if (cell.output_area) {
167 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
168 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
169 }
170
171 // Create callback dict using what is known
172 var that = this;
173 callbacks = {
174 iopub : {
175 output : handle_output,
176 clear_output : handle_clear_output,
177
178 // Special function only registered by widget messages.
179 // Allows us to get the cell for a message so we know
180 // where to add widgets if the code requires it.
181 get_cell : function () {
182 return cell;
183 },
184 },
185 };
186 }
187 return callbacks;
188 };
189
190 WidgetManager.prototype.get_model = function (model_id) {
191 // Look-up a model instance by its id.
192 var model = this._models[model_id];
193 if (model !== undefined && model.id == model_id) {
194 return model;
195 }
196 return null;
197 };
198
199 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
200 // Handle when a comm is opened.
201 var model_id = comm.comm_id;
202 var widget_type_name = msg.content.target_name;
203 var widget_model = new WidgetManager._model_types[widget_type_name](this, model_id, comm);
204 this._models[model_id] = widget_model;
205 };
206
207 IPython.WidgetManager = WidgetManager;
208 return IPython.WidgetManager;
209 });
210 }());
@@ -0,0 +1,22 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Basic Widgets
10 //============================================================================
11
12 define([
13 "notebook/js/widgets/widget_bool",
14 "notebook/js/widgets/widget_button",
15 "notebook/js/widgets/widget_container",
16 "notebook/js/widgets/widget_float",
17 "notebook/js/widgets/widget_image",
18 "notebook/js/widgets/widget_int",
19 "notebook/js/widgets/widget_selection",
20 "notebook/js/widgets/widget_selectioncontainer",
21 "notebook/js/widgets/widget_string",
22 ], function(){ return true; });
@@ -0,0 +1,418 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Base Widget Model and View classes
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgetmanager",
18 "underscore",
19 "backbone"],
20 function(WidgetManager, Underscore, Backbone){
21
22 var WidgetModel = Backbone.Model.extend({
23 constructor: function (widget_manager, model_id, comm) {
24 // Constructor
25 //
26 // Creates a WidgetModel instance.
27 //
28 // Parameters
29 // ----------
30 // widget_manager : WidgetManager instance
31 // model_id : string
32 // An ID unique to this model.
33 // comm : Comm instance (optional)
34 this.widget_manager = widget_manager;
35 this.pending_msgs = 0;
36 this.msg_throttle = 3;
37 this.msg_buffer = null;
38 this.key_value_lock = null;
39 this.id = model_id;
40 this.views = [];
41
42 if (comm !== undefined) {
43 // Remember comm associated with the model.
44 this.comm = comm;
45 comm.model = this;
46
47 // Hook comm messages up to model.
48 comm.on_close($.proxy(this._handle_comm_closed, this));
49 comm.on_msg($.proxy(this._handle_comm_msg, this));
50 }
51 return Backbone.Model.apply(this);
52 },
53
54 send: function (content, callbacks) {
55 // Send a custom msg over the comm.
56 if (this.comm !== undefined) {
57 var data = {method: 'custom', content: content};
58 this.comm.send(data, callbacks);
59 this.pending_msgs++;
60 }
61 },
62
63 _handle_comm_closed: function (msg) {
64 // Handle when a widget is closed.
65 this.trigger('comm:close');
66 delete this.comm.model; // Delete ref so GC will collect widget model.
67 delete this.comm;
68 delete this.model_id; // Delete id from model so widget manager cleans up.
69 _.each(this.views, function(view, i) {
70 view.remove();
71 });
72 },
73
74 _handle_comm_msg: function (msg) {
75 // Handle incoming comm msg.
76 var method = msg.content.data.method;
77 switch (method) {
78 case 'update':
79 this.apply_update(msg.content.data.state);
80 break;
81 case 'custom':
82 this.trigger('msg:custom', msg.content.data.content);
83 break;
84 case 'display':
85 this.widget_manager.display_view(msg, this);
86 break;
87 }
88 },
89
90 apply_update: function (state) {
91 // Handle when a widget is updated via the python side.
92 var that = this;
93 _.each(state, function(value, key) {
94 that.key_value_lock = [key, value];
95 try {
96 that.set(key, that._unpack_models(value));
97 } finally {
98 that.key_value_lock = null;
99 }
100 });
101 },
102
103 _handle_status: function (msg, callbacks) {
104 // Handle status msgs.
105
106 // execution_state : ('busy', 'idle', 'starting')
107 if (this.comm !== undefined) {
108 if (msg.content.execution_state ==='idle') {
109 // Send buffer if this message caused another message to be
110 // throttled.
111 if (this.msg_buffer !== null &&
112 this.msg_throttle === this.pending_msgs) {
113 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
114 this.comm.send(data, callbacks);
115 this.msg_buffer = null;
116 } else {
117 --this.pending_msgs;
118 }
119 }
120 }
121 },
122
123 callbacks: function(view) {
124 // Create msg callbacks for a comm msg.
125 var callbacks = this.widget_manager.callbacks(view);
126
127 if (callbacks.iopub === undefined) {
128 callbacks.iopub = {};
129 }
130
131 var that = this;
132 callbacks.iopub.status = function (msg) {
133 that._handle_status(msg, callbacks);
134 };
135 return callbacks;
136 },
137
138 sync: function (method, model, options) {
139 // Handle sync to the back-end. Called when a model.save() is called.
140
141 // Make sure a comm exists.
142 var error = options.error || function() {
143 console.error('Backbone sync error:', arguments);
144 };
145 if (this.comm === undefined) {
146 error();
147 return false;
148 }
149
150 // Delete any key value pairs that the back-end already knows about.
151 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
152 if (this.key_value_lock !== null) {
153 var key = this.key_value_lock[0];
154 var value = this.key_value_lock[1];
155 if (attrs[key] === value) {
156 delete attrs[key];
157 }
158 }
159
160 // Only sync if there are attributes to send to the back-end.
161 if (_.size(attrs) > 0) {
162
163 // If this message was sent via backbone itself, it will not
164 // have any callbacks. It's important that we create callbacks
165 // so we can listen for status messages, etc...
166 var callbacks = options.callbacks || this.callbacks();
167
168 // Check throttle.
169 if (this.pending_msgs >= this.msg_throttle) {
170 // The throttle has been exceeded, buffer the current msg so
171 // it can be sent once the kernel has finished processing
172 // some of the existing messages.
173
174 // Combine updates if it is a 'patch' sync, otherwise replace updates
175 switch (method) {
176 case 'patch':
177 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
178 break;
179 case 'update':
180 case 'create':
181 this.msg_buffer = attrs;
182 break;
183 default:
184 error();
185 return false;
186 }
187 this.msg_buffer_callbacks = callbacks;
188
189 } else {
190 // We haven't exceeded the throttle, send the message like
191 // normal.
192 var data = {method: 'backbone', sync_data: attrs};
193 this.comm.send(data, callbacks);
194 this.pending_msgs++;
195 }
196 }
197 // Since the comm is a one-way communication, assume the message
198 // arrived. Don't call success since we don't have a model back from the server
199 // this means we miss out on the 'sync' event.
200 },
201
202 save_changes: function(callbacks) {
203 // Push this model's state to the back-end
204 //
205 // This invokes a Backbone.Sync.
206 this.save(this.changedAttributes(), {patch: true, callbacks: callbacks});
207 },
208
209 _pack_models: function(value) {
210 // Replace models with model ids recursively.
211 if (value instanceof Backbone.Model) {
212 return value.id;
213 } else if (value instanceof Object) {
214 var packed = {};
215 var that = this;
216 _.each(value, function(sub_value, key) {
217 packed[key] = that._pack_models(sub_value);
218 });
219 return packed;
220 } else {
221 return value;
222 }
223 },
224
225 _unpack_models: function(value) {
226 // Replace model ids with models recursively.
227 if (value instanceof Object) {
228 var unpacked = {};
229 var that = this;
230 _.each(value, function(sub_value, key) {
231 unpacked[key] = that._unpack_models(sub_value);
232 });
233 return unpacked;
234 } else {
235 var model = this.widget_manager.get_model(value);
236 if (model) {
237 return model;
238 } else {
239 return value;
240 }
241 }
242 },
243
244 });
245 WidgetManager.register_widget_model('WidgetModel', WidgetModel);
246
247
248 var WidgetView = Backbone.View.extend({
249 initialize: function(parameters) {
250 // Public constructor.
251 this.model.on('change',this.update,this);
252 this.options = parameters.options;
253 this.child_views = [];
254 this.model.views.push(this);
255 },
256
257 update: function(){
258 // Triggered on model change.
259 //
260 // Update view to be consistent with this.model
261 },
262
263 create_child_view: function(child_model, options) {
264 // Create and return a child view.
265 //
266 // -given a model and (optionally) a view name if the view name is
267 // not given, it defaults to the model's default view attribute.
268
269 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
270 // it would be great to have the widget manager add the cell metadata
271 // to the subview without having to add it here.
272 var child_view = this.model.widget_manager.create_view(child_model, options || {}, this);
273 this.child_views[child_model.id] = child_view;
274 return child_view;
275 },
276
277 delete_child_view: function(child_model, options) {
278 // Delete a child view that was previously created using create_child_view.
279 var view = this.child_views[child_model.id];
280 if (view !== undefined) {
281 delete this.child_views[child_model.id];
282 view.remove();
283 }
284 },
285
286 do_diff: function(old_list, new_list, removed_callback, added_callback) {
287 // Difference a changed list and call remove and add callbacks for
288 // each removed and added item in the new list.
289 //
290 // Parameters
291 // ----------
292 // old_list : array
293 // new_list : array
294 // removed_callback : Callback(item)
295 // Callback that is called for each item removed.
296 // added_callback : Callback(item)
297 // Callback that is called for each item added.
298
299
300 // removed items
301 _.each(_.difference(old_list, new_list), function(item, index, list) {
302 removed_callback(item);
303 }, this);
304
305 // added items
306 _.each(_.difference(new_list, old_list), function(item, index, list) {
307 added_callback(item);
308 }, this);
309 },
310
311 callbacks: function(){
312 // Create msg callbacks for a comm msg.
313 return this.model.callbacks(this);
314 },
315
316 render: function(){
317 // Render the view.
318 //
319 // By default, this is only called the first time the view is created
320 },
321
322 send: function (content) {
323 // Send a custom msg associated with this view.
324 this.model.send(content, this.callbacks());
325 },
326
327 touch: function () {
328 this.model.save_changes(this.callbacks());
329 },
330 });
331
332
333 var DOMWidgetView = WidgetView.extend({
334 initialize: function (options) {
335 // Public constructor
336
337 // In the future we may want to make changes more granular
338 // (e.g., trigger on visible:change).
339 this.model.on('change', this.update, this);
340 this.model.on('msg:custom', this.on_msg, this);
341 DOMWidgetView.__super__.initialize.apply(this, arguments);
342 },
343
344 on_msg: function(msg) {
345 // Handle DOM specific msgs.
346 switch(msg.msg_type) {
347 case 'add_class':
348 this.add_class(msg.selector, msg.class_list);
349 break;
350 case 'remove_class':
351 this.remove_class(msg.selector, msg.class_list);
352 break;
353 }
354 },
355
356 add_class: function (selector, class_list) {
357 // Add a DOM class to an element.
358 this._get_selector_element(selector).addClass(class_list);
359 },
360
361 remove_class: function (selector, class_list) {
362 // Remove a DOM class from an element.
363 this._get_selector_element(selector).removeClass(class_list);
364 },
365
366 update: function () {
367 // Update the contents of this view
368 //
369 // Called when the model is changed. The model may have been
370 // changed by another view or by a state update from the back-end.
371 // The very first update seems to happen before the element is
372 // finished rendering so we use setTimeout to give the element time
373 // to render
374 var e = this.$el;
375 var visible = this.model.get('visible');
376 setTimeout(function() {e.toggle(visible);},0);
377
378 var css = this.model.get('_css');
379 if (css === undefined) {return;}
380 var that = this;
381 _.each(css, function(css_traits, selector){
382 // Apply the css traits to all elements that match the selector.
383 var elements = that._get_selector_element(selector);
384 if (elements.length > 0) {
385 _.each(css_traits, function(css_value, css_key){
386 elements.css(css_key, css_value);
387 });
388 }
389 });
390 },
391
392 _get_selector_element: function (selector) {
393 // Get the elements via the css selector.
394
395 // If the selector is blank, apply the style to the $el_to_style
396 // element. If the $el_to_style element is not defined, use apply
397 // the style to the view's element.
398 var elements;
399 if (!selector) {
400 if (this.$el_to_style === undefined) {
401 elements = this.$el;
402 } else {
403 elements = this.$el_to_style;
404 }
405 } else {
406 elements = this.$el.find(selector);
407 }
408 return elements;
409 },
410 });
411
412 IPython.WidgetModel = WidgetModel;
413 IPython.WidgetView = WidgetView;
414 IPython.DOMWidgetView = DOMWidgetView;
415
416 // Pass through WidgetManager namespace.
417 return WidgetManager;
418 });
@@ -0,0 +1,125 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // BoolWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
19 var CheckboxView = IPython.DOMWidgetView.extend({
20 render : function(){
21 // Called when view is rendered.
22 this.$el
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
25 .addClass('widget-hlabel')
26 .appendTo(this.$el)
27 .hide();
28 this.$checkbox = $('<input />')
29 .attr('type', 'checkbox')
30 .appendTo(this.$el)
31 .click($.proxy(this.handle_click, this));
32
33 this.$el_to_style = this.$checkbox; // Set default element to style
34 this.update(); // Set defaults.
35 },
36
37 handle_click: function() {
38 // Handles when the checkbox is clicked.
39
40 // Calling model.set will trigger all of the other views of the
41 // model to update.
42 var value = this.model.get('value');
43 this.model.set('value', ! value, {updated_view: this});
44 this.touch();
45 },
46
47 update : function(options){
48 // Update the contents of this view
49 //
50 // Called when the model is changed. The model may have been
51 // changed by another view or by a state update from the back-end.
52 this.$checkbox.prop('checked', this.model.get('value'));
53
54 if (options === undefined || options.updated_view != this) {
55 var disabled = this.model.get('disabled');
56 this.$checkbox.prop('disabled', disabled);
57
58 var description = this.model.get('description');
59 if (description.length === 0) {
60 this.$label.hide();
61 } else {
62 this.$label.text(description);
63 this.$label.show();
64 }
65 }
66 return CheckboxView.__super__.update.apply(this);
67 },
68
69 });
70 WidgetManager.register_widget_view('CheckboxView', CheckboxView);
71
72
73 var ToggleButtonView = IPython.DOMWidgetView.extend({
74 render : function() {
75 // Called when view is rendered.
76 var that = this;
77 this.setElement($('<button />')
78 .addClass('btn')
79 .attr('type', 'button')
80 .on('click', function (e) {
81 e.preventDefault();
82 that.handle_click();
83 }));
84
85 this.update(); // Set defaults.
86 },
87
88 update : function(options){
89 // Update the contents of this view
90 //
91 // Called when the model is changed. The model may have been
92 // changed by another view or by a state update from the back-end.
93 if (this.model.get('value')) {
94 this.$el.addClass('active');
95 } else {
96 this.$el.removeClass('active');
97 }
98
99 if (options === undefined || options.updated_view != this) {
100
101 var disabled = this.model.get('disabled');
102 this.$el.prop('disabled', disabled);
103
104 var description = this.model.get('description');
105 if (description.length === 0) {
106 this.$el.text(' '); // Preserve button height
107 } else {
108 this.$el.text(description);
109 }
110 }
111 return ToggleButtonView.__super__.update.apply(this);
112 },
113
114 handle_click: function(e) {
115 // Handles and validates user input.
116
117 // Calling model.set will trigger all of the other views of the
118 // model to update.
119 var value = this.model.get('value');
120 this.model.set('value', ! value, {updated_view: this});
121 this.touch();
122 },
123 });
124 WidgetManager.register_widget_view('ToggleButtonView', ToggleButtonView);
125 });
@@ -0,0 +1,60 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // ButtonWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
19 var ButtonView = IPython.DOMWidgetView.extend({
20 render : function(){
21 // Called when view is rendered.
22 this.setElement($("<button />")
23 .addClass('btn'));
24
25 this.update(); // Set defaults.
26 },
27
28 update : function(){
29 // Update the contents of this view
30 //
31 // Called when the model is changed. The model may have been
32 // changed by another view or by a state update from the back-end.
33 var description = this.model.get('description');
34 if (description.length === 0) {
35 this.$el.text(' '); // Preserve button height
36 } else {
37 this.$el.text(description);
38 }
39
40 if (this.model.get('disabled')) {
41 this.$el.attr('disabled','disabled');
42 } else {
43 this.$el.removeAttr('disabled');
44 }
45
46 return ButtonView.__super__.update.apply(this);
47 },
48
49 events: {
50 // Dictionary of events and their handlers.
51 'click': '_handle_click',
52 },
53
54 _handle_click: function(){
55 // Handles when the button is clicked.
56 this.send({event: 'click'});
57 },
58 });
59 WidgetManager.register_widget_view('ButtonView', ButtonView);
60 });
@@ -0,0 +1,272 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // ContainerWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager) {
18
19 var ContainerView = IPython.DOMWidgetView.extend({
20 render: function(){
21 // Called when view is rendered.
22 this.$el.addClass('widget-container');
23 this.children={};
24 this.update_children([], this.model.get('_children'));
25 this.model.on('change:_children', function(model, value, options) {
26 this.update_children(model.previous('_children'), value);
27 }, this);
28 this.update();
29 },
30
31 update_children: function(old_list, new_list) {
32 // Called when the children list changes.
33 this.do_diff(old_list,
34 new_list,
35 $.proxy(this.remove_child_model, this),
36 $.proxy(this.add_child_model, this));
37 },
38
39 remove_child_model: function(model) {
40 // Called when a model is removed from the children list.
41 this.child_views[model.id].remove();
42 this.delete_child_view(model);
43 },
44
45 add_child_model: function(model) {
46 // Called when a model is added to the children list.
47 var view = this.create_child_view(model);
48 this.$el.append(view.$el);
49 },
50
51 update: function(){
52 // Update the contents of this view
53 //
54 // Called when the model is changed. The model may have been
55 // changed by another view or by a state update from the back-end.
56 return ContainerView.__super__.update.apply(this);
57 },
58 });
59
60 WidgetManager.register_widget_view('ContainerView', ContainerView);
61
62 var PopupView = IPython.DOMWidgetView.extend({
63 render: function(){
64 // Called when view is rendered.
65 var that = this;
66 this.children={};
67
68 this.$el.on("remove", function(){
69 that.$window.remove();
70 });
71 this.$window = $('<div />')
72 .addClass('modal widget-modal')
73 .appendTo($('#notebook-container'))
74 .mousedown(function(){
75 that.bring_to_front();
76 });
77 this.$title_bar = $('<div />')
78 .addClass('popover-title')
79 .appendTo(this.$window)
80 .mousedown(function(){
81 that.bring_to_front();
82 });
83 this.$close = $('<button />')
84 .addClass('close icon-remove')
85 .css('margin-left', '5px')
86 .appendTo(this.$title_bar)
87 .click(function(){
88 that.hide();
89 event.stopPropagation();
90 });
91 this.$minimize = $('<button />')
92 .addClass('close icon-arrow-down')
93 .appendTo(this.$title_bar)
94 .click(function(){
95 that.popped_out = !that.popped_out;
96 if (!that.popped_out) {
97 that.$minimize
98 .removeClass('icon-arrow-down')
99 .addClass('icon-arrow-up');
100
101 that.$window
102 .draggable('destroy')
103 .resizable('destroy')
104 .removeClass('widget-modal modal')
105 .addClass('docked-widget-modal')
106 .detach()
107 .insertBefore(that.$show_button);
108 that.$show_button.hide();
109 that.$close.hide();
110 } else {
111 that.$minimize
112 .addClass('icon-arrow-down')
113 .removeClass('icon-arrow-up');
114
115 that.$window
116 .removeClass('docked-widget-modal')
117 .addClass('widget-modal modal')
118 .detach()
119 .appendTo($('#notebook-container'))
120 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
121 .resizable()
122 .children('.ui-resizable-handle').show();
123 that.show();
124 that.$show_button.show();
125 that.$close.show();
126 }
127 event.stopPropagation();
128 });
129 this.$title = $('<div />')
130 .addClass('widget-modal-title')
131 .text(' ')
132 .appendTo(this.$title_bar);
133 this.$body = $('<div />')
134 .addClass('modal-body')
135 .addClass('widget-modal-body')
136 .addClass('widget-container')
137 .appendTo(this.$window);
138
139 this.$show_button = $('<button />')
140 .text(' ')
141 .addClass('btn btn-info widget-modal-show')
142 .appendTo(this.$el)
143 .click(function(){
144 that.show();
145 });
146
147 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
148 this.$window.resizable();
149 this.$window.on('resize', function(){
150 that.$body.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
151 });
152
153 this.$el_to_style = this.$body;
154 this._shown_once = false;
155 this.popped_out = true;
156
157 this.update_children([], this.model.get('_children'));
158 this.model.on('change:_children', function(model, value, options) {
159 this.update_children(model.previous('_children'), value);
160 }, this);
161 this.update();
162 },
163
164 hide: function() {
165 // Called when the modal hide button is clicked.
166 this.$window.hide();
167 this.$show_button.removeClass('btn-info');
168 },
169
170 show: function() {
171 // Called when the modal show button is clicked.
172 this.$show_button.addClass('btn-info');
173 this.$window.show();
174 if (this.popped_out) {
175 this.$window.css("positon", "absolute");
176 this.$window.css("top", "0px");
177 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
178 $(window).scrollLeft()) + "px");
179 this.bring_to_front();
180 }
181 },
182
183 bring_to_front: function() {
184 // Make the modal top-most, z-ordered about the other modals.
185 var $widget_modals = $(".widget-modal");
186 var max_zindex = 0;
187 $widget_modals.each(function (index, el){
188 max_zindex = Math.max(max_zindex, parseInt($(el).css('z-index')));
189 });
190
191 // Start z-index of widget modals at 2000
192 max_zindex = Math.max(max_zindex, 2000);
193
194 $widget_modals.each(function (index, el){
195 $el = $(el);
196 if (max_zindex == parseInt($el.css('z-index'))) {
197 $el.css('z-index', max_zindex - 1);
198 }
199 });
200 this.$window.css('z-index', max_zindex);
201 },
202
203 update_children: function(old_list, new_list) {
204 // Called when the children list is modified.
205 this.do_diff(old_list,
206 new_list,
207 $.proxy(this.remove_child_model, this),
208 $.proxy(this.add_child_model, this));
209 },
210
211 remove_child_model: function(model) {
212 // Called when a child is removed from children list.
213 this.child_views[model.id].remove();
214 this.delete_child_view(model);
215 },
216
217 add_child_model: function(model) {
218 // Called when a child is added to children list.
219 var view = this.create_child_view(model);
220 this.$body.append(view.$el);
221 },
222
223 update: function(){
224 // Update the contents of this view
225 //
226 // Called when the model is changed. The model may have been
227 // changed by another view or by a state update from the back-end.
228 var description = this.model.get('description');
229 if (description.length === 0) {
230 this.$title.text(' '); // Preserve title height
231 } else {
232 this.$title.text(description);
233 }
234
235 var button_text = this.model.get('button_text');
236 if (button_text.length === 0) {
237 this.$show_button.text(' '); // Preserve button height
238 } else {
239 this.$show_button.text(button_text);
240 }
241
242 if (!this._shown_once) {
243 this._shown_once = true;
244 this.show();
245 }
246
247 return PopupView.__super__.update.apply(this);
248 },
249
250 _get_selector_element: function(selector) {
251 // Get an element view a 'special' jquery selector. (see widget.js)
252 //
253 // Since the modal actually isn't within the $el in the DOM, we need to extend
254 // the selector logic to allow the user to set css on the modal if need be.
255 // The convention used is:
256 // "modal" - select the modal div
257 // "modal [selector]" - select element(s) within the modal div.
258 // "[selector]" - select elements within $el
259 // "" - select the $el_to_style
260 if (selector.substring(0, 5) == 'modal') {
261 if (selector == 'modal') {
262 return this.$window;
263 } else {
264 return this.$window.find(selector.substring(6));
265 }
266 } else {
267 return PopupView.__super__._get_selector_element.apply(this, [selector]);
268 }
269 },
270 });
271 WidgetManager.register_widget_view('PopupView', PopupView);
272 });
@@ -0,0 +1,42 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // FloatWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget",
18 "notebook/js/widgets/widget_int"],
19 function(WidgetManager, int_widgets){
20
21 var IntSliderView = int_widgets[0];
22 var IntTextView = int_widgets[1];
23
24
25 var FloatSliderView = IntSliderView.extend({
26 _validate_slide_value: function(x) {
27 // Validate the value of the slider before sending it to the back-end
28 // and applying it to the other views on the page.
29 return x;
30 },
31 });
32 WidgetManager.register_widget_view('FloatSliderView', FloatSliderView);
33
34
35 var FloatTextView = IntTextView.extend({
36 _parse_value: function(value) {
37 // Parse the value stored in a string.
38 return parseFloat(value);
39 },
40 });
41 WidgetManager.register_widget_view('FloatTextView', FloatTextView);
42 });
@@ -0,0 +1,51 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // ImageWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
19 var ImageView = IPython.DOMWidgetView.extend({
20 render : function(){
21 // Called when view is rendered.
22 this.setElement($("<img />"));
23 this.update(); // Set defaults.
24 },
25
26 update : function(){
27 // Update the contents of this view
28 //
29 // Called when the model is changed. The model may have been
30 // changed by another view or by a state update from the back-end.
31 var image_src = 'data:image/' + this.model.get('format') + ';base64,' + this.model.get('_b64value');
32 this.$el.attr('src', image_src);
33
34 var width = this.model.get('width');
35 if (width !== undefined && width.length > 0) {
36 this.$el.attr('width', width);
37 } else {
38 this.$el.removeAttr('width');
39 }
40
41 var height = this.model.get('height');
42 if (height !== undefined && height.length > 0) {
43 this.$el.attr('height', height);
44 } else {
45 this.$el.removeAttr('height');
46 }
47 return ImageView.__super__.update.apply(this);
48 },
49 });
50 WidgetManager.register_widget_view('ImageView', ImageView);
51 });
@@ -0,0 +1,284 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // IntWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
19 var IntSliderView = IPython.DOMWidgetView.extend({
20 render : function(){
21 // Called when view is rendered.
22 this.$el
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
25 .appendTo(this.$el)
26 .addClass('widget-hlabel')
27 .hide();
28 this.$slider = $('<div />')
29 .slider({})
30 .addClass('slider');
31
32 // Put the slider in a container
33 this.$slider_container = $('<div />')
34 .addClass('widget-hslider')
35 .append(this.$slider);
36 this.$el_to_style = this.$slider_container; // Set default element to style
37 this.$el.append(this.$slider_container);
38
39 // Set defaults.
40 this.update();
41 },
42
43 update : function(options){
44 // Update the contents of this view
45 //
46 // Called when the model is changed. The model may have been
47 // changed by another view or by a state update from the back-end.
48 if (options === undefined || options.updated_view != this) {
49 // JQuery slider option keys. These keys happen to have a
50 // one-to-one mapping with the corrosponding keys of the model.
51 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
52 var that = this;
53 _.each(jquery_slider_keys, function(key, i) {
54 var model_value = that.model.get(key);
55 if (model_value !== undefined) {
56 that.$slider.slider("option", key, model_value);
57 }
58 });
59
60 // WORKAROUND FOR JQUERY SLIDER BUG.
61 // The horizontal position of the slider handle
62 // depends on the value of the slider at the time
63 // of orientation change. Before applying the new
64 // workaround, we set the value to the minimum to
65 // make sure that the horizontal placement of the
66 // handle in the vertical slider is always
67 // consistent.
68 var orientation = this.model.get('orientation');
69 var value = this.model.get('min');
70 this.$slider.slider('option', 'value', value);
71 this.$slider.slider('option', 'orientation', orientation);
72 value = this.model.get('value');
73 this.$slider.slider('option', 'value', value);
74
75 // Use the right CSS classes for vertical & horizontal sliders
76 if (orientation=='vertical') {
77 this.$slider_container
78 .removeClass('widget-hslider')
79 .addClass('widget-vslider');
80 this.$el
81 .removeClass('widget-hbox-single')
82 .addClass('widget-vbox-single');
83 this.$label
84 .removeClass('widget-hlabel')
85 .addClass('widget-vlabel');
86
87 } else {
88 this.$slider_container
89 .removeClass('widget-vslider')
90 .addClass('widget-hslider');
91 this.$el
92 .removeClass('widget-vbox-single')
93 .addClass('widget-hbox-single');
94 this.$label
95 .removeClass('widget-vlabel')
96 .addClass('widget-hlabel');
97 }
98
99 var description = this.model.get('description');
100 if (description.length === 0) {
101 this.$label.hide();
102 } else {
103 this.$label.text(description);
104 this.$label.show();
105 }
106 }
107 return IntSliderView.__super__.update.apply(this);
108 },
109
110 events: {
111 // Dictionary of events and their handlers.
112 "slide" : "handleSliderChange"
113 },
114
115 handleSliderChange: function(e, ui) {
116 // Called when the slider value is changed.
117
118 // Calling model.set will trigger all of the other views of the
119 // model to update.
120 this.model.set('value', this._validate_slide_value(ui.value), {updated_view: this});
121 this.touch();
122 },
123
124 _validate_slide_value: function(x) {
125 // Validate the value of the slider before sending it to the back-end
126 // and applying it to the other views on the page.
127
128 // Double bit-wise not truncates the decimel (int cast).
129 return ~~x;
130 },
131 });
132 WidgetManager.register_widget_view('IntSliderView', IntSliderView);
133
134
135 var IntTextView = IPython.DOMWidgetView.extend({
136 render : function(){
137 // Called when view is rendered.
138 this.$el
139 .addClass('widget-hbox-single');
140 this.$label = $('<div />')
141 .appendTo(this.$el)
142 .addClass('widget-hlabel')
143 .hide();
144 this.$textbox = $('<input type="text" />')
145 .addClass('input')
146 .addClass('widget-numeric-text')
147 .appendTo(this.$el);
148 this.$el_to_style = this.$textbox; // Set default element to style
149 this.update(); // Set defaults.
150 },
151
152 update : function(options){
153 // Update the contents of this view
154 //
155 // Called when the model is changed. The model may have been
156 // changed by another view or by a state update from the back-end.
157 if (options === undefined || options.updated_view != this) {
158 var value = this.model.get('value');
159 if (this._parse_value(this.$textbox.val()) != value) {
160 this.$textbox.val(value);
161 }
162
163 if (this.model.get('disabled')) {
164 this.$textbox.attr('disabled','disabled');
165 } else {
166 this.$textbox.removeAttr('disabled');
167 }
168
169 var description = this.model.get('description');
170 if (description.length === 0) {
171 this.$label.hide();
172 } else {
173 this.$label.text(description);
174 this.$label.show();
175 }
176 }
177 return IntTextView.__super__.update.apply(this);
178 },
179
180 events: {
181 // Dictionary of events and their handlers.
182 "keyup input" : "handleChanging",
183 "paste input" : "handleChanging",
184 "cut input" : "handleChanging",
185
186 // Fires only when control is validated or looses focus.
187 "change input" : "handleChanged"
188 },
189
190 handleChanging: function(e) {
191 // Handles and validates user input.
192
193 // Try to parse value as a int.
194 var numericalValue = 0;
195 if (e.target.value !== '') {
196 numericalValue = this._parse_value(e.target.value);
197 }
198
199 // If parse failed, reset value to value stored in model.
200 if (isNaN(numericalValue)) {
201 e.target.value = this.model.get('value');
202 } else if (!isNaN(numericalValue)) {
203 if (this.model.get('max') !== undefined) {
204 numericalValue = Math.min(this.model.get('max'), numericalValue);
205 }
206 if (this.model.get('min') !== undefined) {
207 numericalValue = Math.max(this.model.get('min'), numericalValue);
208 }
209
210 // Apply the value if it has changed.
211 if (numericalValue != this.model.get('value')) {
212
213 // Calling model.set will trigger all of the other views of the
214 // model to update.
215 this.model.set('value', numericalValue, {updated_view: this});
216 this.touch();
217 }
218 }
219 },
220
221 handleChanged: function(e) {
222 // Applies validated input.
223 if (this.model.get('value') != e.target.value) {
224 e.target.value = this.model.get('value');
225 }
226 },
227
228 _parse_value: function(value) {
229 // Parse the value stored in a string.
230 return parseInt(value);
231 },
232 });
233 WidgetManager.register_widget_view('IntTextView', IntTextView);
234
235
236 var ProgressView = IPython.DOMWidgetView.extend({
237 render : function(){
238 // Called when view is rendered.
239 this.$el
240 .addClass('widget-hbox-single');
241 this.$label = $('<div />')
242 .appendTo(this.$el)
243 .addClass('widget-hlabel')
244 .hide();
245 this.$progress = $('<div />')
246 .addClass('progress')
247 .addClass('widget-progress')
248 .appendTo(this.$el);
249 this.$el_to_style = this.$progress; // Set default element to style
250 this.$bar = $('<div />')
251 .addClass('bar')
252 .css('width', '50%')
253 .appendTo(this.$progress);
254 this.update(); // Set defaults.
255 },
256
257 update : function(){
258 // Update the contents of this view
259 //
260 // Called when the model is changed. The model may have been
261 // changed by another view or by a state update from the back-end.
262 var value = this.model.get('value');
263 var max = this.model.get('max');
264 var min = this.model.get('min');
265 var percent = 100.0 * (value - min) / (max - min);
266 this.$bar.css('width', percent + '%');
267
268 var description = this.model.get('description');
269 if (description.length === 0) {
270 this.$label.hide();
271 } else {
272 this.$label.text(description);
273 this.$label.show();
274 }
275 return ProgressView.__super__.update.apply(this);
276 },
277 });
278 WidgetManager.register_widget_view('ProgressView', ProgressView);
279
280
281 // Return the slider and text views so they can be inheritted to create the
282 // float versions.
283 return [IntSliderView, IntTextView];
284 });
@@ -0,0 +1,376 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // SelectionWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
19 var DropdownView = IPython.DOMWidgetView.extend({
20 render : function(){
21 // Called when view is rendered.
22 this.$el
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
25 .appendTo(this.$el)
26 .addClass('widget-hlabel')
27 .hide();
28 this.$buttongroup = $('<div />')
29 .addClass('widget_item')
30 .addClass('btn-group')
31 .appendTo(this.$el);
32 this.$el_to_style = this.$buttongroup; // Set default element to style
33 this.$droplabel = $('<button />')
34 .addClass('btn')
35 .addClass('widget-combo-btn')
36 .text(' ')
37 .appendTo(this.$buttongroup);
38 this.$dropbutton = $('<button />')
39 .addClass('btn')
40 .addClass('dropdown-toggle')
41 .addClass('widget-combo-carrot-btn')
42 .attr('data-toggle', 'dropdown')
43 .append($('<span />').addClass("caret"))
44 .appendTo(this.$buttongroup);
45 this.$droplist = $('<ul />')
46 .addClass('dropdown-menu')
47 .appendTo(this.$buttongroup);
48
49 // Set defaults.
50 this.update();
51 },
52
53 update : function(options){
54 // Update the contents of this view
55 //
56 // Called when the model is changed. The model may have been
57 // changed by another view or by a state update from the back-end.
58
59 if (options === undefined || options.updated_view != this) {
60 var selected_item_text = this.model.get('_value');
61 if (selected_item_text.length === 0) {
62 this.$droplabel.text(' ');
63 } else {
64 this.$droplabel.text(selected_item_text);
65 }
66
67 var items = this.model.get('labels');
68 var $replace_droplist = $('<ul />')
69 .addClass('dropdown-menu');
70 var that = this;
71 _.each(items, function(item, i) {
72 var item_button = $('<a href="#"/>')
73 .text(item)
74 .on('click', $.proxy(that.handle_click, that));
75 $replace_droplist.append($('<li />').append(item_button));
76 });
77
78 this.$droplist.replaceWith($replace_droplist);
79 this.$droplist.remove();
80 this.$droplist = $replace_droplist;
81
82 if (this.model.get('disabled')) {
83 this.$buttongroup.attr('disabled','disabled');
84 this.$droplabel.attr('disabled','disabled');
85 this.$dropbutton.attr('disabled','disabled');
86 this.$droplist.attr('disabled','disabled');
87 } else {
88 this.$buttongroup.removeAttr('disabled');
89 this.$droplabel.removeAttr('disabled');
90 this.$dropbutton.removeAttr('disabled');
91 this.$droplist.removeAttr('disabled');
92 }
93
94 var description = this.model.get('description');
95 if (description.length === 0) {
96 this.$label.hide();
97 } else {
98 this.$label.text(description);
99 this.$label.show();
100 }
101 }
102 return DropdownView.__super__.update.apply(this);
103 },
104
105 handle_click: function (e) {
106 // Handle when a value is clicked.
107
108 // Calling model.set will trigger all of the other views of the
109 // model to update.
110 this.model.set('_value', $(e.target).text(), {updated_view: this});
111 this.touch();
112 },
113
114 });
115 WidgetManager.register_widget_view('DropdownView', DropdownView);
116
117
118 var RadioButtonsView = IPython.DOMWidgetView.extend({
119 render : function(){
120 // Called when view is rendered.
121 this.$el
122 .addClass('widget-hbox');
123 this.$label = $('<div />')
124 .appendTo(this.$el)
125 .addClass('widget-hlabel')
126 .hide();
127 this.$container = $('<div />')
128 .appendTo(this.$el)
129 .addClass('widget-container')
130 .addClass('vbox');
131 this.$el_to_style = this.$container; // Set default element to style
132 this.update();
133 },
134
135 update : function(options){
136 // Update the contents of this view
137 //
138 // Called when the model is changed. The model may have been
139 // changed by another view or by a state update from the back-end.
140 if (options === undefined || options.updated_view != this) {
141 // Add missing items to the DOM.
142 var items = this.model.get('labels');
143 var disabled = this.model.get('disabled');
144 var that = this;
145 _.each(items, function(item, index) {
146 var item_query = ' :input[value="' + item + '"]';
147 if (that.$el.find(item_query).length === 0) {
148 var $label = $('<label />')
149 .addClass('radio')
150 .text(item)
151 .appendTo(that.$container);
152
153 $('<input />')
154 .attr('type', 'radio')
155 .addClass(that.model)
156 .val(item)
157 .prependTo($label)
158 .on('click', $.proxy(that.handle_click, that));
159 }
160
161 var $item_element = that.$container.find(item_query);
162 if (that.model.get('_value') == item) {
163 $item_element.prop('checked', true);
164 } else {
165 $item_element.prop('checked', false);
166 }
167 $item_element.prop('disabled', disabled);
168 });
169
170 // Remove items that no longer exist.
171 this.$container.find('input').each(function(i, obj) {
172 var value = $(obj).val();
173 var found = false;
174 _.each(items, function(item, index) {
175 if (item == value) {
176 found = true;
177 return false;
178 }
179 });
180
181 if (!found) {
182 $(obj).parent().remove();
183 }
184 });
185
186 var description = this.model.get('description');
187 if (description.length === 0) {
188 this.$label.hide();
189 } else {
190 this.$label.text(description);
191 this.$label.show();
192 }
193 }
194 return RadioButtonsView.__super__.update.apply(this);
195 },
196
197 handle_click: function (e) {
198 // Handle when a value is clicked.
199
200 // Calling model.set will trigger all of the other views of the
201 // model to update.
202 this.model.set('_value', $(e.target).val(), {updated_view: this});
203 this.touch();
204 },
205 });
206 WidgetManager.register_widget_view('RadioButtonsView', RadioButtonsView);
207
208
209 var ToggleButtonsView = IPython.DOMWidgetView.extend({
210 render : function(){
211 // Called when view is rendered.
212 this.$el
213 .addClass('widget-hbox-single');
214 this.$label = $('<div />')
215 .appendTo(this.$el)
216 .addClass('widget-hlabel')
217 .hide();
218 this.$buttongroup = $('<div />')
219 .addClass('btn-group')
220 .attr('data-toggle', 'buttons-radio')
221 .appendTo(this.$el);
222 this.$el_to_style = this.$buttongroup; // Set default element to style
223 this.update();
224 },
225
226 update : function(options){
227 // Update the contents of this view
228 //
229 // Called when the model is changed. The model may have been
230 // changed by another view or by a state update from the back-end.
231 if (options === undefined || options.updated_view != this) {
232 // Add missing items to the DOM.
233 var items = this.model.get('labels');
234 var disabled = this.model.get('disabled');
235 var that = this;
236 _.each(items, function(item, index) {
237 var item_query = ' :contains("' + item + '")';
238 if (that.$buttongroup.find(item_query).length === 0) {
239 $('<button />')
240 .attr('type', 'button')
241 .addClass('btn')
242 .text(item)
243 .appendTo(that.$buttongroup)
244 .on('click', $.proxy(that.handle_click, that));
245 }
246
247 var $item_element = that.$buttongroup.find(item_query);
248 if (that.model.get('_value') == item) {
249 $item_element.addClass('active');
250 } else {
251 $item_element.removeClass('active');
252 }
253 $item_element.prop('disabled', disabled);
254 });
255
256 // Remove items that no longer exist.
257 this.$buttongroup.find('button').each(function(i, obj) {
258 var value = $(obj).text();
259 var found = false;
260 _.each(items, function(item, index) {
261 if (item == value) {
262 found = true;
263 return false;
264 }
265 });
266
267 if (!found) {
268 $(obj).remove();
269 }
270 });
271
272 var description = this.model.get('description');
273 if (description.length === 0) {
274 this.$label.hide();
275 } else {
276 this.$label.text(description);
277 this.$label.show();
278 }
279 }
280 return ToggleButtonsView.__super__.update.apply(this);
281 },
282
283 handle_click: function (e) {
284 // Handle when a value is clicked.
285
286 // Calling model.set will trigger all of the other views of the
287 // model to update.
288 this.model.set('_value', $(e.target).text(), {updated_view: this});
289 this.touch();
290 },
291 });
292 WidgetManager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
293
294
295 var SelectView = IPython.DOMWidgetView.extend({
296 render : function(){
297 // Called when view is rendered.
298 this.$el
299 .addClass('widget-hbox');
300 this.$label = $('<div />')
301 .appendTo(this.$el)
302 .addClass('widget-hlabel')
303 .hide();
304 this.$listbox = $('<select />')
305 .addClass('widget-listbox')
306 .attr('size', 6)
307 .appendTo(this.$el);
308 this.$el_to_style = this.$listbox; // Set default element to style
309 this.update();
310 },
311
312 update : function(options){
313 // Update the contents of this view
314 //
315 // Called when the model is changed. The model may have been
316 // changed by another view or by a state update from the back-end.
317 if (options === undefined || options.updated_view != this) {
318 // Add missing items to the DOM.
319 var items = this.model.get('labels');
320 var that = this;
321 _.each(items, function(item, index) {
322 var item_query = ' :contains("' + item + '")';
323 if (that.$listbox.find(item_query).length === 0) {
324 $('<option />')
325 .text(item)
326 .attr('_value', item)
327 .appendTo(that.$listbox)
328 .on('click', $.proxy(that.handle_click, that));
329 }
330 });
331
332 // Select the correct element
333 this.$listbox.val(this.model.get('_value'));
334
335 // Disable listbox if needed
336 var disabled = this.model.get('disabled');
337 this.$listbox.prop('disabled', disabled);
338
339 // Remove items that no longer exist.
340 this.$listbox.find('option').each(function(i, obj) {
341 var value = $(obj).text();
342 var found = false;
343 _.each(items, function(item, index) {
344 if (item == value) {
345 found = true;
346 return false;
347 }
348 });
349
350 if (!found) {
351 $(obj).remove();
352 }
353 });
354
355 var description = this.model.get('description');
356 if (description.length === 0) {
357 this.$label.hide();
358 } else {
359 this.$label.text(description);
360 this.$label.show();
361 }
362 }
363 return SelectView.__super__.update.apply(this);
364 },
365
366 handle_click: function (e) {
367 // Handle when a value is clicked.
368
369 // Calling model.set will trigger all of the other views of the
370 // model to update.
371 this.model.set('_value', $(e.target).text(), {updated_view: this});
372 this.touch();
373 },
374 });
375 WidgetManager.register_widget_view('SelectView', SelectView);
376 });
@@ -0,0 +1,244 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // SelectionContainerWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
19 var AccordionView = IPython.DOMWidgetView.extend({
20 render: function(){
21 // Called when view is rendered.
22 var guid = 'accordion' + IPython.utils.uuid();
23 this.$el
24 .attr('id', guid)
25 .addClass('accordion');
26 this.containers = [];
27 this.model_containers = {};
28 this.update_children([], this.model.get('_children'));
29 this.model.on('change:_children', function(model, value, options) {
30 this.update_children(model.previous('_children'), value);
31 }, this);
32 },
33
34 update: function(options) {
35 // Update the contents of this view
36 //
37 // Called when the model is changed. The model may have been
38 // changed by another view or by a state update from the back-end.
39 if (options === undefined || options.updated_view != this) {
40 // Set tab titles
41 var titles = this.model.get('_titles');
42 var that = this;
43 _.each(titles, function(title, page_index) {
44 var accordian = that.containers[page_index];
45 if (accordian !== undefined) {
46 accordian
47 .find('.accordion-heading')
48 .find('.accordion-toggle')
49 .text(title);
50 }
51 });
52
53 // Set selected page
54 var selected_index = this.model.get("selected_index");
55 if (0 <= selected_index && selected_index < this.containers.length) {
56 _.each(this.containers, function(container, index) {
57 if (index==selected_index) {
58 container.find('.accordion-body').collapse('show');
59 } else {
60 container.find('.accordion-body').collapse('hide');
61 }
62 });
63 }
64 }
65 return AccordionView.__super__.update.apply(this);
66 },
67
68 update_children: function(old_list, new_list) {
69 // Called when the children list is modified.
70 this.do_diff(old_list,
71 new_list,
72 $.proxy(this.remove_child_model, this),
73 $.proxy(this.add_child_model, this));
74 },
75
76 remove_child_model: function(model) {
77 // Called when a child is removed from children list.
78 var accordion_group = this.model_containers[model.id];
79 this.containers.splice(accordion_group.container_index, 1);
80 delete this.model_containers[model.id];
81 accordion_group.remove();
82 this.delete_child_view(model);
83 },
84
85 add_child_model: function(model) {
86 // Called when a child is added to children list.
87 var view = this.create_child_view(model);
88 var index = this.containers.length;
89 var uuid = IPython.utils.uuid();
90 var accordion_group = $('<div />')
91 .addClass('accordion-group')
92 .appendTo(this.$el);
93 var accordion_heading = $('<div />')
94 .addClass('accordion-heading')
95 .appendTo(accordion_group);
96 var that = this;
97 var accordion_toggle = $('<a />')
98 .addClass('accordion-toggle')
99 .attr('data-toggle', 'collapse')
100 .attr('data-parent', '#' + this.$el.attr('id'))
101 .attr('href', '#' + uuid)
102 .click(function(evt){
103
104 // Calling model.set will trigger all of the other views of the
105 // model to update.
106 that.model.set("selected_index", index, {updated_view: this});
107 that.touch();
108 })
109 .text('Page ' + index)
110 .appendTo(accordion_heading);
111 var accordion_body = $('<div />', {id: uuid})
112 .addClass('accordion-body collapse')
113 .appendTo(accordion_group);
114 var accordion_inner = $('<div />')
115 .addClass('accordion-inner')
116 .appendTo(accordion_body);
117 var container_index = this.containers.push(accordion_group) - 1;
118 accordion_group.container_index = container_index;
119 this.model_containers[model.id] = accordion_group;
120 accordion_inner.append(view.$el);
121
122 this.update();
123
124 // Stupid workaround to close the bootstrap accordion tabs which
125 // open by default even though they don't have the `in` class
126 // attached to them. For some reason a delay is required.
127 // TODO: Better fix.
128 setTimeout(function(){ that.update(); }, 500);
129 },
130 });
131 WidgetManager.register_widget_view('AccordionView', AccordionView);
132
133
134 var TabView = IPython.DOMWidgetView.extend({
135 initialize: function() {
136 // Public constructor.
137 this.containers = [];
138 TabView.__super__.initialize.apply(this, arguments);
139 },
140
141 render: function(){
142 // Called when view is rendered.
143 var uuid = 'tabs'+IPython.utils.uuid();
144 var that = this;
145 this.$tabs = $('<div />', {id: uuid})
146 .addClass('nav')
147 .addClass('nav-tabs')
148 .appendTo(this.$el);
149 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
150 .addClass('tab-content')
151 .appendTo(this.$el);
152 this.containers = [];
153 this.update_children([], this.model.get('_children'));
154 this.model.on('change:_children', function(model, value, options) {
155 this.update_children(model.previous('_children'), value);
156 }, this);
157 },
158
159 update_children: function(old_list, new_list) {
160 // Called when the children list is modified.
161 this.do_diff(old_list,
162 new_list,
163 $.proxy(this.remove_child_model, this),
164 $.proxy(this.add_child_model, this));
165 },
166
167 remove_child_model: function(model) {
168 // Called when a child is removed from children list.
169 var view = this.child_views[model.id];
170 this.containers.splice(view.parent_tab.tab_text_index, 1);
171 view.parent_tab.remove();
172 view.parent_container.remove();
173 view.remove();
174 this.delete_child_view(model);
175 },
176
177 add_child_model: function(model) {
178 // Called when a child is added to children list.
179 var view = this.create_child_view(model);
180 var index = this.containers.length;
181 var uuid = IPython.utils.uuid();
182
183 var that = this;
184 var tab = $('<li />')
185 .css('list-style-type', 'none')
186 .appendTo(this.$tabs);
187 view.parent_tab = tab;
188
189 var tab_text = $('<a />')
190 .attr('href', '#' + uuid)
191 .attr('data-toggle', 'tab')
192 .text('Page ' + index)
193 .appendTo(tab)
194 .click(function (e) {
195
196 // Calling model.set will trigger all of the other views of the
197 // model to update.
198 that.model.set("selected_index", index, {updated_view: this});
199 that.touch();
200 that.select_page(index);
201 });
202 tab.tab_text_index = this.containers.push(tab_text) - 1;
203
204 var contents_div = $('<div />', {id: uuid})
205 .addClass('tab-pane')
206 .addClass('fade')
207 .append(view.$el)
208 .appendTo(this.$tab_contents);
209 view.parent_container = contents_div;
210 },
211
212 update: function(options) {
213 // Update the contents of this view
214 //
215 // Called when the model is changed. The model may have been
216 // changed by another view or by a state update from the back-end.
217 if (options === undefined || options.updated_view != this) {
218 // Set tab titles
219 var titles = this.model.get('_titles');
220 var that = this;
221 _.each(titles, function(title, page_index) {
222 var tab_text = that.containers[page_index];
223 if (tab_text !== undefined) {
224 tab_text.text(title);
225 }
226 });
227
228 var selected_index = this.model.get('selected_index');
229 if (0 <= selected_index && selected_index < this.containers.length) {
230 this.select_page(selected_index);
231 }
232 }
233 return TabView.__super__.update.apply(this);
234 },
235
236 select_page: function(index) {
237 // Select a page.
238 this.$tabs.find('li')
239 .removeClass('active');
240 this.containers[index].tab('show');
241 },
242 });
243 WidgetManager.register_widget_view('TabView', TabView);
244 });
@@ -0,0 +1,222 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // StringWidget
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
19 var HTMLView = IPython.DOMWidgetView.extend({
20 render : function(){
21 // Called when view is rendered.
22 this.update(); // Set defaults.
23 },
24
25 update : function(){
26 // Update the contents of this view
27 //
28 // Called when the model is changed. The model may have been
29 // changed by another view or by a state update from the back-end.
30 this.$el.html(this.model.get('value')); // CAUTION! .html(...) CALL MANDITORY!!!
31 return HTMLView.__super__.update.apply(this);
32 },
33 });
34 WidgetManager.register_widget_view('HTMLView', HTMLView);
35
36
37 var LatexView = IPython.DOMWidgetView.extend({
38 render : function(){
39 // Called when view is rendered.
40 this.update(); // Set defaults.
41 },
42
43 update : function(){
44 // Update the contents of this view
45 //
46 // Called when the model is changed. The model may have been
47 // changed by another view or by a state update from the back-end.
48 this.$el.text(this.model.get('value'));
49 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$el.get(0)]);
50
51 return LatexView.__super__.update.apply(this);
52 },
53 });
54 WidgetManager.register_widget_view('LatexView', LatexView);
55
56
57 var TextareaView = IPython.DOMWidgetView.extend({
58 render: function(){
59 // Called when view is rendered.
60 this.$el
61 .addClass('widget-hbox');
62 this.$label = $('<div />')
63 .appendTo(this.$el)
64 .addClass('widget-hlabel')
65 .hide();
66 this.$textbox = $('<textarea />')
67 .attr('rows', 5)
68 .addClass('widget-text')
69 .appendTo(this.$el);
70 this.$el_to_style = this.$textbox; // Set default element to style
71 this.update(); // Set defaults.
72
73 this.model.on('msg:custom', $.proxy(this._handle_textarea_msg, this));
74 },
75
76 _handle_textarea_msg: function (content){
77 // Handle when a custom msg is recieved from the back-end.
78 if (content.method == "scroll_to_bottom") {
79 this.scroll_to_bottom();
80 }
81 },
82
83 scroll_to_bottom: function (){
84 // Scroll the text-area view to the bottom.
85 this.$textbox.scrollTop(this.$textbox[0].scrollHeight);
86 },
87
88 update: function(options){
89 // Update the contents of this view
90 //
91 // Called when the model is changed. The model may have been
92 // changed by another view or by a state update from the back-end.
93 if (options === undefined || options.updated_view != this) {
94 this.$textbox.val(this.model.get('value'));
95
96 var disabled = this.model.get('disabled');
97 this.$textbox.prop('disabled', disabled);
98
99 var description = this.model.get('description');
100 if (description.length === 0) {
101 this.$label.hide();
102 } else {
103 this.$label.text(description);
104 this.$label.show();
105 }
106 }
107 return TextareaView.__super__.update.apply(this);
108 },
109
110 events: {
111 // Dictionary of events and their handlers.
112 "keyup textarea" : "handleChanging",
113 "paste textarea" : "handleChanging",
114 "cut textarea" : "handleChanging"
115 },
116
117 handleChanging: function(e) {
118 // Handles and validates user input.
119
120 // Calling model.set will trigger all of the other views of the
121 // model to update.
122 this.model.set('value', e.target.value, {updated_view: this});
123 this.touch();
124 },
125 });
126 WidgetManager.register_widget_view('TextareaView', TextareaView);
127
128
129 var TextView = IPython.DOMWidgetView.extend({
130 render: function(){
131 // Called when view is rendered.
132 this.$el
133 .addClass('widget-hbox-single');
134 this.$label = $('<div />')
135 .addClass('widget-hlabel')
136 .appendTo(this.$el)
137 .hide();
138 this.$textbox = $('<input type="text" />')
139 .addClass('input')
140 .addClass('widget-text')
141 .appendTo(this.$el);
142 this.$el_to_style = this.$textbox; // Set default element to style
143 this.update(); // Set defaults.
144 },
145
146 update: function(options){
147 // Update the contents of this view
148 //
149 // Called when the model is changed. The model may have been
150 // changed by another view or by a state update from the back-end.
151 if (options === undefined || options.updated_view != this) {
152 if (this.$textbox.val() != this.model.get('value')) {
153 this.$textbox.val(this.model.get('value'));
154 }
155
156 var disabled = this.model.get('disabled');
157 this.$textbox.prop('disabled', disabled);
158
159 var description = this.model.get('description');
160 if (description.length === 0) {
161 this.$label.hide();
162 } else {
163 this.$label.text(description);
164 this.$label.show();
165 }
166 }
167 return TextView.__super__.update.apply(this);
168 },
169
170 events: {
171 // Dictionary of events and their handlers.
172 "keyup input" : "handleChanging",
173 "paste input" : "handleChanging",
174 "cut input" : "handleChanging",
175 "keypress input" : "handleKeypress",
176 "blur input" : "handleBlur",
177 "focusout input" : "handleFocusOut"
178 },
179
180 handleChanging: function(e) {
181 // Handles user input.
182
183 // Calling model.set will trigger all of the other views of the
184 // model to update.
185 this.model.set('value', e.target.value, {updated_view: this});
186 this.touch();
187 },
188
189 handleKeypress: function(e) {
190 // Handles text submition
191 if (e.keyCode == 13) { // Return key
192 this.send({event: 'submit'});
193 event.stopPropagation();
194 event.preventDefault();
195 return false;
196 }
197 },
198
199 handleBlur: function(e) {
200 // Prevent a blur from firing if the blur was not user intended.
201 // This is a workaround for the return-key focus loss bug.
202 // TODO: Is the original bug actually a fault of the keyboard
203 // manager?
204 if (e.relatedTarget === null) {
205 event.stopPropagation();
206 event.preventDefault();
207 return false;
208 }
209 },
210
211 handleFocusOut: function(e) {
212 // Prevent a blur from firing if the blur was not user intended.
213 // This is a workaround for the return-key focus loss bug.
214 if (e.relatedTarget === null) {
215 event.stopPropagation();
216 event.preventDefault();
217 return false;
218 }
219 },
220 });
221 WidgetManager.register_widget_view('TextView', TextView);
222 });
@@ -0,0 +1,251 b''
1 .widget-area {
2 /*
3 LESS file that styles IPython notebook widgets and the area they sit in.
4
5 The widget area typically looks something like this:
6 +------------------------------------------+
7 | widget-area |
8 | +--------+---------------------------+ |
9 | | prompt | widget-subarea | |
10 | | | +--------+ +--------+ | |
11 | | | | widget | | widget | | |
12 | | | +--------+ +--------+ | |
13 | +--------+---------------------------+ |
14 +------------------------------------------+
15 */
16
17 page-break-inside : avoid;
18 .hbox();
19
20 .widget-subarea {
21 padding : 0.44em 0.4em 0.4em 1px;
22 margin-left : 6px;
23
24 .border-box-sizing();
25 .vbox();
26 .box-flex2();
27 }
28 }
29
30 /* THE CLASSES BELOW CAN APPEAR ANYWHERE IN THE DOM (POSSIBLEY OUTSIDE OF
31 THE WIDGET AREA). */
32
33 .widget-hlabel {
34 /* Horizontal Label */
35 min-width : 10ex;
36 padding-right : 8px;
37 padding-top : 3px;
38 text-align : right;
39 vertical-align : text-top;
40 }
41
42 .widget-vlabel {
43 /* Vertical Label */
44 padding-bottom : 5px;
45 text-align : center;
46 vertical-align : text-bottom;
47 }
48
49 .slide-track {
50 /* Slider Track */
51 border : 1px solid #CCCCCC;
52 background : #FFFFFF;
53
54 .corner-all(); /* Round the corners of the slide track */
55 }
56
57 .widget-hslider {
58 /* Horizontal jQuery Slider
59
60 Both the horizontal and vertical versions of the slider are characterized
61 by a styled div that contains an invisible jQuery slide div which
62 contains a visible slider handle div. This is requred so we can control
63 how the slider is drawn and 'fix' the issue where the slide handle
64 doesn't stop at the end of the slide.
65
66 Both horizontal and vertical sliders have this div nesting:
67 +------------------------------------------+
68 | widget-(h/v)slider |
69 | +--------+---------------------------+ |
70 | | ui-slider | |
71 | | +------------------+ | |
72 | | | ui-slider-handle | | |
73 | | +------------------+ | |
74 | +--------+---------------------------+ |
75 +------------------------------------------+
76 */
77
78 /* Fix the padding of the slide track so the ui-slider is sized
79 correctly. */
80 padding-left : 8px;
81 padding-right : 5px;
82 overflow : visible;
83
84 /* Default size of the slider */
85 width : 348px;
86 height : 5px;
87 max-height : 5px;
88 margin-top : 11px;
89
90 /* Style the slider track */
91 .slide-track();
92
93 /* Make the div a flex box (makes FF behave correctly). */
94 .hbox();
95
96 .ui-slider {
97 /* Inner, invisible slide div */
98 border : 0px !important;
99 background : none !important;
100
101 .hbox();
102 .box-flex1();
103
104 .ui-slider-handle {
105 width : 14px !important;
106 height : 28px !important;
107 margin-top : -8px !important;
108 }
109 }
110 }
111
112 .widget-vslider {
113 /* Vertical jQuery Slider */
114
115 /* Fix the padding of the slide track so the ui-slider is sized
116 correctly. */
117 padding-bottom : 8px;
118 overflow : visible;
119
120 /* Default size of the slider */
121 width : 5px;
122 max-width : 5px;
123 height : 250px;
124 margin-left : 12px;
125
126 /* Style the slider track */
127 .slide-track();
128
129 /* Make the div a flex box (makes FF behave correctly). */
130 .vbox();
131
132 .ui-slider {
133 /* Inner, invisible slide div */
134 border : 0px !important;
135 background : none !important;
136 margin-left : -4px;
137 margin-top : 5px;
138
139 .vbox();
140 .box-flex1();
141
142 .ui-slider-handle {
143 width : 28px !important;
144 height : 14px !important;
145 margin-left : -9px;
146 }
147 }
148 }
149
150 .widget-text {
151 /* String Textbox - used for TextBoxView and TextAreaView */
152 width : 350px;
153 margin-bottom : 0px;
154 }
155
156 .widget-listbox {
157 /* Listbox */
158 width : 364px;
159 margin-bottom : 0px;
160 }
161
162 .widget-numeric-text {
163 /* Single Line Textbox - used for IntTextView and FloatTextView */
164 width : 150px;
165 }
166
167 .widget-progress {
168 /* Progress Bar */
169 width : 363px;
170
171 .bar {
172 /* Disable progress bar animation */
173 -webkit-transition : none;
174 -moz-transition : none;
175 -ms-transition : none;
176 -o-transition : none;
177 transition : none;
178 }
179 }
180
181 .widget-combo-btn {
182 /* ComboBox Main Button */
183 min-width : 138px; /* + 26px drop arrow btn = 164px */
184 }
185
186 .widget-box {
187 /* The following section sets the style for the invisible div that
188 hold widgets and their accompanying labels.
189
190 Looks like this:
191 +-----------------------------+
192 | widget-box (or similar) |
193 | +-------+---------------+ |
194 | | Label | Actual Widget | |
195 | +-------+---------------+ |
196 +-----------------------------+
197 */
198 margin : 5px;
199
200 .start();
201 .widget-container();
202 }
203
204 .widget-hbox {
205 /* Horizontal widgets */
206 .widget-box();
207 .hbox();
208 }
209
210 .widget-hbox-single {
211 /* Single line horizontal widgets */
212 height : 30px;
213
214 .widget-hbox();
215 }
216
217 .widget-vbox-single {
218 /* For vertical slides */
219 width : 30px;
220
221 .widget-box();
222 .vbox();
223 }
224
225 .widget-modal {
226 /* ContainerWidget - ModalView */
227 overflow : hidden;
228 position : absolute !important;
229 top : 0px;
230 left : 0px;
231 margin-left : 0px !important;
232 }
233
234 .widget-modal-body {
235 /* ContainerWidget - ModalView Body */
236 max-height: none !important;
237 }
238
239 .widget-container {
240 /* ContainerWidget */
241 .border-box-sizing();
242 }
243
244 .docked-widget-modal {
245 /* Horizontal Label */
246 overflow: hidden;
247 position: relative !important;
248 top: 0px !important;
249 left: 0px !important;
250 margin-left: 0px !important;
251 } No newline at end of file
@@ -0,0 +1,69 b''
1 // Test the widget framework.
2 casper.notebook_test(function () {
3 var index;
4
5 this.then(function () {
6
7 // Check if the WidgetManager class is defined.
8 this.test.assert(this.evaluate(function() {
9 return IPython.WidgetManager !== undefined;
10 }), 'WidgetManager class is defined');
11 });
12
13 index = this.append_cell(
14 'from IPython.html import widgets\n' +
15 'from IPython.display import display, clear_output\n' +
16 'print("Success")');
17 this.execute_cell_then(index);
18
19 this.wait(500); // Wait for require.js async callbacks to load dependencies.
20
21 this.then(function () {
22 // Check if the widget manager has been instantiated.
23 this.test.assert(this.evaluate(function() {
24 return IPython.notebook.kernel.widget_manager !== undefined;
25 }), 'Notebook widget manager instantiated');
26 });
27
28 throttle_index = this.append_cell(
29 'import time\n' +
30 'textbox = widgets.TextWidget()\n' +
31 'display(textbox)\n'+
32 'textbox.add_class("my-throttle-textbox")\n' +
33 'def handle_change(name, old, new):\n' +
34 ' print(len(new))\n' +
35 ' time.sleep(0.5)\n' +
36 'textbox.on_trait_change(handle_change, "value")\n' +
37 'print("Success")');
38 this.execute_cell_then(throttle_index, function(index){
39 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
40 'Test throttling cell executed with correct output');
41
42 this.test.assert(this.cell_element_exists(index,
43 '.widget-area .widget-subarea'),
44 'Widget subarea exists.');
45
46 this.test.assert(this.cell_element_exists(index,
47 '.my-throttle-textbox'), 'Textbox exists.');
48
49 // Send 20 characters
50 this.sendKeys('.my-throttle-textbox', '....................');
51 });
52
53 this.wait(2000); // Wait for clicks to execute in kernel
54
55 this.then(function(){
56 var outputs = this.evaluate(function(i) {
57 return IPython.notebook.get_cell(i).output_area.outputs;
58 }, {i : throttle_index});
59
60 // Only 4 outputs should have printed, but because of timing, sometimes
61 // 5 outputs will print. All we need to do is verify num outputs <= 5
62 // because that is much less than 20.
63 this.test.assert(outputs.length <= 5, 'Messages throttled.');
64
65 // We also need to verify that the last state sent was correct.
66 var last_state = outputs[outputs.length-1].text;
67 this.test.assertEquals(last_state, "20\n", "Last state sent when throttling.");
68 });
69 });
@@ -0,0 +1,86 b''
1 // Test widget bool class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var bool_index = this.append_cell(
10 'bool_widgets = [widgets.CheckboxWidget(description="Title", value=True),\n' +
11 ' widgets.ToggleButtonWidget(description="Title", value=True)]\n' +
12 'display(bool_widgets[0])\n' +
13 'display(bool_widgets[1])\n' +
14 'print("Success")');
15 this.execute_cell_then(bool_index, function(index){
16
17 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
18 'Create bool widget cell executed with correct output.');
19
20 this.test.assert(this.cell_element_exists(index,
21 '.widget-area .widget-subarea'),
22 'Widget subarea exists.');
23
24 this.test.assert(this.cell_element_exists(index,
25 '.widget-area .widget-subarea .widget-hbox-single input'),
26 'Checkbox exists.');
27
28 this.test.assert(this.cell_element_function(index,
29 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
30 'Checkbox is checked.');
31
32 this.test.assert(this.cell_element_exists(index,
33 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel'),
34 'Checkbox label exists.');
35
36 this.test.assert(this.cell_element_function(index,
37 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel', 'html')=="Title",
38 'Checkbox labeled correctly.');
39
40 this.test.assert(this.cell_element_exists(index,
41 '.widget-area .widget-subarea button'),
42 'Toggle button exists.');
43
44 this.test.assert(this.cell_element_function(index,
45 '.widget-area .widget-subarea button', 'html')=="Title",
46 'Toggle button labeled correctly.');
47
48 this.test.assert(this.cell_element_function(index,
49 '.widget-area .widget-subarea button', 'hasClass', ['active']),
50 'Toggle button is toggled.');
51
52 });
53
54 index = this.append_cell(
55 'bool_widgets[0].value = False\n' +
56 'bool_widgets[1].value = False\n' +
57 'print("Success")');
58 this.execute_cell_then(index, function(index){
59
60 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
61 'Change bool widget value cell executed with correct output.');
62
63 this.test.assert(! this.cell_element_function(bool_index,
64 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
65 'Checkbox is not checked. (1)');
66
67 this.test.assert(! this.cell_element_function(bool_index,
68 '.widget-area .widget-subarea button', 'hasClass', ['active']),
69 'Toggle button is not toggled. (1)');
70
71 // Try toggling the bool by clicking on the checkbox.
72 this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox-single input', 'click');
73
74 this.test.assert(this.cell_element_function(bool_index,
75 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
76 'Checkbox is checked. (2)');
77
78 // Try toggling the bool by clicking on the toggle button.
79 this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
80
81 this.test.assert(this.cell_element_function(bool_index,
82 '.widget-area .widget-subarea button', 'hasClass', ['active']),
83 'Toggle button is toggled. (3)');
84
85 });
86 }); No newline at end of file
@@ -0,0 +1,43 b''
1 // Test widget button class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var button_index = this.append_cell(
10 'button = widgets.ButtonWidget(description="Title")\n' +
11 'display(button)\n'+
12 'print("Success")\n' +
13 'def handle_click(sender):\n' +
14 ' print("Clicked")\n' +
15 'button.on_click(handle_click)');
16 this.execute_cell_then(button_index, function(index){
17
18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 'Create button cell executed with correct output.');
20
21 this.test.assert(this.cell_element_exists(index,
22 '.widget-area .widget-subarea'),
23 'Widget subarea exists.');
24
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea button'),
27 'Widget button exists.');
28
29 this.test.assert(this.cell_element_function(index,
30 '.widget-area .widget-subarea button', 'html')=='Title',
31 'Set button description.');
32
33 this.cell_element_function(index,
34 '.widget-area .widget-subarea button', 'click');
35 });
36
37 this.wait(500); // Wait for click to execute in kernel and write output
38
39 this.then(function () {
40 this.test.assertEquals(this.get_output_cell(button_index, 1).text, 'Clicked\n',
41 'Button click event fires.');
42 });
43 }); No newline at end of file
@@ -0,0 +1,80 b''
1 // Test container class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var container_index = this.append_cell(
10 'container = widgets.ContainerWidget()\n' +
11 'button = widgets.ButtonWidget()\n'+
12 'container.children = [button]\n'+
13 'display(container)\n'+
14 'container.add_class("my-test-class")\n'+
15 'print("Success")\n');
16 this.execute_cell_then(container_index, function(index){
17
18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 'Create container cell executed with correct output.');
20
21 this.test.assert(this.cell_element_exists(index,
22 '.widget-area .widget-subarea'),
23 'Widget subarea exists.');
24
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea .widget-container'),
27 'Widget container exists.');
28
29 this.test.assert(this.cell_element_exists(index,
30 '.widget-area .widget-subarea .my-test-class'),
31 'add_class works.');
32
33 this.test.assert(this.cell_element_exists(index,
34 '.widget-area .widget-subarea .my-test-class button'),
35 'Container parent/child relationship works.');
36 });
37
38 index = this.append_cell(
39 'container.set_css("float", "right")\n'+
40 'print("Success")\n');
41 this.execute_cell_then(index, function(index){
42
43 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
44 'Set container class CSS cell executed with correct output.');
45
46 this.test.assert(this.cell_element_function(container_index,
47 '.widget-area .widget-subarea .my-test-class', 'css', ['float'])=='right',
48 'set_css works.');
49 });
50
51 index = this.append_cell(
52 'container.remove_class("my-test-class")\n'+
53 'print("Success")\n');
54 this.execute_cell_then(index, function(index){
55
56 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
57 'Remove container class cell executed with correct output.');
58
59 this.test.assert(! this.cell_element_exists(container_index,
60 '.widget-area .widget-subarea .my-test-class'),
61 'remove_class works.');
62 });
63
64 index = this.append_cell(
65 'display(button)\n'+
66 'print("Success")\n');
67 this.execute_cell_then(index, function(index){
68
69 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
70 'Display container child executed with correct output.');
71
72 this.test.assert(! this.cell_element_exists(index,
73 '.widget-area .widget-subarea .widget-container'),
74 'Parent container not displayed.');
75
76 this.test.assert(this.cell_element_exists(index,
77 '.widget-area .widget-subarea button'),
78 'Child displayed.');
79 });
80 }); No newline at end of file
@@ -0,0 +1,108 b''
1 // Test widget float class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var float_text_query_2 = '.widget-area .widget-subarea .widget-hbox-single .my-second-float-text';
10
11 var float_index = this.append_cell(
12 'float_widget = widgets.FloatTextWidget()\n' +
13 'display(float_widget)\n' +
14 'float_widget.add_class("my-second-float-text")\n' +
15 'print("Success")\n');
16 this.execute_cell_then(float_index, function(index){
17
18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 'Create float cell executed with correct output.');
20
21 this.test.assert(this.cell_element_exists(index,
22 '.widget-area .widget-subarea'),
23 'Widget subarea exists.');
24
25 this.test.assert(this.cell_element_exists(index, float_text_query_2),
26 'Widget float textbox exists.');
27
28 this.cell_element_function(float_index, float_text_query_2, 'val', ['']);
29 this.sendKeys(float_text_query_2, '1.05');
30 });
31
32 this.wait(500); // Wait for change to execute in kernel
33
34 index = this.append_cell('print(float_widget.value)\n');
35 this.execute_cell_then(index, function(index){
36 this.test.assertEquals(this.get_output_cell(index).text, '1.05\n',
37 'Float textbox value set.');
38 this.cell_element_function(float_index, float_text_query_2, 'val', ['']);
39 this.sendKeys(float_text_query_2, '123456789.0');
40 });
41
42 this.wait(500); // Wait for change to execute in kernel
43
44 index = this.append_cell('print(float_widget.value)\n');
45 this.execute_cell_then(index, function(index){
46 this.test.assertEquals(this.get_output_cell(index).text, '123456789.0\n',
47 'Long float textbox value set (probably triggers throttling).');
48 this.cell_element_function(float_index, float_text_query_2, 'val', ['']);
49 this.sendKeys(float_text_query_2, '12hello');
50 });
51
52 this.wait(500); // Wait for change to execute in kernel
53
54 index = this.append_cell('print(float_widget.value)\n');
55 this.execute_cell_then(index, function(index){
56 this.test.assertEquals(this.get_output_cell(index).text, '12.0\n',
57 'Invald float textbox value caught and filtered.');
58 });
59
60 index = this.append_cell(
61 'from IPython.html import widgets\n' +
62 'from IPython.display import display, clear_output\n' +
63 'print("Success")');
64 this.execute_cell_then(index);
65
66 var slider_query = '.widget-area .widget-subarea .widget-hbox-single .slider';
67 var float_text_query = '.widget-area .widget-subarea .widget-hbox-single .widget-numeric-text';
68
69 var floatrange_index = this.append_cell(
70 'floatrange = [widgets.BoundedFloatTextWidget(), \n' +
71 ' widgets.FloatSliderWidget()]\n' +
72 '[display(floatrange[i]) for i in range(2)]\n' +
73 'print("Success")\n');
74 this.execute_cell_then(floatrange_index, function(index){
75
76 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
77 'Create float range cell executed with correct output.');
78
79 this.test.assert(this.cell_element_exists(index,
80 '.widget-area .widget-subarea'),
81 'Widget subarea exists.');
82
83 this.test.assert(this.cell_element_exists(index, slider_query),
84 'Widget slider exists.');
85
86 this.test.assert(this.cell_element_exists(index, float_text_query),
87 'Widget float textbox exists.');
88 });
89
90 index = this.append_cell(
91 'for widget in floatrange:\n' +
92 ' widget.max = 50.0\n' +
93 ' widget.min = -50.0\n' +
94 ' widget.value = 25.0\n' +
95 'print("Success")\n');
96 this.execute_cell_then(index, function(index){
97
98 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
99 'Float range properties cell executed with correct output.');
100
101 this.test.assert(this.cell_element_exists(floatrange_index, slider_query),
102 'Widget slider exists.');
103
104 this.test.assert(this.cell_element_function(floatrange_index, slider_query,
105 'slider', ['value']) == 25.0,
106 'Slider set to Python value.');
107 });
108 }); No newline at end of file
@@ -0,0 +1,60 b''
1 // Test image class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 // Get the temporary directory that the test server is running in.
10 var cwd = '';
11 index = this.append_cell('!echo $(pwd)');
12 this.execute_cell_then(index, function(index){
13 cwd = this.get_output_cell(index).text.trim();
14 });
15
16 test_jpg = '/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABUBAQEAAAAAAAAAAAAAAAAAAAME/9oADAMBAAIQAxAAAAECv//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQn//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Qf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Qf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8Qf//Z';
17 test_results = '/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDi6KKK+ZP3EKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Z';
18
19 var image_index = this.append_cell(
20 'import base64\n' +
21 'data = base64.b64decode("' + test_jpg + '")\n' +
22 'image = widgets.ImageWidget()\n' +
23 'image.format = "jpeg"\n' +
24 'image.value = data\n' +
25 'image.width = "50px"\n' +
26 'image.height = "50px"\n' +
27 // Set css that will make the image render within the PhantomJS visible
28 // window. If we don't do this, the captured image will be black.
29 'image.set_css({"background": "blue", "z-index": "9999", "position": "fixed", "top": "0px", "left": "0px"})\n' +
30 'display(image)\n' +
31 'image.add_class("my-test-image")\n' +
32 'print("Success")\n');
33 this.execute_cell_then(image_index, function(index){
34
35 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
36 'Create image executed with correct output.');
37
38 this.test.assert(this.cell_element_exists(index,
39 '.widget-area .widget-subarea'),
40 'Widget subarea exists.');
41
42 this.test.assert(this.cell_element_exists(index,
43 '.widget-area .widget-subarea img'),
44 'Image exists.');
45
46 // Capture a screenshot of the img element as a base64 string.
47 var fs = require('fs');
48 capture_filename = cwd + fs.separator + 'captured.jpg';
49 this.captureSelector(capture_filename, '.my-test-image');
50 var stream = fs.open(capture_filename, 'rb');
51 var captured = btoa(stream.read());
52 stream.close();
53 fs.remove(capture_filename);
54
55 // Uncomment line below to output captured image data to a text file.
56 // fs.write('./captured.txt', captured, 'w');
57
58 this.test.assertEquals(test_results, captured, "Red image data displayed correctly.");
59 });
60 }); No newline at end of file
@@ -0,0 +1,151 b''
1 // Test widget int class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var int_text_query_2 = '.widget-area .widget-subarea .widget-hbox-single .my-second-int-text';
10
11 var int_index = this.append_cell(
12 'int_widget = widgets.IntTextWidget()\n' +
13 'display(int_widget)\n' +
14 'int_widget.add_class("my-second-int-text")\n' +
15 'print("Success")\n');
16 this.execute_cell_then(int_index, function(index){
17
18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 'Create int cell executed with correct output.');
20
21 this.test.assert(this.cell_element_exists(index,
22 '.widget-area .widget-subarea'),
23 'Widget subarea exists.');
24
25 this.test.assert(this.cell_element_exists(index, int_text_query_2),
26 'Widget int textbox exists.');
27
28 this.cell_element_function(int_index, int_text_query_2, 'val', ['']);
29 this.sendKeys(int_text_query_2, '1.05');
30 });
31
32 this.wait(500); // Wait for change to execute in kernel
33
34 index = this.append_cell('print(int_widget.value)\n');
35 this.execute_cell_then(index, function(index){
36 this.test.assertEquals(this.get_output_cell(index).text, '1\n',
37 'Int textbox value set.');
38 this.cell_element_function(int_index, int_text_query_2, 'val', ['']);
39 this.sendKeys(int_text_query_2, '123456789');
40 });
41
42 this.wait(500); // Wait for change to execute in kernel
43
44 index = this.append_cell('print(int_widget.value)\n');
45 this.execute_cell_then(index, function(index){
46 this.test.assertEquals(this.get_output_cell(index).text, '123456789\n',
47 'Long int textbox value set (probably triggers throttling).');
48 this.cell_element_function(int_index, int_text_query_2, 'val', ['']);
49 this.sendKeys(int_text_query_2, '12hello');
50 });
51
52 this.wait(500); // Wait for change to execute in kernel
53
54 index = this.append_cell('print(int_widget.value)\n');
55 this.execute_cell_then(index, function(index){
56 this.test.assertEquals(this.get_output_cell(index).text, '12\n',
57 'Invald int textbox value caught and filtered.');
58 });
59
60 index = this.append_cell(
61 'from IPython.html import widgets\n' +
62 'from IPython.display import display, clear_output\n' +
63 'print("Success")');
64 this.execute_cell_then(index);
65
66 var slider_query = '.widget-area .widget-subarea .widget-hbox-single .slider';
67 var int_text_query = '.widget-area .widget-subarea .widget-hbox-single .my-second-num-test-text';
68
69 var intrange_index = this.append_cell(
70 'intrange = [widgets.BoundedIntTextWidget(),\n' +
71 ' widgets.IntSliderWidget()]\n' +
72 '[display(intrange[i]) for i in range(2)]\n' +
73 'intrange[0].add_class("my-second-num-test-text")\n' +
74 'print("Success")\n');
75 this.execute_cell_then(intrange_index, function(index){
76
77 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
78 'Create int range cell executed with correct output.');
79
80 this.test.assert(this.cell_element_exists(index,
81 '.widget-area .widget-subarea'),
82 'Widget subarea exists.');
83
84 this.test.assert(this.cell_element_exists(index, slider_query),
85 'Widget slider exists.');
86
87 this.test.assert(this.cell_element_exists(index, int_text_query),
88 'Widget int textbox exists.');
89 });
90
91 index = this.append_cell(
92 'for widget in intrange:\n' +
93 ' widget.max = 50\n' +
94 ' widget.min = -50\n' +
95 ' widget.value = 25\n' +
96 'print("Success")\n');
97 this.execute_cell_then(index, function(index){
98
99 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
100 'Int range properties cell executed with correct output.');
101
102 this.test.assert(this.cell_element_exists(intrange_index, slider_query),
103 'Widget slider exists.');
104
105 this.test.assert(this.cell_element_function(intrange_index, slider_query,
106 'slider', ['value']) == 25,
107 'Slider set to Python value.');
108
109 this.test.assert(this.cell_element_function(intrange_index, int_text_query,
110 'val') == 25, 'Int textbox set to Python value.');
111
112 // Clear the int textbox value and then set it to 1 by emulating
113 // keyboard presses.
114 this.cell_element_function(intrange_index, int_text_query, 'val', ['']);
115 this.sendKeys(int_text_query, '1');
116 });
117
118 this.wait(500); // Wait for change to execute in kernel
119
120 index = this.append_cell('print(intrange[0].value)\n');
121 this.execute_cell_then(index, function(index){
122 this.test.assertEquals(this.get_output_cell(index).text, '1\n',
123 'Int textbox set int range value');
124
125 // Clear the int textbox value and then set it to 120 by emulating
126 // keyboard presses.
127 this.cell_element_function(intrange_index, int_text_query, 'val', ['']);
128 this.sendKeys(int_text_query, '120');
129 });
130
131 this.wait(500); // Wait for change to execute in kernel
132
133 index = this.append_cell('print(intrange[0].value)\n');
134 this.execute_cell_then(index, function(index){
135 this.test.assertEquals(this.get_output_cell(index).text, '50\n',
136 'Int textbox value bound');
137
138 // Clear the int textbox value and then set it to 'hello world' by
139 // emulating keyboard presses. 'hello world' should get filtered...
140 this.cell_element_function(intrange_index, int_text_query, 'val', ['']);
141 this.sendKeys(int_text_query, 'hello world');
142 });
143
144 this.wait(500); // Wait for change to execute in kernel
145
146 index = this.append_cell('print(intrange[0].value)\n');
147 this.execute_cell_then(index, function(index){
148 this.test.assertEquals(this.get_output_cell(index).text, '50\n',
149 'Invalid int textbox characters ignored');
150 });
151 }); No newline at end of file
@@ -0,0 +1,108 b''
1 // Test multicontainer class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 // Test tab view
10 var multicontainer1_query = '.widget-area .widget-subarea div div.nav-tabs';
11 var multicontainer1_index = this.append_cell(
12 'multicontainer = widgets.TabWidget()\n' +
13 'page1 = widgets.TextWidget()\n' +
14 'page2 = widgets.TextWidget()\n' +
15 'page3 = widgets.TextWidget()\n' +
16 'multicontainer.children = [page1, page2, page3]\n' +
17 'display(multicontainer)\n' +
18 'multicontainer.selected_index = 0\n' +
19 'print("Success")\n');
20 this.execute_cell_then(multicontainer1_index, function(index){
21
22 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
23 'Create multicontainer cell executed with correct output. (1)');
24
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea'),
27 'Widget subarea exists.');
28
29 this.test.assert(this.cell_element_exists(index, multicontainer1_query),
30 'Widget tab list exists.');
31
32 this.test.assert(this.cell_element_exists(index, multicontainer1_query),
33 'First widget tab list exists.');
34
35 // JQuery selector is 1 based
36 this.click(multicontainer1_query + ' li:nth-child(2) a');
37 });
38
39 this.wait(500); // Wait for change to execute in kernel
40
41 index = this.append_cell(
42 'print(multicontainer.selected_index)\n' +
43 'multicontainer.selected_index = 2'); // 0 based
44 this.execute_cell_then(index, function(index){
45 this.test.assertEquals(this.get_output_cell(index).text, '1\n', // 0 based
46 'selected_index property updated with tab change.');
47
48 // JQuery selector is 1 based
49 this.test.assert(!this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(1)', 'hasClass', ['active']),
50 "Tab 1 is not selected.");
51 this.test.assert(!this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(2)', 'hasClass', ['active']),
52 "Tab 2 is not selected.");
53 this.test.assert(this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(3)', 'hasClass', ['active']),
54 "Tab 3 is selected.");
55 });
56
57 index = this.append_cell('multicontainer.set_title(1, "hello")\nprint("Success")'); // 0 based
58 this.execute_cell_then(index, function(index){
59 this.test.assert(this.cell_element_function(multicontainer1_index, multicontainer1_query +
60 ' li:nth-child(2) a', 'html') == 'hello',
61 'Tab page title set (after display).');
62 });
63
64 // Test accordion view
65 var multicontainer2_query = '.widget-area .widget-subarea .accordion';
66 var multicontainer2_index = this.append_cell(
67 'multicontainer = widgets.AccordionWidget()\n' +
68 'page1 = widgets.TextWidget()\n' +
69 'page2 = widgets.TextWidget()\n' +
70 'page3 = widgets.TextWidget()\n' +
71 'multicontainer.children = [page1, page2, page3]\n' +
72 'multicontainer.set_title(2, "good")\n' +
73 'display(multicontainer)\n' +
74 'multicontainer.selected_index = 0\n' +
75 'print("Success")\n');
76 this.execute_cell_then(multicontainer2_index, function(index){
77
78 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
79 'Create multicontainer cell executed with correct output. (2)');
80
81 this.test.assert(this.cell_element_exists(index,
82 '.widget-area .widget-subarea'),
83 'Widget subarea exists.');
84
85 this.test.assert(this.cell_element_exists(index, multicontainer2_query),
86 'Widget accordion exists.');
87
88 this.test.assert(this.cell_element_exists(index, multicontainer2_query +
89 ' .accordion-group:nth-child(1) .accordion-body'),
90 'First accordion page exists.');
91
92 // JQuery selector is 1 based
93 this.test.assert(this.cell_element_function(index, multicontainer2_query +
94 ' .accordion-group:nth-child(3) .accordion-heading .accordion-toggle',
95 'html')=='good', 'Accordion page title set (before display).');
96
97 // JQuery selector is 1 based
98 this.click(multicontainer2_query + ' .accordion-group:nth-child(2) .accordion-heading .accordion-toggle');
99 });
100
101 this.wait(500); // Wait for change to execute in kernel
102
103 index = this.append_cell('print(multicontainer.selected_index)'); // 0 based
104 this.execute_cell_then(index, function(index){
105 this.test.assertEquals(this.get_output_cell(index).text, '1\n', // 0 based
106 'selected_index property updated with tab change.');
107 });
108 }); No newline at end of file
@@ -0,0 +1,133 b''
1 // Test selection class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var combo_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group .widget-combo-btn';
10 var multibtn_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group[data-toggle="buttons-radio"]';
11 var radio_selector = '.widget-area .widget-subarea .widget-hbox .vbox';
12 var list_selector = '.widget-area .widget-subarea .widget-hbox .widget-listbox';
13
14 var selection_index;
15 var selection_values = 'abcd';
16 var check_state = function(context, index, state){
17 if (0 <= index && index < selection_values.length) {
18 var multibtn_state = context.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(' + (index + 1) + ')', 'hasClass', ['active']);
19 var radio_state = context.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(' + (index + 1) + ') input', 'prop', ['checked']);
20 var list_val = context.cell_element_function(selection_index, list_selector, 'val');
21 var combo_val = context.cell_element_function(selection_index, combo_selector, 'html');
22
23 var val = selection_values.charAt(index);
24 var list_state = (val == list_val);
25 var combo_state = (val == combo_val);
26
27 return multibtn_state == state &&
28 radio_state == state &&
29 list_state == state &&
30 combo_state == state;
31 }
32 return true;
33 };
34
35 var verify_selection = function(context, index){
36 for (var i = 0; i < selection_values.length; i++) {
37 if (!check_state(context, i, i==index)) {
38 return false;
39 }
40 }
41 return true;
42 };
43
44 //values=["' + selection_values + '"[i] for i in range(4)]
45 selection_index = this.append_cell(
46 'values=["' + selection_values + '"[i] for i in range(4)]\n' +
47 'selection = [widgets.DropdownWidget(values=values),\n' +
48 ' widgets.ToggleButtonsWidget(values=values),\n' +
49 ' widgets.RadioButtonsWidget(values=values),\n' +
50 ' widgets.SelectWidget(values=values)]\n' +
51 '[display(selection[i]) for i in range(4)]\n' +
52 'for widget in selection:\n' +
53 ' def handle_change(name,old,new):\n' +
54 ' for other_widget in selection:\n' +
55 ' other_widget.value = new\n' +
56 ' widget.on_trait_change(handle_change, "value")\n' +
57 'print("Success")\n');
58 this.execute_cell_then(selection_index, function(index){
59 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
60 'Create selection cell executed with correct output.');
61
62 this.test.assert(this.cell_element_exists(index,
63 '.widget-area .widget-subarea'),
64 'Widget subarea exists.');
65
66 this.test.assert(this.cell_element_exists(index, combo_selector),
67 'Widget combobox exists.');
68
69 this.test.assert(this.cell_element_exists(index, multibtn_selector),
70 'Widget multibutton exists.');
71
72 this.test.assert(this.cell_element_exists(index, radio_selector),
73 'Widget radio buttons exists.');
74
75 this.test.assert(this.cell_element_exists(index, list_selector),
76 'Widget list exists.');
77
78 // Verify that no items are selected.
79 this.test.assert(verify_selection(this, -1), 'No items selected.');
80 });
81
82 index = this.append_cell(
83 'for widget in selection:\n' +
84 ' widget.value = "a"\n' +
85 'print("Success")\n');
86 this.execute_cell_then(index, function(index){
87 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
88 'Python select item executed with correct output.');
89
90 // Verify that the first item is selected.
91 this.test.assert(verify_selection(this, 0), 'Python selected');
92
93 // Verify that selecting a radio button updates all of the others.
94 this.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(2) input', 'click');
95 });
96 this.wait(500);
97 this.then(function () {
98 this.test.assert(verify_selection(this, 1), 'Radio button selection updated view states correctly.');
99
100 // Verify that selecting a list option updates all of the others.
101 this.cell_element_function(selection_index, list_selector + ' option:nth-child(3)', 'click');
102 });
103 this.wait(500);
104 this.then(function () {
105 this.test.assert(verify_selection(this, 2), 'List selection updated view states correctly.');
106
107 // Verify that selecting a multibutton option updates all of the others.
108 this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click');
109 });
110 this.wait(500);
111 this.then(function () {
112 this.test.assert(verify_selection(this, 3), 'Multibutton selection updated view states correctly.');
113
114 // Verify that selecting a combobox option updates all of the others.
115 this.cell_element_function(selection_index, '.widget-area .widget-subarea .widget-hbox-single .btn-group ul.dropdown-menu li:nth-child(3) a', 'click');
116 });
117 this.wait(500);
118 this.then(function () {
119 this.test.assert(verify_selection(this, 2), 'Combobox selection updated view states correctly.');
120 });
121
122 this.wait(500); // Wait for change to execute in kernel
123
124 index = this.append_cell(
125 'for widget in selection:\n' +
126 ' widget.values = list(widget.values) + ["z"]\n' +
127 'selection[0].value = "z"');
128 this.execute_cell_then(index, function(index){
129
130 // Verify that selecting a combobox option updates all of the others.
131 this.test.assert(verify_selection(this, 4), 'Item added to selection widget.');
132 });
133 }); No newline at end of file
@@ -0,0 +1,52 b''
1 // Test widget string class
2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var string_index = this.append_cell(
10 'string_widget = [widgets.TextWidget(value = "xyz"),\n' +
11 ' widgets.TextareaWidget(value = "xyz"),\n' +
12 ' widgets.HTMLWidget(value = "xyz"),\n' +
13 ' widgets.LatexWidget(value = "$\\\\LaTeX{}$")]\n' +
14 '[display(widget) for widget in string_widget]\n'+
15 'print("Success")');
16 this.execute_cell_then(string_index, function(index){
17
18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 'Create string widget cell executed with correct output.');
20
21 this.test.assert(this.cell_element_exists(index,
22 '.widget-area .widget-subarea'),
23 'Widget subarea exists.');
24
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea .widget-hbox-single input[type=text]'),
27 'Textbox exists.');
28
29 this.test.assert(this.cell_element_exists(index,
30 '.widget-area .widget-subarea .widget-hbox textarea'),
31 'Textarea exists.');
32
33 this.test.assert(this.cell_element_function(index,
34 '.widget-area .widget-subarea .widget-hbox textarea', 'val')=='xyz',
35 'Python set textarea value.');
36
37 this.test.assert(this.cell_element_function(index,
38 '.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val')=='xyz',
39 'Python set textbox value.');
40
41 });
42
43 this.wait(500); // Wait for change to execute in kernel
44
45 index = this.append_cell('print(string_widget.value)');
46 this.execute_cell_then(index, function(index){
47
48 this.test.assert(this.cell_element_exists(string_index,
49 '.widget-area .widget-subarea div span.MathJax_Preview'),
50 'MathJax parsed the LaTeX successfully.');
51 });
52 }); No newline at end of file
@@ -0,0 +1,11 b''
1 from .widget import Widget, DOMWidget, CallbackDispatcher
2
3 from .widget_bool import CheckboxWidget, ToggleButtonWidget
4 from .widget_button import ButtonWidget
5 from .widget_container import ContainerWidget, PopupWidget
6 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
7 from .widget_image import ImageWidget
8 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
9 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
10 from .widget_selectioncontainer import TabWidget, AccordionWidget
11 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
@@ -0,0 +1,419 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
6 #
7 # Distributed under the terms of the Modified BSD License.
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
11
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
16
17 from IPython.kernel.comm import Comm
18 from IPython.config import LoggingConfigurable
19 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple
20 from IPython.utils.py3compat import string_types
21
22 #-----------------------------------------------------------------------------
23 # Classes
24 #-----------------------------------------------------------------------------
25 class CallbackDispatcher(LoggingConfigurable):
26 """A structure for registering and running callbacks"""
27 callbacks = List()
28
29 def __call__(self, *args, **kwargs):
30 """Call all of the registered callbacks."""
31 value = None
32 for callback in self.callbacks:
33 try:
34 local_value = callback(*args, **kwargs)
35 except Exception as e:
36 self.log.warn("Exception in callback %s: %s", callback, e)
37 else:
38 value = local_value if local_value is not None else value
39 return value
40
41 def register_callback(self, callback, remove=False):
42 """(Un)Register a callback
43
44 Parameters
45 ----------
46 callback: method handle
47 Method to be registered or unregistered.
48 remove=False: bool
49 Whether to unregister the callback."""
50
51 # (Un)Register the callback.
52 if remove and callback in self.callbacks:
53 self.callbacks.remove(callback)
54 elif not remove and callback not in self.callbacks:
55 self.callbacks.append(callback)
56
57
58 class Widget(LoggingConfigurable):
59 #-------------------------------------------------------------------------
60 # Class attributes
61 #-------------------------------------------------------------------------
62 _widget_construction_callback = None
63 widgets = {}
64
65 @staticmethod
66 def on_widget_constructed(callback):
67 """Registers a callback to be called when a widget is constructed.
68
69 The callback must have the following signature:
70 callback(widget)"""
71 Widget._widget_construction_callback = callback
72
73 @staticmethod
74 def _call_widget_constructed(widget):
75 """Static method, called when a widget is constructed."""
76 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
77 Widget._widget_construction_callback(widget)
78
79 #-------------------------------------------------------------------------
80 # Traits
81 #-------------------------------------------------------------------------
82 model_name = Unicode('WidgetModel', help="""Name of the backbone model
83 registered in the front-end to create and sync this widget with.""")
84 _view_name = Unicode(help="""Default view registered in the front-end
85 to use to represent the widget.""", sync=True)
86 _comm = Instance('IPython.kernel.comm.Comm')
87
88 closed = Bool(False)
89
90 keys = List()
91 def _keys_default(self):
92 return [name for name in self.traits(sync=True)]
93
94 _property_lock = Tuple((None, None))
95
96 _display_callbacks = Instance(CallbackDispatcher, ())
97 _msg_callbacks = Instance(CallbackDispatcher, ())
98
99 #-------------------------------------------------------------------------
100 # (Con/de)structor
101 #-------------------------------------------------------------------------
102 def __init__(self, **kwargs):
103 """Public constructor"""
104 super(Widget, self).__init__(**kwargs)
105
106 self.on_trait_change(self._handle_property_changed, self.keys)
107 Widget._call_widget_constructed(self)
108
109 def __del__(self):
110 """Object disposal"""
111 self.close()
112
113 #-------------------------------------------------------------------------
114 # Properties
115 #-------------------------------------------------------------------------
116
117 @property
118 def comm(self):
119 """Gets the Comm associated with this widget.
120
121 If a Comm doesn't exist yet, a Comm will be created automagically."""
122 if self._comm is None:
123 # Create a comm.
124 self._comm = Comm(target_name=self.model_name)
125 self._comm.on_msg(self._handle_msg)
126 self._comm.on_close(self._close)
127 Widget.widgets[self.model_id] = self
128
129 # first update
130 self.send_state()
131 return self._comm
132
133 @property
134 def model_id(self):
135 """Gets the model id of this widget.
136
137 If a Comm doesn't exist yet, a Comm will be created automagically."""
138 return self.comm.comm_id
139
140 #-------------------------------------------------------------------------
141 # Methods
142 #-------------------------------------------------------------------------
143 def _close(self):
144 """Private close - cleanup objects, registry entries"""
145 del Widget.widgets[self.model_id]
146 self._comm = None
147 self.closed = True
148
149 def close(self):
150 """Close method.
151
152 Closes the widget which closes the underlying comm.
153 When the comm is closed, all of the widget views are automatically
154 removed from the front-end."""
155 if not self.closed:
156 self._comm.close()
157 self._close()
158
159 def send_state(self, key=None):
160 """Sends the widget state, or a piece of it, to the front-end.
161
162 Parameters
163 ----------
164 key : unicode (optional)
165 A single property's name to sync with the front-end.
166 """
167 self._send({
168 "method" : "update",
169 "state" : self.get_state()
170 })
171
172 def get_state(self, key=None):
173 """Gets the widget state, or a piece of it.
174
175 Parameters
176 ----------
177 key : unicode (optional)
178 A single property's name to get.
179 """
180 keys = self.keys if key is None else [key]
181 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
182
183 def send(self, content):
184 """Sends a custom msg to the widget model in the front-end.
185
186 Parameters
187 ----------
188 content : dict
189 Content of the message to send.
190 """
191 self._send({"method": "custom", "content": content})
192
193 def on_msg(self, callback, remove=False):
194 """(Un)Register a custom msg receive callback.
195
196 Parameters
197 ----------
198 callback: callable
199 callback will be passed two arguments when a message arrives:
200 callback(widget, content)
201 remove: bool
202 True if the callback should be unregistered."""
203 self._msg_callbacks.register_callback(callback, remove=remove)
204
205 def on_displayed(self, callback, remove=False):
206 """(Un)Register a widget displayed callback.
207
208 Parameters
209 ----------
210 callback: method handler
211 Must have a signature of:
212 callback(widget, **kwargs)
213 kwargs from display are passed through without modification.
214 remove: bool
215 True if the callback should be unregistered."""
216 self._display_callbacks.register_callback(callback, remove=remove)
217
218 #-------------------------------------------------------------------------
219 # Support methods
220 #-------------------------------------------------------------------------
221 @contextmanager
222 def _lock_property(self, key, value):
223 """Lock a property-value pair.
224
225 NOTE: This, in addition to the single lock for all state changes, is
226 flawed. In the future we may want to look into buffering state changes
227 back to the front-end."""
228 self._property_lock = (key, value)
229 try:
230 yield
231 finally:
232 self._property_lock = (None, None)
233
234 def _should_send_property(self, key, value):
235 """Check the property lock (property_lock)"""
236 return key != self._property_lock[0] or \
237 value != self._property_lock[1]
238
239 # Event handlers
240 def _handle_msg(self, msg):
241 """Called when a msg is received from the front-end"""
242 data = msg['content']['data']
243 method = data['method']
244 if not method in ['backbone', 'custom']:
245 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
246
247 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
248 if method == 'backbone' and 'sync_data' in data:
249 sync_data = data['sync_data']
250 self._handle_receive_state(sync_data) # handles all methods
251
252 # Handle a custom msg from the front-end
253 elif method == 'custom':
254 if 'content' in data:
255 self._handle_custom_msg(data['content'])
256
257 def _handle_receive_state(self, sync_data):
258 """Called when a state is received from the front-end."""
259 for name in self.keys:
260 if name in sync_data:
261 value = self._unpack_widgets(sync_data[name])
262 with self._lock_property(name, value):
263 setattr(self, name, value)
264
265 def _handle_custom_msg(self, content):
266 """Called when a custom msg is received."""
267 self._msg_callbacks(self, content)
268
269 def _handle_property_changed(self, name, old, new):
270 """Called when a property has been changed."""
271 # Make sure this isn't information that the front-end just sent us.
272 if self._should_send_property(name, new):
273 # Send new state to front-end
274 self.send_state(key=name)
275
276 def _handle_displayed(self, **kwargs):
277 """Called when a view has been displayed for this widget instance"""
278 self._display_callbacks(self, **kwargs)
279
280 def _pack_widgets(self, x):
281 """Recursively converts all widget instances to model id strings.
282
283 Children widgets will be stored and transmitted to the front-end by
284 their model ids. Return value must be JSON-able."""
285 if isinstance(x, dict):
286 return {k: self._pack_widgets(v) for k, v in x.items()}
287 elif isinstance(x, list):
288 return [self._pack_widgets(v) for v in x]
289 elif isinstance(x, Widget):
290 return x.model_id
291 else:
292 return x # Value must be JSON-able
293
294 def _unpack_widgets(self, x):
295 """Recursively converts all model id strings to widget instances.
296
297 Children widgets will be stored and transmitted to the front-end by
298 their model ids."""
299 if isinstance(x, dict):
300 return {k: self._unpack_widgets(v) for k, v in x.items()}
301 elif isinstance(x, list):
302 return [self._unpack_widgets(v) for v in x]
303 elif isinstance(x, string_types):
304 return x if x not in Widget.widgets else Widget.widgets[x]
305 else:
306 return x
307
308 def _ipython_display_(self, **kwargs):
309 """Called when `IPython.display.display` is called on the widget."""
310 # Show view. By sending a display message, the comm is opened and the
311 # initial state is sent.
312 self._send({"method": "display"})
313 self._handle_displayed(**kwargs)
314
315 def _send(self, msg):
316 """Sends a message to the model in the front-end."""
317 self.comm.send(msg)
318
319
320 class DOMWidget(Widget):
321 visible = Bool(True, help="Whether the widget is visible.", sync=True)
322 _css = Dict(sync=True) # Internal CSS property dict
323
324 def get_css(self, key, selector=""):
325 """Get a CSS property of the widget.
326
327 Note: This function does not actually request the CSS from the
328 front-end; Only properties that have been set with set_css can be read.
329
330 Parameters
331 ----------
332 key: unicode
333 CSS key
334 selector: unicode (optional)
335 JQuery selector used when the CSS key/value was set.
336 """
337 if selector in self._css and key in self._css[selector]:
338 return self._css[selector][key]
339 else:
340 return None
341
342 def set_css(self, dict_or_key, value=None, selector=''):
343 """Set one or more CSS properties of the widget.
344
345 This function has two signatures:
346 - set_css(css_dict, selector='')
347 - set_css(key, value, selector='')
348
349 Parameters
350 ----------
351 css_dict : dict
352 CSS key/value pairs to apply
353 key: unicode
354 CSS key
355 value:
356 CSS value
357 selector: unicode (optional, kwarg only)
358 JQuery selector to use to apply the CSS key/value. If no selector
359 is provided, an empty selector is used. An empty selector makes the
360 front-end try to apply the css to a default element. The default
361 element is an attribute unique to each view, which is a DOM element
362 of the view that should be styled with common CSS (see
363 `$el_to_style` in the Javascript code).
364 """
365 if not selector in self._css:
366 self._css[selector] = {}
367 my_css = self._css[selector]
368
369 if value is None:
370 css_dict = dict_or_key
371 else:
372 css_dict = {dict_or_key: value}
373
374 for (key, value) in css_dict.items():
375 if not (key in my_css and value == my_css[key]):
376 my_css[key] = value
377 self.send_state('_css')
378
379 def add_class(self, class_names, selector=""):
380 """Add class[es] to a DOM element.
381
382 Parameters
383 ----------
384 class_names: unicode or list
385 Class name(s) to add to the DOM element(s).
386 selector: unicode (optional)
387 JQuery selector to select the DOM element(s) that the class(es) will
388 be added to.
389 """
390 class_list = class_names
391 if isinstance(class_list, list):
392 class_list = ' '.join(class_list)
393
394 self.send({
395 "msg_type" : "add_class",
396 "class_list" : class_list,
397 "selector" : selector
398 })
399
400 def remove_class(self, class_names, selector=""):
401 """Remove class[es] from a DOM element.
402
403 Parameters
404 ----------
405 class_names: unicode or list
406 Class name(s) to remove from the DOM element(s).
407 selector: unicode (optional)
408 JQuery selector to select the DOM element(s) that the class(es) will
409 be removed from.
410 """
411 class_list = class_names
412 if isinstance(class_list, list):
413 class_list = ' '.join(class_list)
414
415 self.send({
416 "msg_type" : "remove_class",
417 "class_list" : class_list,
418 "selector" : selector,
419 })
@@ -0,0 +1,34 b''
1 """BoolWidget class.
2
3 Represents a boolean using a widget.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, Bool, List
18
19 #-----------------------------------------------------------------------------
20 # Classes
21 #-----------------------------------------------------------------------------
22 class _BoolWidget(DOMWidget):
23 value = Bool(False, help="Bool value", sync=True)
24 description = Unicode('', help="Description of the boolean (label).", sync=True)
25 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
26
27
28 class CheckboxWidget(_BoolWidget):
29 _view_name = Unicode('CheckboxView', sync=True)
30
31
32 class ToggleButtonWidget(_BoolWidget):
33 _view_name = Unicode('ToggleButtonView', sync=True)
34 No newline at end of file
@@ -0,0 +1,56 b''
1 """ButtonWidget class.
2
3 Represents a button in the frontend using a widget. Allows user to listen for
4 click events on the button and trigger backend code when the clicks are fired.
5 """
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2013, the IPython Development Team.
8 #
9 # Distributed under the terms of the Modified BSD License.
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17 from .widget import DOMWidget, CallbackDispatcher
18 from IPython.utils.traitlets import Unicode, Bool
19
20 #-----------------------------------------------------------------------------
21 # Classes
22 #-----------------------------------------------------------------------------
23 class ButtonWidget(DOMWidget):
24 _view_name = Unicode('ButtonView', sync=True)
25
26 # Keys
27 description = Unicode('', help="Description of the button (label).", sync=True)
28 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
29
30 def __init__(self, **kwargs):
31 """Constructor"""
32 super(ButtonWidget, self).__init__(**kwargs)
33 self._click_handlers = CallbackDispatcher()
34 self.on_msg(self._handle_button_msg)
35
36 def on_click(self, callback, remove=False):
37 """Register a callback to execute when the button is clicked.
38
39 The callback will be called with one argument,
40 the clicked button widget instance.
41
42 Parameters
43 ----------
44 remove : bool (optional)
45 Set to true to remove the callback from the list of callbacks."""
46 self._click_handlers.register_callback(callback, remove=remove)
47
48 def _handle_button_msg(self, _, content):
49 """Handle a msg from the front-end.
50
51 Parameters
52 ----------
53 content: dict
54 Content of the msg."""
55 if content.get('event', '') == 'click':
56 self._click_handlers(self)
@@ -0,0 +1,51 b''
1 """ContainerWidget class.
2
3 Represents a container that can be used to group other widgets.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, Bool, List, Instance
18
19 #-----------------------------------------------------------------------------
20 # Classes
21 #-----------------------------------------------------------------------------
22 class ContainerWidget(DOMWidget):
23 _view_name = Unicode('ContainerView', sync=True)
24
25 # Keys, all private and managed by helper methods. Flexible box model
26 # classes...
27 children = List(Instance(DOMWidget))
28 _children = List(Instance(DOMWidget), sync=True)
29
30 def _children_changed(self, name, old, new):
31 """Validate children list.
32
33 Makes sure only one instance of any given model can exist in the
34 children list.
35 An excellent post on uniqifiers is available at
36 http://www.peterbe.com/plog/uniqifiers-benchmark
37 which provides the inspiration for using this implementation. Below
38 I've implemented the `f5` algorithm using Python comprehensions."""
39 if new is not None and isinstance(new, list):
40 seen = {}
41 def add_item(i):
42 seen[i.model_id] = True
43 return i
44 self._children = [add_item(i) for i in new if not i.model_id in seen]
45
46
47 class PopupWidget(ContainerWidget):
48 _view_name = Unicode('PopupView', sync=True)
49
50 description = Unicode(sync=True)
51 button_text = Unicode(sync=True)
@@ -0,0 +1,59 b''
1 """FloatWidget class.
2
3 Represents an unbounded float using a widget.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, CFloat, Bool, List, Enum
18
19 #-----------------------------------------------------------------------------
20 # Classes
21 #-----------------------------------------------------------------------------
22 class _FloatWidget(DOMWidget):
23 value = CFloat(0.0, help="Float value", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
26
27
28 class _BoundedFloatWidget(_FloatWidget):
29 max = CFloat(100.0, help="Max value", sync=True)
30 min = CFloat(0.0, help="Min value", sync=True)
31 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
32
33 def __init__(self, *pargs, **kwargs):
34 """Constructor"""
35 DOMWidget.__init__(self, *pargs, **kwargs)
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
37
38 def _validate(self, name, old, new):
39 """Validate value, max, min."""
40 if self.min > new or new > self.max:
41 self.value = min(max(new, self.min), self.max)
42
43
44 class FloatTextWidget(_FloatWidget):
45 _view_name = Unicode('FloatTextView', sync=True)
46
47
48 class BoundedFloatTextWidget(_BoundedFloatWidget):
49 _view_name = Unicode('FloatTextView', sync=True)
50
51
52 class FloatSliderWidget(_BoundedFloatWidget):
53 _view_name = Unicode('FloatSliderView', sync=True)
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
55 help="Vertical or horizontal.", sync=True)
56
57
58 class FloatProgressWidget(_BoundedFloatWidget):
59 _view_name = Unicode('ProgressView', sync=True)
@@ -0,0 +1,36 b''
1 """ButtonWidget class.
2
3 Represents a button in the frontend using a widget. Allows user to listen for
4 click events on the button and trigger backend code when the clicks are fired.
5 """
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2013, the IPython Development Team.
8 #
9 # Distributed under the terms of the Modified BSD License.
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17 import base64
18
19 from .widget import DOMWidget
20 from IPython.utils.traitlets import Unicode, CUnicode, Bytes
21
22 #-----------------------------------------------------------------------------
23 # Classes
24 #-----------------------------------------------------------------------------
25 class ImageWidget(DOMWidget):
26 _view_name = Unicode('ImageView', sync=True)
27
28 # Define the custom state properties to sync with the front-end
29 format = Unicode('png', sync=True)
30 width = CUnicode(sync=True)
31 height = CUnicode(sync=True)
32 _b64value = Unicode(sync=True)
33
34 value = Bytes()
35 def _value_changed(self, name, old, new):
36 self._b64value = base64.b64encode(new) No newline at end of file
@@ -0,0 +1,59 b''
1 """IntWidget class.
2
3 Represents an unbounded int using a widget.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, CInt, Bool, List, Enum
18
19 #-----------------------------------------------------------------------------
20 # Classes
21 #-----------------------------------------------------------------------------
22 class _IntWidget(DOMWidget):
23 value = CInt(0, help="Int value", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
26
27
28 class _BoundedIntWidget(_IntWidget):
29 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
30 max = CInt(100, help="Max value", sync=True)
31 min = CInt(0, help="Min value", sync=True)
32
33 def __init__(self, *pargs, **kwargs):
34 """Constructor"""
35 DOMWidget.__init__(self, *pargs, **kwargs)
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
37
38 def _validate(self, name, old, new):
39 """Validate value, max, min."""
40 if self.min > new or new > self.max:
41 self.value = min(max(new, self.min), self.max)
42
43
44 class IntTextWidget(_IntWidget):
45 _view_name = Unicode('IntTextView', sync=True)
46
47
48 class BoundedIntTextWidget(_BoundedIntWidget):
49 _view_name = Unicode('IntTextView', sync=True)
50
51
52 class IntSliderWidget(_BoundedIntWidget):
53 _view_name = Unicode('IntSliderView', sync=True)
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
55 help="Vertical or horizontal.", sync=True)
56
57
58 class IntProgressWidget(_BoundedIntWidget):
59 _view_name = Unicode('ProgressView', sync=True)
@@ -0,0 +1,95 b''
1 """SelectionWidget class.
2
3 Represents an enumeration using a widget.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from threading import Lock
17
18 from .widget import DOMWidget
19 from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict
20
21 #-----------------------------------------------------------------------------
22 # SelectionWidget
23 #-----------------------------------------------------------------------------
24 class _SelectionWidget(DOMWidget):
25 value = Any(help="Selected value")
26 values = List(help="List of values the user can select")
27 labels = List(help="""List of string representations for each value.
28 These string representations are used to display the values in the
29 front-end.""", sync=True) # Only synced to the back-end.
30 disabled = Bool(False, help="Enable or disable user changes", sync=True)
31 description = Unicode(help="Description of the value this widget represents", sync=True)
32
33 _value = Unicode(sync=True) # Bi-directionally synced.
34
35 def __init__(self, *pargs, **kwargs):
36 """Constructor"""
37 self.value_lock = Lock()
38 self.on_trait_change(self._string_value_set, ['_value'])
39 DOMWidget.__init__(self, *pargs, **kwargs)
40
41 def _labels_changed(self, name=None, old=None, new=None):
42 """Handles when the value_names Dict has been changed.
43
44 This method sets the _reverse_value_names Dict to the inverse of the new
45 value for the value_names Dict."""
46 if len(new) != len(self.values):
47 raise TypeError('Labels list must be the same size as the values list.')
48
49 def _values_changed(self, name=None, old=None, new=None):
50 """Handles when the value_names Dict has been changed.
51
52 This method sets the _reverse_value_names Dict to the inverse of the new
53 value for the value_names Dict."""
54 if len(new) != len(self.labels):
55 self.labels = [(self.labels[i] if i < len(self.labels) else str(v)) for i, v in enumerate(new)]
56
57 def _value_changed(self, name, old, new):
58 """Called when value has been changed"""
59 if self.value_lock.acquire(False):
60 try:
61 # Make sure the value is in the list of values.
62 if new in self.values:
63 # Set the string version of the value.
64 self._value = self.labels[self.values.index(new)]
65 else:
66 raise TypeError('Value must be a value in the values list.')
67 finally:
68 self.value_lock.release()
69
70 def _string_value_set(self, name, old, new):
71 """Called when _value has been changed."""
72 if self.value_lock.acquire(False):
73 try:
74 if new in self.labels:
75 self.value = self.values[self.labels.index(new)]
76 else:
77 self.value = None
78 finally:
79 self.value_lock.release()
80
81
82 class ToggleButtonsWidget(_SelectionWidget):
83 _view_name = Unicode('ToggleButtonsView', sync=True)
84
85
86 class DropdownWidget(_SelectionWidget):
87 _view_name = Unicode('DropdownView', sync=True)
88
89
90 class RadioButtonsWidget(_SelectionWidget):
91 _view_name = Unicode('RadioButtonsView', sync=True)
92
93
94 class SelectWidget(_SelectionWidget):
95 _view_name = Unicode('SelectView', sync=True)
@@ -0,0 +1,58 b''
1 """SelectionContainerWidget class.
2
3 Represents a multipage container that can be used to group other widgets into
4 pages.
5 """
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2013, the IPython Development Team.
8 #
9 # Distributed under the terms of the Modified BSD License.
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17 from .widget_container import ContainerWidget
18 from IPython.utils.traitlets import Unicode, Dict, CInt, List, Instance
19
20 #-----------------------------------------------------------------------------
21 # Classes
22 #-----------------------------------------------------------------------------
23 class _SelectionContainerWidget(ContainerWidget):
24 _titles = Dict(help="Titles of the pages", sync=True)
25 selected_index = CInt(0, sync=True)
26
27 # Public methods
28 def set_title(self, index, title):
29 """Sets the title of a container page.
30
31 Parameters
32 ----------
33 index : int
34 Index of the container page
35 title : unicode
36 New title"""
37 self._titles[index] = title
38 self.send_state('_titles')
39
40 def get_title(self, index):
41 """Gets the title of a container pages.
42
43 Parameters
44 ----------
45 index : int
46 Index of the container page"""
47 if index in self._titles:
48 return self._titles[index]
49 else:
50 return None
51
52
53 class AccordionWidget(_SelectionContainerWidget):
54 _view_name = Unicode('AccordionView', sync=True)
55
56
57 class TabWidget(_SelectionContainerWidget):
58 _view_name = Unicode('TabView', sync=True)
@@ -0,0 +1,72 b''
1 """StringWidget class.
2
3 Represents a unicode string using a widget.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget, CallbackDispatcher
17 from IPython.utils.traitlets import Unicode, Bool, List
18
19 #-----------------------------------------------------------------------------
20 # Classes
21 #-----------------------------------------------------------------------------
22 class _StringWidget(DOMWidget):
23 value = Unicode(help="String value", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
26
27
28 class HTMLWidget(_StringWidget):
29 _view_name = Unicode('HTMLView', sync=True)
30
31
32 class LatexWidget(_StringWidget):
33 _view_name = Unicode('LatexView', sync=True)
34
35
36 class TextareaWidget(_StringWidget):
37 _view_name = Unicode('TextareaView', sync=True)
38
39 def scroll_to_bottom(self):
40 self.send({"method": "scroll_to_bottom"})
41
42
43 class TextWidget(_StringWidget):
44 _view_name = Unicode('TextView', sync=True)
45
46 def __init__(self, **kwargs):
47 super(TextWidget, self).__init__(**kwargs)
48 self._submission_callbacks = CallbackDispatcher()
49 self.on_msg(self._handle_string_msg)
50
51 def _handle_string_msg(self, _, content):
52 """Handle a msg from the front-end.
53
54 Parameters
55 ----------
56 content: dict
57 Content of the msg."""
58 if content.get('event', '') == 'submit':
59 self._submission_callbacks(self)
60
61 def on_submit(self, callback, remove=False):
62 """(Un)Register a callback to handle text submission.
63
64 Triggered when the user clicks enter.
65
66 Parameters
67 ----------
68 callback: callable
69 Will be called with exactly one argument: the Widget instance
70 remove: bool (optional)
71 Whether to unregister the callback"""
72 self._submission_callbacks.register_callback(callback, remove=remove)
@@ -0,0 +1,25 b''
1 Notebook Widgets
2 ----------------
3
4 Available in the new `IPython.html.widgets` namespace, widgets provide an easy
5 way for IPython notebook users to display GUI controls in the IPython notebook.
6 IPython comes with bundle of built-in widgets and also the ability for users
7 to define their own widgets. A widget is displayed in the front-end using
8 using a view. For example, a FloatRangeWidget can be displayed using a
9 FloatSliderView (which is the default if no view is specified when displaying
10 the widget). IPython also comes with a bundle of views and the ability for the
11 user to define custom views. One widget can be displayed multiple times, in on
12 or more cells, using one or more views. All views will automatically remain in
13 sync with the widget which is accessible in the back-end.
14
15 The widget layer provides an MVC-like architecture on top of the comm layer.
16 It's useful for widgets that can be expressed via a list of properties.
17 Widgets work by synchronizing IPython traitlet models in the back-end with
18 backbone models in the front-end. The widget layer automatically handles
19
20 * delta compression (only sending the state information that has changed)
21 * wiring the message callbacks to the correct cells automatically
22 * inter-view synchronization (handled by backbone)
23 * message throttling (to avoid flooding the kernel)
24 * parent/child relationships between views (which one can override to specify custom parent/child relationships)
25 * ability to manipulate the widget view's DOM from python using CSS, $().addClass, and $().removeClass methods
@@ -0,0 +1,186 b''
1 {
2 "metadata": {
3 "name": ""
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
9 "cells": [
10 {
11 "cell_type": "code",
12 "collapsed": false,
13 "input": [
14 "# Widget related imports\n",
15 "from IPython.html import widgets\n",
16 "from IPython.display import display, clear_output, Javascript\n",
17 "from IPython.utils.traitlets import Unicode\n",
18 "\n",
19 "# nbconvert related imports\n",
20 "from IPython.nbconvert import get_export_names, export_by_name\n",
21 "from IPython.nbconvert.writers import FilesWriter\n",
22 "from IPython.nbformat import current"
23 ],
24 "language": "python",
25 "metadata": {},
26 "outputs": [],
27 "prompt_number": 1
28 },
29 {
30 "cell_type": "markdown",
31 "metadata": {},
32 "source": [
33 "Create a text Widget without displaying it. The widget will be used to store the notebook's name which is otherwise only available in the front-end."
34 ]
35 },
36 {
37 "cell_type": "code",
38 "collapsed": false,
39 "input": [
40 "notebook_name = widgets.TextWidget()"
41 ],
42 "language": "python",
43 "metadata": {},
44 "outputs": [],
45 "prompt_number": 2
46 },
47 {
48 "cell_type": "markdown",
49 "metadata": {},
50 "source": [
51 "Get the current notebook's name by pushing JavaScript to the browser that sets the notebook name in a string widget."
52 ]
53 },
54 {
55 "cell_type": "code",
56 "collapsed": false,
57 "input": [
58 "js = \"\"\"var model = IPython.notebook.kernel.widget_manager.get_model('{model_id}');\n",
59 "model.set('value', IPython.notebook.notebook_name);\n",
60 "model.save();\"\"\".format(model_id=notebook_name.model_id)\n",
61 "display(Javascript(data=js))"
62 ],
63 "language": "python",
64 "metadata": {},
65 "outputs": [
66 {
67 "javascript": [
68 "var model = IPython.notebook.kernel.widget_manager.get_model('8c6583524eb3422c99491730a3e1ce6c');\n",
69 "model.set('value', IPython.notebook.notebook_name);\n",
70 "model.save();"
71 ],
72 "metadata": {},
73 "output_type": "display_data",
74 "text": [
75 "<IPython.core.display.Javascript at 0x164ea50>"
76 ]
77 }
78 ],
79 "prompt_number": 3
80 },
81 {
82 "cell_type": "code",
83 "collapsed": false,
84 "input": [
85 "filename = notebook_name.value\n",
86 "filename"
87 ],
88 "language": "python",
89 "metadata": {},
90 "outputs": [
91 {
92 "metadata": {},
93 "output_type": "pyout",
94 "prompt_number": 4,
95 "text": [
96 "u'Export As (nbconvert).ipynb'"
97 ]
98 }
99 ],
100 "prompt_number": 4
101 },
102 {
103 "cell_type": "markdown",
104 "metadata": {},
105 "source": [
106 "Create the widget that will allow the user to Export the current notebook."
107 ]
108 },
109 {
110 "cell_type": "code",
111 "collapsed": false,
112 "input": [
113 "exporter_names = widgets.DropdownWidget(values=get_export_names(), value='html')\n",
114 "export_button = widgets.ButtonWidget(description=\"Export\")\n",
115 "download_link = widgets.HTMLWidget(visible=False)"
116 ],
117 "language": "python",
118 "metadata": {},
119 "outputs": [],
120 "prompt_number": 5
121 },
122 {
123 "cell_type": "markdown",
124 "metadata": {},
125 "source": [
126 "Export the notebook when the export button is clicked."
127 ]
128 },
129 {
130 "cell_type": "code",
131 "collapsed": false,
132 "input": [
133 "file_writer = FilesWriter()\n",
134 "\n",
135 "def export(name, nb):\n",
136 " \n",
137 " # Get a unique key for the notebook and set it in the resources object.\n",
138 " notebook_name = name[:name.rfind('.')]\n",
139 " resources = {}\n",
140 " resources['unique_key'] = notebook_name\n",
141 " resources['output_files_dir'] = '%s_files' % notebook_name\n",
142 "\n",
143 " # Try to export\n",
144 " try:\n",
145 " output, resources = export_by_name(exporter_names.value, nb)\n",
146 " except ConversionException as e:\n",
147 " download_link.value = \"<br>Could not export notebook!\"\n",
148 " else:\n",
149 " write_results = file_writer.write(output, resources, notebook_name=notebook_name)\n",
150 " \n",
151 " download_link.value = \"<br>Results: <a href='files/{filename}'><i>\\\"{filename}\\\"</i></a>\".format(filename=write_results)\n",
152 " download_link.visible = True\n",
153 " \n",
154 "def handle_export():\n",
155 " with open(filename, 'r') as f:\n",
156 " export(filename, current.read(f, 'json'))\n",
157 "export_button.on_click(handle_export)"
158 ],
159 "language": "python",
160 "metadata": {},
161 "outputs": [],
162 "prompt_number": 6
163 },
164 {
165 "cell_type": "markdown",
166 "metadata": {},
167 "source": [
168 "Display the controls."
169 ]
170 },
171 {
172 "cell_type": "code",
173 "collapsed": false,
174 "input": [
175 "display(exporter_names, export_button, download_link)"
176 ],
177 "language": "python",
178 "metadata": {},
179 "outputs": [],
180 "prompt_number": 7
181 }
182 ],
183 "metadata": {}
184 }
185 ]
186 } No newline at end of file
@@ -0,0 +1,248 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "code",
18 "collapsed": false,
19 "input": [
20 "import base64\n",
21 "from __future__ import print_function # py 2.7 compat.\n",
22 "from IPython.html import widgets # Widget definitions.\n",
23 "from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget."
24 ],
25 "language": "python",
26 "metadata": {},
27 "outputs": [],
28 "prompt_number": 1
29 },
30 {
31 "cell_type": "markdown",
32 "metadata": {},
33 "source": [
34 "This is a custom widget that allows the user to upload file data to the notebook server. The file data is sent via a statefull `value` attribute of the widget. The widget has an upload failed event that fires in the front-end and is echoed to the back-end using a custom msg."
35 ]
36 },
37 {
38 "cell_type": "code",
39 "collapsed": false,
40 "input": [
41 "class FileWidget(widgets.DOMWidget):\n",
42 " _view_name = Unicode('FilePickerView', sync=True)\n",
43 " value = Unicode(sync=True)\n",
44 " filename = Unicode(sync=True)\n",
45 " \n",
46 " def __init__(self, **kwargs):\n",
47 " \"\"\"Constructor\"\"\"\n",
48 " widgets.DOMWidget.__init__(self, **kwargs) # Call the base.\n",
49 " \n",
50 " # Allow the user to register error callbacks with the following signatures:\n",
51 " # callback()\n",
52 " # callback(sender)\n",
53 " self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])\n",
54 " \n",
55 " # Listen for custom msgs\n",
56 " self.on_msg(self._handle_custom_msg)\n",
57 "\n",
58 " def _handle_custom_msg(self, content):\n",
59 " \"\"\"Handle a msg from the front-end.\n",
60 "\n",
61 " Parameters\n",
62 " ----------\n",
63 " content: dict\n",
64 " Content of the msg.\"\"\"\n",
65 " if 'event' in content and content['event'] == 'error':\n",
66 " self.errors()\n",
67 " self.errors(self)\n",
68 " "
69 ],
70 "language": "python",
71 "metadata": {},
72 "outputs": [],
73 "prompt_number": 2
74 },
75 {
76 "cell_type": "code",
77 "collapsed": false,
78 "input": [
79 "%%javascript\n",
80 "\n",
81 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
82 "\n",
83 " var FilePickerView = IPython.WidgetView.extend({\n",
84 " render: function(){\n",
85 " // Render the view.\n",
86 " this.setElement($('<input />')\n",
87 " .attr('type', 'file'));\n",
88 " },\n",
89 " \n",
90 " events: {\n",
91 " // List of events and their handlers.\n",
92 " 'change': 'handle_file_change',\n",
93 " },\n",
94 " \n",
95 " handle_file_change: function(evt) { \n",
96 " // Handle when the user has changed the file.\n",
97 " \n",
98 " // Retrieve the first (and only!) File from the FileList object\n",
99 " var file = evt.target.files[0];\n",
100 " if (file) {\n",
101 "\n",
102 " // Read the file's textual content and set value to those contents.\n",
103 " var that = this;\n",
104 " var file_reader = new FileReader();\n",
105 " file_reader.onload = function(e) {\n",
106 " that.model.set('value', e.target.result);\n",
107 " that.touch();\n",
108 " }\n",
109 " file_reader.readAsText(file);\n",
110 " } else {\n",
111 "\n",
112 " // The file couldn't be opened. Send an error msg to the\n",
113 " // back-end.\n",
114 " this.send({ 'event': 'error' });\n",
115 " }\n",
116 "\n",
117 " // Set the filename of the file.\n",
118 " this.model.set('filename', file.name);\n",
119 " this.touch();\n",
120 " },\n",
121 " });\n",
122 " \n",
123 " // Register the DatePickerView with the widget manager.\n",
124 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
125 "});"
126 ],
127 "language": "python",
128 "metadata": {},
129 "outputs": [
130 {
131 "javascript": [
132 "\n",
133 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
134 "\n",
135 " var FilePickerView = IPython.WidgetView.extend({\n",
136 " render: function(){\n",
137 " // Render the view.\n",
138 " this.setElement($('<input />')\n",
139 " .attr('type', 'file'));\n",
140 " },\n",
141 " \n",
142 " events: {\n",
143 " // List of events and their handlers.\n",
144 " 'change': 'handle_file_change',\n",
145 " },\n",
146 " \n",
147 " handle_file_change: function(evt) { \n",
148 " // Handle when the user has changed the file.\n",
149 " \n",
150 " // Retrieve the first (and only!) File from the FileList object\n",
151 " var file = evt.target.files[0];\n",
152 " if (file) {\n",
153 "\n",
154 " // Read the file's textual content and set value to those contents.\n",
155 " var that = this;\n",
156 " var file_reader = new FileReader();\n",
157 " file_reader.onload = function(e) {\n",
158 " that.model.set('value', e.target.result);\n",
159 " that.touch();\n",
160 " }\n",
161 " file_reader.readAsText(file);\n",
162 " } else {\n",
163 "\n",
164 " // The file couldn't be opened. Send an error msg to the\n",
165 " // back-end.\n",
166 " this.send({ 'event': 'error' });\n",
167 " }\n",
168 "\n",
169 " // Set the filename of the file.\n",
170 " this.model.set('filename', file.name);\n",
171 " this.touch();\n",
172 " },\n",
173 " });\n",
174 " \n",
175 " // Register the DatePickerView with the widget manager.\n",
176 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
177 "});"
178 ],
179 "metadata": {},
180 "output_type": "display_data",
181 "text": [
182 "<IPython.core.display.Javascript at 0x36df2d0>"
183 ]
184 }
185 ],
186 "prompt_number": 3
187 },
188 {
189 "cell_type": "markdown",
190 "metadata": {},
191 "source": [
192 "The following shows how the file widget can be used."
193 ]
194 },
195 {
196 "cell_type": "code",
197 "collapsed": false,
198 "input": [
199 "file_widget = FileWidget()\n",
200 "\n",
201 "# Register an event to echo the filename when it has been changed.\n",
202 "def file_loading():\n",
203 " print(\"Loading %s\" % file_widget.filename)\n",
204 "file_widget.on_trait_change(file_loading, 'filename')\n",
205 "\n",
206 "# Register an event to echo the filename and contents when a file\n",
207 "# has been uploaded.\n",
208 "def file_loaded():\n",
209 " print(\"Loaded, file contents: %s\" % file_widget.value)\n",
210 "file_widget.on_trait_change(file_loaded, 'value')\n",
211 "\n",
212 "# Register an event to print an error message when a file could not\n",
213 "# be opened. Since the error messages are not handled through\n",
214 "# traitlets but instead handled through custom msgs, the registration\n",
215 "# of the handler is different than the two examples above. Instead\n",
216 "# the API provided by the CallbackDispatcher must be used.\n",
217 "def file_failed():\n",
218 " print(\"Could not load file contents of %s\" % file_widget.filename)\n",
219 "file_widget.errors.register_callback(file_failed)\n",
220 "\n",
221 "file_widget"
222 ],
223 "language": "python",
224 "metadata": {},
225 "outputs": [
226 {
227 "output_type": "stream",
228 "stream": "stdout",
229 "text": [
230 "Loading test.txt\n"
231 ]
232 },
233 {
234 "output_type": "stream",
235 "stream": "stdout",
236 "text": [
237 "Loaded, file contents: Hello World!\n",
238 "\n"
239 ]
240 }
241 ],
242 "prompt_number": 4
243 }
244 ],
245 "metadata": {}
246 }
247 ]
248 } No newline at end of file
@@ -0,0 +1,227 b''
1 {
2 "metadata": {
3 "name": ""
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
9 "cells": [
10 {
11 "cell_type": "code",
12 "collapsed": false,
13 "input": [
14 "# Console related imports.\n",
15 "from subprocess import Popen, PIPE\n",
16 "import fcntl\n",
17 "import os\n",
18 "from IPython.utils.py3compat import bytes_to_str, string_types\n",
19 "\n",
20 "# Widget related imports.\n",
21 "from IPython.html import widgets\n",
22 "from IPython.display import display"
23 ],
24 "language": "python",
25 "metadata": {},
26 "outputs": [],
27 "prompt_number": 1
28 },
29 {
30 "cell_type": "markdown",
31 "metadata": {},
32 "source": [
33 "Define function to run a process without blocking the input."
34 ]
35 },
36 {
37 "cell_type": "code",
38 "collapsed": false,
39 "input": [
40 "def read_process(process, append_output):\n",
41 " \"\"\" Try to read the stdout and stderr of a process and render it using \n",
42 " the append_output method provided\n",
43 " \n",
44 " Parameters\n",
45 " ----------\n",
46 " process: Popen handle\n",
47 " append_output: method handle\n",
48 " Callback to render output. Signature of\n",
49 " append_output(output, [prefix=])\"\"\"\n",
50 " \n",
51 " try:\n",
52 " stdout = process.stdout.read()\n",
53 " if stdout is not None and len(stdout) > 0:\n",
54 " append_output(stdout, prefix=' ')\n",
55 " except:\n",
56 " pass\n",
57 " \n",
58 " try:\n",
59 " stderr = process.stderr.read()\n",
60 " if stderr is not None and len(stderr) > 0:\n",
61 " append_output(stderr, prefix='ERR ')\n",
62 " except:\n",
63 " pass\n",
64 "\n",
65 "\n",
66 "def set_pipe_nonblocking(pipe):\n",
67 " \"\"\"Set a pipe as non-blocking\"\"\"\n",
68 " fl = fcntl.fcntl(pipe, fcntl.F_GETFL)\n",
69 " fcntl.fcntl(pipe, fcntl.F_SETFL, fl | os.O_NONBLOCK)\n",
70 "\n",
71 "\n",
72 "kernel = get_ipython().kernel\n",
73 "def run_command(command, append_output, has_user_exited=None):\n",
74 " \"\"\"Run a command asyncronously\n",
75 " \n",
76 " Parameters\n",
77 " ----------\n",
78 " command: str\n",
79 " Shell command to launch a process with.\n",
80 " append_output: method handle\n",
81 " Callback to render output. Signature of\n",
82 " append_output(output, [prefix=])\n",
83 " has_user_exited: method handle\n",
84 " Check to see if the user wants to stop the command.\n",
85 " Must return a boolean.\"\"\"\n",
86 " \n",
87 " # Echo input.\n",
88 " append_output(command, prefix='>>> ')\n",
89 " \n",
90 " # Create the process. Make sure the pipes are set as non-blocking.\n",
91 " process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)\n",
92 " set_pipe_nonblocking(process.stdout)\n",
93 " set_pipe_nonblocking(process.stderr)\n",
94 " \n",
95 " # Only continue to read from the command \n",
96 " while (has_user_exited is None or not has_user_exited()) and process.poll() is None:\n",
97 " read_process(process, append_output)\n",
98 " kernel.do_one_iteration() # Run IPython iteration. This is the code that\n",
99 " # makes this operation non-blocking. This will\n",
100 " # allow widget messages and callbacks to be \n",
101 " # processed.\n",
102 " \n",
103 " # If the process is still running, the user must have exited.\n",
104 " if process.poll() is None:\n",
105 " process.kill()\n",
106 " else:\n",
107 " read_process(process, append_output) # Read remainer\n",
108 " \n",
109 " \n",
110 " \n",
111 " "
112 ],
113 "language": "python",
114 "metadata": {},
115 "outputs": [],
116 "prompt_number": 2
117 },
118 {
119 "cell_type": "markdown",
120 "metadata": {},
121 "source": [
122 "Create the console widgets without displaying them."
123 ]
124 },
125 {
126 "cell_type": "code",
127 "collapsed": false,
128 "input": [
129 "console_container = widgets.ContainerWidget(visible=False)\n",
130 "console_container.set_css('padding', '10px')\n",
131 "\n",
132 "console_style = {\n",
133 " 'font-family': 'monospace',\n",
134 " 'color': '#AAAAAA',\n",
135 " 'background': 'black',\n",
136 " 'width': '800px',\n",
137 "}\n",
138 "\n",
139 "output_box = widgets.TextareaWidget()\n",
140 "output_box.set_css(console_style)\n",
141 "output_box.set_css('height', '400px')\n",
142 "\n",
143 "input_box = widgets.TextWidget()\n",
144 "input_box.set_css(console_style)\n",
145 "\n",
146 "console_container.children = [output_box, input_box]"
147 ],
148 "language": "python",
149 "metadata": {},
150 "outputs": [],
151 "prompt_number": 3
152 },
153 {
154 "cell_type": "markdown",
155 "metadata": {},
156 "source": [
157 "Hook the process execution methods up to our console widgets."
158 ]
159 },
160 {
161 "cell_type": "code",
162 "collapsed": false,
163 "input": [
164 "\n",
165 "def append_output(output, prefix):\n",
166 " if isinstance(output, string_types):\n",
167 " output_str = output\n",
168 " else:\n",
169 " output_str = bytes_to_str(output)\n",
170 " output_lines = output_str.split('\\n')\n",
171 " formatted_output = '\\n'.join([prefix + line for line in output_lines if len(line) > 0]) + '\\n'\n",
172 " output_box.value += formatted_output\n",
173 " output_box.scroll_to_bottom()\n",
174 " \n",
175 "def has_user_exited():\n",
176 " return not console_container.visible\n",
177 "\n",
178 "def handle_input(sender):\n",
179 " sender.disabled = True\n",
180 " try:\n",
181 " command = sender.value\n",
182 " sender.value = ''\n",
183 " run_command(command, append_output=append_output, has_user_exited=has_user_exited)\n",
184 " finally:\n",
185 " sender.disabled = False\n",
186 " \n",
187 "input_box.on_submit(handle_input)"
188 ],
189 "language": "python",
190 "metadata": {},
191 "outputs": [],
192 "prompt_number": 4
193 },
194 {
195 "cell_type": "markdown",
196 "metadata": {},
197 "source": [
198 "Create the button that will be used to display and hide the console. Display both the console container and the new button used to toggle it."
199 ]
200 },
201 {
202 "cell_type": "code",
203 "collapsed": false,
204 "input": [
205 "toggle_button = widgets.ButtonWidget(description=\"Start Console\")\n",
206 "def toggle_console(sender):\n",
207 " console_container.visible = not console_container.visible\n",
208 " if console_container.visible:\n",
209 " toggle_button.description=\"Stop Console\"\n",
210 " input_box.disabled = False\n",
211 " else:\n",
212 " toggle_button.description=\"Start Console\"\n",
213 "toggle_button.on_click(toggle_console)\n",
214 "\n",
215 "display(toggle_button)\n",
216 "display(console_container)"
217 ],
218 "language": "python",
219 "metadata": {},
220 "outputs": [],
221 "prompt_number": 5
222 }
223 ],
224 "metadata": {}
225 }
226 ]
227 } No newline at end of file
@@ -0,0 +1,314 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "[Index](index.ipynb)\n",
21 "\n",
22 "To use IPython widgets in the notebook, the widget namespace needs to be imported."
23 ]
24 },
25 {
26 "cell_type": "code",
27 "collapsed": false,
28 "input": [
29 "from IPython.html import widgets # Widget definitions\n",
30 "from IPython.display import display # Used to display widgets in the notebook"
31 ],
32 "language": "python",
33 "metadata": {},
34 "outputs": [],
35 "prompt_number": 1
36 },
37 {
38 "cell_type": "heading",
39 "level": 1,
40 "metadata": {},
41 "source": [
42 "Basic Widgets"
43 ]
44 },
45 {
46 "cell_type": "markdown",
47 "metadata": {},
48 "source": [
49 "IPython comes with basic widgets that represent common interactive controls. These widgets are\n",
50 "\n",
51 "- CheckboxWidget\n",
52 "- ToggleButtonWidget\n",
53 "- FloatSliderWidget\n",
54 "- BoundedFloatTextWidget\n",
55 "- FloatProgressWidget\n",
56 "- FloatTextWidget\n",
57 "- ImageWidget\n",
58 "- IntSliderWidget\n",
59 "- BoundedIntTextWidget\n",
60 "- IntProgressWidget\n",
61 "- IntTextWidget\n",
62 "- ToggleButtonsWidget\n",
63 "- RadioButtonsWidget\n",
64 "- DropdownWidget\n",
65 "- SelectWidget\n",
66 "- HTMLWidget\n",
67 "- LatexWidget\n",
68 "- TextareaWidget\n",
69 "- TextWidget\n",
70 "- ButtonWidget\n",
71 "\n",
72 "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",
73 "\n",
74 "- ContainerWidget\n",
75 "- PopupWidget\n",
76 "- AccordionWidget\n",
77 "- TabWidget\n",
78 "\n",
79 "To see the complete list of widgets, one can execute the following"
80 ]
81 },
82 {
83 "cell_type": "code",
84 "collapsed": false,
85 "input": [
86 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
87 ],
88 "language": "python",
89 "metadata": {},
90 "outputs": [
91 {
92 "metadata": {},
93 "output_type": "pyout",
94 "prompt_number": 2,
95 "text": [
96 "['AccordionWidget',\n",
97 " 'BoundedFloatTextWidget',\n",
98 " 'BoundedIntTextWidget',\n",
99 " 'ButtonWidget',\n",
100 " 'CheckboxWidget',\n",
101 " 'ContainerWidget',\n",
102 " 'DOMWidget',\n",
103 " 'DropdownWidget',\n",
104 " 'FloatProgressWidget',\n",
105 " 'FloatSliderWidget',\n",
106 " 'FloatTextWidget',\n",
107 " 'HTMLWidget',\n",
108 " 'ImageWidget',\n",
109 " 'IntProgressWidget',\n",
110 " 'IntSliderWidget',\n",
111 " 'IntTextWidget',\n",
112 " 'LatexWidget',\n",
113 " 'PopupWidget',\n",
114 " 'RadioButtonsWidget',\n",
115 " 'SelectWidget',\n",
116 " 'TabWidget',\n",
117 " 'TextWidget',\n",
118 " 'TextareaWidget',\n",
119 " 'ToggleButtonWidget',\n",
120 " 'ToggleButtonsWidget',\n",
121 " 'Widget']"
122 ]
123 }
124 ],
125 "prompt_number": 2
126 },
127 {
128 "cell_type": "markdown",
129 "metadata": {},
130 "source": [
131 "The basic widgets all have sensible default values. Create a *FloatSliderWidget* without displaying it:"
132 ]
133 },
134 {
135 "cell_type": "code",
136 "collapsed": false,
137 "input": [
138 "mywidget = widgets.FloatSliderWidget()"
139 ],
140 "language": "python",
141 "metadata": {},
142 "outputs": [],
143 "prompt_number": 3
144 },
145 {
146 "cell_type": "markdown",
147 "metadata": {},
148 "source": [
149 "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 or must be returned as the last item in the cell. `mywidget` is displayed by"
150 ]
151 },
152 {
153 "cell_type": "code",
154 "collapsed": false,
155 "input": [
156 "display(mywidget)"
157 ],
158 "language": "python",
159 "metadata": {},
160 "outputs": [],
161 "prompt_number": 4
162 },
163 {
164 "cell_type": "markdown",
165 "metadata": {},
166 "source": [
167 "or"
168 ]
169 },
170 {
171 "cell_type": "code",
172 "collapsed": false,
173 "input": [
174 "mywidget"
175 ],
176 "language": "python",
177 "metadata": {},
178 "outputs": [],
179 "prompt_number": 5
180 },
181 {
182 "cell_type": "markdown",
183 "metadata": {},
184 "source": [
185 "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",
186 "\n",
187 "Widgets are manipulated via special instance attributes (traitlets). The names of these traitlets are listed in the widget's `keys` attribute (as seen below). A few of these attributes are common to most widgets. The basic attributes are `value`, `description`, `visible`, and `disabled`. `_css` and `_view_name` are private attributes that exist in all widgets and should not be modified."
188 ]
189 },
190 {
191 "cell_type": "code",
192 "collapsed": false,
193 "input": [
194 "mywidget.keys"
195 ],
196 "language": "python",
197 "metadata": {},
198 "outputs": [
199 {
200 "metadata": {},
201 "output_type": "pyout",
202 "prompt_number": 6,
203 "text": [
204 "['_view_name',\n",
205 " 'orientation',\n",
206 " 'min',\n",
207 " 'max',\n",
208 " '_css',\n",
209 " 'value',\n",
210 " 'disabled',\n",
211 " 'visible',\n",
212 " 'step',\n",
213 " 'description']"
214 ]
215 }
216 ],
217 "prompt_number": 6
218 },
219 {
220 "cell_type": "markdown",
221 "metadata": {},
222 "source": [
223 "Changing a widget's attribute will automatically update that widget everywhere it is displayed in the notebook. Here, the `value` attribute of `mywidget` is set. The slider shown above updates automatically with the new value. Syncing also works in the other direction - changing the value of the displayed widget will update the property's value."
224 ]
225 },
226 {
227 "cell_type": "code",
228 "collapsed": false,
229 "input": [
230 "mywidget.value = 25.0"
231 ],
232 "language": "python",
233 "metadata": {},
234 "outputs": [],
235 "prompt_number": 7
236 },
237 {
238 "cell_type": "markdown",
239 "metadata": {},
240 "source": [
241 "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)."
242 ]
243 },
244 {
245 "cell_type": "code",
246 "collapsed": false,
247 "input": [
248 "mywidget.value"
249 ],
250 "language": "python",
251 "metadata": {},
252 "outputs": [
253 {
254 "metadata": {},
255 "output_type": "pyout",
256 "prompt_number": 8,
257 "text": [
258 "25.0"
259 ]
260 }
261 ],
262 "prompt_number": 8
263 },
264 {
265 "cell_type": "markdown",
266 "metadata": {},
267 "source": [
268 "Widget values can also be set with kwargs during the construction of the widget (as seen below)."
269 ]
270 },
271 {
272 "cell_type": "code",
273 "collapsed": false,
274 "input": [
275 "mysecondwidget = widgets.RadioButtonsWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Item A\")\n",
276 "display(mysecondwidget)"
277 ],
278 "language": "python",
279 "metadata": {},
280 "outputs": [],
281 "prompt_number": 9
282 },
283 {
284 "cell_type": "code",
285 "collapsed": false,
286 "input": [
287 "mysecondwidget.value"
288 ],
289 "language": "python",
290 "metadata": {},
291 "outputs": [
292 {
293 "metadata": {},
294 "output_type": "pyout",
295 "prompt_number": 10,
296 "text": [
297 "'Item A'"
298 ]
299 }
300 ],
301 "prompt_number": 10
302 },
303 {
304 "cell_type": "markdown",
305 "metadata": {},
306 "source": [
307 "In [Part 2](Part 2 - Events.ipynb) of this [series](index.ipynb), you will learn about widget events."
308 ]
309 }
310 ],
311 "metadata": {}
312 }
313 ]
314 } No newline at end of file
@@ -0,0 +1,261 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "[< Back to Part 1](Part 1 - Basics.ipynb) or [Index](index.ipynb)"
21 ]
22 },
23 {
24 "cell_type": "code",
25 "collapsed": false,
26 "input": [
27 "from __future__ import print_function # 2.7 compatability\n",
28 "\n",
29 "from IPython.html import widgets # Widget definitions\n",
30 "from IPython.display import display # Used to display widgets in the notebook"
31 ],
32 "language": "python",
33 "metadata": {},
34 "outputs": [],
35 "prompt_number": 1
36 },
37 {
38 "cell_type": "heading",
39 "level": 1,
40 "metadata": {},
41 "source": [
42 "Traitlet Events"
43 ]
44 },
45 {
46 "cell_type": "markdown",
47 "metadata": {},
48 "source": [
49 "As mentioned in Part 1, the widget attributes are IPython traitlets. Traitlets are eventful. To handle changes, the `on_trait_change` method of the widget can be used to register a callback. The docstring for `on_trait_change` can be seen below. Both the `name` and `remove` properties are optional."
50 ]
51 },
52 {
53 "cell_type": "code",
54 "collapsed": false,
55 "input": [
56 "print(widgets.Widget.on_trait_change.__doc__)"
57 ],
58 "language": "python",
59 "metadata": {},
60 "outputs": [
61 {
62 "output_type": "stream",
63 "stream": "stdout",
64 "text": [
65 "Setup a handler to be called when a trait changes.\n",
66 "\n",
67 " This is used to setup dynamic notifications of trait changes.\n",
68 "\n",
69 " Static handlers can be created by creating methods on a HasTraits\n",
70 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
71 " to create static handler for the trait 'a', create the method\n",
72 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
73 " below).\n",
74 "\n",
75 " Parameters\n",
76 " ----------\n",
77 " handler : callable\n",
78 " A callable that is called when a trait changes. Its\n",
79 " signature can be handler(), handler(name), handler(name, new)\n",
80 " or handler(name, old, new).\n",
81 " name : list, str, None\n",
82 " If None, the handler will apply to all traits. If a list\n",
83 " of str, handler will apply to all names in the list. If a\n",
84 " str, the handler will apply just to that name.\n",
85 " remove : bool\n",
86 " If False (the default), then install the handler. If True\n",
87 " then unintall it.\n",
88 " \n"
89 ]
90 }
91 ],
92 "prompt_number": 2
93 },
94 {
95 "cell_type": "markdown",
96 "metadata": {},
97 "source": [
98 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
99 "\n",
100 "- callback()\n",
101 "- callback(trait_name)\n",
102 "- callback(trait_name, new_value)\n",
103 "- callback(trait_name, old_value, new_value)\n",
104 "\n",
105 "Using this method, an example of how to output an IntSliderWiget's value as it is changed can be seen below."
106 ]
107 },
108 {
109 "cell_type": "code",
110 "collapsed": false,
111 "input": [
112 "int_range = widgets.IntSliderWidget()\n",
113 "display(int_range)\n",
114 "\n",
115 "def on_value_change(name, value):\n",
116 " print(value)\n",
117 "\n",
118 "int_range.on_trait_change(on_value_change, 'value')"
119 ],
120 "language": "python",
121 "metadata": {},
122 "outputs": [],
123 "prompt_number": 3
124 },
125 {
126 "cell_type": "heading",
127 "level": 1,
128 "metadata": {},
129 "source": [
130 "Specialized Events"
131 ]
132 },
133 {
134 "cell_type": "heading",
135 "level": 2,
136 "metadata": {},
137 "source": [
138 "Button Click Event"
139 ]
140 },
141 {
142 "cell_type": "markdown",
143 "metadata": {},
144 "source": [
145 "The `ButtonWidget` is a special widget, like the `ContainerWidget` and `TabWidget`, 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 function to be called when the button is clicked. The docstring of the `on_click` can be seen below."
146 ]
147 },
148 {
149 "cell_type": "code",
150 "collapsed": false,
151 "input": [
152 "print(widgets.ButtonWidget.on_click.__doc__)"
153 ],
154 "language": "python",
155 "metadata": {},
156 "outputs": [
157 {
158 "output_type": "stream",
159 "stream": "stdout",
160 "text": [
161 "Register a callback to execute when the button is clicked.\n",
162 "\n",
163 " The callback will be called with one argument,\n",
164 " the clicked button widget instance.\n",
165 "\n",
166 " Parameters\n",
167 " ----------\n",
168 " remove : bool (optional)\n",
169 " Set to true to remove the callback from the list of callbacks.\n"
170 ]
171 }
172 ],
173 "prompt_number": 4
174 },
175 {
176 "cell_type": "markdown",
177 "metadata": {},
178 "source": [
179 "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."
180 ]
181 },
182 {
183 "cell_type": "code",
184 "collapsed": false,
185 "input": [
186 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
187 "display(button)\n",
188 "\n",
189 "def on_button_clicked(b):\n",
190 " print(\"Button clicked.\")\n",
191 "\n",
192 "button.on_click(on_button_clicked)"
193 ],
194 "language": "python",
195 "metadata": {},
196 "outputs": [
197 {
198 "output_type": "stream",
199 "stream": "stdout",
200 "text": [
201 "Button clicked.\n"
202 ]
203 },
204 {
205 "output_type": "stream",
206 "stream": "stdout",
207 "text": [
208 "Button clicked.\n"
209 ]
210 },
211 {
212 "output_type": "stream",
213 "stream": "stdout",
214 "text": [
215 "Button clicked.\n"
216 ]
217 }
218 ],
219 "prompt_number": 5
220 },
221 {
222 "cell_type": "markdown",
223 "metadata": {},
224 "source": [
225 "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."
226 ]
227 },
228 {
229 "cell_type": "code",
230 "collapsed": false,
231 "input": [
232 "def new_button(clicked):\n",
233 " button = widgets.ButtonWidget()\n",
234 " button.clicks = 0\n",
235 " clicked.clicks += 1\n",
236 " button.description = \"%d\" % clicked.clicks\n",
237 " display(button)\n",
238 " button.on_click(new_button)\n",
239 "button = widgets.ButtonWidget(description = \"Start\")\n",
240 "button.clicks = 0\n",
241 "display(button)\n",
242 "button.on_click(new_button)\n",
243 " "
244 ],
245 "language": "python",
246 "metadata": {},
247 "outputs": [],
248 "prompt_number": 6
249 },
250 {
251 "cell_type": "markdown",
252 "metadata": {},
253 "source": [
254 "In [Part 3](Part 3 - Placement.ipynb) of this [series](index.ipynb), you will learn about widget placement."
255 ]
256 }
257 ],
258 "metadata": {}
259 }
260 ]
261 } No newline at end of file
@@ -0,0 +1,187 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "[< Back to Part 2](Part 2 - Events.ipynb) or [Index](index.ipynb)"
21 ]
22 },
23 {
24 "cell_type": "code",
25 "collapsed": false,
26 "input": [
27 "from IPython.html import widgets # Widget definitions\n",
28 "from IPython.display import display # Used to display widgets in the notebook"
29 ],
30 "language": "python",
31 "metadata": {},
32 "outputs": [],
33 "prompt_number": 1
34 },
35 {
36 "cell_type": "heading",
37 "level": 1,
38 "metadata": {},
39 "source": [
40 "Parent/Child Relationships"
41 ]
42 },
43 {
44 "cell_type": "markdown",
45 "metadata": {},
46 "source": [
47 "To display widget A inside widget B, widget A must be a child of widget B. Only one instance of any particular widget can be child of another. In other words, *widget A* cannot have *widget B* listed twice in it's list of children.\n",
48 "\n",
49 "Widgets that can contain other widgets have a `children` attribute. This attribute can be set via a kwarg in the widget's constructor or after construction. Calling display on an object with children automatically displays those children, too."
50 ]
51 },
52 {
53 "cell_type": "code",
54 "collapsed": false,
55 "input": [
56 "float_range = widgets.FloatSliderWidget()\n",
57 "string = widgets.TextWidget(value='hi')\n",
58 "container = widgets.ContainerWidget(children=[float_range, string])\n",
59 "\n",
60 "display(container) # Displays the `container` and all of it's children."
61 ],
62 "language": "python",
63 "metadata": {},
64 "outputs": [],
65 "prompt_number": 2
66 },
67 {
68 "cell_type": "markdown",
69 "metadata": {},
70 "source": [
71 "Children can also be added to parents after the parent has been displayed. The parent is responsible for rendering its children."
72 ]
73 },
74 {
75 "cell_type": "code",
76 "collapsed": false,
77 "input": [
78 "container = widgets.ContainerWidget()\n",
79 "display(container)\n",
80 "\n",
81 "int_range = widgets.IntSliderWidget()\n",
82 "container.children=[int_range]\n"
83 ],
84 "language": "python",
85 "metadata": {},
86 "outputs": [],
87 "prompt_number": 3
88 },
89 {
90 "cell_type": "heading",
91 "level": 1,
92 "metadata": {},
93 "source": [
94 "Visibility"
95 ]
96 },
97 {
98 "cell_type": "markdown",
99 "metadata": {},
100 "source": [
101 "Sometimes it is necessary to hide or show widgets in place, without having to redisplay the widget.\n",
102 "The `visibility` property of widgets can be used to hide or show widgets that have already been displayed (as seen below)."
103 ]
104 },
105 {
106 "cell_type": "code",
107 "collapsed": false,
108 "input": [
109 "string = widgets.LatexWidget(value=\"Hello World!\")\n",
110 "display(string) "
111 ],
112 "language": "python",
113 "metadata": {},
114 "outputs": [],
115 "prompt_number": 4
116 },
117 {
118 "cell_type": "code",
119 "collapsed": false,
120 "input": [
121 "string.visible=False"
122 ],
123 "language": "python",
124 "metadata": {},
125 "outputs": [],
126 "prompt_number": 5
127 },
128 {
129 "cell_type": "code",
130 "collapsed": false,
131 "input": [
132 "string.visible=True"
133 ],
134 "language": "python",
135 "metadata": {},
136 "outputs": [],
137 "prompt_number": 6
138 },
139 {
140 "cell_type": "markdown",
141 "metadata": {},
142 "source": [
143 "In the example below, a form is rendered, which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox."
144 ]
145 },
146 {
147 "cell_type": "code",
148 "collapsed": false,
149 "input": [
150 "form = widgets.ContainerWidget()\n",
151 "first = widgets.TextWidget(description=\"First Name:\")\n",
152 "last = widgets.TextWidget(description=\"Last Name:\")\n",
153 "\n",
154 "student = widgets.CheckboxWidget(description=\"Student:\", value=False)\n",
155 "school_info = widgets.ContainerWidget(visible=False, children=[\n",
156 " widgets.TextWidget(description=\"School:\"),\n",
157 " widgets.IntTextWidget(description=\"Grade:\", min=0, max=12)\n",
158 " ])\n",
159 "\n",
160 "pet = widgets.TextWidget(description=\"Pet's Name:\")\n",
161 "form.children = [first, last, student, school_info, pet]\n",
162 "display(form)\n",
163 "\n",
164 "def on_student_toggle(name, value):\n",
165 " if value:\n",
166 " school_info.visible = True\n",
167 " else:\n",
168 " school_info.visible = False\n",
169 "student.on_trait_change(on_student_toggle, 'value')\n"
170 ],
171 "language": "python",
172 "metadata": {},
173 "outputs": [],
174 "prompt_number": 7
175 },
176 {
177 "cell_type": "markdown",
178 "metadata": {},
179 "source": [
180 "In [Part 4](Part 4 - Styles.ipynb) of this [series](index.ipynb), you will learn about widget styling."
181 ]
182 }
183 ],
184 "metadata": {}
185 }
186 ]
187 } No newline at end of file
@@ -0,0 +1,368 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "[< Back to Part 3](Part 3 - Placement.ipynb) or [Index](index.ipynb)"
21 ]
22 },
23 {
24 "cell_type": "code",
25 "collapsed": false,
26 "input": [
27 "from IPython.html import widgets # Widget definitions\n",
28 "from IPython.display import display # Used to display widgets in the notebook"
29 ],
30 "language": "python",
31 "metadata": {},
32 "outputs": [],
33 "prompt_number": 1
34 },
35 {
36 "cell_type": "heading",
37 "level": 1,
38 "metadata": {},
39 "source": [
40 "CSS"
41 ]
42 },
43 {
44 "cell_type": "markdown",
45 "metadata": {},
46 "source": [
47 "When trying to design an attractive widget GUI, styling becomes important.\n",
48 "Most widget views are DOM (document object model) elements that can be controlled with CSS.\n",
49 "There are two helper methods that allow the manipulation of the widget's CSS.\n",
50 "The first is the `Widget.set_css` method.\n",
51 "This method allows one or more CSS attributes to be set at once. "
52 ]
53 },
54 {
55 "cell_type": "code",
56 "collapsed": false,
57 "input": [
58 "print(widgets.DOMWidget.set_css.__doc__)"
59 ],
60 "language": "python",
61 "metadata": {},
62 "outputs": [
63 {
64 "output_type": "stream",
65 "stream": "stdout",
66 "text": [
67 "Set one or more CSS properties of the widget.\n",
68 "\n",
69 " This function has two signatures:\n",
70 " - set_css(css_dict, selector='')\n",
71 " - set_css(key, value, selector='')\n",
72 "\n",
73 " Parameters\n",
74 " ----------\n",
75 " css_dict : dict\n",
76 " CSS key/value pairs to apply\n",
77 " key: unicode\n",
78 " CSS key\n",
79 " value:\n",
80 " CSS value\n",
81 " selector: unicode (optional, kwarg only)\n",
82 " JQuery selector to use to apply the CSS key/value. If no selector \n",
83 " is provided, an empty selector is used. An empty selector makes the \n",
84 " front-end try to apply the css to a default element. The default\n",
85 " element is an attribute unique to each view, which is a DOM element\n",
86 " of the view that should be styled with common CSS (see \n",
87 " `$el_to_style` in the Javascript code).\n",
88 " \n"
89 ]
90 }
91 ],
92 "prompt_number": 2
93 },
94 {
95 "cell_type": "markdown",
96 "metadata": {},
97 "source": [
98 "The second is `get_css` which allows CSS attributesto be read.\n",
99 "Note that this method will only read CSS attributes that have been set using the `set_css` method."
100 ]
101 },
102 {
103 "cell_type": "code",
104 "collapsed": false,
105 "input": [
106 "print(widgets.DOMWidget.get_css.__doc__)"
107 ],
108 "language": "python",
109 "metadata": {},
110 "outputs": [
111 {
112 "output_type": "stream",
113 "stream": "stdout",
114 "text": [
115 "Get a CSS property of the widget.\n",
116 "\n",
117 " Note: This function does not actually request the CSS from the \n",
118 " front-end; Only properties that have been set with set_css can be read.\n",
119 "\n",
120 " Parameters\n",
121 " ----------\n",
122 " key: unicode\n",
123 " CSS key\n",
124 " selector: unicode (optional)\n",
125 " JQuery selector used when the CSS key/value was set.\n",
126 " \n"
127 ]
128 }
129 ],
130 "prompt_number": 3
131 },
132 {
133 "cell_type": "markdown",
134 "metadata": {},
135 "source": [
136 "Below is an example that applies CSS attributes to a container to emphasize text."
137 ]
138 },
139 {
140 "cell_type": "code",
141 "collapsed": false,
142 "input": [
143 "label = widgets.LatexWidget()\n",
144 "label.value = \"$\\\\textbf{ALERT:} Hello World!$\"\n",
145 "container = widgets.ContainerWidget(children=[label])\n",
146 "\n",
147 "# set_css used to set a single CSS attribute.\n",
148 "container.set_css('border', '3px solid black') # Border the container\n",
149 "\n",
150 "# set_css used to set multiple CSS attributes.\n",
151 "container.set_css({'padding': '6px', # Add padding to the container\n",
152 " 'background': 'yellow'}) # Fill the container yellow\n",
153 "\n",
154 "display(container)"
155 ],
156 "language": "python",
157 "metadata": {},
158 "outputs": [],
159 "prompt_number": 4
160 },
161 {
162 "cell_type": "heading",
163 "level": 1,
164 "metadata": {},
165 "source": [
166 "CSS Classes"
167 ]
168 },
169 {
170 "cell_type": "markdown",
171 "metadata": {},
172 "source": [
173 "In some cases, it is necessary to apply CSS classes to your widgets.\n",
174 "CSS classes allow DOM elements to be indentified in Javascript and CSS.\n",
175 "The notebook defines its own set of classes to stylize its elements.\n",
176 "The `add_class` widget method allows you to add CSS classes to your widget."
177 ]
178 },
179 {
180 "cell_type": "code",
181 "collapsed": false,
182 "input": [
183 "print(widgets.DOMWidget.add_class.__doc__)"
184 ],
185 "language": "python",
186 "metadata": {},
187 "outputs": [
188 {
189 "output_type": "stream",
190 "stream": "stdout",
191 "text": [
192 "Add class[es] to a DOM element.\n",
193 "\n",
194 " Parameters\n",
195 " ----------\n",
196 " class_names: unicode or list\n",
197 " Class name(s) to add to the DOM element(s).\n",
198 " selector: unicode (optional)\n",
199 " JQuery selector to select the DOM element(s) that the class(es) will\n",
200 " be added to.\n",
201 " \n"
202 ]
203 }
204 ],
205 "prompt_number": 5
206 },
207 {
208 "cell_type": "markdown",
209 "metadata": {},
210 "source": [
211 "Since `add_class` is a DOM operation, **it will only affect widgets that have already been displayed**.\n",
212 "`add_class` must be called after the widget has been displayed.\n",
213 "Extending the example above, the corners of the container can be rounded by adding the `corner-all` CSS class to the container."
214 ]
215 },
216 {
217 "cell_type": "code",
218 "collapsed": false,
219 "input": [
220 "container = widgets.ContainerWidget()\n",
221 "container.set_css({'border': '3px solid black',\n",
222 " 'padding': '6px', \n",
223 " 'background': 'yellow'}) \n",
224 "\n",
225 "label = widgets.LatexWidget()\n",
226 "label.value = \"$\\\\textbf{ALERT:} Hello World!$\"\n",
227 "container.children = [label]\n",
228 "display(container)\n",
229 "container.add_class('corner-all') # Must be called AFTER display"
230 ],
231 "language": "python",
232 "metadata": {},
233 "outputs": [],
234 "prompt_number": 6
235 },
236 {
237 "cell_type": "markdown",
238 "metadata": {},
239 "source": [
240 "The IPython notebook uses [bootstrap](http://getbootstrap.com/\u200e) for styling.\n",
241 "The example above can be simplified by using a bootstrap class:"
242 ]
243 },
244 {
245 "cell_type": "code",
246 "collapsed": false,
247 "input": [
248 "label = widgets.LatexWidget(value = \"$\\\\textbf{ALERT:} Hello World!$\")\n",
249 "display(label)\n",
250 "\n",
251 "# Apply twitter bootstrap alert class to the label.\n",
252 "label.add_class(\"alert\")"
253 ],
254 "language": "python",
255 "metadata": {},
256 "outputs": [],
257 "prompt_number": 7
258 },
259 {
260 "cell_type": "markdown",
261 "metadata": {},
262 "source": [
263 "The example below shows how bootstrap classes can be used to change button apearance."
264 ]
265 },
266 {
267 "cell_type": "code",
268 "collapsed": false,
269 "input": [
270 "# List of the bootstrap button styles\n",
271 "button_classes = ['Default', 'btn-primary', 'btn-info', 'btn-success', \n",
272 " 'btn-warning', 'btn-danger', 'btn-inverse', 'btn-link']\n",
273 "\n",
274 "# Create each button and apply the style. Also add margin to the buttons so they space\n",
275 "# themselves nicely.\n",
276 "for i in range(8):\n",
277 " button = widgets.ButtonWidget(description=button_classes[i])\n",
278 " button.set_css(\"margin\", \"5px\")\n",
279 " display(button)\n",
280 " if i > 0: # Don't add a class the first button.\n",
281 " button.add_class(button_classes[i])\n",
282 " "
283 ],
284 "language": "python",
285 "metadata": {},
286 "outputs": [],
287 "prompt_number": 8
288 },
289 {
290 "cell_type": "markdown",
291 "metadata": {},
292 "source": [
293 "It is also useful to be able to remove CSS classes from widgets.\n",
294 "The `remove_class` method allows you to remove classes from widgets that have been displayed.\n",
295 "Like `add_class`, it must be called after the widget has been displayed."
296 ]
297 },
298 {
299 "cell_type": "code",
300 "collapsed": false,
301 "input": [
302 "print(widgets.DOMWidget.remove_class.__doc__)"
303 ],
304 "language": "python",
305 "metadata": {},
306 "outputs": [
307 {
308 "output_type": "stream",
309 "stream": "stdout",
310 "text": [
311 "Remove class[es] from a DOM element.\n",
312 "\n",
313 " Parameters\n",
314 " ----------\n",
315 " class_names: unicode or list\n",
316 " Class name(s) to remove from the DOM element(s).\n",
317 " selector: unicode (optional)\n",
318 " JQuery selector to select the DOM element(s) that the class(es) will\n",
319 " be removed from.\n",
320 " \n"
321 ]
322 }
323 ],
324 "prompt_number": 9
325 },
326 {
327 "cell_type": "markdown",
328 "metadata": {},
329 "source": [
330 "The example below animates an alert using different bootstrap styles."
331 ]
332 },
333 {
334 "cell_type": "code",
335 "collapsed": false,
336 "input": [
337 "import time\n",
338 "label = widgets.LatexWidget(value = \"$\\\\textbf{ALERT:} Hello World!$\")\n",
339 "display(label)\n",
340 "\n",
341 "# Apply twitter bootstrap alert class to the label.\n",
342 "label.add_class(\"alert\")\n",
343 "\n",
344 "# Animate through additional bootstrap label styles 3 times\n",
345 "additional_alert_styles = ['alert-error', 'alert-info', 'alert-success']\n",
346 "for i in range(3 * len(additional_alert_styles)):\n",
347 " label.add_class(additional_alert_styles[i % 3])\n",
348 " label.remove_class(additional_alert_styles[(i-1) % 3])\n",
349 " time.sleep(1)\n",
350 " "
351 ],
352 "language": "python",
353 "metadata": {},
354 "outputs": [],
355 "prompt_number": 10
356 },
357 {
358 "cell_type": "markdown",
359 "metadata": {},
360 "source": [
361 "In [Part 5](Part 5 - Alignment.ipynb) of this [series](index.ipynb), you will learn about widget alignment."
362 ]
363 }
364 ],
365 "metadata": {}
366 }
367 ]
368 } No newline at end of file
@@ -0,0 +1,331 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "[< Back to Part 4](Part 4 - Styles.ipynb) or [Index](index.ipynb)"
21 ]
22 },
23 {
24 "cell_type": "code",
25 "collapsed": false,
26 "input": [
27 "from IPython.html import widgets # Widget definitions\n",
28 "from IPython.display import display # Used to display widgets in the notebook"
29 ],
30 "language": "python",
31 "metadata": {},
32 "outputs": [],
33 "prompt_number": 1
34 },
35 {
36 "cell_type": "heading",
37 "level": 1,
38 "metadata": {},
39 "source": [
40 "Alignment"
41 ]
42 },
43 {
44 "cell_type": "markdown",
45 "metadata": {},
46 "source": [
47 "Most widgets have a `description` attribute, which allows a label for the widget to be defined.\n",
48 "The label of the widget has a fixed minimum width.\n",
49 "The text of the label is always right aligned and the widget is left aligned:"
50 ]
51 },
52 {
53 "cell_type": "code",
54 "collapsed": false,
55 "input": [
56 "display(widgets.TextWidget(description=\"a:\"))\n",
57 "display(widgets.TextWidget(description=\"aa:\"))\n",
58 "display(widgets.TextWidget(description=\"aaa:\"))"
59 ],
60 "language": "python",
61 "metadata": {},
62 "outputs": [],
63 "prompt_number": 2
64 },
65 {
66 "cell_type": "markdown",
67 "metadata": {},
68 "source": [
69 "If a label is longer than the minimum width, the widget is shifted to the right:"
70 ]
71 },
72 {
73 "cell_type": "code",
74 "collapsed": false,
75 "input": [
76 "display(widgets.TextWidget(description=\"a:\"))\n",
77 "display(widgets.TextWidget(description=\"aa:\"))\n",
78 "display(widgets.TextWidget(description=\"aaa:\"))\n",
79 "display(widgets.TextWidget(description=\"aaaaaaaaaaaaaaaaaa:\"))"
80 ],
81 "language": "python",
82 "metadata": {},
83 "outputs": [],
84 "prompt_number": 3
85 },
86 {
87 "cell_type": "markdown",
88 "metadata": {},
89 "source": [
90 "If a `description` is not set for the widget, the label is not displayed:"
91 ]
92 },
93 {
94 "cell_type": "code",
95 "collapsed": false,
96 "input": [
97 "display(widgets.TextWidget(description=\"a:\"))\n",
98 "display(widgets.TextWidget(description=\"aa:\"))\n",
99 "display(widgets.TextWidget(description=\"aaa:\"))\n",
100 "display(widgets.TextWidget())"
101 ],
102 "language": "python",
103 "metadata": {},
104 "outputs": [],
105 "prompt_number": 4
106 },
107 {
108 "cell_type": "heading",
109 "level": 1,
110 "metadata": {},
111 "source": [
112 "Custom Alignment"
113 ]
114 },
115 {
116 "cell_type": "markdown",
117 "metadata": {},
118 "source": [
119 "`ContainerWidget`s allow for custom alignment of widgets.\n",
120 "The `hbox` and `vbox` CSS classes cause the `ContainerWidget` to horizontally or vertically align its children."
121 ]
122 },
123 {
124 "cell_type": "code",
125 "collapsed": false,
126 "input": [
127 "child_style = {\n",
128 " 'background': '#77CC77',\n",
129 " 'padding': '25px',\n",
130 " 'margin': '5px',\n",
131 " 'font-size': 'xx-large',\n",
132 " 'color': 'white',\n",
133 "}\n",
134 "\n",
135 "def make_container(title):\n",
136 " header = widgets.LatexWidget(value=title) \n",
137 " display(header)\n",
138 " header.set_css({\n",
139 " 'font-size': '30pt',\n",
140 " 'margin-top': '40pt',\n",
141 " 'margin-bottom': '20pt',\n",
142 " })\n",
143 " \n",
144 " container = widgets.ContainerWidget()\n",
145 " container.set_css('background', '#999999')\n",
146 " display(container)\n",
147 " return container\n",
148 "\n",
149 "def fill_container(container):\n",
150 " components = []\n",
151 " for i in range(3):\n",
152 " components.append(widgets.LatexWidget(value=\"ABC\"[i]))\n",
153 " components[i].set_css(child_style)\n",
154 " container.children = components\n",
155 " \n",
156 "container = make_container('VBox')\n",
157 "container.add_class('vbox')\n",
158 "fill_container(container)\n",
159 "\n",
160 "container = make_container('HBox')\n",
161 "container.add_class('hbox')\n",
162 "fill_container(container)\n"
163 ],
164 "language": "python",
165 "metadata": {},
166 "outputs": [],
167 "prompt_number": 5
168 },
169 {
170 "cell_type": "markdown",
171 "metadata": {},
172 "source": [
173 "The `start`, `center`, and `end` classes adjust the alignment of the widgets on the axis where they are being rendered.\n",
174 "Below is an example of the different alignments."
175 ]
176 },
177 {
178 "cell_type": "code",
179 "collapsed": false,
180 "input": [
181 "container = make_container('HBox Pack Start')\n",
182 "container.add_class('hbox')\n",
183 "container.add_class('start')\n",
184 "fill_container(container)\n",
185 " \n",
186 "container = make_container('HBox Pack Center')\n",
187 "container.add_class('hbox')\n",
188 "container.add_class('center')\n",
189 "fill_container(container)\n",
190 " \n",
191 "container = make_container('HBox Pack End')\n",
192 "container.add_class('hbox')\n",
193 "container.add_class('end')\n",
194 "fill_container(container)"
195 ],
196 "language": "python",
197 "metadata": {},
198 "outputs": [],
199 "prompt_number": 6
200 },
201 {
202 "cell_type": "markdown",
203 "metadata": {},
204 "source": [
205 "The `box-flex0`, `box-flex1`, and `box-flex2` DOM classes modify the container's flexibility. Changing a container flexibility affects how and if the container will occupy the remaining space. Applying `box-flex0` has the same result as not applying flex. Below is an example of different flex configurations. The number on the boxes correspond to the applied flex."
206 ]
207 },
208 {
209 "cell_type": "code",
210 "collapsed": false,
211 "input": [
212 "def fill_container(container, flexes):\n",
213 " components = []\n",
214 " for i in range(len(flexes)):\n",
215 " components.append(widgets.ContainerWidget())\n",
216 " components[i].set_css(child_style)\n",
217 " \n",
218 " label = widgets.LatexWidget(value=str(flexes[i]))\n",
219 " components[i].children = [label]\n",
220 " container.children = components\n",
221 " \n",
222 " for i in range(len(flexes)):\n",
223 " if flexes[i] == 0:\n",
224 " components[i].add_class('box-flex0')\n",
225 " elif flexes[i] == 1:\n",
226 " components[i].add_class('box-flex1')\n",
227 " elif flexes[i] == 2:\n",
228 " components[i].add_class('box-flex2')\n",
229 " \n",
230 "container = make_container('Different Flex Configurations')\n",
231 "container.add_class('hbox')\n",
232 "fill_container(container, [0, 0, 0])\n",
233 " \n",
234 "container = make_container('')\n",
235 "container.add_class('hbox')\n",
236 "fill_container(container, [0, 0, 1])\n",
237 " \n",
238 "container = make_container('')\n",
239 "container.add_class('hbox')\n",
240 "fill_container(container, [0, 1, 1])\n",
241 " \n",
242 "container = make_container('')\n",
243 "container.add_class('hbox')\n",
244 "fill_container(container, [0, 2, 2])\n",
245 " \n",
246 "container = make_container('')\n",
247 "container.add_class('hbox')\n",
248 "fill_container(container, [0, 1, 2])\n",
249 " \n",
250 "container = make_container('')\n",
251 "container.add_class('hbox')\n",
252 "fill_container(container, [1, 1, 2])"
253 ],
254 "language": "python",
255 "metadata": {},
256 "outputs": [],
257 "prompt_number": 7
258 },
259 {
260 "cell_type": "markdown",
261 "metadata": {},
262 "source": [
263 "The `align_start`, `align_center`, and `align_end` DOM classes adjust the alignment of the widgets on the axis perpindicular to the one that they are being rendered on. Below is an example of the different alignments."
264 ]
265 },
266 {
267 "cell_type": "code",
268 "collapsed": false,
269 "input": [
270 "def fill_container(container):\n",
271 " components = []\n",
272 " for i in range(3):\n",
273 " components.append(widgets.LatexWidget(parent=container, value=\"ABC\"[i]))\n",
274 " components[i].set_css(child_style)\n",
275 " components[i].set_css('height', str((i+1) * 50) + 'px')\n",
276 " container.children = components\n",
277 "\n",
278 "container = make_container('HBox Align Start')\n",
279 "container.add_class(\"hbox\")\n",
280 "container.add_class(\"align-start\")\n",
281 "fill_container(container)\n",
282 " \n",
283 "container = make_container('HBox Align Center')\n",
284 "container.add_class(\"hbox\")\n",
285 "container.add_class(\"align-center\")\n",
286 "fill_container(container)\n",
287 " \n",
288 "container = make_container('HBox Align End')\n",
289 "container.add_class(\"hbox\")\n",
290 "container.add_class(\"align-end\")\n",
291 "fill_container(container)"
292 ],
293 "language": "python",
294 "metadata": {},
295 "outputs": [],
296 "prompt_number": 8
297 },
298 {
299 "cell_type": "markdown",
300 "metadata": {},
301 "source": [
302 "By default the widget area is a `vbox`; however, there are many uses for a `hbox`. The example below uses a `hbox` to display a set of vertical sliders, like an equalizer."
303 ]
304 },
305 {
306 "cell_type": "code",
307 "collapsed": false,
308 "input": [
309 "container = widgets.ContainerWidget()\n",
310 "container.children=[widgets.FloatSliderWidget(orientation='vertical', description=str(i+1), value=50.0) \n",
311 " for i in range(15)]\n",
312 "display(container)\n",
313 "container.add_class('hbox')"
314 ],
315 "language": "python",
316 "metadata": {},
317 "outputs": [],
318 "prompt_number": 9
319 },
320 {
321 "cell_type": "markdown",
322 "metadata": {},
323 "source": [
324 "In [Part 6](Part 6 - Custom Widget.ipynb) of this [series](index.ipynb), you will learn how to create your own custom widget."
325 ]
326 }
327 ],
328 "metadata": {}
329 }
330 ]
331 } No newline at end of file
This diff has been collapsed as it changes many lines, (1058 lines changed) Show them Hide them
@@ -0,0 +1,1058 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "markdown",
18 "metadata": {},
19 "source": [
20 "[< Back to Part 5](Part 5 - Alignment.ipynb) or [Index](index.ipynb)\n",
21 "\n",
22 "Before reading, make sure to review\n",
23 "\n",
24 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
25 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
26 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
27 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
28 ]
29 },
30 {
31 "cell_type": "code",
32 "collapsed": false,
33 "input": [
34 "from __future__ import print_function # For py 2.7 compat\n",
35 "\n",
36 "from IPython.html import widgets # Widget definitions\n",
37 "from IPython.display import display # Used to display widgets in the notebook\n",
38 "from IPython.utils.traitlets import Unicode # Used to declare attributes of our widget"
39 ],
40 "language": "python",
41 "metadata": {},
42 "outputs": [],
43 "prompt_number": 1
44 },
45 {
46 "cell_type": "heading",
47 "level": 1,
48 "metadata": {},
49 "source": [
50 "Abstract"
51 ]
52 },
53 {
54 "cell_type": "markdown",
55 "metadata": {},
56 "source": [
57 "This notebook implements a custom date picker widget,\n",
58 "in order to demonstrate the widget creation process.\n",
59 "\n",
60 "To create a custom widget, both Python and JavaScript code is required."
61 ]
62 },
63 {
64 "cell_type": "heading",
65 "level": 1,
66 "metadata": {},
67 "source": [
68 "Section 1 - Basics"
69 ]
70 },
71 {
72 "cell_type": "heading",
73 "level": 2,
74 "metadata": {},
75 "source": [
76 "Python"
77 ]
78 },
79 {
80 "cell_type": "markdown",
81 "metadata": {},
82 "source": [
83 "When starting a project like this, it is often easiest to make a simple base implementation,\n",
84 "to verify that the underlying framework is working as expected.\n",
85 "To start, we will create an empty widget and make sure that it can be rendered.\n",
86 "The first step is to define the widget in Python."
87 ]
88 },
89 {
90 "cell_type": "code",
91 "collapsed": false,
92 "input": [
93 "class DateWidget(widgets.DOMWidget):\n",
94 " _view_name = Unicode('DatePickerView', sync=True)"
95 ],
96 "language": "python",
97 "metadata": {},
98 "outputs": [],
99 "prompt_number": 2
100 },
101 {
102 "cell_type": "markdown",
103 "metadata": {},
104 "source": [
105 "Our widget inherits from `widgets.DOMWidget` since it is intended that it will be displayed in the notebook directly.\n",
106 "The `_view_name` trait is special; the widget framework will read the `_view_name` trait to determine what Backbone view the widget is associated with.\n",
107 "**Using `sync=True` is very important** because it tells the widget framework that that specific traitlet should be synced between the front- and back-ends."
108 ]
109 },
110 {
111 "cell_type": "heading",
112 "level": 2,
113 "metadata": {},
114 "source": [
115 "JavaScript"
116 ]
117 },
118 {
119 "cell_type": "markdown",
120 "metadata": {},
121 "source": [
122 "In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies.\n",
123 "All IPython widget code depends on `notebook/js/widgets/widget.js`,\n",
124 "where the base widget model and base view are defined.\n",
125 "We use require.js to load this file:"
126 ]
127 },
128 {
129 "cell_type": "code",
130 "collapsed": false,
131 "input": [
132 "%%javascript\n",
133 "\n",
134 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
135 "\n",
136 "});"
137 ],
138 "language": "python",
139 "metadata": {},
140 "outputs": [
141 {
142 "javascript": [
143 "\n",
144 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
145 "\n",
146 "});"
147 ],
148 "metadata": {},
149 "output_type": "display_data",
150 "text": [
151 "<IPython.core.display.Javascript at 0x109491690>"
152 ]
153 }
154 ],
155 "prompt_number": 3
156 },
157 {
158 "cell_type": "markdown",
159 "metadata": {},
160 "source": [
161 "Now we need to define a view that can be used to represent the model.\n",
162 "To do this, the `IPython.DOMWidgetView` is extended.\n",
163 "**A render function must be defined**.\n",
164 "The render function is used to render a widget view instance to the DOM.\n",
165 "For now, the render function renders a div that contains the text *Hello World!*\n",
166 "Lastly, the view needs to be registered with the widget manager.\n",
167 "\n",
168 "**Final JavaScript code below:**"
169 ]
170 },
171 {
172 "cell_type": "code",
173 "collapsed": false,
174 "input": [
175 "%%javascript\n",
176 "\n",
177 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
178 " \n",
179 " // Define the DatePickerView\n",
180 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
181 " render: function(){ this.$el.text('Hello World!'); },\n",
182 " });\n",
183 " \n",
184 " // Register the DatePickerView with the widget manager.\n",
185 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
186 "});"
187 ],
188 "language": "python",
189 "metadata": {},
190 "outputs": [
191 {
192 "javascript": [
193 "\n",
194 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
195 " \n",
196 " // Define the DatePickerView\n",
197 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
198 " render: function(){ this.$el.text('Hello World!'); },\n",
199 " });\n",
200 " \n",
201 " // Register the DatePickerView with the widget manager.\n",
202 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
203 "});"
204 ],
205 "metadata": {},
206 "output_type": "display_data",
207 "text": [
208 "<IPython.core.display.Javascript at 0x1094917d0>"
209 ]
210 }
211 ],
212 "prompt_number": 4
213 },
214 {
215 "cell_type": "heading",
216 "level": 2,
217 "metadata": {},
218 "source": [
219 "Test"
220 ]
221 },
222 {
223 "cell_type": "markdown",
224 "metadata": {},
225 "source": [
226 "To test what we have so far, create the widget, just like you would the builtin widgets:"
227 ]
228 },
229 {
230 "cell_type": "code",
231 "collapsed": false,
232 "input": [
233 "DateWidget()"
234 ],
235 "language": "python",
236 "metadata": {},
237 "outputs": [],
238 "prompt_number": 5
239 },
240 {
241 "cell_type": "heading",
242 "level": 1,
243 "metadata": {},
244 "source": [
245 "Section 2 - Something useful"
246 ]
247 },
248 {
249 "cell_type": "heading",
250 "level": 2,
251 "metadata": {},
252 "source": [
253 "Python"
254 ]
255 },
256 {
257 "cell_type": "markdown",
258 "metadata": {},
259 "source": [
260 "In the last section we created a simple widget that displayed *Hello World!*\n",
261 "To make an actual date widget, we need to add a property that will be synced between the Python model and the JavaScript model.\n",
262 "The new attribute must be a traitlet, so the widget machinery can handle it.\n",
263 "The traitlet must be constructed with a `sync=True` keyword argument, to tell the widget machinery knows to synchronize it with the front-end.\n",
264 "Adding this to the code from the last section:"
265 ]
266 },
267 {
268 "cell_type": "code",
269 "collapsed": false,
270 "input": [
271 "class DateWidget(widgets.DOMWidget):\n",
272 " _view_name = Unicode('DatePickerView', sync=True)\n",
273 " value = Unicode(sync=True)"
274 ],
275 "language": "python",
276 "metadata": {},
277 "outputs": [],
278 "prompt_number": 6
279 },
280 {
281 "cell_type": "heading",
282 "level": 2,
283 "metadata": {},
284 "source": [
285 "JavaScript"
286 ]
287 },
288 {
289 "cell_type": "markdown",
290 "metadata": {},
291 "source": [
292 "In the JavaScript, there is no need to define counterparts to the traitlets.\n",
293 "When the JavaScript model is created for the first time,\n",
294 "it copies all of the traitlet `sync=True` attributes from the Python model.\n",
295 "We need to replace *Hello World!* with an actual HTML date picker widget."
296 ]
297 },
298 {
299 "cell_type": "code",
300 "collapsed": false,
301 "input": [
302 "%%javascript\n",
303 "\n",
304 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
305 " \n",
306 " // Define the DatePickerView\n",
307 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
308 " render: function(){\n",
309 " \n",
310 " // Create the date picker control.\n",
311 " this.$date = $('<input />')\n",
312 " .attr('type', 'date')\n",
313 " .appendTo(this.$el);\n",
314 " },\n",
315 " });\n",
316 " \n",
317 " // Register the DatePickerView with the widget manager.\n",
318 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
319 "});"
320 ],
321 "language": "python",
322 "metadata": {},
323 "outputs": [
324 {
325 "javascript": [
326 "\n",
327 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
328 " \n",
329 " // Define the DatePickerView\n",
330 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
331 " render: function(){\n",
332 " \n",
333 " // Create the date picker control.\n",
334 " this.$date = $('<input />')\n",
335 " .attr('type', 'date')\n",
336 " .appendTo(this.$el);\n",
337 " },\n",
338 " });\n",
339 " \n",
340 " // Register the DatePickerView with the widget manager.\n",
341 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
342 "});"
343 ],
344 "metadata": {},
345 "output_type": "display_data",
346 "text": [
347 "<IPython.core.display.Javascript at 0x109491750>"
348 ]
349 }
350 ],
351 "prompt_number": 7
352 },
353 {
354 "cell_type": "markdown",
355 "metadata": {},
356 "source": [
357 "In order to get the HTML date picker to update itself with the value set in the back-end, we need to implement an `update()` method."
358 ]
359 },
360 {
361 "cell_type": "code",
362 "collapsed": false,
363 "input": [
364 "%%javascript\n",
365 "\n",
366 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
367 " \n",
368 " // Define the DatePickerView\n",
369 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
370 " render: function(){\n",
371 " \n",
372 " // Create the date picker control.\n",
373 " this.$date = $('<input />')\n",
374 " .attr('type', 'date')\n",
375 " .appendTo(this.$el);\n",
376 " },\n",
377 " \n",
378 " update: function() {\n",
379 " \n",
380 " // Set the value of the date control and then call base.\n",
381 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
382 " return DatePickerView.__super__.update.apply(this);\n",
383 " },\n",
384 " });\n",
385 " \n",
386 " // Register the DatePickerView with the widget manager.\n",
387 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
388 "});"
389 ],
390 "language": "python",
391 "metadata": {},
392 "outputs": [
393 {
394 "javascript": [
395 "\n",
396 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
397 " \n",
398 " // Define the DatePickerView\n",
399 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
400 " render: function(){\n",
401 " \n",
402 " // Create the date picker control.\n",
403 " this.$date = $('<input />')\n",
404 " .attr('type', 'date')\n",
405 " .appendTo(this.$el);\n",
406 " },\n",
407 " \n",
408 " update: function() {\n",
409 " \n",
410 " // Set the value of the date control and then call base.\n",
411 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
412 " return DatePickerView.__super__.update.apply(this);\n",
413 " },\n",
414 " });\n",
415 " \n",
416 " // Register the DatePickerView with the widget manager.\n",
417 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
418 "});"
419 ],
420 "metadata": {},
421 "output_type": "display_data",
422 "text": [
423 "<IPython.core.display.Javascript at 0x109491750>"
424 ]
425 }
426 ],
427 "prompt_number": 8
428 },
429 {
430 "cell_type": "markdown",
431 "metadata": {},
432 "source": [
433 "To get the changed value from the frontend to publish itself to the backend,\n",
434 "we need to listen to the change event triggered by the HTM date control and set the value in the model.\n",
435 "After the date change event fires and the new value is set in the model,\n",
436 "it is very important that we call `this.touch()` to let the widget machinery know which view changed the model.\n",
437 "This is important because the widget machinery needs to know which cell to route the message callbacks to.\n",
438 "\n",
439 "**Final JavaScript code below:**"
440 ]
441 },
442 {
443 "cell_type": "code",
444 "collapsed": false,
445 "input": [
446 "%%javascript\n",
447 "\n",
448 "\n",
449 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
450 " \n",
451 " // Define the DatePickerView\n",
452 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
453 " render: function(){\n",
454 " \n",
455 " // Create the date picker control.\n",
456 " this.$date = $('<input />')\n",
457 " .attr('type', 'date')\n",
458 " .appendTo(this.$el);\n",
459 " },\n",
460 " \n",
461 " update: function() {\n",
462 " \n",
463 " // Set the value of the date control and then call base.\n",
464 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
465 " return DatePickerView.__super__.update.apply(this);\n",
466 " },\n",
467 " \n",
468 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
469 " events: {\"change\": \"handle_date_change\"},\n",
470 " \n",
471 " // Callback for when the date is changed.\n",
472 " handle_date_change: function(event) {\n",
473 " this.model.set('value', this.$date.val());\n",
474 " this.touch();\n",
475 " },\n",
476 " });\n",
477 " \n",
478 " // Register the DatePickerView with the widget manager.\n",
479 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
480 "});"
481 ],
482 "language": "python",
483 "metadata": {},
484 "outputs": [
485 {
486 "javascript": [
487 "\n",
488 "\n",
489 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
490 " \n",
491 " // Define the DatePickerView\n",
492 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
493 " render: function(){\n",
494 " \n",
495 " // Create the date picker control.\n",
496 " this.$date = $('<input />')\n",
497 " .attr('type', 'date')\n",
498 " .appendTo(this.$el);\n",
499 " },\n",
500 " \n",
501 " update: function() {\n",
502 " \n",
503 " // Set the value of the date control and then call base.\n",
504 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
505 " return DatePickerView.__super__.update.apply(this);\n",
506 " },\n",
507 " \n",
508 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
509 " events: {\"change\": \"handle_date_change\"},\n",
510 " \n",
511 " // Callback for when the date is changed.\n",
512 " handle_date_change: function(event) {\n",
513 " this.model.set('value', this.$date.val());\n",
514 " this.touch();\n",
515 " },\n",
516 " });\n",
517 " \n",
518 " // Register the DatePickerView with the widget manager.\n",
519 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
520 "});"
521 ],
522 "metadata": {},
523 "output_type": "display_data",
524 "text": [
525 "<IPython.core.display.Javascript at 0x109491b10>"
526 ]
527 }
528 ],
529 "prompt_number": 9
530 },
531 {
532 "cell_type": "heading",
533 "level": 2,
534 "metadata": {},
535 "source": [
536 "Test"
537 ]
538 },
539 {
540 "cell_type": "markdown",
541 "metadata": {},
542 "source": [
543 "To test, create the widget the same way that the other widgets are created."
544 ]
545 },
546 {
547 "cell_type": "code",
548 "collapsed": false,
549 "input": [
550 "my_widget = DateWidget()\n",
551 "display(my_widget)"
552 ],
553 "language": "python",
554 "metadata": {},
555 "outputs": [],
556 "prompt_number": 10
557 },
558 {
559 "cell_type": "markdown",
560 "metadata": {},
561 "source": [
562 "Display the widget again to make sure that both views remain in sync."
563 ]
564 },
565 {
566 "cell_type": "code",
567 "collapsed": false,
568 "input": [
569 "my_widget"
570 ],
571 "language": "python",
572 "metadata": {},
573 "outputs": [],
574 "prompt_number": 11
575 },
576 {
577 "cell_type": "markdown",
578 "metadata": {},
579 "source": [
580 "Read the date from Python"
581 ]
582 },
583 {
584 "cell_type": "code",
585 "collapsed": false,
586 "input": [
587 "my_widget.value"
588 ],
589 "language": "python",
590 "metadata": {},
591 "outputs": [
592 {
593 "metadata": {},
594 "output_type": "pyout",
595 "prompt_number": 12,
596 "text": [
597 "u''"
598 ]
599 }
600 ],
601 "prompt_number": 12
602 },
603 {
604 "cell_type": "markdown",
605 "metadata": {},
606 "source": [
607 "Set the date from Python"
608 ]
609 },
610 {
611 "cell_type": "code",
612 "collapsed": false,
613 "input": [
614 "my_widget.value = \"1998-12-01\" # December 1st, 1998"
615 ],
616 "language": "python",
617 "metadata": {},
618 "outputs": [],
619 "prompt_number": 13
620 },
621 {
622 "cell_type": "heading",
623 "level": 1,
624 "metadata": {},
625 "source": [
626 "Section 3 - Extra credit"
627 ]
628 },
629 {
630 "cell_type": "markdown",
631 "metadata": {},
632 "source": [
633 "The 3rd party `dateutil` library is required to continue. https://pypi.python.org/pypi/python-dateutil"
634 ]
635 },
636 {
637 "cell_type": "code",
638 "collapsed": false,
639 "input": [
640 "# Import the dateutil library to parse date strings.\n",
641 "from dateutil import parser"
642 ],
643 "language": "python",
644 "metadata": {},
645 "outputs": [],
646 "prompt_number": 14
647 },
648 {
649 "cell_type": "markdown",
650 "metadata": {},
651 "source": [
652 "In the last section we created a fully working date picker widget.\n",
653 "Now we will add custom validation and support for labels.\n",
654 "So far, only the ISO date format \"YYYY-MM-DD\" is supported.\n",
655 "Now, we will add support for all of the date formats recognized by the Python dateutil library."
656 ]
657 },
658 {
659 "cell_type": "heading",
660 "level": 2,
661 "metadata": {},
662 "source": [
663 "Python"
664 ]
665 },
666 {
667 "cell_type": "markdown",
668 "metadata": {},
669 "source": [
670 "The standard property name used for widget labels is `description`.\n",
671 "In the code block below, `description` has been added to the Python widget."
672 ]
673 },
674 {
675 "cell_type": "code",
676 "collapsed": false,
677 "input": [
678 "class DateWidget(widgets.DOMWidget):\n",
679 " _view_name = Unicode('DatePickerView', sync=True)\n",
680 " value = Unicode(sync=True)\n",
681 " description = Unicode(sync=True)"
682 ],
683 "language": "python",
684 "metadata": {},
685 "outputs": [],
686 "prompt_number": 15
687 },
688 {
689 "cell_type": "markdown",
690 "metadata": {},
691 "source": [
692 "The traitlet machinery searches the class that the trait is defined in for methods with \"`_changed`\" suffixed onto their names. Any method with the format \"`_X_changed`\" will be called when \"`X`\" is modified.\n",
693 "We can take advantage of this to perform validation and parsing of different date string formats.\n",
694 "Below, a method that listens to value has been added to the DateWidget."
695 ]
696 },
697 {
698 "cell_type": "code",
699 "collapsed": false,
700 "input": [
701 "class DateWidget(widgets.DOMWidget):\n",
702 " _view_name = Unicode('DatePickerView', sync=True)\n",
703 " value = Unicode(sync=True)\n",
704 " description = Unicode(sync=True)\n",
705 "\n",
706 " # This function automatically gets called by the traitlet machinery when\n",
707 " # value is modified because of this function's name.\n",
708 " def _value_changed(self, name, old_value, new_value):\n",
709 " pass"
710 ],
711 "language": "python",
712 "metadata": {},
713 "outputs": [],
714 "prompt_number": 16
715 },
716 {
717 "cell_type": "markdown",
718 "metadata": {},
719 "source": [
720 "Now the function parses the date string,\n",
721 "and only sets the value in the correct format."
722 ]
723 },
724 {
725 "cell_type": "code",
726 "collapsed": false,
727 "input": [
728 "class DateWidget(widgets.DOMWidget):\n",
729 " _view_name = Unicode('DatePickerView', sync=True)\n",
730 " value = Unicode(sync=True)\n",
731 " description = Unicode(sync=True)\n",
732 " \n",
733 " # This function automatically gets called by the traitlet machinery when\n",
734 " # value is modified because of this function's name.\n",
735 " def _value_changed(self, name, old_value, new_value):\n",
736 " \n",
737 " # Parse the date time value.\n",
738 " try:\n",
739 " parsed_date = parser.parse(new_value)\n",
740 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
741 " except:\n",
742 " parsed_date_string = ''\n",
743 " \n",
744 " # Set the parsed date string if the current date string is different.\n",
745 " if self.value != parsed_date_string:\n",
746 " self.value = parsed_date_string"
747 ],
748 "language": "python",
749 "metadata": {},
750 "outputs": [],
751 "prompt_number": 17
752 },
753 {
754 "cell_type": "markdown",
755 "metadata": {},
756 "source": [
757 "Finally, a `CallbackDispatcher` is added so the user can perform custom validation.\n",
758 "If any one of the callbacks registered with the dispatcher returns False,\n",
759 "the new date is not set.\n",
760 "\n",
761 "**Final Python code below:**"
762 ]
763 },
764 {
765 "cell_type": "code",
766 "collapsed": false,
767 "input": [
768 "class DateWidget(widgets.DOMWidget):\n",
769 " _view_name = Unicode('DatePickerView', sync=True)\n",
770 " value = Unicode(sync=True)\n",
771 " description = Unicode(sync=True)\n",
772 " \n",
773 " def __init__(self, **kwargs):\n",
774 " super(DateWidget, self).__init__(**kwargs)\n",
775 " \n",
776 " self.validate = widgets.CallbackDispatcher()\n",
777 " \n",
778 " # This function automatically gets called by the traitlet machinery when\n",
779 " # value is modified because of this function's name.\n",
780 " def _value_changed(self, name, old_value, new_value):\n",
781 " \n",
782 " # Parse the date time value.\n",
783 " try:\n",
784 " parsed_date = parser.parse(new_value)\n",
785 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
786 " except:\n",
787 " parsed_date_string = ''\n",
788 " \n",
789 " # Set the parsed date string if the current date string is different.\n",
790 " if old_value != new_value:\n",
791 " valid = self.validate(parsed_date)\n",
792 " if valid in (None, True):\n",
793 " self.value = parsed_date_string\n",
794 " else:\n",
795 " self.value = old_value\n",
796 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
797 " # We need to force the back-end to send the front-end the state\n",
798 " # to make sure that the date control date doesn't change."
799 ],
800 "language": "python",
801 "metadata": {},
802 "outputs": [],
803 "prompt_number": 18
804 },
805 {
806 "cell_type": "heading",
807 "level": 2,
808 "metadata": {},
809 "source": [
810 "JavaScript"
811 ]
812 },
813 {
814 "cell_type": "markdown",
815 "metadata": {},
816 "source": [
817 "Using the Javascript code from the last section,\n",
818 "we add a label to the date time object.\n",
819 "The label is a div with the `widget-hlabel` class applied to it.\n",
820 "`widget-hlabel` is a class provided by the widget framework that applies special styling to a div to make it look like the rest of the horizontal labels used with the built-in widgets.\n",
821 "Similar to the `widget-hlabel` class is the `widget-hbox-single` class.\n",
822 "The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget.\n",
823 "\n",
824 "We hide the label if the description value is blank."
825 ]
826 },
827 {
828 "cell_type": "code",
829 "collapsed": false,
830 "input": [
831 "%%javascript\n",
832 "\n",
833 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
834 " \n",
835 " // Define the DatePickerView\n",
836 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
837 " render: function(){\n",
838 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
839 " it fit with the other built in widgets.*/\n",
840 " // Create a label.\n",
841 " this.$label = $('<div />')\n",
842 " .addClass('widget-hlabel')\n",
843 " .appendTo(this.$el)\n",
844 " .hide(); // Hide the label by default.\n",
845 " \n",
846 " // Create the date picker control.\n",
847 " this.$date = $('<input />')\n",
848 " .attr('type', 'date')\n",
849 " .appendTo(this.$el);\n",
850 " },\n",
851 " \n",
852 " update: function() {\n",
853 " \n",
854 " // Set the value of the date control and then call base.\n",
855 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
856 " \n",
857 " // Hide or show the label depending on the existance of a description.\n",
858 " var description = this.model.get('description');\n",
859 " if (description == undefined || description == '') {\n",
860 " this.$label.hide();\n",
861 " } else {\n",
862 " this.$label.show();\n",
863 " this.$label.text(description);\n",
864 " }\n",
865 " \n",
866 " return DatePickerView.__super__.update.apply(this);\n",
867 " },\n",
868 " \n",
869 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
870 " events: {\"change\": \"handle_date_change\"},\n",
871 " \n",
872 " // Callback for when the date is changed.\n",
873 " handle_date_change: function(event) {\n",
874 " this.model.set('value', this.$date.val());\n",
875 " this.touch();\n",
876 " },\n",
877 " });\n",
878 " \n",
879 " // Register the DatePickerView with the widget manager.\n",
880 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
881 "});"
882 ],
883 "language": "python",
884 "metadata": {},
885 "outputs": [
886 {
887 "javascript": [
888 "\n",
889 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
890 " \n",
891 " // Define the DatePickerView\n",
892 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
893 " render: function(){\n",
894 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
895 " it fit with the other built in widgets.*/\n",
896 " // Create a label.\n",
897 " this.$label = $('<div />')\n",
898 " .addClass('widget-hlabel')\n",
899 " .appendTo(this.$el)\n",
900 " .hide(); // Hide the label by default.\n",
901 " \n",
902 " // Create the date picker control.\n",
903 " this.$date = $('<input />')\n",
904 " .attr('type', 'date')\n",
905 " .appendTo(this.$el);\n",
906 " },\n",
907 " \n",
908 " update: function() {\n",
909 " \n",
910 " // Set the value of the date control and then call base.\n",
911 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
912 " \n",
913 " // Hide or show the label depending on the existance of a description.\n",
914 " var description = this.model.get('description');\n",
915 " if (description == undefined || description == '') {\n",
916 " this.$label.hide();\n",
917 " } else {\n",
918 " this.$label.show();\n",
919 " this.$label.text(description);\n",
920 " }\n",
921 " \n",
922 " return DatePickerView.__super__.update.apply(this);\n",
923 " },\n",
924 " \n",
925 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
926 " events: {\"change\": \"handle_date_change\"},\n",
927 " \n",
928 " // Callback for when the date is changed.\n",
929 " handle_date_change: function(event) {\n",
930 " this.model.set('value', this.$date.val());\n",
931 " this.touch();\n",
932 " },\n",
933 " });\n",
934 " \n",
935 " // Register the DatePickerView with the widget manager.\n",
936 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
937 "});"
938 ],
939 "metadata": {},
940 "output_type": "display_data",
941 "text": [
942 "<IPython.core.display.Javascript at 0x1094eef90>"
943 ]
944 }
945 ],
946 "prompt_number": 19
947 },
948 {
949 "cell_type": "heading",
950 "level": 2,
951 "metadata": {},
952 "source": [
953 "Test"
954 ]
955 },
956 {
957 "cell_type": "markdown",
958 "metadata": {},
959 "source": [
960 "To test the drawing of the label we create the widget like normal but supply the additional description property a value."
961 ]
962 },
963 {
964 "cell_type": "code",
965 "collapsed": false,
966 "input": [
967 "# Add some additional widgets for aesthetic purpose\n",
968 "display(widgets.TextWidget(description=\"First:\"))\n",
969 "display(widgets.TextWidget(description=\"Last:\"))\n",
970 "\n",
971 "my_widget = DateWidget()\n",
972 "display(my_widget)\n",
973 "my_widget.description=\"DOB:\""
974 ],
975 "language": "python",
976 "metadata": {},
977 "outputs": [],
978 "prompt_number": 20
979 },
980 {
981 "cell_type": "markdown",
982 "metadata": {},
983 "source": [
984 "Now we will try to create a widget that only accepts dates in the year 2014. We render the widget without a description to verify that it can still render without a label."
985 ]
986 },
987 {
988 "cell_type": "code",
989 "collapsed": false,
990 "input": [
991 "my_widget = DateWidget()\n",
992 "display(my_widget)\n",
993 "\n",
994 "def require_2014(date):\n",
995 " return not date is None and date.year == 2014\n",
996 "my_widget.validate.register_callback(require_2014)"
997 ],
998 "language": "python",
999 "metadata": {},
1000 "outputs": [],
1001 "prompt_number": 21
1002 },
1003 {
1004 "cell_type": "code",
1005 "collapsed": false,
1006 "input": [
1007 "# Try setting a valid date\n",
1008 "my_widget.value = \"December 2, 2014\""
1009 ],
1010 "language": "python",
1011 "metadata": {},
1012 "outputs": [],
1013 "prompt_number": 22
1014 },
1015 {
1016 "cell_type": "code",
1017 "collapsed": false,
1018 "input": [
1019 "# Try setting an invalid date\n",
1020 "my_widget.value = \"June 12, 1999\""
1021 ],
1022 "language": "python",
1023 "metadata": {},
1024 "outputs": [],
1025 "prompt_number": 23
1026 },
1027 {
1028 "cell_type": "code",
1029 "collapsed": false,
1030 "input": [
1031 "my_widget.value"
1032 ],
1033 "language": "python",
1034 "metadata": {},
1035 "outputs": [
1036 {
1037 "metadata": {},
1038 "output_type": "pyout",
1039 "prompt_number": 24,
1040 "text": [
1041 "u'2014-12-02'"
1042 ]
1043 }
1044 ],
1045 "prompt_number": 24
1046 },
1047 {
1048 "cell_type": "markdown",
1049 "metadata": {},
1050 "source": [
1051 "This concludes Part 6 of the [series](index.ipynb)."
1052 ]
1053 }
1054 ],
1055 "metadata": {}
1056 }
1057 ]
1058 } No newline at end of file
@@ -0,0 +1,203 b''
1 {
2 "metadata": {
3 "name": ""
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
9 "cells": [
10 {
11 "cell_type": "heading",
12 "level": 1,
13 "metadata": {},
14 "source": [
15 "Variable Inspector Widget"
16 ]
17 },
18 {
19 "cell_type": "heading",
20 "level": 2,
21 "metadata": {},
22 "source": [
23 "A short example implementation"
24 ]
25 },
26 {
27 "cell_type": "markdown",
28 "metadata": {},
29 "source": [
30 "This notebook demonstrates how one can use the widgets already built-in to IPython to create a working variable inspector much like the ones seen in popular commercial scientific computing environments."
31 ]
32 },
33 {
34 "cell_type": "code",
35 "collapsed": false,
36 "input": [
37 "from IPython.html import widgets # Loads the Widget framework.\n",
38 "from IPython.core.magics.namespace import NamespaceMagics # Used to query namespace.\n",
39 "\n",
40 "# For this example, hide these names, just to avoid polluting the namespace further\n",
41 "get_ipython().user_ns_hidden['widgets'] = widgets\n",
42 "get_ipython().user_ns_hidden['NamespaceMagics'] = NamespaceMagics"
43 ],
44 "language": "python",
45 "metadata": {},
46 "outputs": [],
47 "prompt_number": 1
48 },
49 {
50 "cell_type": "code",
51 "collapsed": false,
52 "input": [
53 "class VariableInspectorWindow(object):\n",
54 " instance = None\n",
55 " \n",
56 " def __init__(self, ipython):\n",
57 " \"\"\"Public constructor.\"\"\"\n",
58 " if VariableInspectorWindow.instance is not None:\n",
59 " raise Exception(\"\"\"Only one instance of the Variable Inspector can exist at a \n",
60 " time. Call close() on the active instance before creating a new instance.\n",
61 " If you have lost the handle to the active instance, you can re-obtain it\n",
62 " via `VariableInspectorWindow.instance`.\"\"\")\n",
63 " \n",
64 " VariableInspectorWindow.instance = self\n",
65 " self.closed = False\n",
66 " self.namespace = NamespaceMagics()\n",
67 " self.namespace.shell = ipython.kernel.shell\n",
68 " \n",
69 " self._popout = widgets.PopupWidget()\n",
70 " self._popout.description = \"Variable Inspector\"\n",
71 " self._popout.button_text = self._popout.description\n",
72 "\n",
73 " self._modal_body = widgets.ContainerWidget()\n",
74 " self._modal_body.set_css('overflow-y', 'scroll')\n",
75 "\n",
76 " self._modal_body_label = widgets.HTMLWidget(value = 'Not hooked')\n",
77 " self._modal_body.children = [self._modal_body_label]\n",
78 "\n",
79 " self._modal_footer = widgets.ContainerWidget()\n",
80 " self._var_filter = widgets.ToggleButtonsWidget(description=\"Method:\", values=['List', 'Details'], value='List')\n",
81 " self._modal_footer.children = [self._var_filter]\n",
82 "\n",
83 " self._popout.children = [\n",
84 " self._modal_body, \n",
85 " self._modal_footer,\n",
86 " ]\n",
87 " \n",
88 " self._ipython = ipython\n",
89 " self._ipython.register_post_execute(self._fill)\n",
90 " self._var_filter.on_trait_change(self._fill, 'value')\n",
91 "\n",
92 " def close(self):\n",
93 " \"\"\"Close and remove hooks.\"\"\"\n",
94 " if not self.closed:\n",
95 " del self._ipython._post_execute[self._fill]\n",
96 " self._popout.close()\n",
97 " self.closed = True\n",
98 " VariableInspectorWindow.instance = None\n",
99 "\n",
100 " def _fill(self):\n",
101 " \"\"\"Fill self with variable information.\"\"\"\n",
102 " values = self.namespace.who_ls()\n",
103 " mode = self._var_filter.value\n",
104 " if mode == \"List\":\n",
105 " self._modal_body_label.value = '<br>'.join(values)\n",
106 " elif mode == \"Details\":\n",
107 " self._modal_body_label.value = '<table class=\"table table-bordered table-striped\"><tr><th>Name</th><th>Type</th><th>Value</th></tr><tr><td>' + \\\n",
108 " '</td></tr><tr><td>'.join(['{0}</td><td>{1}</td><td>{2}'.format(v, type(eval(v)).__name__, str(eval(v))) for v in values]) + \\\n",
109 " '</td></tr></table>'\n",
110 "\n",
111 " def _ipython_display_(self):\n",
112 " \"\"\"Called when display() or pyout is used to display the Variable \n",
113 " Inspector.\"\"\"\n",
114 " self._popout._ipython_display_()\n",
115 " self._modal_footer.add_class('modal-footer')\n",
116 " self._popout.add_class('vbox')\n",
117 " self._modal_body.add_class('box-flex1')\n"
118 ],
119 "language": "python",
120 "metadata": {},
121 "outputs": [],
122 "prompt_number": 2
123 },
124 {
125 "cell_type": "code",
126 "collapsed": false,
127 "input": [
128 "inspector = VariableInspectorWindow(get_ipython())\n",
129 "inspector"
130 ],
131 "language": "python",
132 "metadata": {},
133 "outputs": [],
134 "prompt_number": 3
135 },
136 {
137 "cell_type": "heading",
138 "level": 1,
139 "metadata": {},
140 "source": [
141 "Test"
142 ]
143 },
144 {
145 "cell_type": "code",
146 "collapsed": false,
147 "input": [
148 "a = 5"
149 ],
150 "language": "python",
151 "metadata": {},
152 "outputs": [],
153 "prompt_number": 4
154 },
155 {
156 "cell_type": "code",
157 "collapsed": false,
158 "input": [
159 "b = 3.0"
160 ],
161 "language": "python",
162 "metadata": {},
163 "outputs": [],
164 "prompt_number": 5
165 },
166 {
167 "cell_type": "code",
168 "collapsed": false,
169 "input": [
170 "c = a * b"
171 ],
172 "language": "python",
173 "metadata": {},
174 "outputs": [],
175 "prompt_number": 6
176 },
177 {
178 "cell_type": "code",
179 "collapsed": false,
180 "input": [
181 "d = \"String\""
182 ],
183 "language": "python",
184 "metadata": {},
185 "outputs": [],
186 "prompt_number": 7
187 },
188 {
189 "cell_type": "code",
190 "collapsed": false,
191 "input": [
192 "del b"
193 ],
194 "language": "python",
195 "metadata": {},
196 "outputs": [],
197 "prompt_number": 8
198 }
199 ],
200 "metadata": {}
201 }
202 ]
203 } No newline at end of file
@@ -0,0 +1,67 b''
1 {
2 "metadata": {
3 "name": ""
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
9 "cells": [
10 {
11 "cell_type": "heading",
12 "level": 1,
13 "metadata": {},
14 "source": [
15 "Widgets"
16 ]
17 },
18 {
19 "cell_type": "markdown",
20 "metadata": {},
21 "source": [
22 "This directory includes a tutorial and collection of examples related to the IPython notebook widget framework."
23 ]
24 },
25 {
26 "cell_type": "heading",
27 "level": 2,
28 "metadata": {},
29 "source": [
30 "Tutorial"
31 ]
32 },
33 {
34 "cell_type": "markdown",
35 "metadata": {},
36 "source": [
37 "- [Part 1 - Basics](Part 1 - Basics.ipynb) \n",
38 "- [Part 2 - Events](Part 2 - Events.ipynb) \n",
39 "- [Part 3 - Placement](Part 3 - Placement.ipynb) \n",
40 "- [Part 4 - Styles](Part 4 - Styles.ipynb) \n",
41 "- [Part 5 - Alignment](Part 5 - Alignment.ipynb) \n",
42 "- [Part 6 - Custom Widget](Part 6 - Custom Widget.ipynb) "
43 ]
44 },
45 {
46 "cell_type": "heading",
47 "level": 2,
48 "metadata": {},
49 "source": [
50 "Examples"
51 ]
52 },
53 {
54 "cell_type": "markdown",
55 "metadata": {},
56 "source": [
57 "- [Variable Inspector](Variable Inspector.ipynb) \n",
58 "- [Export As (nbconvert)](Export As (nbconvert%29.ipynb) \n",
59 "- [Nonblocking Console](Nonblocking Console.ipynb) \n",
60 "- [File Upload Widget](File Upload Widget.ipynb) "
61 ]
62 }
63 ],
64 "metadata": {}
65 }
66 ]
67 } No newline at end of file
@@ -110,12 +110,23 b' def display(*objs, **kwargs):'
110
110
111 from IPython.core.interactiveshell import InteractiveShell
111 from IPython.core.interactiveshell import InteractiveShell
112
112
113 if raw:
113 if not raw:
114 for obj in objs:
115 publish_display_data('display', obj, metadata)
116 else:
117 format = InteractiveShell.instance().display_formatter.format
114 format = InteractiveShell.instance().display_formatter.format
118 for obj in objs:
115
116 for obj in objs:
117
118 # If _ipython_display_ is defined, use that to display this object.
119 display_method = getattr(obj, '_ipython_display_', None)
120 if display_method is not None:
121 try:
122 display_method(**kwargs)
123 except NotImplementedError:
124 pass
125 else:
126 continue
127 if raw:
128 publish_display_data('display', obj, metadata)
129 else:
119 format_dict, md_dict = format(obj, include=include, exclude=exclude)
130 format_dict, md_dict = format(obj, include=include, exclude=exclude)
120 if metadata:
131 if metadata:
121 # kwarg-specified metadata gets precedence
132 # kwarg-specified metadata gets precedence
@@ -241,6 +241,14 b' class DisplayHook(Configurable):'
241 """
241 """
242 self.check_for_underscore()
242 self.check_for_underscore()
243 if result is not None and not self.quiet():
243 if result is not None and not self.quiet():
244 # If _ipython_display_ is defined, use that to display this object.
245 display_method = getattr(result, '_ipython_display_', None)
246 if display_method is not None:
247 try:
248 return display_method()
249 except NotImplementedError:
250 pass
251
244 self.start_displayhook()
252 self.start_displayhook()
245 self.write_output_prompt()
253 self.write_output_prompt()
246 format_dict, md_dict = self.compute_format_data(result)
254 format_dict, md_dict = self.compute_format_data(result)
@@ -101,3 +101,21 b''
101 -moz-box-pack: center;
101 -moz-box-pack: center;
102 box-pack: center;
102 box-pack: center;
103 }
103 }
104
105 .align-start {
106 -webkit-box-align: start;
107 -moz-box-align: start;
108 box-align: start;
109 }
110
111 .align-end {
112 -webkit-box-align: end;
113 -moz-box-align: end;
114 box-align: end;
115 }
116
117 .align-center {
118 -webkit-box-align: center;
119 -moz-box-align: center;
120 box-align: center;
121 }
@@ -105,6 +105,7 b' var IPython = (function (IPython) {'
105 }
105 }
106 };
106 };
107
107
108 CodeCell.msg_cells = {};
108
109
109 CodeCell.prototype = new IPython.Cell();
110 CodeCell.prototype = new IPython.Cell();
110
111
@@ -132,8 +133,28 b' var IPython = (function (IPython) {'
132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 inner_cell.append(input_area);
134 inner_cell.append(input_area);
134 input.append(prompt).append(inner_cell);
135 input.append(prompt).append(inner_cell);
136
137 var widget_area = $('<div/>')
138 .addClass('widget-area')
139 .hide();
140 this.widget_area = widget_area;
141 var widget_prompt = $('<div/>')
142 .addClass('prompt')
143 .appendTo(widget_area);
144 var widget_subarea = $('<div/>')
145 .addClass('widget-subarea')
146 .appendTo(widget_area);
147 this.widget_subarea = widget_subarea;
148 var widget_clear_buton = $('<button />')
149 .addClass('close')
150 .html('&times;')
151 .click(function() {
152 widget_area.slideUp('', function(){ widget_subarea.html(''); });
153 })
154 .appendTo(widget_prompt);
155
135 var output = $('<div></div>');
156 var output = $('<div></div>');
136 cell.append(input).append(output);
157 cell.append(input).append(widget_area).append(output);
137 this.element = cell;
158 this.element = cell;
138 this.output_area = new IPython.OutputArea(output, true);
159 this.output_area = new IPython.OutputArea(output, true);
139 this.completer = new IPython.Completer(this);
160 this.completer = new IPython.Completer(this);
@@ -283,6 +304,13 b' var IPython = (function (IPython) {'
283 */
304 */
284 CodeCell.prototype.execute = function () {
305 CodeCell.prototype.execute = function () {
285 this.output_area.clear_output();
306 this.output_area.clear_output();
307
308 // Clear widget area
309 this.widget_subarea.html('');
310 this.widget_subarea.height('');
311 this.widget_area.height('');
312 this.widget_area.hide();
313
286 this.set_input_prompt('*');
314 this.set_input_prompt('*');
287 this.element.addClass("running");
315 this.element.addClass("running");
288 if (this.last_msg_id) {
316 if (this.last_msg_id) {
@@ -290,7 +318,12 b' var IPython = (function (IPython) {'
290 }
318 }
291 var callbacks = this.get_callbacks();
319 var callbacks = this.get_callbacks();
292
320
321 var old_msg_id = this.last_msg_id;
293 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
322 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
323 if (old_msg_id) {
324 delete CodeCell.msg_cells[old_msg_id];
325 }
326 CodeCell.msg_cells[this.last_msg_id] = this;
294 };
327 };
295
328
296 /**
329 /**
@@ -14,7 +14,8 b''
14 // as injecting require.js make marked not to put itself in the globals,
14 // as injecting require.js make marked not to put itself in the globals,
15 // which make both this file fail at setting marked configuration, and textcell.js
15 // which make both this file fail at setting marked configuration, and textcell.js
16 // which search marked into global.
16 // which search marked into global.
17 require(['components/marked/lib/marked'],
17 require(['components/marked/lib/marked',
18 'notebook/js/widgets/init'],
18
19
19 function (marked) {
20 function (marked) {
20
21
@@ -301,6 +301,17 b' var IPython = (function (IPython) {'
301 };
301 };
302
302
303 /**
303 /**
304 * Try to get a particular cell by msg_id.
305 *
306 * @method get_msg_cell
307 * @param {String} msg_id A message UUID
308 * @return {Cell} Cell or null if no cell was found.
309 */
310 Notebook.prototype.get_msg_cell = function (msg_id) {
311 return IPython.CodeCell.msg_cells[msg_id] || null;
312 };
313
314 /**
304 * Count the cells in this notebook.
315 * Count the cells in this notebook.
305 *
316 *
306 * @method ncells
317 * @method ncells
@@ -1295,7 +1306,8 b' var IPython = (function (IPython) {'
1295
1306
1296
1307
1297 /**
1308 /**
1298 * Once a session is started, link the code cells to the kernel
1309 * Once a session is started, link the code cells to the kernel and pass the
1310 * comm manager to the widget manager
1299 *
1311 *
1300 */
1312 */
1301 Notebook.prototype._session_started = function(){
1313 Notebook.prototype._session_started = function(){
@@ -11,5 +11,3 b''
11 @import "savewidget.less";
11 @import "savewidget.less";
12 @import "toolbar.less";
12 @import "toolbar.less";
13 @import "tooltip.less";
13 @import "tooltip.less";
14
15
@@ -7,3 +7,4 b''
7 @import "outputarea.less";
7 @import "outputarea.less";
8 @import "renderedhtml.less";
8 @import "renderedhtml.less";
9 @import "textcell.less";
9 @import "textcell.less";
10 @import "widgets.less";
@@ -86,7 +86,7 b' var IPython = (function (IPython) {'
86 try {
86 try {
87 f(comm, msg);
87 f(comm, msg);
88 } catch (e) {
88 } catch (e) {
89 console.log("Exception opening new comm:", e, msg);
89 console.log("Exception opening new comm:", e, e.stack, msg);
90 comm.close();
90 comm.close();
91 this.unregister_comm(comm);
91 this.unregister_comm(comm);
92 }
92 }
@@ -102,7 +102,7 b' var IPython = (function (IPython) {'
102 try {
102 try {
103 comm.handle_close(msg);
103 comm.handle_close(msg);
104 } catch (e) {
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
@@ -115,7 +115,7 b' var IPython = (function (IPython) {'
115 try {
115 try {
116 comm.handle_msg(msg);
116 comm.handle_msg(msg);
117 } catch (e) {
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
@@ -176,7 +176,7 b' var IPython = (function (IPython) {'
176 try {
176 try {
177 callback(msg);
177 callback(msg);
178 } catch (e) {
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 };
@@ -47,6 +47,7 b' var IPython = (function (IPython) {'
47 this.bind_events();
47 this.bind_events();
48 this.init_iopub_handlers();
48 this.init_iopub_handlers();
49 this.comm_manager = new IPython.CommManager(this);
49 this.comm_manager = new IPython.CommManager(this);
50 this.widget_manager = new IPython.WidgetManager(this.comm_manager);
50 };
51 };
51
52
52
53
@@ -507,7 +508,7 b' var IPython = (function (IPython) {'
507 try {
508 try {
508 callbacks.iopub.status(msg);
509 callbacks.iopub.status(msg);
509 } catch (e) {
510 } catch (e) {
510 console.log("Exception in status msg handler", e);
511 console.log("Exception in status msg handler", e, e.stack);
511 }
512 }
512 }
513 }
513
514
@@ -18,6 +18,9 b''
18 .start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;}
18 .start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;}
19 .end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;}
19 .end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;}
20 .center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;}
20 .center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;}
21 .align-start{-webkit-box-align:start;-moz-box-align:start;box-align:start;}
22 .align-end{-webkit-box-align:end;-moz-box-align:end;box-align:end;}
23 .align-center{-webkit-box-align:center;-moz-box-align:center;box-align:center;}
21 div.error{margin:2em;text-align:center;}
24 div.error{margin:2em;text-align:center;}
22 div.error>h1{font-size:500%;line-height:normal;}
25 div.error>h1{font-size:500%;line-height:normal;}
23 div.error>p{font-size:200%;line-height:normal;}
26 div.error>p{font-size:200%;line-height:normal;}
@@ -152,3 +155,22 b' div.text_cell_input{color:#000000;border:1px solid #cfcfcf;border-radius:4px;bac'
152 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:5px;color:#000000;}
155 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:5px;color:#000000;}
153 a.anchor-link:link{text-decoration:none;padding:0px 20px;visibility:hidden;}
156 a.anchor-link:link{text-decoration:none;padding:0px 20px;visibility:hidden;}
154 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible;}
157 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible;}
158 .widget-area{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;}.widget-area .widget-subarea{padding:0.44em 0.4em 0.4em 1px;margin-left:6px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;-webkit-box-flex:2;-moz-box-flex:2;box-flex:2;}
159 .widget-hlabel{min-width:10ex;padding-right:8px;padding-top:3px;text-align:right;vertical-align:text-top;}
160 .widget-vlabel{padding-bottom:5px;text-align:center;vertical-align:text-bottom;}
161 .slide-track{border:1px solid #CCCCCC;background:#FFFFFF;border-radius:4px;}
162 .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:348px;height:5px;max-height:5px;margin-top:11px;border:1px solid #CCCCCC;background:#FFFFFF;border-radius:4px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;}.widget-hslider .ui-slider{border:0px !important;background:none !important;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important;}
163 .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #CCCCCC;background:#FFFFFF;border-radius:4px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;}.widget-vslider .ui-slider{border:0px !important;background:none !important;margin-left:-4px;margin-top:5px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px;}
164 .widget-text{width:350px;margin-bottom:0px;}
165 .widget-listbox{width:364px;margin-bottom:0px;}
166 .widget-numeric-text{width:150px;}
167 .widget-progress{width:363px;}.widget-progress .bar{-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}
168 .widget-combo-btn{min-width:138px;}
169 .widget-box{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;}
170 .widget-hbox{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;}
171 .widget-hbox-single{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;height:30px;}
172 .widget-vbox-single{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;width:30px;}
173 .widget-modal{overflow:hidden;position:absolute !important;top:0px;left:0px;margin-left:0px !important;}
174 .widget-modal-body{max-height:none !important;}
175 .widget-container{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;}
176 .docked-widget-modal{overflow:hidden;position:relative !important;top:0px !important;left:0px !important;margin-left:0px !important;}
@@ -1385,6 +1385,9 b' ul.icons-ul{list-style-type:none;text-indent:-0.7142857142857143em;margin-left:2'
1385 .start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;}
1385 .start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;}
1386 .end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;}
1386 .end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;}
1387 .center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;}
1387 .center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;}
1388 .align-start{-webkit-box-align:start;-moz-box-align:start;box-align:start;}
1389 .align-end{-webkit-box-align:end;-moz-box-align:end;box-align:end;}
1390 .align-center{-webkit-box-align:center;-moz-box-align:center;box-align:center;}
1388 div.error{margin:2em;text-align:center;}
1391 div.error{margin:2em;text-align:center;}
1389 div.error>h1{font-size:500%;line-height:normal;}
1392 div.error>h1{font-size:500%;line-height:normal;}
1390 div.error>p{font-size:200%;line-height:normal;}
1393 div.error>p{font-size:200%;line-height:normal;}
@@ -1533,6 +1536,25 b' div.text_cell_input{color:#000000;border:1px solid #cfcfcf;border-radius:4px;bac'
1533 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:5px;color:#000000;}
1536 div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:5px;color:#000000;}
1534 a.anchor-link:link{text-decoration:none;padding:0px 20px;visibility:hidden;}
1537 a.anchor-link:link{text-decoration:none;padding:0px 20px;visibility:hidden;}
1535 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible;}
1538 h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible;}
1539 .widget-area{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;}.widget-area .widget-subarea{padding:0.44em 0.4em 0.4em 1px;margin-left:6px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;-webkit-box-flex:2;-moz-box-flex:2;box-flex:2;}
1540 .widget-hlabel{min-width:10ex;padding-right:8px;padding-top:3px;text-align:right;vertical-align:text-top;}
1541 .widget-vlabel{padding-bottom:5px;text-align:center;vertical-align:text-bottom;}
1542 .slide-track{border:1px solid #CCCCCC;background:#FFFFFF;border-radius:4px;}
1543 .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:348px;height:5px;max-height:5px;margin-top:11px;border:1px solid #CCCCCC;background:#FFFFFF;border-radius:4px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;}.widget-hslider .ui-slider{border:0px !important;background:none !important;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important;}
1544 .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #CCCCCC;background:#FFFFFF;border-radius:4px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;}.widget-vslider .ui-slider{border:0px !important;background:none !important;margin-left:-4px;margin-top:5px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px;}
1545 .widget-text{width:350px;margin-bottom:0px;}
1546 .widget-listbox{width:364px;margin-bottom:0px;}
1547 .widget-numeric-text{width:150px;}
1548 .widget-progress{width:363px;}.widget-progress .bar{-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}
1549 .widget-combo-btn{min-width:138px;}
1550 .widget-box{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;}
1551 .widget-hbox{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;}
1552 .widget-hbox-single{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;height:30px;}
1553 .widget-vbox-single{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:5px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;width:30px;}
1554 .widget-modal{overflow:hidden;position:absolute !important;top:0px;left:0px;margin-left:0px !important;}
1555 .widget-modal-body{max-height:none !important;}
1556 .widget-container{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;}
1557 .docked-widget-modal{overflow:hidden;position:relative !important;top:0px !important;left:0px !important;margin-left:0px !important;}
1536 body{background-color:#ffffff;}
1558 body{background-color:#ffffff;}
1537 body.notebook_app{overflow:hidden;}
1559 body.notebook_app{overflow:hidden;}
1538 span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%;}
1560 span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%;}
@@ -21,7 +21,18 b''
21 require.config({
21 require.config({
22 baseUrl: '{{static_url("")}}',
22 baseUrl: '{{static_url("")}}',
23 paths: {
23 paths: {
24 nbextensions : '{{ base_project_url }}nbextensions'
24 nbextensions : '{{ base_project_url }}nbextensions',
25 underscore : '{{static_url("components/underscore/underscore-min")}}',
26 backbone : '{{static_url("components/backbone/backbone-min")}}',
27 },
28 shim: {
29 underscore: {
30 exports: '_'
31 },
32 backbone: {
33 deps: ["underscore", "jquery"],
34 exports: "Backbone"
35 }
25 }
36 }
26 });
37 });
27 </script>
38 </script>
@@ -59,19 +59,17 b' casper.delete_current_notebook = function () {'
59
59
60 // wait for output in a given cell
60 // wait for output in a given cell
61 casper.wait_for_output = function (cell_num) {
61 casper.wait_for_output = function (cell_num) {
62 this.then(function() {
62 this.waitFor(function (c) {
63 this.waitFor(function (c) {
63 return this.evaluate(function get_output(c) {
64 return this.evaluate(function get_output(c) {
64 var cell = IPython.notebook.get_cell(c);
65 var cell = IPython.notebook.get_cell(c);
65 return cell.output_area.outputs.length != 0;
66 return cell.output_area.outputs.length != 0;
67 },
68 // pass parameter from the test suite js to the browser code js
69 {c : cell_num});
70 },
66 },
71 function then() { },
67 // pass parameter from the test suite js to the browser code js
72 function timeout() {
68 {c : cell_num});
73 this.echo("wait_for_output timedout!");
69 },
74 });
70 function then() { },
71 function timeout() {
72 this.echo("wait_for_output timed out!");
75 });
73 });
76 };
74 };
77
75
@@ -8546,13 +8546,104 b''
8546 ],
8546 ],
8547 "metadata": {},
8547 "metadata": {},
8548 "output_type": "pyout",
8548 "output_type": "pyout",
8549 "prompt_number": 16,
8549 "prompt_number": 17,
8550 "text": [
8550 "text": [
8551 "Polynomial([-20., 71., -15., 1.], [-1., 1.], [-1., 1.])"
8551 "Polynomial([-20., 71., -15., 1.], [-1., 1.], [-1., 1.])"
8552 ]
8552 ]
8553 }
8553 }
8554 ],
8554 ],
8555 "prompt_number": 16
8555 "prompt_number": 17
8556 },
8557 {
8558 "cell_type": "heading",
8559 "level": 2,
8560 "metadata": {},
8561 "source": [
8562 "More complex display with `_ipython_display_`"
8563 ]
8564 },
8565 {
8566 "cell_type": "markdown",
8567 "metadata": {},
8568 "source": [
8569 "Rich reprs can only display one object or mime-type at a time.\n",
8570 "Sometimes this is not enough,\n",
8571 "because you need to get javascript on the page, or you want LaTeX and a PNG.\n",
8572 "This can be done with multiple calls to display easily enough,\n",
8573 "but then users need to know more than they should.\n",
8574 "\n",
8575 "In **IPython 2.0**, we added a special `_ipython_display_` method,\n",
8576 "that allows your objects to take control of displaying.\n",
8577 "If this method is defined, IPython will call it, and make no effort to display the object itself.\n",
8578 "It's a way for you to say \"Back off, IPython, I got this.\""
8579 ]
8580 },
8581 {
8582 "cell_type": "code",
8583 "collapsed": false,
8584 "input": [
8585 "import json\n",
8586 "import uuid\n",
8587 "from IPython.display import display_javascript, display_html, display\n",
8588 "\n",
8589 "class FlotPlot(object):\n",
8590 " def __init__(self, x, y):\n",
8591 " self.x = x\n",
8592 " self.y = y\n",
8593 " self.uuid = str(uuid.uuid4())\n",
8594 " \n",
8595 " def _ipython_display_(self):\n",
8596 " json_data = json.dumps(zip(self.x, self.y))\n",
8597 " display_html('<div id=\"{}\" style=\"height: 300px; width:80%;\"></div>'.format(self.uuid),\n",
8598 " raw=True\n",
8599 " )\n",
8600 " display_javascript(\"\"\"\n",
8601 " require([\"//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js\"], function() {\n",
8602 " var line = JSON.parse(\"%s\");\n",
8603 " console.log(line);\n",
8604 " $.plot(\"#%s\", [line]);\n",
8605 " });\n",
8606 " \"\"\" % (json_data, self.uuid), raw=True)\n"
8607 ],
8608 "language": "python",
8609 "metadata": {},
8610 "outputs": [],
8611 "prompt_number": 17
8612 },
8613 {
8614 "cell_type": "code",
8615 "collapsed": false,
8616 "input": [
8617 "import numpy as np\n",
8618 "x = np.linspace(0,10)\n",
8619 "y = np.sin(x)\n",
8620 "FlotPlot(x, np.sin(x))"
8621 ],
8622 "language": "python",
8623 "metadata": {},
8624 "outputs": [
8625 {
8626 "html": [
8627 "<div id=\"6cc58a29-42e4-43ff-9204-1043632e90f4\" style=\"height: 300px; width:80%;\"></div>"
8628 ],
8629 "metadata": {},
8630 "output_type": "display_data"
8631 },
8632 {
8633 "javascript": [
8634 "\n",
8635 " require([\"//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js\"], function() {\n",
8636 " var line = JSON.parse(\"[[0.0, 0.0], [0.20408163265306123, 0.20266793654820095], [0.40816326530612246, 0.39692414892492234], [0.61224489795918369, 0.57470604121617908], [0.81632653061224492, 0.72863478346935029], [1.0204081632653061, 0.85232156971961837], [1.2244897959183674, 0.94063278511248671], [1.4285714285714286, 0.98990307637212394], [1.6326530612244898, 0.99808748213471832], [1.8367346938775511, 0.96484630898376322], [2.0408163265306123, 0.89155923041100371], [2.2448979591836737, 0.7812680235262639], [2.4489795918367347, 0.63855032022660208], [2.6530612244897958, 0.46932961277720098], [2.8571428571428572, 0.28062939951435684], [3.0612244897959187, 0.080281674842813497], [3.2653061224489797, -0.12339813736217871], [3.4693877551020407, -0.32195631507261868], [3.6734693877551021, -0.50715170948451438], [3.8775510204081636, -0.67129779355193209], [4.0816326530612246, -0.80758169096833643], [4.2857142857142856, -0.91034694431078278], [4.4897959183673475, -0.97532828606704558], [4.6938775510204085, -0.99982866838408957], [4.8979591836734695, -0.98283120392563061], [5.1020408163265305, -0.92504137173820289], [5.3061224489795915, -0.82885773637304272], [5.5102040816326534, -0.69827239556539955], [5.7142857142857144, -0.53870528838615628], [5.9183673469387754, -0.35677924089893803], [6.1224489795918373, -0.16004508604325057], [6.3265306122448983, 0.043331733368683463], [6.5306122448979593, 0.24491007101197931], [6.7346938775510203, 0.43632342647181932], [6.9387755102040813, 0.6096271964908323], [7.1428571428571432, 0.75762841539272019], [7.3469387755102042, 0.87418429881973347], [7.5510204081632653, 0.95445719973875187], [7.7551020408163271, 0.99511539477766364], [7.9591836734693882, 0.99447136726361685], [8.1632653061224492, 0.95255184753146038], [8.3673469387755102, 0.87109670348232071], [8.5714285714285712, 0.75348672743963763], [8.7755102040816322, 0.60460331650615429], [8.979591836734695, 0.43062587038273736], [9.183673469387756, 0.23877531564403087], [9.387755102040817, 0.037014401485062368], [9.591836734693878, -0.16628279384875641], [9.795918367346939, -0.36267842882654883], [10.0, -0.54402111088936989]]\");\n",
8637 " console.log(line);\n",
8638 " $.plot(\"#6cc58a29-42e4-43ff-9204-1043632e90f4\", [line]);\n",
8639 " });\n",
8640 " "
8641 ],
8642 "metadata": {},
8643 "output_type": "display_data"
8644 }
8645 ],
8646 "prompt_number": 19
8556 }
8647 }
8557 ],
8648 ],
8558 "metadata": {}
8649 "metadata": {}
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now