##// END OF EJS Templates
Renamed widgets......
Jonathan Frederic -
Show More
@@ -1,125 +1,125 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // BoolWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18 18
19 var CheckBoxView = IPython.DOMWidgetView.extend({
19 var CheckboxView = IPython.DOMWidgetView.extend({
20 20 render : function(){
21 21 // Called when view is rendered.
22 22 this.$el
23 23 .addClass('widget-hbox-single');
24 24 this.$label = $('<div />')
25 25 .addClass('widget-hlabel')
26 26 .appendTo(this.$el)
27 27 .hide();
28 28 this.$checkbox = $('<input />')
29 29 .attr('type', 'checkbox')
30 30 .appendTo(this.$el)
31 31 .click($.proxy(this.handle_click, this));
32 32
33 33 this.$el_to_style = this.$checkbox; // Set default element to style
34 34 this.update(); // Set defaults.
35 35 },
36 36
37 37 handle_click: function() {
38 38 // Handles when the checkbox is clicked.
39 39
40 40 // Calling model.set will trigger all of the other views of the
41 41 // model to update.
42 42 var value = this.model.get('value');
43 43 this.model.set('value', ! value, {updated_view: this});
44 44 this.touch();
45 45 },
46 46
47 47 update : function(options){
48 48 // Update the contents of this view
49 49 //
50 50 // Called when the model is changed. The model may have been
51 51 // changed by another view or by a state update from the back-end.
52 52 this.$checkbox.prop('checked', this.model.get('value'));
53 53
54 54 if (options === undefined || options.updated_view != this) {
55 55 var disabled = this.model.get('disabled');
56 56 this.$checkbox.prop('disabled', disabled);
57 57
58 58 var description = this.model.get('description');
59 59 if (description.length === 0) {
60 60 this.$label.hide();
61 61 } else {
62 62 this.$label.text(description);
63 63 this.$label.show();
64 64 }
65 65 }
66 return CheckBoxView.__super__.update.apply(this);
66 return CheckboxView.__super__.update.apply(this);
67 67 },
68 68
69 69 });
70 WidgetManager.register_widget_view('CheckBoxView', CheckBoxView);
70 WidgetManager.register_widget_view('CheckboxView', CheckboxView);
71 71
72 72
73 73 var ToggleButtonView = IPython.DOMWidgetView.extend({
74 74 render : function() {
75 75 // Called when view is rendered.
76 76 var that = this;
77 77 this.setElement($('<button />')
78 78 .addClass('btn')
79 79 .attr('type', 'button')
80 80 .on('click', function (e) {
81 81 e.preventDefault();
82 82 that.handle_click();
83 83 }));
84 84
85 85 this.update(); // Set defaults.
86 86 },
87 87
88 88 update : function(options){
89 89 // Update the contents of this view
90 90 //
91 91 // Called when the model is changed. The model may have been
92 92 // changed by another view or by a state update from the back-end.
93 93 if (this.model.get('value')) {
94 94 this.$el.addClass('active');
95 95 } else {
96 96 this.$el.removeClass('active');
97 97 }
98 98
99 99 if (options === undefined || options.updated_view != this) {
100 100
101 101 var disabled = this.model.get('disabled');
102 102 this.$el.prop('disabled', disabled);
103 103
104 104 var description = this.model.get('description');
105 105 if (description.length === 0) {
106 106 this.$el.text(' '); // Preserve button height
107 107 } else {
108 108 this.$el.text(description);
109 109 }
110 110 }
111 111 return ToggleButtonView.__super__.update.apply(this);
112 112 },
113 113
114 114 handle_click: function(e) {
115 115 // Handles and validates user input.
116 116
117 117 // Calling model.set will trigger all of the other views of the
118 118 // model to update.
119 119 var value = this.model.get('value');
120 120 this.model.set('value', ! value, {updated_view: this});
121 121 this.touch();
122 122 },
123 123 });
124 124 WidgetManager.register_widget_view('ToggleButtonView', ToggleButtonView);
125 125 });
@@ -1,376 +1,376 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // SelectionWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18 18
19 19 var DropdownView = IPython.DOMWidgetView.extend({
20 20 render : function(){
21 21 // Called when view is rendered.
22 22 this.$el
23 23 .addClass('widget-hbox-single');
24 24 this.$label = $('<div />')
25 25 .appendTo(this.$el)
26 26 .addClass('widget-hlabel')
27 27 .hide();
28 28 this.$buttongroup = $('<div />')
29 29 .addClass('widget_item')
30 30 .addClass('btn-group')
31 31 .appendTo(this.$el);
32 32 this.$el_to_style = this.$buttongroup; // Set default element to style
33 33 this.$droplabel = $('<button />')
34 34 .addClass('btn')
35 35 .addClass('widget-combo-btn')
36 36 .text(' ')
37 37 .appendTo(this.$buttongroup);
38 38 this.$dropbutton = $('<button />')
39 39 .addClass('btn')
40 40 .addClass('dropdown-toggle')
41 41 .addClass('widget-combo-carrot-btn')
42 42 .attr('data-toggle', 'dropdown')
43 43 .append($('<span />').addClass("caret"))
44 44 .appendTo(this.$buttongroup);
45 45 this.$droplist = $('<ul />')
46 46 .addClass('dropdown-menu')
47 47 .appendTo(this.$buttongroup);
48 48
49 49 // Set defaults.
50 50 this.update();
51 51 },
52 52
53 53 update : function(options){
54 54 // Update the contents of this view
55 55 //
56 56 // Called when the model is changed. The model may have been
57 57 // changed by another view or by a state update from the back-end.
58 58
59 59 if (options === undefined || options.updated_view != this) {
60 60 var selected_item_text = this.model.get('_value');
61 61 if (selected_item_text.length === 0) {
62 62 this.$droplabel.text(' ');
63 63 } else {
64 64 this.$droplabel.text(selected_item_text);
65 65 }
66 66
67 67 var items = this.model.get('labels');
68 68 var $replace_droplist = $('<ul />')
69 69 .addClass('dropdown-menu');
70 70 var that = this;
71 71 _.each(items, function(item, i) {
72 72 var item_button = $('<a href="#"/>')
73 73 .text(item)
74 74 .on('click', $.proxy(that.handle_click, that));
75 75 $replace_droplist.append($('<li />').append(item_button));
76 76 });
77 77
78 78 this.$droplist.replaceWith($replace_droplist);
79 79 this.$droplist.remove();
80 80 this.$droplist = $replace_droplist;
81 81
82 82 if (this.model.get('disabled')) {
83 83 this.$buttongroup.attr('disabled','disabled');
84 84 this.$droplabel.attr('disabled','disabled');
85 85 this.$dropbutton.attr('disabled','disabled');
86 86 this.$droplist.attr('disabled','disabled');
87 87 } else {
88 88 this.$buttongroup.removeAttr('disabled');
89 89 this.$droplabel.removeAttr('disabled');
90 90 this.$dropbutton.removeAttr('disabled');
91 91 this.$droplist.removeAttr('disabled');
92 92 }
93 93
94 94 var description = this.model.get('description');
95 95 if (description.length === 0) {
96 96 this.$label.hide();
97 97 } else {
98 98 this.$label.text(description);
99 99 this.$label.show();
100 100 }
101 101 }
102 102 return DropdownView.__super__.update.apply(this);
103 103 },
104 104
105 105 handle_click: function (e) {
106 106 // Handle when a value is clicked.
107 107
108 108 // Calling model.set will trigger all of the other views of the
109 109 // model to update.
110 110 this.model.set('_value', $(e.target).text(), {updated_view: this});
111 111 this.touch();
112 112 },
113 113
114 114 });
115 115 WidgetManager.register_widget_view('DropdownView', DropdownView);
116 116
117 117
118 118 var RadioButtonsView = IPython.DOMWidgetView.extend({
119 119 render : function(){
120 120 // Called when view is rendered.
121 121 this.$el
122 122 .addClass('widget-hbox');
123 123 this.$label = $('<div />')
124 124 .appendTo(this.$el)
125 125 .addClass('widget-hlabel')
126 126 .hide();
127 127 this.$container = $('<div />')
128 128 .appendTo(this.$el)
129 129 .addClass('widget-container')
130 130 .addClass('vbox');
131 131 this.$el_to_style = this.$container; // Set default element to style
132 132 this.update();
133 133 },
134 134
135 135 update : function(options){
136 136 // Update the contents of this view
137 137 //
138 138 // Called when the model is changed. The model may have been
139 139 // changed by another view or by a state update from the back-end.
140 140 if (options === undefined || options.updated_view != this) {
141 141 // Add missing items to the DOM.
142 142 var items = this.model.get('labels');
143 143 var disabled = this.model.get('disabled');
144 144 var that = this;
145 145 _.each(items, function(item, index) {
146 146 var item_query = ' :input[value="' + item + '"]';
147 147 if (that.$el.find(item_query).length === 0) {
148 148 var $label = $('<label />')
149 149 .addClass('radio')
150 150 .text(item)
151 151 .appendTo(that.$container);
152 152
153 153 $('<input />')
154 154 .attr('type', 'radio')
155 155 .addClass(that.model)
156 156 .val(item)
157 157 .prependTo($label)
158 158 .on('click', $.proxy(that.handle_click, that));
159 159 }
160 160
161 161 var $item_element = that.$container.find(item_query);
162 162 if (that.model.get('_value') == item) {
163 163 $item_element.prop('checked', true);
164 164 } else {
165 165 $item_element.prop('checked', false);
166 166 }
167 167 $item_element.prop('disabled', disabled);
168 168 });
169 169
170 170 // Remove items that no longer exist.
171 171 this.$container.find('input').each(function(i, obj) {
172 172 var value = $(obj).val();
173 173 var found = false;
174 174 _.each(items, function(item, index) {
175 175 if (item == value) {
176 176 found = true;
177 177 return false;
178 178 }
179 179 });
180 180
181 181 if (!found) {
182 182 $(obj).parent().remove();
183 183 }
184 184 });
185 185
186 186 var description = this.model.get('description');
187 187 if (description.length === 0) {
188 188 this.$label.hide();
189 189 } else {
190 190 this.$label.text(description);
191 191 this.$label.show();
192 192 }
193 193 }
194 194 return RadioButtonsView.__super__.update.apply(this);
195 195 },
196 196
197 197 handle_click: function (e) {
198 198 // Handle when a value is clicked.
199 199
200 200 // Calling model.set will trigger all of the other views of the
201 201 // model to update.
202 202 this.model.set('_value', $(e.target).val(), {updated_view: this});
203 203 this.touch();
204 204 },
205 205 });
206 206 WidgetManager.register_widget_view('RadioButtonsView', RadioButtonsView);
207 207
208 208
209 209 var ToggleButtonsView = IPython.DOMWidgetView.extend({
210 210 render : function(){
211 211 // Called when view is rendered.
212 212 this.$el
213 213 .addClass('widget-hbox-single');
214 214 this.$label = $('<div />')
215 215 .appendTo(this.$el)
216 216 .addClass('widget-hlabel')
217 217 .hide();
218 218 this.$buttongroup = $('<div />')
219 219 .addClass('btn-group')
220 220 .attr('data-toggle', 'buttons-radio')
221 221 .appendTo(this.$el);
222 222 this.$el_to_style = this.$buttongroup; // Set default element to style
223 223 this.update();
224 224 },
225 225
226 226 update : function(options){
227 227 // Update the contents of this view
228 228 //
229 229 // Called when the model is changed. The model may have been
230 230 // changed by another view or by a state update from the back-end.
231 231 if (options === undefined || options.updated_view != this) {
232 232 // Add missing items to the DOM.
233 233 var items = this.model.get('labels');
234 234 var disabled = this.model.get('disabled');
235 235 var that = this;
236 236 _.each(items, function(item, index) {
237 237 var item_query = ' :contains("' + item + '")';
238 238 if (that.$buttongroup.find(item_query).length === 0) {
239 239 $('<button />')
240 240 .attr('type', 'button')
241 241 .addClass('btn')
242 242 .text(item)
243 243 .appendTo(that.$buttongroup)
244 244 .on('click', $.proxy(that.handle_click, that));
245 245 }
246 246
247 247 var $item_element = that.$buttongroup.find(item_query);
248 248 if (that.model.get('_value') == item) {
249 249 $item_element.addClass('active');
250 250 } else {
251 251 $item_element.removeClass('active');
252 252 }
253 253 $item_element.prop('disabled', disabled);
254 254 });
255 255
256 256 // Remove items that no longer exist.
257 257 this.$buttongroup.find('button').each(function(i, obj) {
258 258 var value = $(obj).text();
259 259 var found = false;
260 260 _.each(items, function(item, index) {
261 261 if (item == value) {
262 262 found = true;
263 263 return false;
264 264 }
265 265 });
266 266
267 267 if (!found) {
268 268 $(obj).remove();
269 269 }
270 270 });
271 271
272 272 var description = this.model.get('description');
273 273 if (description.length === 0) {
274 274 this.$label.hide();
275 275 } else {
276 276 this.$label.text(description);
277 277 this.$label.show();
278 278 }
279 279 }
280 280 return ToggleButtonsView.__super__.update.apply(this);
281 281 },
282 282
283 283 handle_click: function (e) {
284 284 // Handle when a value is clicked.
285 285
286 286 // Calling model.set will trigger all of the other views of the
287 287 // model to update.
288 288 this.model.set('_value', $(e.target).text(), {updated_view: this});
289 289 this.touch();
290 290 },
291 291 });
292 292 WidgetManager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
293 293
294 294
295 var ListBoxView = IPython.DOMWidgetView.extend({
295 var SelectView = IPython.DOMWidgetView.extend({
296 296 render : function(){
297 297 // Called when view is rendered.
298 298 this.$el
299 299 .addClass('widget-hbox');
300 300 this.$label = $('<div />')
301 301 .appendTo(this.$el)
302 302 .addClass('widget-hlabel')
303 303 .hide();
304 304 this.$listbox = $('<select />')
305 305 .addClass('widget-listbox')
306 306 .attr('size', 6)
307 307 .appendTo(this.$el);
308 308 this.$el_to_style = this.$listbox; // Set default element to style
309 309 this.update();
310 310 },
311 311
312 312 update : function(options){
313 313 // Update the contents of this view
314 314 //
315 315 // Called when the model is changed. The model may have been
316 316 // changed by another view or by a state update from the back-end.
317 317 if (options === undefined || options.updated_view != this) {
318 318 // Add missing items to the DOM.
319 319 var items = this.model.get('labels');
320 320 var that = this;
321 321 _.each(items, function(item, index) {
322 322 var item_query = ' :contains("' + item + '")';
323 323 if (that.$listbox.find(item_query).length === 0) {
324 324 $('<option />')
325 325 .text(item)
326 326 .attr('_value', item)
327 327 .appendTo(that.$listbox)
328 328 .on('click', $.proxy(that.handle_click, that));
329 329 }
330 330 });
331 331
332 332 // Select the correct element
333 333 this.$listbox.val(this.model.get('_value'));
334 334
335 335 // Disable listbox if needed
336 336 var disabled = this.model.get('disabled');
337 337 this.$listbox.prop('disabled', disabled);
338 338
339 339 // Remove items that no longer exist.
340 340 this.$listbox.find('option').each(function(i, obj) {
341 341 var value = $(obj).text();
342 342 var found = false;
343 343 _.each(items, function(item, index) {
344 344 if (item == value) {
345 345 found = true;
346 346 return false;
347 347 }
348 348 });
349 349
350 350 if (!found) {
351 351 $(obj).remove();
352 352 }
353 353 });
354 354
355 355 var description = this.model.get('description');
356 356 if (description.length === 0) {
357 357 this.$label.hide();
358 358 } else {
359 359 this.$label.text(description);
360 360 this.$label.show();
361 361 }
362 362 }
363 return ListBoxView.__super__.update.apply(this);
363 return SelectView.__super__.update.apply(this);
364 364 },
365 365
366 366 handle_click: function (e) {
367 367 // Handle when a value is clicked.
368 368
369 369 // Calling model.set will trigger all of the other views of the
370 370 // model to update.
371 371 this.model.set('_value', $(e.target).text(), {updated_view: this});
372 372 this.touch();
373 373 },
374 374 });
375 WidgetManager.register_widget_view('ListBoxView', ListBoxView);
375 WidgetManager.register_widget_view('SelectView', SelectView);
376 376 });
@@ -1,222 +1,222 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // StringWidget
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18 18
19 19 var HTMLView = IPython.DOMWidgetView.extend({
20 20 render : function(){
21 21 // Called when view is rendered.
22 22 this.update(); // Set defaults.
23 23 },
24 24
25 25 update : function(){
26 26 // Update the contents of this view
27 27 //
28 28 // Called when the model is changed. The model may have been
29 29 // changed by another view or by a state update from the back-end.
30 30 this.$el.html(this.model.get('value')); // CAUTION! .html(...) CALL MANDITORY!!!
31 31 return HTMLView.__super__.update.apply(this);
32 32 },
33 33 });
34 34 WidgetManager.register_widget_view('HTMLView', HTMLView);
35 35
36 36
37 37 var LatexView = IPython.DOMWidgetView.extend({
38 38 render : function(){
39 39 // Called when view is rendered.
40 40 this.update(); // Set defaults.
41 41 },
42 42
43 43 update : function(){
44 44 // Update the contents of this view
45 45 //
46 46 // Called when the model is changed. The model may have been
47 47 // changed by another view or by a state update from the back-end.
48 48 this.$el.text(this.model.get('value'));
49 49 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$el.get(0)]);
50 50
51 51 return LatexView.__super__.update.apply(this);
52 52 },
53 53 });
54 54 WidgetManager.register_widget_view('LatexView', LatexView);
55 55
56 56
57 var TextAreaView = IPython.DOMWidgetView.extend({
57 var TextareaView = IPython.DOMWidgetView.extend({
58 58 render: function(){
59 59 // Called when view is rendered.
60 60 this.$el
61 61 .addClass('widget-hbox');
62 62 this.$label = $('<div />')
63 63 .appendTo(this.$el)
64 64 .addClass('widget-hlabel')
65 65 .hide();
66 66 this.$textbox = $('<textarea />')
67 67 .attr('rows', 5)
68 68 .addClass('widget-text')
69 69 .appendTo(this.$el);
70 70 this.$el_to_style = this.$textbox; // Set default element to style
71 71 this.update(); // Set defaults.
72 72
73 73 this.model.on('msg:custom', $.proxy(this._handle_textarea_msg, this));
74 74 },
75 75
76 76 _handle_textarea_msg: function (content){
77 77 // Handle when a custom msg is recieved from the back-end.
78 78 if (content.method == "scroll_to_bottom") {
79 79 this.scroll_to_bottom();
80 80 }
81 81 },
82 82
83 83 scroll_to_bottom: function (){
84 84 // Scroll the text-area view to the bottom.
85 85 this.$textbox.scrollTop(this.$textbox[0].scrollHeight);
86 86 },
87 87
88 88 update: function(options){
89 89 // Update the contents of this view
90 90 //
91 91 // Called when the model is changed. The model may have been
92 92 // changed by another view or by a state update from the back-end.
93 93 if (options === undefined || options.updated_view != this) {
94 94 this.$textbox.val(this.model.get('value'));
95 95
96 96 var disabled = this.model.get('disabled');
97 97 this.$textbox.prop('disabled', disabled);
98 98
99 99 var description = this.model.get('description');
100 100 if (description.length === 0) {
101 101 this.$label.hide();
102 102 } else {
103 103 this.$label.text(description);
104 104 this.$label.show();
105 105 }
106 106 }
107 return TextAreaView.__super__.update.apply(this);
107 return TextareaView.__super__.update.apply(this);
108 108 },
109 109
110 110 events: {
111 111 // Dictionary of events and their handlers.
112 112 "keyup textarea" : "handleChanging",
113 113 "paste textarea" : "handleChanging",
114 114 "cut textarea" : "handleChanging"
115 115 },
116 116
117 117 handleChanging: function(e) {
118 118 // Handles and validates user input.
119 119
120 120 // Calling model.set will trigger all of the other views of the
121 121 // model to update.
122 122 this.model.set('value', e.target.value, {updated_view: this});
123 123 this.touch();
124 124 },
125 125 });
126 WidgetManager.register_widget_view('TextAreaView', TextAreaView);
126 WidgetManager.register_widget_view('TextareaView', TextareaView);
127 127
128 128
129 var TextBoxView = IPython.DOMWidgetView.extend({
129 var TextView = IPython.DOMWidgetView.extend({
130 130 render: function(){
131 131 // Called when view is rendered.
132 132 this.$el
133 133 .addClass('widget-hbox-single');
134 134 this.$label = $('<div />')
135 135 .addClass('widget-hlabel')
136 136 .appendTo(this.$el)
137 137 .hide();
138 138 this.$textbox = $('<input type="text" />')
139 139 .addClass('input')
140 140 .addClass('widget-text')
141 141 .appendTo(this.$el);
142 142 this.$el_to_style = this.$textbox; // Set default element to style
143 143 this.update(); // Set defaults.
144 144 },
145 145
146 146 update: function(options){
147 147 // Update the contents of this view
148 148 //
149 149 // Called when the model is changed. The model may have been
150 150 // changed by another view or by a state update from the back-end.
151 151 if (options === undefined || options.updated_view != this) {
152 152 if (this.$textbox.val() != this.model.get('value')) {
153 153 this.$textbox.val(this.model.get('value'));
154 154 }
155 155
156 156 var disabled = this.model.get('disabled');
157 157 this.$textbox.prop('disabled', disabled);
158 158
159 159 var description = this.model.get('description');
160 160 if (description.length === 0) {
161 161 this.$label.hide();
162 162 } else {
163 163 this.$label.text(description);
164 164 this.$label.show();
165 165 }
166 166 }
167 return TextBoxView.__super__.update.apply(this);
167 return TextView.__super__.update.apply(this);
168 168 },
169 169
170 170 events: {
171 171 // Dictionary of events and their handlers.
172 172 "keyup input" : "handleChanging",
173 173 "paste input" : "handleChanging",
174 174 "cut input" : "handleChanging",
175 175 "keypress input" : "handleKeypress",
176 176 "blur input" : "handleBlur",
177 177 "focusout input" : "handleFocusOut"
178 178 },
179 179
180 180 handleChanging: function(e) {
181 181 // Handles user input.
182 182
183 183 // Calling model.set will trigger all of the other views of the
184 184 // model to update.
185 185 this.model.set('value', e.target.value, {updated_view: this});
186 186 this.touch();
187 187 },
188 188
189 189 handleKeypress: function(e) {
190 190 // Handles text submition
191 191 if (e.keyCode == 13) { // Return key
192 192 this.send({event: 'submit'});
193 193 event.stopPropagation();
194 194 event.preventDefault();
195 195 return false;
196 196 }
197 197 },
198 198
199 199 handleBlur: function(e) {
200 200 // Prevent a blur from firing if the blur was not user intended.
201 201 // This is a workaround for the return-key focus loss bug.
202 202 // TODO: Is the original bug actually a fault of the keyboard
203 203 // manager?
204 204 if (e.relatedTarget === null) {
205 205 event.stopPropagation();
206 206 event.preventDefault();
207 207 return false;
208 208 }
209 209 },
210 210
211 211 handleFocusOut: function(e) {
212 212 // Prevent a blur from firing if the blur was not user intended.
213 213 // This is a workaround for the return-key focus loss bug.
214 214 if (e.relatedTarget === null) {
215 215 event.stopPropagation();
216 216 event.preventDefault();
217 217 return false;
218 218 }
219 219 },
220 220 });
221 WidgetManager.register_widget_view('TextBoxView', TextBoxView);
221 WidgetManager.register_widget_view('TextView', TextView);
222 222 });
@@ -1,69 +1,69 b''
1 1 // Test the widget framework.
2 2 casper.notebook_test(function () {
3 3 var index;
4 4
5 5 this.then(function () {
6 6
7 7 // Check if the WidgetManager class is defined.
8 8 this.test.assert(this.evaluate(function() {
9 9 return IPython.WidgetManager !== undefined;
10 10 }), 'WidgetManager class is defined');
11 11 });
12 12
13 13 index = this.append_cell(
14 14 'from IPython.html import widgets\n' +
15 15 'from IPython.display import display, clear_output\n' +
16 16 'print("Success")');
17 17 this.execute_cell_then(index);
18 18
19 19 this.wait(500); // Wait for require.js async callbacks to load dependencies.
20 20
21 21 this.then(function () {
22 22 // Check if the widget manager has been instantiated.
23 23 this.test.assert(this.evaluate(function() {
24 24 return IPython.notebook.kernel.widget_manager !== undefined;
25 25 }), 'Notebook widget manager instantiated');
26 26 });
27 27
28 28 throttle_index = this.append_cell(
29 29 'import time\n' +
30 'textbox = widgets.TextBoxWidget()\n' +
30 'textbox = widgets.TextWidget()\n' +
31 31 'display(textbox)\n'+
32 32 'textbox.add_class("my-throttle-textbox")\n' +
33 33 'def handle_change(name, old, new):\n' +
34 34 ' print(len(new))\n' +
35 35 ' time.sleep(0.5)\n' +
36 36 'textbox.on_trait_change(handle_change, "value")\n' +
37 37 'print("Success")');
38 38 this.execute_cell_then(throttle_index, function(index){
39 39 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
40 40 'Test throttling cell executed with correct output');
41 41
42 42 this.test.assert(this.cell_element_exists(index,
43 43 '.widget-area .widget-subarea'),
44 44 'Widget subarea exists.');
45 45
46 46 this.test.assert(this.cell_element_exists(index,
47 47 '.my-throttle-textbox'), 'Textbox exists.');
48 48
49 49 // Send 20 characters
50 50 this.sendKeys('.my-throttle-textbox', '....................');
51 51 });
52 52
53 53 this.wait(2000); // Wait for clicks to execute in kernel
54 54
55 55 this.then(function(){
56 56 var outputs = this.evaluate(function(i) {
57 57 return IPython.notebook.get_cell(i).output_area.outputs;
58 58 }, {i : throttle_index});
59 59
60 60 // Only 4 outputs should have printed, but because of timing, sometimes
61 61 // 5 outputs will print. All we need to do is verify num outputs <= 5
62 62 // because that is much less than 20.
63 63 this.test.assert(outputs.length <= 5, 'Messages throttled.');
64 64
65 65 // We also need to verify that the last state sent was correct.
66 66 var last_state = outputs[outputs.length-1].text;
67 67 this.test.assertEquals(last_state, "20\n", "Last state sent when throttling.");
68 68 });
69 69 });
@@ -1,86 +1,86 b''
1 1 // Test widget bool class
2 2 casper.notebook_test(function () {
3 3 index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 6 'print("Success")');
7 7 this.execute_cell_then(index);
8 8
9 9 var bool_index = this.append_cell(
10 'bool_widgets = [widgets.CheckBoxWidget(description="Title", value=True),\n' +
10 'bool_widgets = [widgets.CheckboxWidget(description="Title", value=True),\n' +
11 11 ' widgets.ToggleButtonWidget(description="Title", value=True)]\n' +
12 12 'display(bool_widgets[0])\n' +
13 13 'display(bool_widgets[1])\n' +
14 14 'print("Success")');
15 15 this.execute_cell_then(bool_index, function(index){
16 16
17 17 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
18 18 'Create bool widget cell executed with correct output.');
19 19
20 20 this.test.assert(this.cell_element_exists(index,
21 21 '.widget-area .widget-subarea'),
22 22 'Widget subarea exists.');
23 23
24 24 this.test.assert(this.cell_element_exists(index,
25 25 '.widget-area .widget-subarea .widget-hbox-single input'),
26 26 'Checkbox exists.');
27 27
28 28 this.test.assert(this.cell_element_function(index,
29 29 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
30 30 'Checkbox is checked.');
31 31
32 32 this.test.assert(this.cell_element_exists(index,
33 33 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel'),
34 34 'Checkbox label exists.');
35 35
36 36 this.test.assert(this.cell_element_function(index,
37 37 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel', 'html')=="Title",
38 38 'Checkbox labeled correctly.');
39 39
40 40 this.test.assert(this.cell_element_exists(index,
41 41 '.widget-area .widget-subarea button'),
42 42 'Toggle button exists.');
43 43
44 44 this.test.assert(this.cell_element_function(index,
45 45 '.widget-area .widget-subarea button', 'html')=="Title",
46 46 'Toggle button labeled correctly.');
47 47
48 48 this.test.assert(this.cell_element_function(index,
49 49 '.widget-area .widget-subarea button', 'hasClass', ['active']),
50 50 'Toggle button is toggled.');
51 51
52 52 });
53 53
54 54 index = this.append_cell(
55 55 'bool_widgets[0].value = False\n' +
56 56 'bool_widgets[1].value = False\n' +
57 57 'print("Success")');
58 58 this.execute_cell_then(index, function(index){
59 59
60 60 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
61 61 'Change bool widget value cell executed with correct output.');
62 62
63 63 this.test.assert(! this.cell_element_function(bool_index,
64 64 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
65 65 'Checkbox is not checked. (1)');
66 66
67 67 this.test.assert(! this.cell_element_function(bool_index,
68 68 '.widget-area .widget-subarea button', 'hasClass', ['active']),
69 69 'Toggle button is not toggled. (1)');
70 70
71 71 // Try toggling the bool by clicking on the checkbox.
72 72 this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox-single input', 'click');
73 73
74 74 this.test.assert(this.cell_element_function(bool_index,
75 75 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
76 76 'Checkbox is checked. (2)');
77 77
78 78 // Try toggling the bool by clicking on the toggle button.
79 79 this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
80 80
81 81 this.test.assert(this.cell_element_function(bool_index,
82 82 '.widget-area .widget-subarea button', 'hasClass', ['active']),
83 83 'Toggle button is toggled. (3)');
84 84
85 85 });
86 86 }); No newline at end of file
@@ -1,108 +1,108 b''
1 1 // Test multicontainer class
2 2 casper.notebook_test(function () {
3 3 index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 6 'print("Success")');
7 7 this.execute_cell_then(index);
8 8
9 9 // Test tab view
10 10 var multicontainer1_query = '.widget-area .widget-subarea div div.nav-tabs';
11 11 var multicontainer1_index = this.append_cell(
12 12 'multicontainer = widgets.TabWidget()\n' +
13 'page1 = widgets.TextBoxWidget()\n' +
14 'page2 = widgets.TextBoxWidget()\n' +
15 'page3 = widgets.TextBoxWidget()\n' +
13 'page1 = widgets.TextWidget()\n' +
14 'page2 = widgets.TextWidget()\n' +
15 'page3 = widgets.TextWidget()\n' +
16 16 'multicontainer.children = [page1, page2, page3]\n' +
17 17 'display(multicontainer)\n' +
18 18 'multicontainer.selected_index = 0\n' +
19 19 'print("Success")\n');
20 20 this.execute_cell_then(multicontainer1_index, function(index){
21 21
22 22 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
23 23 'Create multicontainer cell executed with correct output. (1)');
24 24
25 25 this.test.assert(this.cell_element_exists(index,
26 26 '.widget-area .widget-subarea'),
27 27 'Widget subarea exists.');
28 28
29 29 this.test.assert(this.cell_element_exists(index, multicontainer1_query),
30 30 'Widget tab list exists.');
31 31
32 32 this.test.assert(this.cell_element_exists(index, multicontainer1_query),
33 33 'First widget tab list exists.');
34 34
35 35 // JQuery selector is 1 based
36 36 this.click(multicontainer1_query + ' li:nth-child(2) a');
37 37 });
38 38
39 39 this.wait(500); // Wait for change to execute in kernel
40 40
41 41 index = this.append_cell(
42 42 'print(multicontainer.selected_index)\n' +
43 43 'multicontainer.selected_index = 2'); // 0 based
44 44 this.execute_cell_then(index, function(index){
45 45 this.test.assertEquals(this.get_output_cell(index).text, '1\n', // 0 based
46 46 'selected_index property updated with tab change.');
47 47
48 48 // JQuery selector is 1 based
49 49 this.test.assert(!this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(1)', 'hasClass', ['active']),
50 50 "Tab 1 is not selected.");
51 51 this.test.assert(!this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(2)', 'hasClass', ['active']),
52 52 "Tab 2 is not selected.");
53 53 this.test.assert(this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(3)', 'hasClass', ['active']),
54 54 "Tab 3 is selected.");
55 55 });
56 56
57 57 index = this.append_cell('multicontainer.set_title(1, "hello")\nprint("Success")'); // 0 based
58 58 this.execute_cell_then(index, function(index){
59 59 this.test.assert(this.cell_element_function(multicontainer1_index, multicontainer1_query +
60 60 ' li:nth-child(2) a', 'html') == 'hello',
61 61 'Tab page title set (after display).');
62 62 });
63 63
64 64 // Test accordion view
65 65 var multicontainer2_query = '.widget-area .widget-subarea .accordion';
66 66 var multicontainer2_index = this.append_cell(
67 67 'multicontainer = widgets.AccordionWidget()\n' +
68 68 'page1 = widgets.TextBoxWidget()\n' +
69 69 'page2 = widgets.TextBoxWidget()\n' +
70 70 'page3 = widgets.TextBoxWidget()\n' +
71 71 'multicontainer.children = [page1, page2, page3]\n' +
72 72 'multicontainer.set_title(2, "good")\n' +
73 73 'display(multicontainer)\n' +
74 74 'multicontainer.selected_index = 0\n' +
75 75 'print("Success")\n');
76 76 this.execute_cell_then(multicontainer2_index, function(index){
77 77
78 78 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
79 79 'Create multicontainer cell executed with correct output. (2)');
80 80
81 81 this.test.assert(this.cell_element_exists(index,
82 82 '.widget-area .widget-subarea'),
83 83 'Widget subarea exists.');
84 84
85 85 this.test.assert(this.cell_element_exists(index, multicontainer2_query),
86 86 'Widget accordion exists.');
87 87
88 88 this.test.assert(this.cell_element_exists(index, multicontainer2_query +
89 89 ' .accordion-group:nth-child(1) .accordion-body'),
90 90 'First accordion page exists.');
91 91
92 92 // JQuery selector is 1 based
93 93 this.test.assert(this.cell_element_function(index, multicontainer2_query +
94 94 ' .accordion-group:nth-child(3) .accordion-heading .accordion-toggle',
95 95 'html')=='good', 'Accordion page title set (before display).');
96 96
97 97 // JQuery selector is 1 based
98 98 this.click(multicontainer2_query + ' .accordion-group:nth-child(2) .accordion-heading .accordion-toggle');
99 99 });
100 100
101 101 this.wait(500); // Wait for change to execute in kernel
102 102
103 103 index = this.append_cell('print(multicontainer.selected_index)'); // 0 based
104 104 this.execute_cell_then(index, function(index){
105 105 this.test.assertEquals(this.get_output_cell(index).text, '1\n', // 0 based
106 106 'selected_index property updated with tab change.');
107 107 });
108 108 }); No newline at end of file
@@ -1,133 +1,133 b''
1 1 // Test selection class
2 2 casper.notebook_test(function () {
3 3 index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 6 'print("Success")');
7 7 this.execute_cell_then(index);
8 8
9 9 var combo_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group .widget-combo-btn';
10 10 var multibtn_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group[data-toggle="buttons-radio"]';
11 11 var radio_selector = '.widget-area .widget-subarea .widget-hbox .vbox';
12 12 var list_selector = '.widget-area .widget-subarea .widget-hbox .widget-listbox';
13 13
14 14 var selection_index;
15 15 var selection_values = 'abcd';
16 16 var check_state = function(context, index, state){
17 17 if (0 <= index && index < selection_values.length) {
18 18 var multibtn_state = context.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(' + (index + 1) + ')', 'hasClass', ['active']);
19 19 var radio_state = context.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(' + (index + 1) + ') input', 'prop', ['checked']);
20 20 var list_val = context.cell_element_function(selection_index, list_selector, 'val');
21 21 var combo_val = context.cell_element_function(selection_index, combo_selector, 'html');
22 22
23 23 var val = selection_values.charAt(index);
24 24 var list_state = (val == list_val);
25 25 var combo_state = (val == combo_val);
26 26
27 27 return multibtn_state == state &&
28 28 radio_state == state &&
29 29 list_state == state &&
30 30 combo_state == state;
31 31 }
32 32 return true;
33 33 };
34 34
35 35 var verify_selection = function(context, index){
36 36 for (var i = 0; i < selection_values.length; i++) {
37 37 if (!check_state(context, i, i==index)) {
38 38 return false;
39 39 }
40 40 }
41 41 return true;
42 42 };
43 43
44 44 //values=["' + selection_values + '"[i] for i in range(4)]
45 45 selection_index = this.append_cell(
46 46 'values=["' + selection_values + '"[i] for i in range(4)]\n' +
47 47 'selection = [widgets.DropdownWidget(values=values),\n' +
48 48 ' widgets.ToggleButtonsWidget(values=values),\n' +
49 49 ' widgets.RadioButtonsWidget(values=values),\n' +
50 ' widgets.ListBoxWidget(values=values)]\n' +
50 ' widgets.SelectWidget(values=values)]\n' +
51 51 '[display(selection[i]) for i in range(4)]\n' +
52 52 'for widget in selection:\n' +
53 53 ' def handle_change(name,old,new):\n' +
54 54 ' for other_widget in selection:\n' +
55 55 ' other_widget.value = new\n' +
56 56 ' widget.on_trait_change(handle_change, "value")\n' +
57 57 'print("Success")\n');
58 58 this.execute_cell_then(selection_index, function(index){
59 59 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
60 60 'Create selection cell executed with correct output.');
61 61
62 62 this.test.assert(this.cell_element_exists(index,
63 63 '.widget-area .widget-subarea'),
64 64 'Widget subarea exists.');
65 65
66 66 this.test.assert(this.cell_element_exists(index, combo_selector),
67 67 'Widget combobox exists.');
68 68
69 69 this.test.assert(this.cell_element_exists(index, multibtn_selector),
70 70 'Widget multibutton exists.');
71 71
72 72 this.test.assert(this.cell_element_exists(index, radio_selector),
73 73 'Widget radio buttons exists.');
74 74
75 75 this.test.assert(this.cell_element_exists(index, list_selector),
76 76 'Widget list exists.');
77 77
78 78 // Verify that no items are selected.
79 79 this.test.assert(verify_selection(this, -1), 'No items selected.');
80 80 });
81 81
82 82 index = this.append_cell(
83 83 'for widget in selection:\n' +
84 84 ' widget.value = "a"\n' +
85 85 'print("Success")\n');
86 86 this.execute_cell_then(index, function(index){
87 87 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
88 88 'Python select item executed with correct output.');
89 89
90 90 // Verify that the first item is selected.
91 91 this.test.assert(verify_selection(this, 0), 'Python selected');
92 92
93 93 // Verify that selecting a radio button updates all of the others.
94 94 this.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(2) input', 'click');
95 95 });
96 96 this.wait(500);
97 97 this.then(function () {
98 98 this.test.assert(verify_selection(this, 1), 'Radio button selection updated view states correctly.');
99 99
100 100 // Verify that selecting a list option updates all of the others.
101 101 this.cell_element_function(selection_index, list_selector + ' option:nth-child(3)', 'click');
102 102 });
103 103 this.wait(500);
104 104 this.then(function () {
105 105 this.test.assert(verify_selection(this, 2), 'List selection updated view states correctly.');
106 106
107 107 // Verify that selecting a multibutton option updates all of the others.
108 108 this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click');
109 109 });
110 110 this.wait(500);
111 111 this.then(function () {
112 112 this.test.assert(verify_selection(this, 3), 'Multibutton selection updated view states correctly.');
113 113
114 114 // Verify that selecting a combobox option updates all of the others.
115 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 116 });
117 117 this.wait(500);
118 118 this.then(function () {
119 119 this.test.assert(verify_selection(this, 2), 'Combobox selection updated view states correctly.');
120 120 });
121 121
122 122 this.wait(500); // Wait for change to execute in kernel
123 123
124 124 index = this.append_cell(
125 125 'for widget in selection:\n' +
126 126 ' widget.values = list(widget.values) + ["z"]\n' +
127 127 'selection[0].value = "z"');
128 128 this.execute_cell_then(index, function(index){
129 129
130 130 // Verify that selecting a combobox option updates all of the others.
131 131 this.test.assert(verify_selection(this, 4), 'Item added to selection widget.');
132 132 });
133 133 }); No newline at end of file
@@ -1,52 +1,52 b''
1 1 // Test widget string class
2 2 casper.notebook_test(function () {
3 3 index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 6 'print("Success")');
7 7 this.execute_cell_then(index);
8 8
9 9 var string_index = this.append_cell(
10 'string_widget = [widgets.TextBoxWidget(value = "xyz"),\n' +
11 ' widgets.TextAreaWidget(value = "xyz"),\n' +
10 'string_widget = [widgets.TextWidget(value = "xyz"),\n' +
11 ' widgets.TextareaWidget(value = "xyz"),\n' +
12 12 ' widgets.HTMLWidget(value = "xyz"),\n' +
13 13 ' widgets.LatexWidget(value = "$\\\\LaTeX{}$")]\n' +
14 14 '[display(widget) for widget in string_widget]\n'+
15 15 'print("Success")');
16 16 this.execute_cell_then(string_index, function(index){
17 17
18 18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 19 'Create string widget cell executed with correct output.');
20 20
21 21 this.test.assert(this.cell_element_exists(index,
22 22 '.widget-area .widget-subarea'),
23 23 'Widget subarea exists.');
24 24
25 25 this.test.assert(this.cell_element_exists(index,
26 26 '.widget-area .widget-subarea .widget-hbox-single input[type=text]'),
27 27 'Textbox exists.');
28 28
29 29 this.test.assert(this.cell_element_exists(index,
30 30 '.widget-area .widget-subarea .widget-hbox textarea'),
31 31 'Textarea exists.');
32 32
33 33 this.test.assert(this.cell_element_function(index,
34 34 '.widget-area .widget-subarea .widget-hbox textarea', 'val')=='xyz',
35 35 'Python set textarea value.');
36 36
37 37 this.test.assert(this.cell_element_function(index,
38 38 '.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val')=='xyz',
39 39 'Python set textbox value.');
40 40
41 41 });
42 42
43 43 this.wait(500); // Wait for change to execute in kernel
44 44
45 45 index = this.append_cell('print(string_widget.value)');
46 46 this.execute_cell_then(index, function(index){
47 47
48 48 this.test.assert(this.cell_element_exists(string_index,
49 49 '.widget-area .widget-subarea div span.MathJax_Preview'),
50 50 'MathJax parsed the LaTeX successfully.');
51 51 });
52 52 }); No newline at end of file
@@ -1,11 +1,11 b''
1 1 from .widget import Widget, DOMWidget, CallbackDispatcher
2 2
3 from .widget_bool import CheckBoxWidget, ToggleButtonWidget
3 from .widget_bool import CheckboxWidget, ToggleButtonWidget
4 4 from .widget_button import ButtonWidget
5 5 from .widget_container import ContainerWidget, PopupWidget
6 6 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
7 7 from .widget_image import ImageWidget
8 8 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
9 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, ListBoxWidget
9 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
10 10 from .widget_selectioncontainer import TabWidget, AccordionWidget
11 from .widget_string import HTMLWidget, LatexWidget, TextBoxWidget, TextAreaWidget
11 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
@@ -1,34 +1,34 b''
1 1 """BoolWidget class.
2 2
3 3 Represents a boolean using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import DOMWidget
17 17 from IPython.utils.traitlets import Unicode, Bool, List
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Classes
21 21 #-----------------------------------------------------------------------------
22 22 class _BoolWidget(DOMWidget):
23 23 value = Bool(False, help="Bool value", sync=True)
24 24 description = Unicode('', help="Description of the boolean (label).", sync=True)
25 25 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
26 26
27 27
28 class CheckBoxWidget(_BoolWidget):
29 _view_name = Unicode('CheckBoxView', sync=True)
28 class CheckboxWidget(_BoolWidget):
29 _view_name = Unicode('CheckboxView', sync=True)
30 30
31 31
32 32 class ToggleButtonWidget(_BoolWidget):
33 33 _view_name = Unicode('ToggleButtonView', sync=True)
34 34 No newline at end of file
@@ -1,95 +1,95 b''
1 1 """SelectionWidget class.
2 2
3 3 Represents an enumeration using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from threading import Lock
17 17
18 18 from .widget import DOMWidget
19 19 from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # SelectionWidget
23 23 #-----------------------------------------------------------------------------
24 24 class _SelectionWidget(DOMWidget):
25 25 value = Any(help="Selected value")
26 26 values = List(help="List of values the user can select")
27 27 labels = List(help="""List of string representations for each value.
28 28 These string representations are used to display the values in the
29 29 front-end.""", sync=True) # Only synced to the back-end.
30 30 disabled = Bool(False, help="Enable or disable user changes", sync=True)
31 31 description = Unicode(help="Description of the value this widget represents", sync=True)
32 32
33 33 _value = Unicode(sync=True) # Bi-directionally synced.
34 34
35 35 def __init__(self, *pargs, **kwargs):
36 36 """Constructor"""
37 37 self.value_lock = Lock()
38 38 self.on_trait_change(self._string_value_set, ['_value'])
39 39 DOMWidget.__init__(self, *pargs, **kwargs)
40 40
41 41 def _labels_changed(self, name=None, old=None, new=None):
42 42 """Handles when the value_names Dict has been changed.
43 43
44 44 This method sets the _reverse_value_names Dict to the inverse of the new
45 45 value for the value_names Dict."""
46 46 if len(new) != len(self.values):
47 47 raise TypeError('Labels list must be the same size as the values list.')
48 48
49 49 def _values_changed(self, name=None, old=None, new=None):
50 50 """Handles when the value_names Dict has been changed.
51 51
52 52 This method sets the _reverse_value_names Dict to the inverse of the new
53 53 value for the value_names Dict."""
54 54 if len(new) != len(self.labels):
55 55 self.labels = [(self.labels[i] if i < len(self.labels) else str(v)) for i, v in enumerate(new)]
56 56
57 57 def _value_changed(self, name, old, new):
58 58 """Called when value has been changed"""
59 59 if self.value_lock.acquire(False):
60 60 try:
61 61 # Make sure the value is in the list of values.
62 62 if new in self.values:
63 63 # Set the string version of the value.
64 64 self._value = self.labels[self.values.index(new)]
65 65 else:
66 66 raise TypeError('Value must be a value in the values list.')
67 67 finally:
68 68 self.value_lock.release()
69 69
70 70 def _string_value_set(self, name, old, new):
71 71 """Called when _value has been changed."""
72 72 if self.value_lock.acquire(False):
73 73 try:
74 74 if new in self.labels:
75 75 self.value = self.values[self.labels.index(new)]
76 76 else:
77 77 self.value = None
78 78 finally:
79 79 self.value_lock.release()
80 80
81 81
82 82 class ToggleButtonsWidget(_SelectionWidget):
83 83 _view_name = Unicode('ToggleButtonsView', sync=True)
84 84
85 85
86 86 class DropdownWidget(_SelectionWidget):
87 87 _view_name = Unicode('DropdownView', sync=True)
88 88
89 89
90 90 class RadioButtonsWidget(_SelectionWidget):
91 91 _view_name = Unicode('RadioButtonsView', sync=True)
92 92
93 93
94 class ListBoxWidget(_SelectionWidget):
95 _view_name = Unicode('ListBoxView', sync=True)
94 class SelectWidget(_SelectionWidget):
95 _view_name = Unicode('SelectView', sync=True)
@@ -1,72 +1,72 b''
1 1 """StringWidget class.
2 2
3 3 Represents a unicode string using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import DOMWidget, CallbackDispatcher
17 17 from IPython.utils.traitlets import Unicode, Bool, List
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Classes
21 21 #-----------------------------------------------------------------------------
22 22 class _StringWidget(DOMWidget):
23 23 value = Unicode(help="String value", sync=True)
24 24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 25 description = Unicode(help="Description of the value this widget represents", sync=True)
26 26
27 27
28 28 class HTMLWidget(_StringWidget):
29 29 _view_name = Unicode('HTMLView', sync=True)
30 30
31 31
32 32 class LatexWidget(_StringWidget):
33 33 _view_name = Unicode('LatexView', sync=True)
34 34
35 35
36 class TextAreaWidget(_StringWidget):
37 _view_name = Unicode('TextAreaView', sync=True)
36 class TextareaWidget(_StringWidget):
37 _view_name = Unicode('TextareaView', sync=True)
38 38
39 39 def scroll_to_bottom(self):
40 40 self.send({"method": "scroll_to_bottom"})
41 41
42 42
43 class TextBoxWidget(_StringWidget):
44 _view_name = Unicode('TextBoxView', sync=True)
43 class TextWidget(_StringWidget):
44 _view_name = Unicode('TextView', sync=True)
45 45
46 46 def __init__(self, **kwargs):
47 super(TextBoxWidget, self).__init__(**kwargs)
47 super(TextWidget, self).__init__(**kwargs)
48 48 self._submission_callbacks = CallbackDispatcher()
49 49 self.on_msg(self._handle_string_msg)
50 50
51 51 def _handle_string_msg(self, _, content):
52 52 """Handle a msg from the front-end.
53 53
54 54 Parameters
55 55 ----------
56 56 content: dict
57 57 Content of the msg."""
58 58 if content.get('event', '') == 'submit':
59 59 self._submission_callbacks(self)
60 60
61 61 def on_submit(self, callback, remove=False):
62 62 """(Un)Register a callback to handle text submission.
63 63
64 64 Triggered when the user clicks enter.
65 65
66 66 Parameters
67 67 ----------
68 68 callback: callable
69 69 Will be called with exactly one argument: the Widget instance
70 70 remove: bool (optional)
71 71 Whether to unregister the callback"""
72 72 self._submission_callbacks.register_callback(callback, remove=remove)
@@ -1,186 +1,186 b''
1 1 {
2 2 "metadata": {
3 3 "name": ""
4 4 },
5 5 "nbformat": 3,
6 6 "nbformat_minor": 0,
7 7 "worksheets": [
8 8 {
9 9 "cells": [
10 10 {
11 11 "cell_type": "code",
12 12 "collapsed": false,
13 13 "input": [
14 14 "# Widget related imports\n",
15 15 "from IPython.html import widgets\n",
16 16 "from IPython.display import display, clear_output, Javascript\n",
17 17 "from IPython.utils.traitlets import Unicode\n",
18 18 "\n",
19 19 "# nbconvert related imports\n",
20 20 "from IPython.nbconvert import get_export_names, export_by_name\n",
21 21 "from IPython.nbconvert.writers import FilesWriter\n",
22 22 "from IPython.nbformat import current"
23 23 ],
24 24 "language": "python",
25 25 "metadata": {},
26 26 "outputs": [],
27 27 "prompt_number": 1
28 28 },
29 29 {
30 30 "cell_type": "markdown",
31 31 "metadata": {},
32 32 "source": [
33 "Create a textbox Widget without displaying it. The widget will be used to store the notebook's name which is otherwise only available in the front-end."
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 34 ]
35 35 },
36 36 {
37 37 "cell_type": "code",
38 38 "collapsed": false,
39 39 "input": [
40 "notebook_name = widgets.TextBoxWidget()"
40 "notebook_name = widgets.TextWidget()"
41 41 ],
42 42 "language": "python",
43 43 "metadata": {},
44 44 "outputs": [],
45 45 "prompt_number": 2
46 46 },
47 47 {
48 48 "cell_type": "markdown",
49 49 "metadata": {},
50 50 "source": [
51 51 "Get the current notebook's name by pushing JavaScript to the browser that sets the notebook name in a string widget."
52 52 ]
53 53 },
54 54 {
55 55 "cell_type": "code",
56 56 "collapsed": false,
57 57 "input": [
58 58 "js = \"\"\"var model = IPython.notebook.kernel.widget_manager.get_model('{model_id}');\n",
59 59 "model.set('value', IPython.notebook.notebook_name);\n",
60 60 "model.save();\"\"\".format(model_id=notebook_name.model_id)\n",
61 61 "display(Javascript(data=js))"
62 62 ],
63 63 "language": "python",
64 64 "metadata": {},
65 65 "outputs": [
66 66 {
67 67 "javascript": [
68 68 "var model = IPython.notebook.kernel.widget_manager.get_model('8c6583524eb3422c99491730a3e1ce6c');\n",
69 69 "model.set('value', IPython.notebook.notebook_name);\n",
70 70 "model.save();"
71 71 ],
72 72 "metadata": {},
73 73 "output_type": "display_data",
74 74 "text": [
75 75 "<IPython.core.display.Javascript at 0x164ea50>"
76 76 ]
77 77 }
78 78 ],
79 79 "prompt_number": 3
80 80 },
81 81 {
82 82 "cell_type": "code",
83 83 "collapsed": false,
84 84 "input": [
85 85 "filename = notebook_name.value\n",
86 86 "filename"
87 87 ],
88 88 "language": "python",
89 89 "metadata": {},
90 90 "outputs": [
91 91 {
92 92 "metadata": {},
93 93 "output_type": "pyout",
94 94 "prompt_number": 4,
95 95 "text": [
96 96 "u'Export As (nbconvert).ipynb'"
97 97 ]
98 98 }
99 99 ],
100 100 "prompt_number": 4
101 101 },
102 102 {
103 103 "cell_type": "markdown",
104 104 "metadata": {},
105 105 "source": [
106 106 "Create the widget that will allow the user to Export the current notebook."
107 107 ]
108 108 },
109 109 {
110 110 "cell_type": "code",
111 111 "collapsed": false,
112 112 "input": [
113 113 "exporter_names = widgets.DropdownWidget(values=get_export_names(), value='html')\n",
114 114 "export_button = widgets.ButtonWidget(description=\"Export\")\n",
115 115 "download_link = widgets.HTMLWidget(visible=False)"
116 116 ],
117 117 "language": "python",
118 118 "metadata": {},
119 119 "outputs": [],
120 120 "prompt_number": 5
121 121 },
122 122 {
123 123 "cell_type": "markdown",
124 124 "metadata": {},
125 125 "source": [
126 126 "Export the notebook when the export button is clicked."
127 127 ]
128 128 },
129 129 {
130 130 "cell_type": "code",
131 131 "collapsed": false,
132 132 "input": [
133 133 "file_writer = FilesWriter()\n",
134 134 "\n",
135 135 "def export(name, nb):\n",
136 136 " \n",
137 137 " # Get a unique key for the notebook and set it in the resources object.\n",
138 138 " notebook_name = name[:name.rfind('.')]\n",
139 139 " resources = {}\n",
140 140 " resources['unique_key'] = notebook_name\n",
141 141 " resources['output_files_dir'] = '%s_files' % notebook_name\n",
142 142 "\n",
143 143 " # Try to export\n",
144 144 " try:\n",
145 145 " output, resources = export_by_name(exporter_names.value, nb)\n",
146 146 " except ConversionException as e:\n",
147 147 " download_link.value = \"<br>Could not export notebook!\"\n",
148 148 " else:\n",
149 149 " write_results = file_writer.write(output, resources, notebook_name=notebook_name)\n",
150 150 " \n",
151 151 " download_link.value = \"<br>Results: <a href='files/{filename}'><i>\\\"{filename}\\\"</i></a>\".format(filename=write_results)\n",
152 152 " download_link.visible = True\n",
153 153 " \n",
154 154 "def handle_export():\n",
155 155 " with open(filename, 'r') as f:\n",
156 156 " export(filename, current.read(f, 'json'))\n",
157 157 "export_button.on_click(handle_export)"
158 158 ],
159 159 "language": "python",
160 160 "metadata": {},
161 161 "outputs": [],
162 162 "prompt_number": 6
163 163 },
164 164 {
165 165 "cell_type": "markdown",
166 166 "metadata": {},
167 167 "source": [
168 168 "Display the controls."
169 169 ]
170 170 },
171 171 {
172 172 "cell_type": "code",
173 173 "collapsed": false,
174 174 "input": [
175 175 "display(exporter_names, export_button, download_link)"
176 176 ],
177 177 "language": "python",
178 178 "metadata": {},
179 179 "outputs": [],
180 180 "prompt_number": 7
181 181 }
182 182 ],
183 183 "metadata": {}
184 184 }
185 185 ]
186 186 } No newline at end of file
@@ -1,248 +1,248 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "code",
18 18 "collapsed": false,
19 19 "input": [
20 20 "import base64\n",
21 21 "from __future__ import print_function # py 2.7 compat.\n",
22 22 "from IPython.html import widgets # Widget definitions.\n",
23 23 "from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget."
24 24 ],
25 25 "language": "python",
26 26 "metadata": {},
27 27 "outputs": [],
28 28 "prompt_number": 1
29 29 },
30 30 {
31 31 "cell_type": "markdown",
32 32 "metadata": {},
33 33 "source": [
34 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 35 ]
36 36 },
37 37 {
38 38 "cell_type": "code",
39 39 "collapsed": false,
40 40 "input": [
41 41 "class FileWidget(widgets.DOMWidget):\n",
42 42 " _view_name = Unicode('FilePickerView', sync=True)\n",
43 43 " value = Unicode(sync=True)\n",
44 44 " filename = Unicode(sync=True)\n",
45 45 " \n",
46 46 " def __init__(self, **kwargs):\n",
47 47 " \"\"\"Constructor\"\"\"\n",
48 48 " widgets.DOMWidget.__init__(self, **kwargs) # Call the base.\n",
49 49 " \n",
50 50 " # Allow the user to register error callbacks with the following signatures:\n",
51 51 " # callback()\n",
52 52 " # callback(sender)\n",
53 53 " self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])\n",
54 54 " \n",
55 55 " # Listen for custom msgs\n",
56 56 " self.on_msg(self._handle_custom_msg)\n",
57 57 "\n",
58 58 " def _handle_custom_msg(self, content):\n",
59 59 " \"\"\"Handle a msg from the front-end.\n",
60 60 "\n",
61 61 " Parameters\n",
62 62 " ----------\n",
63 63 " content: dict\n",
64 64 " Content of the msg.\"\"\"\n",
65 65 " if 'event' in content and content['event'] == 'error':\n",
66 66 " self.errors()\n",
67 67 " self.errors(self)\n",
68 68 " "
69 69 ],
70 70 "language": "python",
71 71 "metadata": {},
72 72 "outputs": [],
73 73 "prompt_number": 2
74 74 },
75 75 {
76 76 "cell_type": "code",
77 77 "collapsed": false,
78 78 "input": [
79 79 "%%javascript\n",
80 80 "\n",
81 81 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
82 82 "\n",
83 83 " var FilePickerView = IPython.WidgetView.extend({\n",
84 84 " render: function(){\n",
85 85 " // Render the view.\n",
86 86 " this.setElement($('<input />')\n",
87 87 " .attr('type', 'file'));\n",
88 88 " },\n",
89 89 " \n",
90 90 " events: {\n",
91 91 " // List of events and their handlers.\n",
92 92 " 'change': 'handle_file_change',\n",
93 93 " },\n",
94 94 " \n",
95 95 " handle_file_change: function(evt) { \n",
96 96 " // Handle when the user has changed the file.\n",
97 97 " \n",
98 98 " // Retrieve the first (and only!) File from the FileList object\n",
99 99 " var file = evt.target.files[0];\n",
100 100 " if (file) {\n",
101 101 "\n",
102 102 " // Read the file's textual content and set value to those contents.\n",
103 103 " var that = this;\n",
104 104 " var file_reader = new FileReader();\n",
105 105 " file_reader.onload = function(e) {\n",
106 106 " that.model.set('value', e.target.result);\n",
107 107 " that.touch();\n",
108 108 " }\n",
109 109 " file_reader.readAsText(file);\n",
110 110 " } else {\n",
111 111 "\n",
112 112 " // The file couldn't be opened. Send an error msg to the\n",
113 113 " // back-end.\n",
114 114 " this.send({ 'event': 'error' });\n",
115 115 " }\n",
116 116 "\n",
117 117 " // Set the filename of the file.\n",
118 118 " this.model.set('filename', file.name);\n",
119 119 " this.touch();\n",
120 120 " },\n",
121 121 " });\n",
122 122 " \n",
123 123 " // Register the DatePickerView with the widget manager.\n",
124 124 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
125 125 "});"
126 126 ],
127 127 "language": "python",
128 128 "metadata": {},
129 129 "outputs": [
130 130 {
131 131 "javascript": [
132 132 "\n",
133 133 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
134 134 "\n",
135 135 " var FilePickerView = IPython.WidgetView.extend({\n",
136 136 " render: function(){\n",
137 137 " // Render the view.\n",
138 138 " this.setElement($('<input />')\n",
139 139 " .attr('type', 'file'));\n",
140 140 " },\n",
141 141 " \n",
142 142 " events: {\n",
143 143 " // List of events and their handlers.\n",
144 144 " 'change': 'handle_file_change',\n",
145 145 " },\n",
146 146 " \n",
147 147 " handle_file_change: function(evt) { \n",
148 148 " // Handle when the user has changed the file.\n",
149 149 " \n",
150 150 " // Retrieve the first (and only!) File from the FileList object\n",
151 151 " var file = evt.target.files[0];\n",
152 152 " if (file) {\n",
153 153 "\n",
154 154 " // Read the file's textual content and set value to those contents.\n",
155 155 " var that = this;\n",
156 156 " var file_reader = new FileReader();\n",
157 157 " file_reader.onload = function(e) {\n",
158 158 " that.model.set('value', e.target.result);\n",
159 159 " that.touch();\n",
160 160 " }\n",
161 161 " file_reader.readAsText(file);\n",
162 162 " } else {\n",
163 163 "\n",
164 164 " // The file couldn't be opened. Send an error msg to the\n",
165 165 " // back-end.\n",
166 166 " this.send({ 'event': 'error' });\n",
167 167 " }\n",
168 168 "\n",
169 169 " // Set the filename of the file.\n",
170 170 " this.model.set('filename', file.name);\n",
171 171 " this.touch();\n",
172 172 " },\n",
173 173 " });\n",
174 174 " \n",
175 175 " // Register the DatePickerView with the widget manager.\n",
176 176 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
177 177 "});"
178 178 ],
179 179 "metadata": {},
180 180 "output_type": "display_data",
181 181 "text": [
182 "<IPython.core.display.Javascript at 0x22870d0>"
182 "<IPython.core.display.Javascript at 0x36df2d0>"
183 183 ]
184 184 }
185 185 ],
186 186 "prompt_number": 3
187 187 },
188 188 {
189 189 "cell_type": "markdown",
190 190 "metadata": {},
191 191 "source": [
192 192 "The following shows how the file widget can be used."
193 193 ]
194 194 },
195 195 {
196 196 "cell_type": "code",
197 197 "collapsed": false,
198 198 "input": [
199 199 "file_widget = FileWidget()\n",
200 200 "\n",
201 201 "# Register an event to echo the filename when it has been changed.\n",
202 202 "def file_loading():\n",
203 203 " print(\"Loading %s\" % file_widget.filename)\n",
204 204 "file_widget.on_trait_change(file_loading, 'filename')\n",
205 205 "\n",
206 206 "# Register an event to echo the filename and contents when a file\n",
207 207 "# has been uploaded.\n",
208 208 "def file_loaded():\n",
209 209 " print(\"Loaded, file contents: %s\" % file_widget.value)\n",
210 210 "file_widget.on_trait_change(file_loaded, 'value')\n",
211 211 "\n",
212 212 "# Register an event to print an error message when a file could not\n",
213 213 "# be opened. Since the error messages are not handled through\n",
214 214 "# traitlets but instead handled through custom msgs, the registration\n",
215 215 "# of the handler is different than the two examples above. Instead\n",
216 216 "# the API provided by the CallbackDispatcher must be used.\n",
217 217 "def file_failed():\n",
218 218 " print(\"Could not load file contents of %s\" % file_widget.filename)\n",
219 219 "file_widget.errors.register_callback(file_failed)\n",
220 220 "\n",
221 221 "file_widget"
222 222 ],
223 223 "language": "python",
224 224 "metadata": {},
225 225 "outputs": [
226 226 {
227 227 "output_type": "stream",
228 228 "stream": "stdout",
229 229 "text": [
230 230 "Loading test.txt\n"
231 231 ]
232 232 },
233 233 {
234 234 "output_type": "stream",
235 235 "stream": "stdout",
236 236 "text": [
237 237 "Loaded, file contents: Hello World!\n",
238 238 "\n"
239 239 ]
240 240 }
241 241 ],
242 242 "prompt_number": 4
243 243 }
244 244 ],
245 245 "metadata": {}
246 246 }
247 247 ]
248 248 } No newline at end of file
@@ -1,227 +1,227 b''
1 1 {
2 2 "metadata": {
3 3 "name": ""
4 4 },
5 5 "nbformat": 3,
6 6 "nbformat_minor": 0,
7 7 "worksheets": [
8 8 {
9 9 "cells": [
10 10 {
11 11 "cell_type": "code",
12 12 "collapsed": false,
13 13 "input": [
14 14 "# Console related imports.\n",
15 15 "from subprocess import Popen, PIPE\n",
16 16 "import fcntl\n",
17 17 "import os\n",
18 18 "from IPython.utils.py3compat import bytes_to_str, string_types\n",
19 19 "\n",
20 20 "# Widget related imports.\n",
21 21 "from IPython.html import widgets\n",
22 22 "from IPython.display import display"
23 23 ],
24 24 "language": "python",
25 25 "metadata": {},
26 26 "outputs": [],
27 27 "prompt_number": 1
28 28 },
29 29 {
30 30 "cell_type": "markdown",
31 31 "metadata": {},
32 32 "source": [
33 33 "Define function to run a process without blocking the input."
34 34 ]
35 35 },
36 36 {
37 37 "cell_type": "code",
38 38 "collapsed": false,
39 39 "input": [
40 40 "def read_process(process, append_output):\n",
41 41 " \"\"\" Try to read the stdout and stderr of a process and render it using \n",
42 42 " the append_output method provided\n",
43 43 " \n",
44 44 " Parameters\n",
45 45 " ----------\n",
46 46 " process: Popen handle\n",
47 47 " append_output: method handle\n",
48 48 " Callback to render output. Signature of\n",
49 49 " append_output(output, [prefix=])\"\"\"\n",
50 50 " \n",
51 51 " try:\n",
52 52 " stdout = process.stdout.read()\n",
53 53 " if stdout is not None and len(stdout) > 0:\n",
54 54 " append_output(stdout, prefix=' ')\n",
55 55 " except:\n",
56 56 " pass\n",
57 57 " \n",
58 58 " try:\n",
59 59 " stderr = process.stderr.read()\n",
60 60 " if stderr is not None and len(stderr) > 0:\n",
61 61 " append_output(stderr, prefix='ERR ')\n",
62 62 " except:\n",
63 63 " pass\n",
64 64 "\n",
65 65 "\n",
66 66 "def set_pipe_nonblocking(pipe):\n",
67 67 " \"\"\"Set a pipe as non-blocking\"\"\"\n",
68 68 " fl = fcntl.fcntl(pipe, fcntl.F_GETFL)\n",
69 69 " fcntl.fcntl(pipe, fcntl.F_SETFL, fl | os.O_NONBLOCK)\n",
70 70 "\n",
71 71 "\n",
72 72 "kernel = get_ipython().kernel\n",
73 73 "def run_command(command, append_output, has_user_exited=None):\n",
74 74 " \"\"\"Run a command asyncronously\n",
75 75 " \n",
76 76 " Parameters\n",
77 77 " ----------\n",
78 78 " command: str\n",
79 79 " Shell command to launch a process with.\n",
80 80 " append_output: method handle\n",
81 81 " Callback to render output. Signature of\n",
82 82 " append_output(output, [prefix=])\n",
83 83 " has_user_exited: method handle\n",
84 84 " Check to see if the user wants to stop the command.\n",
85 85 " Must return a boolean.\"\"\"\n",
86 86 " \n",
87 87 " # Echo input.\n",
88 88 " append_output(command, prefix='>>> ')\n",
89 89 " \n",
90 90 " # Create the process. Make sure the pipes are set as non-blocking.\n",
91 91 " process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)\n",
92 92 " set_pipe_nonblocking(process.stdout)\n",
93 93 " set_pipe_nonblocking(process.stderr)\n",
94 94 " \n",
95 95 " # Only continue to read from the command \n",
96 96 " while (has_user_exited is None or not has_user_exited()) and process.poll() is None:\n",
97 97 " read_process(process, append_output)\n",
98 98 " kernel.do_one_iteration() # Run IPython iteration. This is the code that\n",
99 99 " # makes this operation non-blocking. This will\n",
100 100 " # allow widget messages and callbacks to be \n",
101 101 " # processed.\n",
102 102 " \n",
103 103 " # If the process is still running, the user must have exited.\n",
104 104 " if process.poll() is None:\n",
105 105 " process.kill()\n",
106 106 " else:\n",
107 107 " read_process(process, append_output) # Read remainer\n",
108 108 " \n",
109 109 " \n",
110 110 " \n",
111 111 " "
112 112 ],
113 113 "language": "python",
114 114 "metadata": {},
115 115 "outputs": [],
116 116 "prompt_number": 2
117 117 },
118 118 {
119 119 "cell_type": "markdown",
120 120 "metadata": {},
121 121 "source": [
122 122 "Create the console widgets without displaying them."
123 123 ]
124 124 },
125 125 {
126 126 "cell_type": "code",
127 127 "collapsed": false,
128 128 "input": [
129 129 "console_container = widgets.ContainerWidget(visible=False)\n",
130 130 "console_container.set_css('padding', '10px')\n",
131 131 "\n",
132 132 "console_style = {\n",
133 133 " 'font-family': 'monospace',\n",
134 134 " 'color': '#AAAAAA',\n",
135 135 " 'background': 'black',\n",
136 136 " 'width': '800px',\n",
137 137 "}\n",
138 138 "\n",
139 "output_box = widgets.TextAreaWidget()\n",
139 "output_box = widgets.TextareaWidget()\n",
140 140 "output_box.set_css(console_style)\n",
141 141 "output_box.set_css('height', '400px')\n",
142 142 "\n",
143 "input_box = widgets.TextBoxWidget()\n",
143 "input_box = widgets.TextWidget()\n",
144 144 "input_box.set_css(console_style)\n",
145 145 "\n",
146 146 "console_container.children = [output_box, input_box]"
147 147 ],
148 148 "language": "python",
149 149 "metadata": {},
150 150 "outputs": [],
151 151 "prompt_number": 3
152 152 },
153 153 {
154 154 "cell_type": "markdown",
155 155 "metadata": {},
156 156 "source": [
157 157 "Hook the process execution methods up to our console widgets."
158 158 ]
159 159 },
160 160 {
161 161 "cell_type": "code",
162 162 "collapsed": false,
163 163 "input": [
164 164 "\n",
165 165 "def append_output(output, prefix):\n",
166 166 " if isinstance(output, string_types):\n",
167 167 " output_str = output\n",
168 168 " else:\n",
169 169 " output_str = bytes_to_str(output)\n",
170 170 " output_lines = output_str.split('\\n')\n",
171 171 " formatted_output = '\\n'.join([prefix + line for line in output_lines if len(line) > 0]) + '\\n'\n",
172 172 " output_box.value += formatted_output\n",
173 173 " output_box.scroll_to_bottom()\n",
174 174 " \n",
175 175 "def has_user_exited():\n",
176 176 " return not console_container.visible\n",
177 177 "\n",
178 178 "def handle_input(sender):\n",
179 179 " sender.disabled = True\n",
180 180 " try:\n",
181 181 " command = sender.value\n",
182 182 " sender.value = ''\n",
183 183 " run_command(command, append_output=append_output, has_user_exited=has_user_exited)\n",
184 184 " finally:\n",
185 185 " sender.disabled = False\n",
186 186 " \n",
187 187 "input_box.on_submit(handle_input)"
188 188 ],
189 189 "language": "python",
190 190 "metadata": {},
191 191 "outputs": [],
192 192 "prompt_number": 4
193 193 },
194 194 {
195 195 "cell_type": "markdown",
196 196 "metadata": {},
197 197 "source": [
198 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 199 ]
200 200 },
201 201 {
202 202 "cell_type": "code",
203 203 "collapsed": false,
204 204 "input": [
205 205 "toggle_button = widgets.ButtonWidget(description=\"Start Console\")\n",
206 "def toggle_console():\n",
206 "def toggle_console(sender):\n",
207 207 " console_container.visible = not console_container.visible\n",
208 208 " if console_container.visible:\n",
209 209 " toggle_button.description=\"Stop Console\"\n",
210 210 " input_box.disabled = False\n",
211 211 " else:\n",
212 212 " toggle_button.description=\"Start Console\"\n",
213 213 "toggle_button.on_click(toggle_console)\n",
214 214 "\n",
215 215 "display(toggle_button)\n",
216 216 "display(console_container)"
217 217 ],
218 218 "language": "python",
219 219 "metadata": {},
220 220 "outputs": [],
221 221 "prompt_number": 5
222 222 }
223 223 ],
224 224 "metadata": {}
225 225 }
226 226 ]
227 227 } No newline at end of file
@@ -1,314 +1,314 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "[Index](index.ipynb)\n",
21 21 "\n",
22 22 "To use IPython widgets in the notebook, the widget namespace needs to be imported."
23 23 ]
24 24 },
25 25 {
26 26 "cell_type": "code",
27 27 "collapsed": false,
28 28 "input": [
29 29 "from IPython.html import widgets # Widget definitions\n",
30 30 "from IPython.display import display # Used to display widgets in the notebook"
31 31 ],
32 32 "language": "python",
33 33 "metadata": {},
34 34 "outputs": [],
35 35 "prompt_number": 1
36 36 },
37 37 {
38 38 "cell_type": "heading",
39 39 "level": 1,
40 40 "metadata": {},
41 41 "source": [
42 42 "Basic Widgets"
43 43 ]
44 44 },
45 45 {
46 46 "cell_type": "markdown",
47 47 "metadata": {},
48 48 "source": [
49 49 "IPython comes with basic widgets that represent common interactive controls. These widgets are\n",
50 50 "\n",
51 "- CheckBoxWidget\n",
51 "- CheckboxWidget\n",
52 52 "- ToggleButtonWidget\n",
53 53 "- FloatSliderWidget\n",
54 54 "- BoundedFloatTextWidget\n",
55 55 "- FloatProgressWidget\n",
56 56 "- FloatTextWidget\n",
57 57 "- ImageWidget\n",
58 58 "- IntSliderWidget\n",
59 59 "- BoundedIntTextWidget\n",
60 60 "- IntProgressWidget\n",
61 61 "- IntTextWidget\n",
62 62 "- ToggleButtonsWidget\n",
63 63 "- RadioButtonsWidget\n",
64 64 "- DropdownWidget\n",
65 "- ListBoxWidget\n",
65 "- SelectWidget\n",
66 66 "- HTMLWidget\n",
67 67 "- LatexWidget\n",
68 "- TextAreaWidget\n",
69 "- TextBoxWidget\n",
68 "- TextareaWidget\n",
69 "- TextWidget\n",
70 70 "- ButtonWidget\n",
71 71 "\n",
72 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 73 "\n",
74 74 "- ContainerWidget\n",
75 75 "- PopupWidget\n",
76 76 "- AccordionWidget\n",
77 77 "- TabWidget\n",
78 78 "\n",
79 79 "To see the complete list of widgets, one can execute the following"
80 80 ]
81 81 },
82 82 {
83 83 "cell_type": "code",
84 84 "collapsed": false,
85 85 "input": [
86 86 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
87 87 ],
88 88 "language": "python",
89 89 "metadata": {},
90 90 "outputs": [
91 91 {
92 92 "metadata": {},
93 93 "output_type": "pyout",
94 94 "prompt_number": 2,
95 95 "text": [
96 96 "['AccordionWidget',\n",
97 97 " 'BoundedFloatTextWidget',\n",
98 98 " 'BoundedIntTextWidget',\n",
99 99 " 'ButtonWidget',\n",
100 " 'CheckBoxWidget',\n",
100 " 'CheckboxWidget',\n",
101 101 " 'ContainerWidget',\n",
102 102 " 'DOMWidget',\n",
103 103 " 'DropdownWidget',\n",
104 104 " 'FloatProgressWidget',\n",
105 105 " 'FloatSliderWidget',\n",
106 106 " 'FloatTextWidget',\n",
107 107 " 'HTMLWidget',\n",
108 108 " 'ImageWidget',\n",
109 109 " 'IntProgressWidget',\n",
110 110 " 'IntSliderWidget',\n",
111 111 " 'IntTextWidget',\n",
112 112 " 'LatexWidget',\n",
113 " 'ListBoxWidget',\n",
114 113 " 'PopupWidget',\n",
115 114 " 'RadioButtonsWidget',\n",
115 " 'SelectWidget',\n",
116 116 " 'TabWidget',\n",
117 " 'TextAreaWidget',\n",
118 " 'TextBoxWidget',\n",
117 " 'TextWidget',\n",
118 " 'TextareaWidget',\n",
119 119 " 'ToggleButtonWidget',\n",
120 120 " 'ToggleButtonsWidget',\n",
121 121 " 'Widget']"
122 122 ]
123 123 }
124 124 ],
125 125 "prompt_number": 2
126 126 },
127 127 {
128 128 "cell_type": "markdown",
129 129 "metadata": {},
130 130 "source": [
131 131 "The basic widgets all have sensible default values. Create a *FloatSliderWidget* without displaying it:"
132 132 ]
133 133 },
134 134 {
135 135 "cell_type": "code",
136 136 "collapsed": false,
137 137 "input": [
138 138 "mywidget = widgets.FloatSliderWidget()"
139 139 ],
140 140 "language": "python",
141 141 "metadata": {},
142 142 "outputs": [],
143 143 "prompt_number": 3
144 144 },
145 145 {
146 146 "cell_type": "markdown",
147 147 "metadata": {},
148 148 "source": [
149 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 150 ]
151 151 },
152 152 {
153 153 "cell_type": "code",
154 154 "collapsed": false,
155 155 "input": [
156 156 "display(mywidget)"
157 157 ],
158 158 "language": "python",
159 159 "metadata": {},
160 160 "outputs": [],
161 161 "prompt_number": 4
162 162 },
163 163 {
164 164 "cell_type": "markdown",
165 165 "metadata": {},
166 166 "source": [
167 167 "or"
168 168 ]
169 169 },
170 170 {
171 171 "cell_type": "code",
172 172 "collapsed": false,
173 173 "input": [
174 174 "mywidget"
175 175 ],
176 176 "language": "python",
177 177 "metadata": {},
178 178 "outputs": [],
179 179 "prompt_number": 5
180 180 },
181 181 {
182 182 "cell_type": "markdown",
183 183 "metadata": {},
184 184 "source": [
185 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 186 "\n",
187 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 188 ]
189 189 },
190 190 {
191 191 "cell_type": "code",
192 192 "collapsed": false,
193 193 "input": [
194 194 "mywidget.keys"
195 195 ],
196 196 "language": "python",
197 197 "metadata": {},
198 198 "outputs": [
199 199 {
200 200 "metadata": {},
201 201 "output_type": "pyout",
202 202 "prompt_number": 6,
203 203 "text": [
204 204 "['_view_name',\n",
205 205 " 'orientation',\n",
206 206 " 'min',\n",
207 207 " 'max',\n",
208 208 " '_css',\n",
209 209 " 'value',\n",
210 210 " 'disabled',\n",
211 211 " 'visible',\n",
212 212 " 'step',\n",
213 213 " 'description']"
214 214 ]
215 215 }
216 216 ],
217 217 "prompt_number": 6
218 218 },
219 219 {
220 220 "cell_type": "markdown",
221 221 "metadata": {},
222 222 "source": [
223 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 224 ]
225 225 },
226 226 {
227 227 "cell_type": "code",
228 228 "collapsed": false,
229 229 "input": [
230 230 "mywidget.value = 25.0"
231 231 ],
232 232 "language": "python",
233 233 "metadata": {},
234 234 "outputs": [],
235 235 "prompt_number": 7
236 236 },
237 237 {
238 238 "cell_type": "markdown",
239 239 "metadata": {},
240 240 "source": [
241 241 "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)."
242 242 ]
243 243 },
244 244 {
245 245 "cell_type": "code",
246 246 "collapsed": false,
247 247 "input": [
248 248 "mywidget.value"
249 249 ],
250 250 "language": "python",
251 251 "metadata": {},
252 252 "outputs": [
253 253 {
254 254 "metadata": {},
255 255 "output_type": "pyout",
256 256 "prompt_number": 8,
257 257 "text": [
258 258 "25.0"
259 259 ]
260 260 }
261 261 ],
262 262 "prompt_number": 8
263 263 },
264 264 {
265 265 "cell_type": "markdown",
266 266 "metadata": {},
267 267 "source": [
268 268 "Widget values can also be set with kwargs during the construction of the widget (as seen below)."
269 269 ]
270 270 },
271 271 {
272 272 "cell_type": "code",
273 273 "collapsed": false,
274 274 "input": [
275 275 "mysecondwidget = widgets.RadioButtonsWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Item A\")\n",
276 276 "display(mysecondwidget)"
277 277 ],
278 278 "language": "python",
279 279 "metadata": {},
280 280 "outputs": [],
281 281 "prompt_number": 9
282 282 },
283 283 {
284 284 "cell_type": "code",
285 285 "collapsed": false,
286 286 "input": [
287 287 "mysecondwidget.value"
288 288 ],
289 289 "language": "python",
290 290 "metadata": {},
291 291 "outputs": [
292 292 {
293 293 "metadata": {},
294 294 "output_type": "pyout",
295 295 "prompt_number": 10,
296 296 "text": [
297 297 "'Item A'"
298 298 ]
299 299 }
300 300 ],
301 301 "prompt_number": 10
302 302 },
303 303 {
304 304 "cell_type": "markdown",
305 305 "metadata": {},
306 306 "source": [
307 307 "In [Part 2](Part 2 - Events.ipynb) of this [series](index.ipynb), you will learn about widget events."
308 308 ]
309 309 }
310 310 ],
311 311 "metadata": {}
312 312 }
313 313 ]
314 314 } No newline at end of file
@@ -1,276 +1,261 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "[< Back to Part 1](Part 1 - Basics.ipynb) or [Index](index.ipynb)"
21 21 ]
22 22 },
23 23 {
24 24 "cell_type": "code",
25 25 "collapsed": false,
26 26 "input": [
27 27 "from __future__ import print_function # 2.7 compatability\n",
28 28 "\n",
29 29 "from IPython.html import widgets # Widget definitions\n",
30 30 "from IPython.display import display # Used to display widgets in the notebook"
31 31 ],
32 32 "language": "python",
33 33 "metadata": {},
34 34 "outputs": [],
35 35 "prompt_number": 1
36 36 },
37 37 {
38 38 "cell_type": "heading",
39 39 "level": 1,
40 40 "metadata": {},
41 41 "source": [
42 42 "Traitlet Events"
43 43 ]
44 44 },
45 45 {
46 46 "cell_type": "markdown",
47 47 "metadata": {},
48 48 "source": [
49 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 50 ]
51 51 },
52 52 {
53 53 "cell_type": "code",
54 54 "collapsed": false,
55 55 "input": [
56 56 "print(widgets.Widget.on_trait_change.__doc__)"
57 57 ],
58 58 "language": "python",
59 59 "metadata": {},
60 60 "outputs": [
61 61 {
62 62 "output_type": "stream",
63 63 "stream": "stdout",
64 64 "text": [
65 65 "Setup a handler to be called when a trait changes.\n",
66 66 "\n",
67 67 " This is used to setup dynamic notifications of trait changes.\n",
68 68 "\n",
69 69 " Static handlers can be created by creating methods on a HasTraits\n",
70 70 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
71 71 " to create static handler for the trait 'a', create the method\n",
72 72 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
73 73 " below).\n",
74 74 "\n",
75 75 " Parameters\n",
76 76 " ----------\n",
77 77 " handler : callable\n",
78 78 " A callable that is called when a trait changes. Its\n",
79 79 " signature can be handler(), handler(name), handler(name, new)\n",
80 80 " or handler(name, old, new).\n",
81 81 " name : list, str, None\n",
82 82 " If None, the handler will apply to all traits. If a list\n",
83 83 " of str, handler will apply to all names in the list. If a\n",
84 84 " str, the handler will apply just to that name.\n",
85 85 " remove : bool\n",
86 86 " If False (the default), then install the handler. If True\n",
87 87 " then unintall it.\n",
88 88 " \n"
89 89 ]
90 90 }
91 91 ],
92 92 "prompt_number": 2
93 93 },
94 94 {
95 95 "cell_type": "markdown",
96 96 "metadata": {},
97 97 "source": [
98 98 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
99 99 "\n",
100 100 "- callback()\n",
101 101 "- callback(trait_name)\n",
102 102 "- callback(trait_name, new_value)\n",
103 103 "- callback(trait_name, old_value, new_value)\n",
104 104 "\n",
105 105 "Using this method, an example of how to output an IntSliderWiget's value as it is changed can be seen below."
106 106 ]
107 107 },
108 108 {
109 109 "cell_type": "code",
110 110 "collapsed": false,
111 111 "input": [
112 112 "int_range = widgets.IntSliderWidget()\n",
113 113 "display(int_range)\n",
114 114 "\n",
115 115 "def on_value_change(name, value):\n",
116 116 " print(value)\n",
117 117 "\n",
118 118 "int_range.on_trait_change(on_value_change, 'value')"
119 119 ],
120 120 "language": "python",
121 121 "metadata": {},
122 "outputs": [
123 {
124 "output_type": "stream",
125 "stream": "stdout",
126 "text": [
127 "1\n"
128 ]
129 },
130 {
131 "output_type": "stream",
132 "stream": "stdout",
133 "text": [
134 "2\n"
135 ]
136 },
137 {
138 "output_type": "stream",
139 "stream": "stdout",
140 "text": [
141 "3\n"
142 ]
143 }
144 ],
122 "outputs": [],
145 123 "prompt_number": 3
146 124 },
147 125 {
148 126 "cell_type": "heading",
149 127 "level": 1,
150 128 "metadata": {},
151 129 "source": [
152 130 "Specialized Events"
153 131 ]
154 132 },
155 133 {
156 134 "cell_type": "heading",
157 135 "level": 2,
158 136 "metadata": {},
159 137 "source": [
160 138 "Button Click Event"
161 139 ]
162 140 },
163 141 {
164 142 "cell_type": "markdown",
165 143 "metadata": {},
166 144 "source": [
167 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."
168 146 ]
169 147 },
170 148 {
171 149 "cell_type": "code",
172 150 "collapsed": false,
173 151 "input": [
174 152 "print(widgets.ButtonWidget.on_click.__doc__)"
175 153 ],
176 154 "language": "python",
177 155 "metadata": {},
178 156 "outputs": [
179 157 {
180 158 "output_type": "stream",
181 159 "stream": "stdout",
182 160 "text": [
183 161 "Register a callback to execute when the button is clicked.\n",
184 162 "\n",
185 163 " The callback will be called with one argument,\n",
186 164 " the clicked button widget instance.\n",
187 165 "\n",
188 166 " Parameters\n",
189 167 " ----------\n",
190 168 " remove : bool (optional)\n",
191 169 " Set to true to remove the callback from the list of callbacks.\n"
192 170 ]
193 171 }
194 172 ],
195 173 "prompt_number": 4
196 174 },
197 175 {
198 176 "cell_type": "markdown",
199 177 "metadata": {},
200 178 "source": [
201 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."
202 180 ]
203 181 },
204 182 {
205 183 "cell_type": "code",
206 184 "collapsed": false,
207 185 "input": [
208 186 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
209 187 "display(button)\n",
210 188 "\n",
211 189 "def on_button_clicked(b):\n",
212 190 " print(\"Button clicked.\")\n",
213 191 "\n",
214 192 "button.on_click(on_button_clicked)"
215 193 ],
216 194 "language": "python",
217 195 "metadata": {},
218 196 "outputs": [
219 197 {
220 198 "output_type": "stream",
221 199 "stream": "stdout",
222 200 "text": [
223 201 "Button clicked.\n"
224 202 ]
225 203 },
226 204 {
227 205 "output_type": "stream",
228 206 "stream": "stdout",
229 207 "text": [
230 208 "Button clicked.\n"
231 209 ]
210 },
211 {
212 "output_type": "stream",
213 "stream": "stdout",
214 "text": [
215 "Button clicked.\n"
216 ]
232 217 }
233 218 ],
234 219 "prompt_number": 5
235 220 },
236 221 {
237 222 "cell_type": "markdown",
238 223 "metadata": {},
239 224 "source": [
240 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."
241 226 ]
242 227 },
243 228 {
244 229 "cell_type": "code",
245 230 "collapsed": false,
246 231 "input": [
247 232 "def new_button(clicked):\n",
248 233 " button = widgets.ButtonWidget()\n",
249 234 " button.clicks = 0\n",
250 235 " clicked.clicks += 1\n",
251 236 " button.description = \"%d\" % clicked.clicks\n",
252 237 " display(button)\n",
253 238 " button.on_click(new_button)\n",
254 239 "button = widgets.ButtonWidget(description = \"Start\")\n",
255 240 "button.clicks = 0\n",
256 241 "display(button)\n",
257 242 "button.on_click(new_button)\n",
258 243 " "
259 244 ],
260 245 "language": "python",
261 246 "metadata": {},
262 247 "outputs": [],
263 248 "prompt_number": 6
264 249 },
265 250 {
266 251 "cell_type": "markdown",
267 252 "metadata": {},
268 253 "source": [
269 254 "In [Part 3](Part 3 - Placement.ipynb) of this [series](index.ipynb), you will learn about widget placement."
270 255 ]
271 256 }
272 257 ],
273 258 "metadata": {}
274 259 }
275 260 ]
276 261 } No newline at end of file
@@ -1,187 +1,187 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "[< Back to Part 2](Part 2 - Events.ipynb) or [Index](index.ipynb)"
21 21 ]
22 22 },
23 23 {
24 24 "cell_type": "code",
25 25 "collapsed": false,
26 26 "input": [
27 27 "from IPython.html import widgets # Widget definitions\n",
28 28 "from IPython.display import display # Used to display widgets in the notebook"
29 29 ],
30 30 "language": "python",
31 31 "metadata": {},
32 32 "outputs": [],
33 33 "prompt_number": 1
34 34 },
35 35 {
36 36 "cell_type": "heading",
37 37 "level": 1,
38 38 "metadata": {},
39 39 "source": [
40 40 "Parent/Child Relationships"
41 41 ]
42 42 },
43 43 {
44 44 "cell_type": "markdown",
45 45 "metadata": {},
46 46 "source": [
47 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 48 "\n",
49 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 50 ]
51 51 },
52 52 {
53 53 "cell_type": "code",
54 54 "collapsed": false,
55 55 "input": [
56 56 "float_range = widgets.FloatSliderWidget()\n",
57 "string = widgets.TextBoxWidget(value='hi')\n",
57 "string = widgets.TextWidget(value='hi')\n",
58 58 "container = widgets.ContainerWidget(children=[float_range, string])\n",
59 59 "\n",
60 60 "display(container) # Displays the `container` and all of it's children."
61 61 ],
62 62 "language": "python",
63 63 "metadata": {},
64 64 "outputs": [],
65 65 "prompt_number": 2
66 66 },
67 67 {
68 68 "cell_type": "markdown",
69 69 "metadata": {},
70 70 "source": [
71 71 "Children can also be added to parents after the parent has been displayed. The parent is responsible for rendering its children."
72 72 ]
73 73 },
74 74 {
75 75 "cell_type": "code",
76 76 "collapsed": false,
77 77 "input": [
78 78 "container = widgets.ContainerWidget()\n",
79 79 "display(container)\n",
80 80 "\n",
81 81 "int_range = widgets.IntSliderWidget()\n",
82 82 "container.children=[int_range]\n"
83 83 ],
84 84 "language": "python",
85 85 "metadata": {},
86 86 "outputs": [],
87 87 "prompt_number": 3
88 88 },
89 89 {
90 90 "cell_type": "heading",
91 91 "level": 1,
92 92 "metadata": {},
93 93 "source": [
94 94 "Visibility"
95 95 ]
96 96 },
97 97 {
98 98 "cell_type": "markdown",
99 99 "metadata": {},
100 100 "source": [
101 101 "Sometimes it is necessary to hide or show widgets in place, without having to redisplay the widget.\n",
102 102 "The `visibility` property of widgets can be used to hide or show widgets that have already been displayed (as seen below)."
103 103 ]
104 104 },
105 105 {
106 106 "cell_type": "code",
107 107 "collapsed": false,
108 108 "input": [
109 109 "string = widgets.LatexWidget(value=\"Hello World!\")\n",
110 110 "display(string) "
111 111 ],
112 112 "language": "python",
113 113 "metadata": {},
114 114 "outputs": [],
115 115 "prompt_number": 4
116 116 },
117 117 {
118 118 "cell_type": "code",
119 119 "collapsed": false,
120 120 "input": [
121 121 "string.visible=False"
122 122 ],
123 123 "language": "python",
124 124 "metadata": {},
125 125 "outputs": [],
126 126 "prompt_number": 5
127 127 },
128 128 {
129 129 "cell_type": "code",
130 130 "collapsed": false,
131 131 "input": [
132 132 "string.visible=True"
133 133 ],
134 134 "language": "python",
135 135 "metadata": {},
136 136 "outputs": [],
137 137 "prompt_number": 6
138 138 },
139 139 {
140 140 "cell_type": "markdown",
141 141 "metadata": {},
142 142 "source": [
143 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 144 ]
145 145 },
146 146 {
147 147 "cell_type": "code",
148 148 "collapsed": false,
149 149 "input": [
150 150 "form = widgets.ContainerWidget()\n",
151 "first = widgets.TextBoxWidget(description=\"First Name:\")\n",
152 "last = widgets.TextBoxWidget(description=\"Last Name:\")\n",
151 "first = widgets.TextWidget(description=\"First Name:\")\n",
152 "last = widgets.TextWidget(description=\"Last Name:\")\n",
153 153 "\n",
154 "student = widgets.CheckBoxWidget(description=\"Student:\", value=False)\n",
154 "student = widgets.CheckboxWidget(description=\"Student:\", value=False)\n",
155 155 "school_info = widgets.ContainerWidget(visible=False, children=[\n",
156 " widgets.TextBoxWidget(description=\"School:\"),\n",
156 " widgets.TextWidget(description=\"School:\"),\n",
157 157 " widgets.IntTextWidget(description=\"Grade:\", min=0, max=12)\n",
158 158 " ])\n",
159 159 "\n",
160 "pet = widgets.TextBoxWidget(description=\"Pet's Name:\")\n",
160 "pet = widgets.TextWidget(description=\"Pet's Name:\")\n",
161 161 "form.children = [first, last, student, school_info, pet]\n",
162 162 "display(form)\n",
163 163 "\n",
164 164 "def on_student_toggle(name, value):\n",
165 165 " if value:\n",
166 166 " school_info.visible = True\n",
167 167 " else:\n",
168 168 " school_info.visible = False\n",
169 169 "student.on_trait_change(on_student_toggle, 'value')\n"
170 170 ],
171 171 "language": "python",
172 172 "metadata": {},
173 173 "outputs": [],
174 174 "prompt_number": 7
175 175 },
176 176 {
177 177 "cell_type": "markdown",
178 178 "metadata": {},
179 179 "source": [
180 180 "In [Part 4](Part 4 - Styles.ipynb) of this [series](index.ipynb), you will learn about widget styling."
181 181 ]
182 182 }
183 183 ],
184 184 "metadata": {}
185 185 }
186 186 ]
187 187 } No newline at end of file
@@ -1,331 +1,331 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "[< Back to Part 4](Part 4 - Styles.ipynb) or [Index](index.ipynb)"
21 21 ]
22 22 },
23 23 {
24 24 "cell_type": "code",
25 25 "collapsed": false,
26 26 "input": [
27 27 "from IPython.html import widgets # Widget definitions\n",
28 28 "from IPython.display import display # Used to display widgets in the notebook"
29 29 ],
30 30 "language": "python",
31 31 "metadata": {},
32 32 "outputs": [],
33 33 "prompt_number": 1
34 34 },
35 35 {
36 36 "cell_type": "heading",
37 37 "level": 1,
38 38 "metadata": {},
39 39 "source": [
40 40 "Alignment"
41 41 ]
42 42 },
43 43 {
44 44 "cell_type": "markdown",
45 45 "metadata": {},
46 46 "source": [
47 47 "Most widgets have a `description` attribute, which allows a label for the widget to be defined.\n",
48 48 "The label of the widget has a fixed minimum width.\n",
49 49 "The text of the label is always right aligned and the widget is left aligned:"
50 50 ]
51 51 },
52 52 {
53 53 "cell_type": "code",
54 54 "collapsed": false,
55 55 "input": [
56 "display(widgets.TextBoxWidget(description=\"a:\"))\n",
57 "display(widgets.TextBoxWidget(description=\"aa:\"))\n",
58 "display(widgets.TextBoxWidget(description=\"aaa:\"))"
56 "display(widgets.TextWidget(description=\"a:\"))\n",
57 "display(widgets.TextWidget(description=\"aa:\"))\n",
58 "display(widgets.TextWidget(description=\"aaa:\"))"
59 59 ],
60 60 "language": "python",
61 61 "metadata": {},
62 62 "outputs": [],
63 "prompt_number": 3
63 "prompt_number": 2
64 64 },
65 65 {
66 66 "cell_type": "markdown",
67 67 "metadata": {},
68 68 "source": [
69 69 "If a label is longer than the minimum width, the widget is shifted to the right:"
70 70 ]
71 71 },
72 72 {
73 73 "cell_type": "code",
74 74 "collapsed": false,
75 75 "input": [
76 "display(widgets.TextBoxWidget(description=\"a:\"))\n",
77 "display(widgets.TextBoxWidget(description=\"aa:\"))\n",
78 "display(widgets.TextBoxWidget(description=\"aaa:\"))\n",
79 "display(widgets.TextBoxWidget(description=\"aaaaaaaaaaaaaaaaaa:\"))"
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 80 ],
81 81 "language": "python",
82 82 "metadata": {},
83 83 "outputs": [],
84 "prompt_number": 4
84 "prompt_number": 3
85 85 },
86 86 {
87 87 "cell_type": "markdown",
88 88 "metadata": {},
89 89 "source": [
90 90 "If a `description` is not set for the widget, the label is not displayed:"
91 91 ]
92 92 },
93 93 {
94 94 "cell_type": "code",
95 95 "collapsed": false,
96 96 "input": [
97 "display(widgets.TextBoxWidget(description=\"a:\"))\n",
98 "display(widgets.TextBoxWidget(description=\"aa:\"))\n",
99 "display(widgets.TextBoxWidget(description=\"aaa:\"))\n",
100 "display(widgets.TextBoxWidget())"
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 101 ],
102 102 "language": "python",
103 103 "metadata": {},
104 104 "outputs": [],
105 "prompt_number": 5
105 "prompt_number": 4
106 106 },
107 107 {
108 108 "cell_type": "heading",
109 109 "level": 1,
110 110 "metadata": {},
111 111 "source": [
112 112 "Custom Alignment"
113 113 ]
114 114 },
115 115 {
116 116 "cell_type": "markdown",
117 117 "metadata": {},
118 118 "source": [
119 119 "`ContainerWidget`s allow for custom alignment of widgets.\n",
120 120 "The `hbox` and `vbox` CSS classes cause the `ContainerWidget` to horizontally or vertically align its children."
121 121 ]
122 122 },
123 123 {
124 124 "cell_type": "code",
125 125 "collapsed": false,
126 126 "input": [
127 127 "child_style = {\n",
128 128 " 'background': '#77CC77',\n",
129 129 " 'padding': '25px',\n",
130 130 " 'margin': '5px',\n",
131 131 " 'font-size': 'xx-large',\n",
132 132 " 'color': 'white',\n",
133 133 "}\n",
134 134 "\n",
135 135 "def make_container(title):\n",
136 136 " header = widgets.LatexWidget(value=title) \n",
137 137 " display(header)\n",
138 138 " header.set_css({\n",
139 139 " 'font-size': '30pt',\n",
140 140 " 'margin-top': '40pt',\n",
141 141 " 'margin-bottom': '20pt',\n",
142 142 " })\n",
143 143 " \n",
144 144 " container = widgets.ContainerWidget()\n",
145 145 " container.set_css('background', '#999999')\n",
146 146 " display(container)\n",
147 147 " return container\n",
148 148 "\n",
149 149 "def fill_container(container):\n",
150 150 " components = []\n",
151 151 " for i in range(3):\n",
152 152 " components.append(widgets.LatexWidget(value=\"ABC\"[i]))\n",
153 153 " components[i].set_css(child_style)\n",
154 154 " container.children = components\n",
155 155 " \n",
156 156 "container = make_container('VBox')\n",
157 157 "container.add_class('vbox')\n",
158 158 "fill_container(container)\n",
159 159 "\n",
160 160 "container = make_container('HBox')\n",
161 161 "container.add_class('hbox')\n",
162 162 "fill_container(container)\n"
163 163 ],
164 164 "language": "python",
165 165 "metadata": {},
166 166 "outputs": [],
167 "prompt_number": 6
167 "prompt_number": 5
168 168 },
169 169 {
170 170 "cell_type": "markdown",
171 171 "metadata": {},
172 172 "source": [
173 173 "The `start`, `center`, and `end` classes adjust the alignment of the widgets on the axis where they are being rendered.\n",
174 174 "Below is an example of the different alignments."
175 175 ]
176 176 },
177 177 {
178 178 "cell_type": "code",
179 179 "collapsed": false,
180 180 "input": [
181 181 "container = make_container('HBox Pack Start')\n",
182 182 "container.add_class('hbox')\n",
183 183 "container.add_class('start')\n",
184 184 "fill_container(container)\n",
185 185 " \n",
186 186 "container = make_container('HBox Pack Center')\n",
187 187 "container.add_class('hbox')\n",
188 188 "container.add_class('center')\n",
189 189 "fill_container(container)\n",
190 190 " \n",
191 191 "container = make_container('HBox Pack End')\n",
192 192 "container.add_class('hbox')\n",
193 193 "container.add_class('end')\n",
194 194 "fill_container(container)"
195 195 ],
196 196 "language": "python",
197 197 "metadata": {},
198 198 "outputs": [],
199 199 "prompt_number": 6
200 200 },
201 201 {
202 202 "cell_type": "markdown",
203 203 "metadata": {},
204 204 "source": [
205 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 206 ]
207 207 },
208 208 {
209 209 "cell_type": "code",
210 210 "collapsed": false,
211 211 "input": [
212 212 "def fill_container(container, flexes):\n",
213 213 " components = []\n",
214 214 " for i in range(len(flexes)):\n",
215 215 " components.append(widgets.ContainerWidget())\n",
216 216 " components[i].set_css(child_style)\n",
217 217 " \n",
218 218 " label = widgets.LatexWidget(value=str(flexes[i]))\n",
219 219 " components[i].children = [label]\n",
220 220 " container.children = components\n",
221 221 " \n",
222 222 " for i in range(len(flexes)):\n",
223 223 " if flexes[i] == 0:\n",
224 224 " components[i].add_class('box-flex0')\n",
225 225 " elif flexes[i] == 1:\n",
226 226 " components[i].add_class('box-flex1')\n",
227 227 " elif flexes[i] == 2:\n",
228 228 " components[i].add_class('box-flex2')\n",
229 229 " \n",
230 230 "container = make_container('Different Flex Configurations')\n",
231 231 "container.add_class('hbox')\n",
232 232 "fill_container(container, [0, 0, 0])\n",
233 233 " \n",
234 234 "container = make_container('')\n",
235 235 "container.add_class('hbox')\n",
236 236 "fill_container(container, [0, 0, 1])\n",
237 237 " \n",
238 238 "container = make_container('')\n",
239 239 "container.add_class('hbox')\n",
240 240 "fill_container(container, [0, 1, 1])\n",
241 241 " \n",
242 242 "container = make_container('')\n",
243 243 "container.add_class('hbox')\n",
244 244 "fill_container(container, [0, 2, 2])\n",
245 245 " \n",
246 246 "container = make_container('')\n",
247 247 "container.add_class('hbox')\n",
248 248 "fill_container(container, [0, 1, 2])\n",
249 249 " \n",
250 250 "container = make_container('')\n",
251 251 "container.add_class('hbox')\n",
252 252 "fill_container(container, [1, 1, 2])"
253 253 ],
254 254 "language": "python",
255 255 "metadata": {},
256 256 "outputs": [],
257 257 "prompt_number": 7
258 258 },
259 259 {
260 260 "cell_type": "markdown",
261 261 "metadata": {},
262 262 "source": [
263 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 264 ]
265 265 },
266 266 {
267 267 "cell_type": "code",
268 268 "collapsed": false,
269 269 "input": [
270 270 "def fill_container(container):\n",
271 271 " components = []\n",
272 272 " for i in range(3):\n",
273 273 " components.append(widgets.LatexWidget(parent=container, value=\"ABC\"[i]))\n",
274 274 " components[i].set_css(child_style)\n",
275 275 " components[i].set_css('height', str((i+1) * 50) + 'px')\n",
276 276 " container.children = components\n",
277 277 "\n",
278 278 "container = make_container('HBox Align Start')\n",
279 279 "container.add_class(\"hbox\")\n",
280 280 "container.add_class(\"align-start\")\n",
281 281 "fill_container(container)\n",
282 282 " \n",
283 283 "container = make_container('HBox Align Center')\n",
284 284 "container.add_class(\"hbox\")\n",
285 285 "container.add_class(\"align-center\")\n",
286 286 "fill_container(container)\n",
287 287 " \n",
288 288 "container = make_container('HBox Align End')\n",
289 289 "container.add_class(\"hbox\")\n",
290 290 "container.add_class(\"align-end\")\n",
291 291 "fill_container(container)"
292 292 ],
293 293 "language": "python",
294 294 "metadata": {},
295 295 "outputs": [],
296 296 "prompt_number": 8
297 297 },
298 298 {
299 299 "cell_type": "markdown",
300 300 "metadata": {},
301 301 "source": [
302 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 303 ]
304 304 },
305 305 {
306 306 "cell_type": "code",
307 307 "collapsed": false,
308 308 "input": [
309 309 "container = widgets.ContainerWidget()\n",
310 310 "container.children=[widgets.FloatSliderWidget(orientation='vertical', description=str(i+1), value=50.0) \n",
311 311 " for i in range(15)]\n",
312 312 "display(container)\n",
313 313 "container.add_class('hbox')"
314 314 ],
315 315 "language": "python",
316 316 "metadata": {},
317 317 "outputs": [],
318 318 "prompt_number": 9
319 319 },
320 320 {
321 321 "cell_type": "markdown",
322 322 "metadata": {},
323 323 "source": [
324 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 325 ]
326 326 }
327 327 ],
328 328 "metadata": {}
329 329 }
330 330 ]
331 331 } No newline at end of file
@@ -1,1058 +1,1058 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "[< Back to Part 5](Part 5 - Alignment.ipynb) or [Index](index.ipynb)\n",
21 21 "\n",
22 22 "Before reading, make sure to review\n",
23 23 "\n",
24 24 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
25 25 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
26 26 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
27 27 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
28 28 ]
29 29 },
30 30 {
31 31 "cell_type": "code",
32 32 "collapsed": false,
33 33 "input": [
34 34 "from __future__ import print_function # For py 2.7 compat\n",
35 35 "\n",
36 36 "from IPython.html import widgets # Widget definitions\n",
37 37 "from IPython.display import display # Used to display widgets in the notebook\n",
38 38 "from IPython.utils.traitlets import Unicode # Used to declare attributes of our widget"
39 39 ],
40 40 "language": "python",
41 41 "metadata": {},
42 42 "outputs": [],
43 43 "prompt_number": 1
44 44 },
45 45 {
46 46 "cell_type": "heading",
47 47 "level": 1,
48 48 "metadata": {},
49 49 "source": [
50 50 "Abstract"
51 51 ]
52 52 },
53 53 {
54 54 "cell_type": "markdown",
55 55 "metadata": {},
56 56 "source": [
57 57 "This notebook implements a custom date picker widget,\n",
58 58 "in order to demonstrate the widget creation process.\n",
59 59 "\n",
60 60 "To create a custom widget, both Python and JavaScript code is required."
61 61 ]
62 62 },
63 63 {
64 64 "cell_type": "heading",
65 65 "level": 1,
66 66 "metadata": {},
67 67 "source": [
68 68 "Section 1 - Basics"
69 69 ]
70 70 },
71 71 {
72 72 "cell_type": "heading",
73 73 "level": 2,
74 74 "metadata": {},
75 75 "source": [
76 76 "Python"
77 77 ]
78 78 },
79 79 {
80 80 "cell_type": "markdown",
81 81 "metadata": {},
82 82 "source": [
83 83 "When starting a project like this, it is often easiest to make a simple base implementation,\n",
84 84 "to verify that the underlying framework is working as expected.\n",
85 85 "To start, we will create an empty widget and make sure that it can be rendered.\n",
86 86 "The first step is to define the widget in Python."
87 87 ]
88 88 },
89 89 {
90 90 "cell_type": "code",
91 91 "collapsed": false,
92 92 "input": [
93 93 "class DateWidget(widgets.DOMWidget):\n",
94 94 " _view_name = Unicode('DatePickerView', sync=True)"
95 95 ],
96 96 "language": "python",
97 97 "metadata": {},
98 98 "outputs": [],
99 99 "prompt_number": 2
100 100 },
101 101 {
102 102 "cell_type": "markdown",
103 103 "metadata": {},
104 104 "source": [
105 105 "Our widget inherits from `widgets.DOMWidget` since it is intended that it will be displayed in the notebook directly.\n",
106 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 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 108 ]
109 109 },
110 110 {
111 111 "cell_type": "heading",
112 112 "level": 2,
113 113 "metadata": {},
114 114 "source": [
115 115 "JavaScript"
116 116 ]
117 117 },
118 118 {
119 119 "cell_type": "markdown",
120 120 "metadata": {},
121 121 "source": [
122 122 "In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies.\n",
123 123 "All IPython widget code depends on `notebook/js/widgets/widget.js`,\n",
124 124 "where the base widget model and base view are defined.\n",
125 125 "We use require.js to load this file:"
126 126 ]
127 127 },
128 128 {
129 129 "cell_type": "code",
130 130 "collapsed": false,
131 131 "input": [
132 132 "%%javascript\n",
133 133 "\n",
134 134 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
135 135 "\n",
136 136 "});"
137 137 ],
138 138 "language": "python",
139 139 "metadata": {},
140 140 "outputs": [
141 141 {
142 142 "javascript": [
143 143 "\n",
144 144 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
145 145 "\n",
146 146 "});"
147 147 ],
148 148 "metadata": {},
149 149 "output_type": "display_data",
150 150 "text": [
151 151 "<IPython.core.display.Javascript at 0x109491690>"
152 152 ]
153 153 }
154 154 ],
155 155 "prompt_number": 3
156 156 },
157 157 {
158 158 "cell_type": "markdown",
159 159 "metadata": {},
160 160 "source": [
161 161 "Now we need to define a view that can be used to represent the model.\n",
162 162 "To do this, the `IPython.DOMWidgetView` is extended.\n",
163 163 "**A render function must be defined**.\n",
164 164 "The render function is used to render a widget view instance to the DOM.\n",
165 165 "For now, the render function renders a div that contains the text *Hello World!*\n",
166 166 "Lastly, the view needs to be registered with the widget manager.\n",
167 167 "\n",
168 168 "**Final JavaScript code below:**"
169 169 ]
170 170 },
171 171 {
172 172 "cell_type": "code",
173 173 "collapsed": false,
174 174 "input": [
175 175 "%%javascript\n",
176 176 "\n",
177 177 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
178 178 " \n",
179 179 " // Define the DatePickerView\n",
180 180 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
181 181 " render: function(){ this.$el.text('Hello World!'); },\n",
182 182 " });\n",
183 183 " \n",
184 184 " // Register the DatePickerView with the widget manager.\n",
185 185 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
186 186 "});"
187 187 ],
188 188 "language": "python",
189 189 "metadata": {},
190 190 "outputs": [
191 191 {
192 192 "javascript": [
193 193 "\n",
194 194 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
195 195 " \n",
196 196 " // Define the DatePickerView\n",
197 197 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
198 198 " render: function(){ this.$el.text('Hello World!'); },\n",
199 199 " });\n",
200 200 " \n",
201 201 " // Register the DatePickerView with the widget manager.\n",
202 202 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
203 203 "});"
204 204 ],
205 205 "metadata": {},
206 206 "output_type": "display_data",
207 207 "text": [
208 208 "<IPython.core.display.Javascript at 0x1094917d0>"
209 209 ]
210 210 }
211 211 ],
212 212 "prompt_number": 4
213 213 },
214 214 {
215 215 "cell_type": "heading",
216 216 "level": 2,
217 217 "metadata": {},
218 218 "source": [
219 219 "Test"
220 220 ]
221 221 },
222 222 {
223 223 "cell_type": "markdown",
224 224 "metadata": {},
225 225 "source": [
226 226 "To test what we have so far, create the widget, just like you would the builtin widgets:"
227 227 ]
228 228 },
229 229 {
230 230 "cell_type": "code",
231 231 "collapsed": false,
232 232 "input": [
233 233 "DateWidget()"
234 234 ],
235 235 "language": "python",
236 236 "metadata": {},
237 237 "outputs": [],
238 238 "prompt_number": 5
239 239 },
240 240 {
241 241 "cell_type": "heading",
242 242 "level": 1,
243 243 "metadata": {},
244 244 "source": [
245 245 "Section 2 - Something useful"
246 246 ]
247 247 },
248 248 {
249 249 "cell_type": "heading",
250 250 "level": 2,
251 251 "metadata": {},
252 252 "source": [
253 253 "Python"
254 254 ]
255 255 },
256 256 {
257 257 "cell_type": "markdown",
258 258 "metadata": {},
259 259 "source": [
260 260 "In the last section we created a simple widget that displayed *Hello World!*\n",
261 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 262 "The new attribute must be a traitlet, so the widget machinery can handle it.\n",
263 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 264 "Adding this to the code from the last section:"
265 265 ]
266 266 },
267 267 {
268 268 "cell_type": "code",
269 269 "collapsed": false,
270 270 "input": [
271 271 "class DateWidget(widgets.DOMWidget):\n",
272 272 " _view_name = Unicode('DatePickerView', sync=True)\n",
273 273 " value = Unicode(sync=True)"
274 274 ],
275 275 "language": "python",
276 276 "metadata": {},
277 277 "outputs": [],
278 278 "prompt_number": 6
279 279 },
280 280 {
281 281 "cell_type": "heading",
282 282 "level": 2,
283 283 "metadata": {},
284 284 "source": [
285 285 "JavaScript"
286 286 ]
287 287 },
288 288 {
289 289 "cell_type": "markdown",
290 290 "metadata": {},
291 291 "source": [
292 292 "In the JavaScript, there is no need to define counterparts to the traitlets.\n",
293 293 "When the JavaScript model is created for the first time,\n",
294 294 "it copies all of the traitlet `sync=True` attributes from the Python model.\n",
295 295 "We need to replace *Hello World!* with an actual HTML date picker widget."
296 296 ]
297 297 },
298 298 {
299 299 "cell_type": "code",
300 300 "collapsed": false,
301 301 "input": [
302 302 "%%javascript\n",
303 303 "\n",
304 304 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
305 305 " \n",
306 306 " // Define the DatePickerView\n",
307 307 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
308 308 " render: function(){\n",
309 309 " \n",
310 310 " // Create the date picker control.\n",
311 311 " this.$date = $('<input />')\n",
312 312 " .attr('type', 'date')\n",
313 313 " .appendTo(this.$el);\n",
314 314 " },\n",
315 315 " });\n",
316 316 " \n",
317 317 " // Register the DatePickerView with the widget manager.\n",
318 318 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
319 319 "});"
320 320 ],
321 321 "language": "python",
322 322 "metadata": {},
323 323 "outputs": [
324 324 {
325 325 "javascript": [
326 326 "\n",
327 327 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
328 328 " \n",
329 329 " // Define the DatePickerView\n",
330 330 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
331 331 " render: function(){\n",
332 332 " \n",
333 333 " // Create the date picker control.\n",
334 334 " this.$date = $('<input />')\n",
335 335 " .attr('type', 'date')\n",
336 336 " .appendTo(this.$el);\n",
337 337 " },\n",
338 338 " });\n",
339 339 " \n",
340 340 " // Register the DatePickerView with the widget manager.\n",
341 341 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
342 342 "});"
343 343 ],
344 344 "metadata": {},
345 345 "output_type": "display_data",
346 346 "text": [
347 347 "<IPython.core.display.Javascript at 0x109491750>"
348 348 ]
349 349 }
350 350 ],
351 351 "prompt_number": 7
352 352 },
353 353 {
354 354 "cell_type": "markdown",
355 355 "metadata": {},
356 356 "source": [
357 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 358 ]
359 359 },
360 360 {
361 361 "cell_type": "code",
362 362 "collapsed": false,
363 363 "input": [
364 364 "%%javascript\n",
365 365 "\n",
366 366 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
367 367 " \n",
368 368 " // Define the DatePickerView\n",
369 369 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
370 370 " render: function(){\n",
371 371 " \n",
372 372 " // Create the date picker control.\n",
373 373 " this.$date = $('<input />')\n",
374 374 " .attr('type', 'date')\n",
375 375 " .appendTo(this.$el);\n",
376 376 " },\n",
377 377 " \n",
378 378 " update: function() {\n",
379 379 " \n",
380 380 " // Set the value of the date control and then call base.\n",
381 381 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
382 382 " return DatePickerView.__super__.update.apply(this);\n",
383 383 " },\n",
384 384 " });\n",
385 385 " \n",
386 386 " // Register the DatePickerView with the widget manager.\n",
387 387 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
388 388 "});"
389 389 ],
390 390 "language": "python",
391 391 "metadata": {},
392 392 "outputs": [
393 393 {
394 394 "javascript": [
395 395 "\n",
396 396 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
397 397 " \n",
398 398 " // Define the DatePickerView\n",
399 399 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
400 400 " render: function(){\n",
401 401 " \n",
402 402 " // Create the date picker control.\n",
403 403 " this.$date = $('<input />')\n",
404 404 " .attr('type', 'date')\n",
405 405 " .appendTo(this.$el);\n",
406 406 " },\n",
407 407 " \n",
408 408 " update: function() {\n",
409 409 " \n",
410 410 " // Set the value of the date control and then call base.\n",
411 411 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
412 412 " return DatePickerView.__super__.update.apply(this);\n",
413 413 " },\n",
414 414 " });\n",
415 415 " \n",
416 416 " // Register the DatePickerView with the widget manager.\n",
417 417 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
418 418 "});"
419 419 ],
420 420 "metadata": {},
421 421 "output_type": "display_data",
422 422 "text": [
423 423 "<IPython.core.display.Javascript at 0x109491750>"
424 424 ]
425 425 }
426 426 ],
427 427 "prompt_number": 8
428 428 },
429 429 {
430 430 "cell_type": "markdown",
431 431 "metadata": {},
432 432 "source": [
433 433 "To get the changed value from the frontend to publish itself to the backend,\n",
434 434 "we need to listen to the change event triggered by the HTM date control and set the value in the model.\n",
435 435 "After the date change event fires and the new value is set in the model,\n",
436 436 "it is very important that we call `this.touch()` to let the widget machinery know which view changed the model.\n",
437 437 "This is important because the widget machinery needs to know which cell to route the message callbacks to.\n",
438 438 "\n",
439 439 "**Final JavaScript code below:**"
440 440 ]
441 441 },
442 442 {
443 443 "cell_type": "code",
444 444 "collapsed": false,
445 445 "input": [
446 446 "%%javascript\n",
447 447 "\n",
448 448 "\n",
449 449 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
450 450 " \n",
451 451 " // Define the DatePickerView\n",
452 452 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
453 453 " render: function(){\n",
454 454 " \n",
455 455 " // Create the date picker control.\n",
456 456 " this.$date = $('<input />')\n",
457 457 " .attr('type', 'date')\n",
458 458 " .appendTo(this.$el);\n",
459 459 " },\n",
460 460 " \n",
461 461 " update: function() {\n",
462 462 " \n",
463 463 " // Set the value of the date control and then call base.\n",
464 464 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
465 465 " return DatePickerView.__super__.update.apply(this);\n",
466 466 " },\n",
467 467 " \n",
468 468 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
469 469 " events: {\"change\": \"handle_date_change\"},\n",
470 470 " \n",
471 471 " // Callback for when the date is changed.\n",
472 472 " handle_date_change: function(event) {\n",
473 473 " this.model.set('value', this.$date.val());\n",
474 474 " this.touch();\n",
475 475 " },\n",
476 476 " });\n",
477 477 " \n",
478 478 " // Register the DatePickerView with the widget manager.\n",
479 479 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
480 480 "});"
481 481 ],
482 482 "language": "python",
483 483 "metadata": {},
484 484 "outputs": [
485 485 {
486 486 "javascript": [
487 487 "\n",
488 488 "\n",
489 489 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
490 490 " \n",
491 491 " // Define the DatePickerView\n",
492 492 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
493 493 " render: function(){\n",
494 494 " \n",
495 495 " // Create the date picker control.\n",
496 496 " this.$date = $('<input />')\n",
497 497 " .attr('type', 'date')\n",
498 498 " .appendTo(this.$el);\n",
499 499 " },\n",
500 500 " \n",
501 501 " update: function() {\n",
502 502 " \n",
503 503 " // Set the value of the date control and then call base.\n",
504 504 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
505 505 " return DatePickerView.__super__.update.apply(this);\n",
506 506 " },\n",
507 507 " \n",
508 508 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
509 509 " events: {\"change\": \"handle_date_change\"},\n",
510 510 " \n",
511 511 " // Callback for when the date is changed.\n",
512 512 " handle_date_change: function(event) {\n",
513 513 " this.model.set('value', this.$date.val());\n",
514 514 " this.touch();\n",
515 515 " },\n",
516 516 " });\n",
517 517 " \n",
518 518 " // Register the DatePickerView with the widget manager.\n",
519 519 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
520 520 "});"
521 521 ],
522 522 "metadata": {},
523 523 "output_type": "display_data",
524 524 "text": [
525 525 "<IPython.core.display.Javascript at 0x109491b10>"
526 526 ]
527 527 }
528 528 ],
529 529 "prompt_number": 9
530 530 },
531 531 {
532 532 "cell_type": "heading",
533 533 "level": 2,
534 534 "metadata": {},
535 535 "source": [
536 536 "Test"
537 537 ]
538 538 },
539 539 {
540 540 "cell_type": "markdown",
541 541 "metadata": {},
542 542 "source": [
543 543 "To test, create the widget the same way that the other widgets are created."
544 544 ]
545 545 },
546 546 {
547 547 "cell_type": "code",
548 548 "collapsed": false,
549 549 "input": [
550 550 "my_widget = DateWidget()\n",
551 551 "display(my_widget)"
552 552 ],
553 553 "language": "python",
554 554 "metadata": {},
555 555 "outputs": [],
556 556 "prompt_number": 10
557 557 },
558 558 {
559 559 "cell_type": "markdown",
560 560 "metadata": {},
561 561 "source": [
562 562 "Display the widget again to make sure that both views remain in sync."
563 563 ]
564 564 },
565 565 {
566 566 "cell_type": "code",
567 567 "collapsed": false,
568 568 "input": [
569 569 "my_widget"
570 570 ],
571 571 "language": "python",
572 572 "metadata": {},
573 573 "outputs": [],
574 574 "prompt_number": 11
575 575 },
576 576 {
577 577 "cell_type": "markdown",
578 578 "metadata": {},
579 579 "source": [
580 580 "Read the date from Python"
581 581 ]
582 582 },
583 583 {
584 584 "cell_type": "code",
585 585 "collapsed": false,
586 586 "input": [
587 587 "my_widget.value"
588 588 ],
589 589 "language": "python",
590 590 "metadata": {},
591 591 "outputs": [
592 592 {
593 593 "metadata": {},
594 594 "output_type": "pyout",
595 595 "prompt_number": 12,
596 596 "text": [
597 597 "u''"
598 598 ]
599 599 }
600 600 ],
601 601 "prompt_number": 12
602 602 },
603 603 {
604 604 "cell_type": "markdown",
605 605 "metadata": {},
606 606 "source": [
607 607 "Set the date from Python"
608 608 ]
609 609 },
610 610 {
611 611 "cell_type": "code",
612 612 "collapsed": false,
613 613 "input": [
614 614 "my_widget.value = \"1998-12-01\" # December 1st, 1998"
615 615 ],
616 616 "language": "python",
617 617 "metadata": {},
618 618 "outputs": [],
619 619 "prompt_number": 13
620 620 },
621 621 {
622 622 "cell_type": "heading",
623 623 "level": 1,
624 624 "metadata": {},
625 625 "source": [
626 626 "Section 3 - Extra credit"
627 627 ]
628 628 },
629 629 {
630 630 "cell_type": "markdown",
631 631 "metadata": {},
632 632 "source": [
633 633 "The 3rd party `dateutil` library is required to continue. https://pypi.python.org/pypi/python-dateutil"
634 634 ]
635 635 },
636 636 {
637 637 "cell_type": "code",
638 638 "collapsed": false,
639 639 "input": [
640 640 "# Import the dateutil library to parse date strings.\n",
641 641 "from dateutil import parser"
642 642 ],
643 643 "language": "python",
644 644 "metadata": {},
645 645 "outputs": [],
646 646 "prompt_number": 14
647 647 },
648 648 {
649 649 "cell_type": "markdown",
650 650 "metadata": {},
651 651 "source": [
652 652 "In the last section we created a fully working date picker widget.\n",
653 653 "Now we will add custom validation and support for labels.\n",
654 654 "So far, only the ISO date format \"YYYY-MM-DD\" is supported.\n",
655 655 "Now, we will add support for all of the date formats recognized by the Python dateutil library."
656 656 ]
657 657 },
658 658 {
659 659 "cell_type": "heading",
660 660 "level": 2,
661 661 "metadata": {},
662 662 "source": [
663 663 "Python"
664 664 ]
665 665 },
666 666 {
667 667 "cell_type": "markdown",
668 668 "metadata": {},
669 669 "source": [
670 670 "The standard property name used for widget labels is `description`.\n",
671 671 "In the code block below, `description` has been added to the Python widget."
672 672 ]
673 673 },
674 674 {
675 675 "cell_type": "code",
676 676 "collapsed": false,
677 677 "input": [
678 678 "class DateWidget(widgets.DOMWidget):\n",
679 679 " _view_name = Unicode('DatePickerView', sync=True)\n",
680 680 " value = Unicode(sync=True)\n",
681 681 " description = Unicode(sync=True)"
682 682 ],
683 683 "language": "python",
684 684 "metadata": {},
685 685 "outputs": [],
686 686 "prompt_number": 15
687 687 },
688 688 {
689 689 "cell_type": "markdown",
690 690 "metadata": {},
691 691 "source": [
692 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 693 "We can take advantage of this to perform validation and parsing of different date string formats.\n",
694 694 "Below, a method that listens to value has been added to the DateWidget."
695 695 ]
696 696 },
697 697 {
698 698 "cell_type": "code",
699 699 "collapsed": false,
700 700 "input": [
701 701 "class DateWidget(widgets.DOMWidget):\n",
702 702 " _view_name = Unicode('DatePickerView', sync=True)\n",
703 703 " value = Unicode(sync=True)\n",
704 704 " description = Unicode(sync=True)\n",
705 705 "\n",
706 706 " # This function automatically gets called by the traitlet machinery when\n",
707 707 " # value is modified because of this function's name.\n",
708 708 " def _value_changed(self, name, old_value, new_value):\n",
709 709 " pass"
710 710 ],
711 711 "language": "python",
712 712 "metadata": {},
713 713 "outputs": [],
714 714 "prompt_number": 16
715 715 },
716 716 {
717 717 "cell_type": "markdown",
718 718 "metadata": {},
719 719 "source": [
720 720 "Now the function parses the date string,\n",
721 721 "and only sets the value in the correct format."
722 722 ]
723 723 },
724 724 {
725 725 "cell_type": "code",
726 726 "collapsed": false,
727 727 "input": [
728 728 "class DateWidget(widgets.DOMWidget):\n",
729 729 " _view_name = Unicode('DatePickerView', sync=True)\n",
730 730 " value = Unicode(sync=True)\n",
731 731 " description = Unicode(sync=True)\n",
732 732 " \n",
733 733 " # This function automatically gets called by the traitlet machinery when\n",
734 734 " # value is modified because of this function's name.\n",
735 735 " def _value_changed(self, name, old_value, new_value):\n",
736 736 " \n",
737 737 " # Parse the date time value.\n",
738 738 " try:\n",
739 739 " parsed_date = parser.parse(new_value)\n",
740 740 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
741 741 " except:\n",
742 742 " parsed_date_string = ''\n",
743 743 " \n",
744 744 " # Set the parsed date string if the current date string is different.\n",
745 745 " if self.value != parsed_date_string:\n",
746 746 " self.value = parsed_date_string"
747 747 ],
748 748 "language": "python",
749 749 "metadata": {},
750 750 "outputs": [],
751 751 "prompt_number": 17
752 752 },
753 753 {
754 754 "cell_type": "markdown",
755 755 "metadata": {},
756 756 "source": [
757 757 "Finally, a `CallbackDispatcher` is added so the user can perform custom validation.\n",
758 758 "If any one of the callbacks registered with the dispatcher returns False,\n",
759 759 "the new date is not set.\n",
760 760 "\n",
761 761 "**Final Python code below:**"
762 762 ]
763 763 },
764 764 {
765 765 "cell_type": "code",
766 766 "collapsed": false,
767 767 "input": [
768 768 "class DateWidget(widgets.DOMWidget):\n",
769 769 " _view_name = Unicode('DatePickerView', sync=True)\n",
770 770 " value = Unicode(sync=True)\n",
771 771 " description = Unicode(sync=True)\n",
772 772 " \n",
773 773 " def __init__(self, **kwargs):\n",
774 774 " super(DateWidget, self).__init__(**kwargs)\n",
775 775 " \n",
776 776 " self.validate = widgets.CallbackDispatcher()\n",
777 777 " \n",
778 778 " # This function automatically gets called by the traitlet machinery when\n",
779 779 " # value is modified because of this function's name.\n",
780 780 " def _value_changed(self, name, old_value, new_value):\n",
781 781 " \n",
782 782 " # Parse the date time value.\n",
783 783 " try:\n",
784 784 " parsed_date = parser.parse(new_value)\n",
785 785 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
786 786 " except:\n",
787 787 " parsed_date_string = ''\n",
788 788 " \n",
789 789 " # Set the parsed date string if the current date string is different.\n",
790 790 " if old_value != new_value:\n",
791 791 " valid = self.validate(parsed_date)\n",
792 792 " if valid in (None, True):\n",
793 793 " self.value = parsed_date_string\n",
794 794 " else:\n",
795 795 " self.value = old_value\n",
796 796 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
797 797 " # We need to force the back-end to send the front-end the state\n",
798 798 " # to make sure that the date control date doesn't change."
799 799 ],
800 800 "language": "python",
801 801 "metadata": {},
802 802 "outputs": [],
803 803 "prompt_number": 18
804 804 },
805 805 {
806 806 "cell_type": "heading",
807 807 "level": 2,
808 808 "metadata": {},
809 809 "source": [
810 810 "JavaScript"
811 811 ]
812 812 },
813 813 {
814 814 "cell_type": "markdown",
815 815 "metadata": {},
816 816 "source": [
817 817 "Using the Javascript code from the last section,\n",
818 818 "we add a label to the date time object.\n",
819 819 "The label is a div with the `widget-hlabel` class applied to it.\n",
820 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 821 "Similar to the `widget-hlabel` class is the `widget-hbox-single` class.\n",
822 822 "The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget.\n",
823 823 "\n",
824 824 "We hide the label if the description value is blank."
825 825 ]
826 826 },
827 827 {
828 828 "cell_type": "code",
829 829 "collapsed": false,
830 830 "input": [
831 831 "%%javascript\n",
832 832 "\n",
833 833 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
834 834 " \n",
835 835 " // Define the DatePickerView\n",
836 836 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
837 837 " render: function(){\n",
838 838 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
839 839 " it fit with the other built in widgets.*/\n",
840 840 " // Create a label.\n",
841 841 " this.$label = $('<div />')\n",
842 842 " .addClass('widget-hlabel')\n",
843 843 " .appendTo(this.$el)\n",
844 844 " .hide(); // Hide the label by default.\n",
845 845 " \n",
846 846 " // Create the date picker control.\n",
847 847 " this.$date = $('<input />')\n",
848 848 " .attr('type', 'date')\n",
849 849 " .appendTo(this.$el);\n",
850 850 " },\n",
851 851 " \n",
852 852 " update: function() {\n",
853 853 " \n",
854 854 " // Set the value of the date control and then call base.\n",
855 855 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
856 856 " \n",
857 857 " // Hide or show the label depending on the existance of a description.\n",
858 858 " var description = this.model.get('description');\n",
859 859 " if (description == undefined || description == '') {\n",
860 860 " this.$label.hide();\n",
861 861 " } else {\n",
862 862 " this.$label.show();\n",
863 863 " this.$label.text(description);\n",
864 864 " }\n",
865 865 " \n",
866 866 " return DatePickerView.__super__.update.apply(this);\n",
867 867 " },\n",
868 868 " \n",
869 869 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
870 870 " events: {\"change\": \"handle_date_change\"},\n",
871 871 " \n",
872 872 " // Callback for when the date is changed.\n",
873 873 " handle_date_change: function(event) {\n",
874 874 " this.model.set('value', this.$date.val());\n",
875 875 " this.touch();\n",
876 876 " },\n",
877 877 " });\n",
878 878 " \n",
879 879 " // Register the DatePickerView with the widget manager.\n",
880 880 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
881 881 "});"
882 882 ],
883 883 "language": "python",
884 884 "metadata": {},
885 885 "outputs": [
886 886 {
887 887 "javascript": [
888 888 "\n",
889 889 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
890 890 " \n",
891 891 " // Define the DatePickerView\n",
892 892 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
893 893 " render: function(){\n",
894 894 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
895 895 " it fit with the other built in widgets.*/\n",
896 896 " // Create a label.\n",
897 897 " this.$label = $('<div />')\n",
898 898 " .addClass('widget-hlabel')\n",
899 899 " .appendTo(this.$el)\n",
900 900 " .hide(); // Hide the label by default.\n",
901 901 " \n",
902 902 " // Create the date picker control.\n",
903 903 " this.$date = $('<input />')\n",
904 904 " .attr('type', 'date')\n",
905 905 " .appendTo(this.$el);\n",
906 906 " },\n",
907 907 " \n",
908 908 " update: function() {\n",
909 909 " \n",
910 910 " // Set the value of the date control and then call base.\n",
911 911 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
912 912 " \n",
913 913 " // Hide or show the label depending on the existance of a description.\n",
914 914 " var description = this.model.get('description');\n",
915 915 " if (description == undefined || description == '') {\n",
916 916 " this.$label.hide();\n",
917 917 " } else {\n",
918 918 " this.$label.show();\n",
919 919 " this.$label.text(description);\n",
920 920 " }\n",
921 921 " \n",
922 922 " return DatePickerView.__super__.update.apply(this);\n",
923 923 " },\n",
924 924 " \n",
925 925 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
926 926 " events: {\"change\": \"handle_date_change\"},\n",
927 927 " \n",
928 928 " // Callback for when the date is changed.\n",
929 929 " handle_date_change: function(event) {\n",
930 930 " this.model.set('value', this.$date.val());\n",
931 931 " this.touch();\n",
932 932 " },\n",
933 933 " });\n",
934 934 " \n",
935 935 " // Register the DatePickerView with the widget manager.\n",
936 936 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
937 937 "});"
938 938 ],
939 939 "metadata": {},
940 940 "output_type": "display_data",
941 941 "text": [
942 942 "<IPython.core.display.Javascript at 0x1094eef90>"
943 943 ]
944 944 }
945 945 ],
946 946 "prompt_number": 19
947 947 },
948 948 {
949 949 "cell_type": "heading",
950 950 "level": 2,
951 951 "metadata": {},
952 952 "source": [
953 953 "Test"
954 954 ]
955 955 },
956 956 {
957 957 "cell_type": "markdown",
958 958 "metadata": {},
959 959 "source": [
960 960 "To test the drawing of the label we create the widget like normal but supply the additional description property a value."
961 961 ]
962 962 },
963 963 {
964 964 "cell_type": "code",
965 965 "collapsed": false,
966 966 "input": [
967 967 "# Add some additional widgets for aesthetic purpose\n",
968 "display(widgets.TextBoxWidget(description=\"First:\"))\n",
969 "display(widgets.TextBoxWidget(description=\"Last:\"))\n",
968 "display(widgets.TextWidget(description=\"First:\"))\n",
969 "display(widgets.TextWidget(description=\"Last:\"))\n",
970 970 "\n",
971 971 "my_widget = DateWidget()\n",
972 972 "display(my_widget)\n",
973 973 "my_widget.description=\"DOB:\""
974 974 ],
975 975 "language": "python",
976 976 "metadata": {},
977 977 "outputs": [],
978 978 "prompt_number": 20
979 979 },
980 980 {
981 981 "cell_type": "markdown",
982 982 "metadata": {},
983 983 "source": [
984 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 985 ]
986 986 },
987 987 {
988 988 "cell_type": "code",
989 989 "collapsed": false,
990 990 "input": [
991 991 "my_widget = DateWidget()\n",
992 992 "display(my_widget)\n",
993 993 "\n",
994 994 "def require_2014(date):\n",
995 995 " return not date is None and date.year == 2014\n",
996 996 "my_widget.validate.register_callback(require_2014)"
997 997 ],
998 998 "language": "python",
999 999 "metadata": {},
1000 1000 "outputs": [],
1001 1001 "prompt_number": 21
1002 1002 },
1003 1003 {
1004 1004 "cell_type": "code",
1005 1005 "collapsed": false,
1006 1006 "input": [
1007 1007 "# Try setting a valid date\n",
1008 1008 "my_widget.value = \"December 2, 2014\""
1009 1009 ],
1010 1010 "language": "python",
1011 1011 "metadata": {},
1012 1012 "outputs": [],
1013 1013 "prompt_number": 22
1014 1014 },
1015 1015 {
1016 1016 "cell_type": "code",
1017 1017 "collapsed": false,
1018 1018 "input": [
1019 1019 "# Try setting an invalid date\n",
1020 1020 "my_widget.value = \"June 12, 1999\""
1021 1021 ],
1022 1022 "language": "python",
1023 1023 "metadata": {},
1024 1024 "outputs": [],
1025 1025 "prompt_number": 23
1026 1026 },
1027 1027 {
1028 1028 "cell_type": "code",
1029 1029 "collapsed": false,
1030 1030 "input": [
1031 1031 "my_widget.value"
1032 1032 ],
1033 1033 "language": "python",
1034 1034 "metadata": {},
1035 1035 "outputs": [
1036 1036 {
1037 1037 "metadata": {},
1038 1038 "output_type": "pyout",
1039 1039 "prompt_number": 24,
1040 1040 "text": [
1041 1041 "u'2014-12-02'"
1042 1042 ]
1043 1043 }
1044 1044 ],
1045 1045 "prompt_number": 24
1046 1046 },
1047 1047 {
1048 1048 "cell_type": "markdown",
1049 1049 "metadata": {},
1050 1050 "source": [
1051 1051 "This concludes Part 6 of the [series](index.ipynb)."
1052 1052 ]
1053 1053 }
1054 1054 ],
1055 1055 "metadata": {}
1056 1056 }
1057 1057 ]
1058 1058 } No newline at end of file
@@ -1,69 +1,67 b''
1 1 {
2 2 "metadata": {
3 3 "name": ""
4 4 },
5 5 "nbformat": 3,
6 6 "nbformat_minor": 0,
7 7 "worksheets": [
8 8 {
9 9 "cells": [
10 10 {
11 11 "cell_type": "heading",
12 12 "level": 1,
13 13 "metadata": {},
14 14 "source": [
15 15 "Widgets"
16 16 ]
17 17 },
18 18 {
19 19 "cell_type": "markdown",
20 20 "metadata": {},
21 21 "source": [
22 22 "This directory includes a tutorial and collection of examples related to the IPython notebook widget framework."
23 23 ]
24 24 },
25 25 {
26 26 "cell_type": "heading",
27 27 "level": 2,
28 28 "metadata": {},
29 29 "source": [
30 30 "Tutorial"
31 31 ]
32 32 },
33 33 {
34 34 "cell_type": "markdown",
35 35 "metadata": {},
36 36 "source": [
37 37 "- [Part 1 - Basics](Part 1 - Basics.ipynb) \n",
38 38 "- [Part 2 - Events](Part 2 - Events.ipynb) \n",
39 39 "- [Part 3 - Placement](Part 3 - Placement.ipynb) \n",
40 40 "- [Part 4 - Styles](Part 4 - Styles.ipynb) \n",
41 41 "- [Part 5 - Alignment](Part 5 - Alignment.ipynb) \n",
42 42 "- [Part 6 - Custom Widget](Part 6 - Custom Widget.ipynb) "
43 43 ]
44 44 },
45 45 {
46 46 "cell_type": "heading",
47 47 "level": 2,
48 48 "metadata": {},
49 49 "source": [
50 50 "Examples"
51 51 ]
52 52 },
53 53 {
54 54 "cell_type": "markdown",
55 55 "metadata": {},
56 56 "source": [
57 "- [Widget Tester](Widget Tester.ipynb) \n",
58 57 "- [Variable Inspector](Variable Inspector.ipynb) \n",
59 58 "- [Export As (nbconvert)](Export As (nbconvert%29.ipynb) \n",
60 59 "- [Nonblocking Console](Nonblocking Console.ipynb) \n",
61 "- [D3](D3.ipynb) \n",
62 60 "- [File Upload Widget](File Upload Widget.ipynb) "
63 61 ]
64 62 }
65 63 ],
66 64 "metadata": {}
67 65 }
68 66 ]
69 67 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now