Show More
@@ -5,8 +5,9 b' define([' | |||||
5 | "widgets/js/widget", |
|
5 | "widgets/js/widget", | |
6 | "base/js/utils", |
|
6 | "base/js/utils", | |
7 | "jquery", |
|
7 | "jquery", | |
|
8 | "underscore", | |||
8 | "bootstrap", |
|
9 | "bootstrap", | |
9 | ], function(widget, utils, $){ |
|
10 | ], function(widget, utils, $, _){ | |
10 |
|
11 | |||
11 | var DropdownView = widget.DOMWidgetView.extend({ |
|
12 | var DropdownView = widget.DOMWidgetView.extend({ | |
12 | render : function(){ |
|
13 | render : function(){ | |
@@ -52,19 +53,19 b' define([' | |||||
52 | /** |
|
53 | /** | |
53 | * Update the contents of this view |
|
54 | * Update the contents of this view | |
54 | * |
|
55 | * | |
55 |
* Called when the model is changed. The model may have been |
|
56 | * Called when the model is changed. The model may have been | |
56 | * changed by another view or by a state update from the back-end. |
|
57 | * changed by another view or by a state update from the back-end. | |
57 | */ |
|
58 | */ | |
58 |
|
59 | |||
59 | if (options === undefined || options.updated_view != this) { |
|
60 | if (options === undefined || options.updated_view != this) { | |
60 |
var selected_item_text = this.model.get(' |
|
61 | var selected_item_text = this.model.get('selected_label'); | |
61 | if (selected_item_text.trim().length === 0) { |
|
62 | if (selected_item_text.trim().length === 0) { | |
62 | this.$droplabel.html(" "); |
|
63 | this.$droplabel.html(" "); | |
63 | } else { |
|
64 | } else { | |
64 | this.$droplabel.text(selected_item_text); |
|
65 | this.$droplabel.text(selected_item_text); | |
65 | } |
|
66 | } | |
66 |
|
67 | |||
67 |
var items = this.model.get('_ |
|
68 | var items = this.model.get('_options_labels'); | |
68 | var $replace_droplist = $('<ul />') |
|
69 | var $replace_droplist = $('<ul />') | |
69 | .addClass('dropdown-menu'); |
|
70 | .addClass('dropdown-menu'); | |
70 | // Copy the style |
|
71 | // Copy the style | |
@@ -150,7 +151,7 b' define([' | |||||
150 | * Calling model.set will trigger all of the other views of the |
|
151 | * Calling model.set will trigger all of the other views of the | |
151 | * model to update. |
|
152 | * model to update. | |
152 | */ |
|
153 | */ | |
153 |
this.model.set(' |
|
154 | this.model.set('selected_label', $(e.target).text(), {updated_view: this}); | |
154 | this.touch(); |
|
155 | this.touch(); | |
155 |
|
156 | |||
156 | // Manually hide the droplist. |
|
157 | // Manually hide the droplist. | |
@@ -188,7 +189,7 b' define([' | |||||
188 | */ |
|
189 | */ | |
189 | if (options === undefined || options.updated_view != this) { |
|
190 | if (options === undefined || options.updated_view != this) { | |
190 | // Add missing items to the DOM. |
|
191 | // Add missing items to the DOM. | |
191 |
var items = this.model.get('_ |
|
192 | var items = this.model.get('_options_labels'); | |
192 | var disabled = this.model.get('disabled'); |
|
193 | var disabled = this.model.get('disabled'); | |
193 | var that = this; |
|
194 | var that = this; | |
194 | _.each(items, function(item, index) { |
|
195 | _.each(items, function(item, index) { | |
@@ -209,7 +210,7 b' define([' | |||||
209 | } |
|
210 | } | |
210 |
|
211 | |||
211 | var $item_element = that.$container.find(item_query); |
|
212 | var $item_element = that.$container.find(item_query); | |
212 |
if (that.model.get(' |
|
213 | if (that.model.get('selected_label') == item) { | |
213 | $item_element.prop('checked', true); |
|
214 | $item_element.prop('checked', true); | |
214 | } else { |
|
215 | } else { | |
215 | $item_element.prop('checked', false); |
|
216 | $item_element.prop('checked', false); | |
@@ -263,7 +264,7 b' define([' | |||||
263 | * Calling model.set will trigger all of the other views of the |
|
264 | * Calling model.set will trigger all of the other views of the | |
264 | * model to update. |
|
265 | * model to update. | |
265 | */ |
|
266 | */ | |
266 |
this.model.set(' |
|
267 | this.model.set('selected_label', $(e.target).val(), {updated_view: this}); | |
267 | this.touch(); |
|
268 | this.touch(); | |
268 | }, |
|
269 | }, | |
269 | }); |
|
270 | }); | |
@@ -305,7 +306,7 b' define([' | |||||
305 | */ |
|
306 | */ | |
306 | if (options === undefined || options.updated_view != this) { |
|
307 | if (options === undefined || options.updated_view != this) { | |
307 | // Add missing items to the DOM. |
|
308 | // Add missing items to the DOM. | |
308 |
var items = this.model.get('_ |
|
309 | var items = this.model.get('_options_labels'); | |
309 | var disabled = this.model.get('disabled'); |
|
310 | var disabled = this.model.get('disabled'); | |
310 | var that = this; |
|
311 | var that = this; | |
311 | var item_html; |
|
312 | var item_html; | |
@@ -328,7 +329,7 b' define([' | |||||
328 | .on('click', $.proxy(that.handle_click, that)); |
|
329 | .on('click', $.proxy(that.handle_click, that)); | |
329 | that.update_style_traits($item_element); |
|
330 | that.update_style_traits($item_element); | |
330 | } |
|
331 | } | |
331 |
if (that.model.get(' |
|
332 | if (that.model.get('selected_label') == item) { | |
332 | $item_element.addClass('active'); |
|
333 | $item_element.addClass('active'); | |
333 | } else { |
|
334 | } else { | |
334 | $item_element.removeClass('active'); |
|
335 | $item_element.removeClass('active'); | |
@@ -410,7 +411,7 b' define([' | |||||
410 | * Calling model.set will trigger all of the other views of the |
|
411 | * Calling model.set will trigger all of the other views of the | |
411 | * model to update. |
|
412 | * model to update. | |
412 | */ |
|
413 | */ | |
413 |
this.model.set(' |
|
414 | this.model.set('selected_label', $(e.target).attr('value'), {updated_view: this}); | |
414 | this.touch(); |
|
415 | this.touch(); | |
415 | }, |
|
416 | }, | |
416 | }); |
|
417 | }); | |
@@ -443,7 +444,7 b' define([' | |||||
443 | */ |
|
444 | */ | |
444 | if (options === undefined || options.updated_view != this) { |
|
445 | if (options === undefined || options.updated_view != this) { | |
445 | // Add missing items to the DOM. |
|
446 | // Add missing items to the DOM. | |
446 |
var items = this.model.get('_ |
|
447 | var items = this.model.get('_options_labels'); | |
447 | var that = this; |
|
448 | var that = this; | |
448 | _.each(items, function(item, index) { |
|
449 | _.each(items, function(item, index) { | |
449 | var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]'; |
|
450 | var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]'; | |
@@ -451,14 +452,14 b' define([' | |||||
451 | $('<option />') |
|
452 | $('<option />') | |
452 | .text(item) |
|
453 | .text(item) | |
453 | .attr('data-value', encodeURIComponent(item)) |
|
454 | .attr('data-value', encodeURIComponent(item)) | |
454 |
.attr(' |
|
455 | .attr('selected_label', item) | |
455 | .appendTo(that.$listbox) |
|
456 | .appendTo(that.$listbox) | |
456 | .on('click', $.proxy(that.handle_click, that)); |
|
457 | .on('click', $.proxy(that.handle_click, that)); | |
457 | } |
|
458 | } | |
458 | }); |
|
459 | }); | |
459 |
|
460 | |||
460 | // Select the correct element |
|
461 | // Select the correct element | |
461 |
this.$listbox.val(this.model.get(' |
|
462 | this.$listbox.val(this.model.get('selected_label')); | |
462 |
|
463 | |||
463 | // Disable listbox if needed |
|
464 | // Disable listbox if needed | |
464 | var disabled = this.model.get('disabled'); |
|
465 | var disabled = this.model.get('disabled'); | |
@@ -509,9 +510,35 b' define([' | |||||
509 | * Calling model.set will trigger all of the other views of the |
|
510 | * Calling model.set will trigger all of the other views of the | |
510 | * model to update. |
|
511 | * model to update. | |
511 | */ |
|
512 | */ | |
512 |
this.model.set(' |
|
513 | this.model.set('selected_label', $(e.target).text(), {updated_view: this}); | |
513 | this.touch(); |
|
514 | this.touch(); | |
514 |
}, |
|
515 | }, | |
|
516 | }); | |||
|
517 | var SelectMultipleView = SelectView.extend({ | |||
|
518 | render: function(){ | |||
|
519 | SelectMultipleView.__super__.render.apply(this); | |||
|
520 | this.$el.removeClass('widget-select') | |||
|
521 | .addClass('widget-select-multiple'); | |||
|
522 | this.$listbox.attr('multiple', true) | |||
|
523 | .on('input', $.proxy(this.handle_click, this)); | |||
|
524 | return this; | |||
|
525 | }, | |||
|
526 | ||||
|
527 | update: function(){ | |||
|
528 | SelectMultipleView.__super__.update.apply(this, arguments); | |||
|
529 | this.$listbox.val(this.model.get('selected_labels')); | |||
|
530 | }, | |||
|
531 | ||||
|
532 | handle_click: function (e) { | |||
|
533 | // Handle when a value is clicked. | |||
|
534 | ||||
|
535 | // Calling model.set will trigger all of the other views of the | |||
|
536 | // model to update. | |||
|
537 | this.model.set('selected_labels', | |||
|
538 | (this.$listbox.val() || []).slice(), | |||
|
539 | {updated_view: this}); | |||
|
540 | this.touch(); | |||
|
541 | }, | |||
515 | }); |
|
542 | }); | |
516 |
|
543 | |||
517 | return { |
|
544 | return { | |
@@ -519,5 +546,6 b' define([' | |||||
519 | 'RadioButtonsView': RadioButtonsView, |
|
546 | 'RadioButtonsView': RadioButtonsView, | |
520 | 'ToggleButtonsView': ToggleButtonsView, |
|
547 | 'ToggleButtonsView': ToggleButtonsView, | |
521 | 'SelectView': SelectView, |
|
548 | 'SelectView': SelectView, | |
|
549 | 'SelectMultipleView': SelectMultipleView, | |||
522 | }; |
|
550 | }; | |
523 | }); |
|
551 | }); |
@@ -43,11 +43,11 b' casper.notebook_test(function () {' | |||||
43 |
|
43 | |||
44 | //values=["' + selection_values + '"[i] for i in range(4)] |
|
44 | //values=["' + selection_values + '"[i] for i in range(4)] | |
45 | selection_index = this.append_cell( |
|
45 | selection_index = this.append_cell( | |
46 |
' |
|
46 | 'options=["' + selection_values + '"[i] for i in range(4)]\n' + | |
47 |
'selection = [widgets.Dropdown( |
|
47 | 'selection = [widgets.Dropdown(options=options),\n' + | |
48 |
' widgets.ToggleButtons( |
|
48 | ' widgets.ToggleButtons(options=options),\n' + | |
49 |
' widgets.RadioButtons( |
|
49 | ' widgets.RadioButtons(options=options),\n' + | |
50 |
' widgets.Select( |
|
50 | ' widgets.Select(options=options)]\n' + | |
51 | '[display(selection[i]) for i in range(4)]\n' + |
|
51 | '[display(selection[i]) for i in range(4)]\n' + | |
52 | 'for widget in selection:\n' + |
|
52 | 'for widget in selection:\n' + | |
53 | ' def handle_change(name,old,new):\n' + |
|
53 | ' def handle_change(name,old,new):\n' + | |
@@ -136,9 +136,9 b' casper.notebook_test(function () {' | |||||
136 | index = this.append_cell( |
|
136 | index = this.append_cell( | |
137 | 'from copy import copy\n' + |
|
137 | 'from copy import copy\n' + | |
138 | 'for widget in selection:\n' + |
|
138 | 'for widget in selection:\n' + | |
139 |
' d = copy(widget. |
|
139 | ' d = copy(widget.options)\n' + | |
140 | ' d.append("z")\n' + |
|
140 | ' d.append("z")\n' + | |
141 |
' widget. |
|
141 | ' widget.options = d\n' + | |
142 | 'selection[0].value = "z"'); |
|
142 | 'selection[0].value = "z"'); | |
143 | this.execute_cell_then(index, function(index){ |
|
143 | this.execute_cell_then(index, function(index){ | |
144 |
|
144 |
@@ -7,7 +7,7 b' from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgres' | |||||
7 | from .widget_image import Image |
|
7 | from .widget_image import Image | |
8 | from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider |
|
8 | from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider | |
9 | from .widget_output import Output |
|
9 | from .widget_output import Output | |
10 | from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select |
|
10 | from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select, SelectMultiple | |
11 | from .widget_selectioncontainer import Tab, Accordion |
|
11 | from .widget_selectioncontainer import Tab, Accordion | |
12 | from .widget_string import HTML, Latex, Text, Textarea |
|
12 | from .widget_string import HTML, Latex, Text, Textarea | |
13 | from .interaction import interact, interactive, fixed, interact_manual |
|
13 | from .interaction import interact, interactive, fixed, interact_manual |
@@ -59,7 +59,7 b' def _widget_abbrev_single_value(o):' | |||||
59 | if isinstance(o, string_types): |
|
59 | if isinstance(o, string_types): | |
60 | return Text(value=unicode_type(o)) |
|
60 | return Text(value=unicode_type(o)) | |
61 | elif isinstance(o, dict): |
|
61 | elif isinstance(o, dict): | |
62 |
return Dropdown( |
|
62 | return Dropdown(options=o) | |
63 | elif isinstance(o, bool): |
|
63 | elif isinstance(o, bool): | |
64 | return Checkbox(value=o) |
|
64 | return Checkbox(value=o) | |
65 | elif isinstance(o, float): |
|
65 | elif isinstance(o, float): | |
@@ -76,7 +76,7 b' def _widget_abbrev(o):' | |||||
76 | float_or_int = (float, int) |
|
76 | float_or_int = (float, int) | |
77 | if isinstance(o, (list, tuple)): |
|
77 | if isinstance(o, (list, tuple)): | |
78 | if o and all(isinstance(x, string_types) for x in o): |
|
78 | if o and all(isinstance(x, string_types) for x in o): | |
79 |
return Dropdown( |
|
79 | return Dropdown(options=[unicode_type(k) for k in o]) | |
80 | elif _matches(o, (float_or_int, float_or_int)): |
|
80 | elif _matches(o, (float_or_int, float_or_int)): | |
81 | min, max, value = _get_min_max_value(o[0], o[1]) |
|
81 | min, max, value = _get_min_max_value(o[0], o[1]) | |
82 | if all(isinstance(_, int) for _ in o): |
|
82 | if all(isinstance(_, int) for _ in o): |
@@ -118,7 +118,7 b' def test_single_value_dict():' | |||||
118 | check_widget(w, |
|
118 | check_widget(w, | |
119 | cls=widgets.Dropdown, |
|
119 | cls=widgets.Dropdown, | |
120 | description='d', |
|
120 | description='d', | |
121 |
|
|
121 | options=d, | |
122 | value=next(iter(d.values())), |
|
122 | value=next(iter(d.values())), | |
123 | ) |
|
123 | ) | |
124 |
|
124 | |||
@@ -229,7 +229,7 b' def test_list_tuple_str():' | |||||
229 | d = dict( |
|
229 | d = dict( | |
230 | cls=widgets.Dropdown, |
|
230 | cls=widgets.Dropdown, | |
231 | value=first, |
|
231 | value=first, | |
232 |
|
|
232 | options=values | |
233 | ) |
|
233 | ) | |
234 | check_widgets(c, tup=d, lis=d) |
|
234 | check_widgets(c, tup=d, lis=d) | |
235 |
|
235 | |||
@@ -287,12 +287,12 b' def test_default_values():' | |||||
287 | ), |
|
287 | ), | |
288 | h=dict( |
|
288 | h=dict( | |
289 | cls=widgets.Dropdown, |
|
289 | cls=widgets.Dropdown, | |
290 |
|
|
290 | options={'a': 1, 'b': 2}, | |
291 | value=2 |
|
291 | value=2 | |
292 | ), |
|
292 | ), | |
293 | j=dict( |
|
293 | j=dict( | |
294 | cls=widgets.Dropdown, |
|
294 | cls=widgets.Dropdown, | |
295 |
|
|
295 | options=['hi', 'there'], | |
296 | value='there' |
|
296 | value='there' | |
297 | ), |
|
297 | ), | |
298 | ) |
|
298 | ) | |
@@ -310,12 +310,12 b' def test_default_out_of_bounds():' | |||||
310 | ), |
|
310 | ), | |
311 | h=dict( |
|
311 | h=dict( | |
312 | cls=widgets.Dropdown, |
|
312 | cls=widgets.Dropdown, | |
313 |
|
|
313 | options={'a': 1}, | |
314 | value=1, |
|
314 | value=1, | |
315 | ), |
|
315 | ), | |
316 | j=dict( |
|
316 | j=dict( | |
317 | cls=widgets.Dropdown, |
|
317 | cls=widgets.Dropdown, | |
318 |
|
|
318 | options=['hi', 'there'], | |
319 | value='hi', |
|
319 | value='hi', | |
320 | ), |
|
320 | ), | |
321 | ) |
|
321 | ) | |
@@ -634,3 +634,59 b' def test_float_range_logic():' | |||||
634 | frsw(lower=5) |
|
634 | frsw(lower=5) | |
635 | with nt.assert_raises(ValueError): |
|
635 | with nt.assert_raises(ValueError): | |
636 | frsw(upper=5) |
|
636 | frsw(upper=5) | |
|
637 | ||||
|
638 | ||||
|
639 | def test_multiple_selection(): | |||
|
640 | smw = widgets.SelectMultiple | |||
|
641 | ||||
|
642 | # degenerate multiple select | |||
|
643 | w = smw() | |||
|
644 | check_widget(w, value=tuple(), options=None, selected_labels=tuple()) | |||
|
645 | ||||
|
646 | # don't accept random other value when no options | |||
|
647 | with nt.assert_raises(KeyError): | |||
|
648 | w.value = (2,) | |||
|
649 | check_widget(w, value=tuple(), selected_labels=tuple()) | |||
|
650 | ||||
|
651 | # basic multiple select | |||
|
652 | w = smw(options=[(1, 1)], value=[1]) | |||
|
653 | check_widget(w, cls=smw, value=(1,), options=[(1, 1)]) | |||
|
654 | ||||
|
655 | # don't accept random other value | |||
|
656 | with nt.assert_raises(KeyError): | |||
|
657 | w.value = w.value + (2,) | |||
|
658 | check_widget(w, value=(1,), selected_labels=(1,)) | |||
|
659 | ||||
|
660 | # change options | |||
|
661 | w.options = w.options + [(2, 2)] | |||
|
662 | check_widget(w, options=[(1, 1), (2,2)]) | |||
|
663 | ||||
|
664 | # change value | |||
|
665 | w.value = w.value + (2,) | |||
|
666 | check_widget(w, value=(1, 2), selected_labels=(1, 2)) | |||
|
667 | ||||
|
668 | # change value name | |||
|
669 | w.selected_labels = (1,) | |||
|
670 | check_widget(w, value=(1,)) | |||
|
671 | ||||
|
672 | # don't accept random other names when no options | |||
|
673 | with nt.assert_raises(KeyError): | |||
|
674 | w.selected_labels = (3,) | |||
|
675 | check_widget(w, value=(1,)) | |||
|
676 | ||||
|
677 | # don't accept selected_label (from superclass) | |||
|
678 | with nt.assert_raises(AttributeError): | |||
|
679 | w.selected_label = 3 | |||
|
680 | ||||
|
681 | # don't return selected_label (from superclass) | |||
|
682 | with nt.assert_raises(AttributeError): | |||
|
683 | print(w.selected_label) | |||
|
684 | ||||
|
685 | # dict style | |||
|
686 | w.options = {1: 1} | |||
|
687 | check_widget(w, options={1: 1}) | |||
|
688 | ||||
|
689 | # updating | |||
|
690 | with nt.assert_raises(KeyError): | |||
|
691 | w.value = (2,) | |||
|
692 | check_widget(w, options={1: 1}) |
@@ -30,38 +30,38 b' from IPython.utils.warn import DeprecatedClass' | |||||
30 | class _Selection(DOMWidget): |
|
30 | class _Selection(DOMWidget): | |
31 | """Base class for Selection widgets |
|
31 | """Base class for Selection widgets | |
32 |
|
32 | |||
33 |
`` |
|
33 | ``options`` can be specified as a list or dict. If given as a list, | |
34 | it will be transformed to a dict of the form ``{str(value):value}``. |
|
34 | it will be transformed to a dict of the form ``{str(value):value}``. | |
35 | """ |
|
35 | """ | |
36 |
|
36 | |||
37 | value = Any(help="Selected value") |
|
37 | value = Any(help="Selected value") | |
38 |
|
|
38 | selected_label = Unicode(help="The label of the selected value", sync=True) | |
39 |
|
|
39 | options = Any(help="""List of (key, value) tuples or dict of values that the | |
40 | user can select. |
|
40 | user can select. | |
41 |
|
41 | |||
42 | The keys of this list are the strings that will be displayed in the UI, |
|
42 | The keys of this list are the strings that will be displayed in the UI, | |
43 | representing the actual Python choices. |
|
43 | representing the actual Python choices. | |
44 |
|
44 | |||
45 |
The keys of this list are also available as _ |
|
45 | The keys of this list are also available as _options_labels. | |
46 | """) |
|
46 | """) | |
47 |
|
47 | |||
48 |
_ |
|
48 | _options_dict = Dict() | |
49 |
_ |
|
49 | _options_labels = Tuple(sync=True) | |
50 |
_ |
|
50 | _options_values = Tuple() | |
51 |
|
51 | |||
52 | disabled = Bool(False, help="Enable or disable user changes", sync=True) |
|
52 | disabled = Bool(False, help="Enable or disable user changes", sync=True) | |
53 | description = Unicode(help="Description of the value this widget represents", sync=True) |
|
53 | description = Unicode(help="Description of the value this widget represents", sync=True) | |
54 |
|
54 | |||
55 | def __init__(self, *args, **kwargs): |
|
55 | def __init__(self, *args, **kwargs): | |
56 | self.value_lock = Lock() |
|
56 | self.value_lock = Lock() | |
57 |
self. |
|
57 | self.options_lock = Lock() | |
58 |
self.on_trait_change(self._ |
|
58 | self.on_trait_change(self._options_readonly_changed, ['_options_dict', '_options_labels', '_options_values', '_options']) | |
59 |
if ' |
|
59 | if 'options' in kwargs: | |
60 |
self. |
|
60 | self.options = kwargs.pop('options') | |
61 | DOMWidget.__init__(self, *args, **kwargs) |
|
61 | DOMWidget.__init__(self, *args, **kwargs) | |
62 |
self._value_in_ |
|
62 | self._value_in_options() | |
63 |
|
63 | |||
64 |
def _make_ |
|
64 | def _make_options(self, x): | |
65 | # If x is a dict, convert it to list format. |
|
65 | # If x is a dict, convert it to list format. | |
66 | if isinstance(x, (OrderedDict, dict)): |
|
66 | if isinstance(x, (OrderedDict, dict)): | |
67 | return [(k, v) for k, v in x.items()] |
|
67 | return [(k, v) for k, v in x.items()] | |
@@ -70,7 +70,7 b' class _Selection(DOMWidget):' | |||||
70 | if not isinstance(x, (list, tuple)): |
|
70 | if not isinstance(x, (list, tuple)): | |
71 | raise ValueError('x') |
|
71 | raise ValueError('x') | |
72 |
|
72 | |||
73 | # If x is an ordinary list, use the values as names. |
|
73 | # If x is an ordinary list, use the option values as names. | |
74 | for y in x: |
|
74 | for y in x: | |
75 | if not isinstance(y, (list, tuple)) or len(y) < 2: |
|
75 | if not isinstance(y, (list, tuple)) or len(y) < 2: | |
76 | return [(i, i) for i in x] |
|
76 | return [(i, i) for i in x] | |
@@ -78,42 +78,42 b' class _Selection(DOMWidget):' | |||||
78 | # Value is already in the correct format. |
|
78 | # Value is already in the correct format. | |
79 | return x |
|
79 | return x | |
80 |
|
80 | |||
81 |
def _ |
|
81 | def _options_changed(self, name, old, new): | |
82 |
"""Handles when the |
|
82 | """Handles when the options tuple has been changed. | |
83 |
|
83 | |||
84 |
Setting |
|
84 | Setting options implies setting option labels from the keys of the dict. | |
85 |
""" |
|
85 | """ | |
86 |
if self. |
|
86 | if self.options_lock.acquire(False): | |
87 | try: |
|
87 | try: | |
88 |
self. |
|
88 | self.options = new | |
89 |
|
89 | |||
90 |
|
|
90 | options = self._make_options(new) | |
91 |
self._ |
|
91 | self._options_dict = {i[0]: i[1] for i in options} | |
92 |
self._ |
|
92 | self._options_labels = [i[0] for i in options] | |
93 |
self._ |
|
93 | self._options_values = [i[1] for i in options] | |
94 |
self._value_in_ |
|
94 | self._value_in_options() | |
95 | finally: |
|
95 | finally: | |
96 |
self. |
|
96 | self.options_lock.release() | |
97 |
|
97 | |||
98 |
def _value_in_ |
|
98 | def _value_in_options(self): | |
99 | # ensure that the chosen value is one of the choices |
|
99 | # ensure that the chosen value is one of the choices | |
100 | if self._value_values: |
|
|||
101 | if self.value not in self._value_values: |
|
|||
102 | self.value = next(iter(self._value_values)) |
|
|||
103 |
|
100 | |||
104 | def _values_readonly_changed(self, name, old, new): |
|
101 | if self._options_values: | |
105 | if not self.values_lock.locked(): |
|
102 | if self.value not in self._options_values: | |
106 | raise TraitError("`.%s` is a read-only trait. Use the `.values` tuple instead." % name) |
|
103 | self.value = next(iter(self._options_values)) | |
107 |
|
104 | |||
|
105 | def _options_readonly_changed(self, name, old, new): | |||
|
106 | if not self.options_lock.locked(): | |||
|
107 | raise TraitError("`.%s` is a read-only trait. Use the `.options` tuple instead." % name) | |||
108 | def _value_changed(self, name, old, new): |
|
108 | def _value_changed(self, name, old, new): | |
109 | """Called when value has been changed""" |
|
109 | """Called when value has been changed""" | |
110 | if self.value_lock.acquire(False): |
|
110 | if self.value_lock.acquire(False): | |
111 | try: |
|
111 | try: | |
112 | # Reverse dictionary lookup for the value name |
|
112 | # Reverse dictionary lookup for the value name | |
113 |
for k,v in self._ |
|
113 | for k, v in self._options_dict.items(): | |
114 | if new == v: |
|
114 | if new == v: | |
115 | # set the selected value name |
|
115 | # set the selected value name | |
116 |
self. |
|
116 | self.selected_label = k | |
117 | return |
|
117 | return | |
118 | # undo the change, and raise KeyError |
|
118 | # undo the change, and raise KeyError | |
119 | self.value = old |
|
119 | self.value = old | |
@@ -121,11 +121,68 b' class _Selection(DOMWidget):' | |||||
121 | finally: |
|
121 | finally: | |
122 | self.value_lock.release() |
|
122 | self.value_lock.release() | |
123 |
|
123 | |||
124 |
def _ |
|
124 | def _selected_label_changed(self, name, old, new): | |
125 | """Called when the value name has been changed (typically by the frontend).""" |
|
125 | """Called when the value name has been changed (typically by the frontend).""" | |
126 | if self.value_lock.acquire(False): |
|
126 | if self.value_lock.acquire(False): | |
127 | try: |
|
127 | try: | |
128 |
self.value = self._ |
|
128 | self.value = self._options_dict[new] | |
|
129 | finally: | |||
|
130 | self.value_lock.release() | |||
|
131 | ||||
|
132 | ||||
|
133 | class _MultipleSelection(_Selection): | |||
|
134 | """Base class for MultipleSelection widgets. | |||
|
135 | ||||
|
136 | As with ``_Selection``, ``options`` can be specified as a list or dict. If | |||
|
137 | given as a list, it will be transformed to a dict of the form | |||
|
138 | ``{str(value): value}``. | |||
|
139 | ||||
|
140 | Despite their names, ``value`` (and ``selected_label``) will be tuples, even | |||
|
141 | if only a single option is selected. | |||
|
142 | """ | |||
|
143 | ||||
|
144 | value = Tuple(help="Selected values") | |||
|
145 | selected_labels = Tuple(help="The labels of the selected options", | |||
|
146 | sync=True) | |||
|
147 | ||||
|
148 | @property | |||
|
149 | def selected_label(self): | |||
|
150 | raise AttributeError( | |||
|
151 | "Does not support selected_label, use selected_labels") | |||
|
152 | ||||
|
153 | def _value_in_options(self): | |||
|
154 | # ensure that the chosen value is one of the choices | |||
|
155 | if self.options: | |||
|
156 | old_value = self.value or [] | |||
|
157 | new_value = [] | |||
|
158 | for value in old_value: | |||
|
159 | if value in self._options_dict.values(): | |||
|
160 | new_value.append(value) | |||
|
161 | if new_value: | |||
|
162 | self.value = new_value | |||
|
163 | else: | |||
|
164 | self.value = [next(iter(self._options_dict.values()))] | |||
|
165 | ||||
|
166 | def _value_changed(self, name, old, new): | |||
|
167 | """Called when value has been changed""" | |||
|
168 | if self.value_lock.acquire(False): | |||
|
169 | try: | |||
|
170 | self.selected_labels = [ | |||
|
171 | self._options_labels[self._options_values.index(v)] | |||
|
172 | for v in new | |||
|
173 | ] | |||
|
174 | except: | |||
|
175 | self.value = old | |||
|
176 | raise KeyError(new) | |||
|
177 | finally: | |||
|
178 | self.value_lock.release() | |||
|
179 | ||||
|
180 | def _selected_labels_changed(self, name, old, new): | |||
|
181 | """Called when the selected label has been changed (typically by the | |||
|
182 | frontend).""" | |||
|
183 | if self.value_lock.acquire(False): | |||
|
184 | try: | |||
|
185 | self.value = [self._options_dict[name] for name in new] | |||
129 | finally: |
|
186 | finally: | |
130 | self.value_lock.release() |
|
187 | self.value_lock.release() | |
131 |
|
188 | |||
@@ -165,6 +222,15 b' class Select(_Selection):' | |||||
165 | _view_name = Unicode('SelectView', sync=True) |
|
222 | _view_name = Unicode('SelectView', sync=True) | |
166 |
|
223 | |||
167 |
|
224 | |||
|
225 | @register('IPython.SelectMultiple') | |||
|
226 | class SelectMultiple(_MultipleSelection): | |||
|
227 | """Listbox that allows many items to be selected at any given time. | |||
|
228 | Despite their names, inherited from ``_Selection``, the currently chosen | |||
|
229 | option values, ``value``, or their labels, ``selected_labels`` must both be | |||
|
230 | updated with a list-like object.""" | |||
|
231 | _view_name = Unicode('SelectMultipleView', sync=True) | |||
|
232 | ||||
|
233 | ||||
168 | # Remove in IPython 4.0 |
|
234 | # Remove in IPython 4.0 | |
169 | ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget') |
|
235 | ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget') | |
170 | DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget') |
|
236 | DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget') |
@@ -86,7 +86,7 b'' | |||||
86 | }, |
|
86 | }, | |
87 | "outputs": [], |
|
87 | "outputs": [], | |
88 | "source": [ |
|
88 | "source": [ | |
89 |
"exporter_names = widgets.Dropdown( |
|
89 | "exporter_names = widgets.Dropdown(options=get_export_names(), value='html')\n", | |
90 | "export_button = widgets.Button(description=\"Export\")\n", |
|
90 | "export_button = widgets.Button(description=\"Export\")\n", | |
91 | "download_link = widgets.HTML(visible=False)" |
|
91 | "download_link = widgets.HTML(visible=False)" | |
92 | ] |
|
92 | ] |
@@ -274,7 +274,7 b'' | |||||
274 | "cell_type": "markdown", |
|
274 | "cell_type": "markdown", | |
275 | "metadata": {}, |
|
275 | "metadata": {}, | |
276 | "source": [ |
|
276 | "source": [ | |
277 |
"There are four widgets that can be used to display single selection lists. All |
|
277 | "There are four widgets that can be used to display single selection lists, and one that can be used to display multiple selection lists. All inherit from the same base class. You can specify the **enumeration of selectable options by passing a list**. You can **also specify the enumeration as a dictionary**, in which case the **keys will be used as the item displayed** in the list and the corresponding **value will be returned** when an item is selected." | |
278 | ] |
|
278 | ] | |
279 | }, |
|
279 | }, | |
280 | { |
|
280 | { | |
@@ -298,7 +298,7 b'' | |||||
298 | "source": [ |
|
298 | "source": [ | |
299 | "from IPython.display import display\n", |
|
299 | "from IPython.display import display\n", | |
300 | "w = widgets.Dropdown(\n", |
|
300 | "w = widgets.Dropdown(\n", | |
301 |
" |
|
301 | " options=['1', '2', '3'],\n", | |
302 | " value='2',\n", |
|
302 | " value='2',\n", | |
303 | " description='Number:',\n", |
|
303 | " description='Number:',\n", | |
304 | ")\n", |
|
304 | ")\n", | |
@@ -332,7 +332,7 b'' | |||||
332 | "outputs": [], |
|
332 | "outputs": [], | |
333 | "source": [ |
|
333 | "source": [ | |
334 | "w = widgets.Dropdown(\n", |
|
334 | "w = widgets.Dropdown(\n", | |
335 |
" |
|
335 | " options={'One': 1, 'Two': 2, 'Three': 3},\n", | |
336 | " value=2,\n", |
|
336 | " value=2,\n", | |
337 | " description='Number:',\n", |
|
337 | " description='Number:',\n", | |
338 | ")\n", |
|
338 | ")\n", | |
@@ -371,7 +371,7 b'' | |||||
371 | "source": [ |
|
371 | "source": [ | |
372 | "widgets.RadioButtons(\n", |
|
372 | "widgets.RadioButtons(\n", | |
373 | " description='Pizza topping:',\n", |
|
373 | " description='Pizza topping:',\n", | |
374 |
" |
|
374 | " options=['pepperoni', 'pineapple', 'anchovies'],\n", | |
375 | ")" |
|
375 | ")" | |
376 | ] |
|
376 | ] | |
377 | }, |
|
377 | }, | |
@@ -396,7 +396,7 b'' | |||||
396 | "source": [ |
|
396 | "source": [ | |
397 | "widgets.Select(\n", |
|
397 | "widgets.Select(\n", | |
398 | " description='OS:',\n", |
|
398 | " description='OS:',\n", | |
399 |
" |
|
399 | " options=['Linux', 'Windows', 'OSX'],\n", | |
400 | ")" |
|
400 | ")" | |
401 | ] |
|
401 | ] | |
402 | }, |
|
402 | }, | |
@@ -421,12 +421,46 b'' | |||||
421 | "source": [ |
|
421 | "source": [ | |
422 | "widgets.ToggleButtons(\n", |
|
422 | "widgets.ToggleButtons(\n", | |
423 | " description='Speed:',\n", |
|
423 | " description='Speed:',\n", | |
424 |
" |
|
424 | " options=['Slow', 'Regular', 'Fast'],\n", | |
425 | ")" |
|
425 | ")" | |
426 | ] |
|
426 | ] | |
427 | }, |
|
427 | }, | |
428 | { |
|
428 | { | |
429 | "cell_type": "markdown", |
|
429 | "cell_type": "markdown", | |
|
430 | "metadata": {}, | |||
|
431 | "source": [ | |||
|
432 | "### SelectMultiple\n", | |||
|
433 | "Multiple values can be selected with <kbd>shift</kbd> and <kbd>ctrl</kbd> pressed and mouse clicks or arrow keys." | |||
|
434 | ] | |||
|
435 | }, | |||
|
436 | { | |||
|
437 | "cell_type": "code", | |||
|
438 | "execution_count": null, | |||
|
439 | "metadata": { | |||
|
440 | "collapsed": true | |||
|
441 | }, | |||
|
442 | "outputs": [], | |||
|
443 | "source": [ | |||
|
444 | "w = widgets.SelectMultiple(\n", | |||
|
445 | " description=\"Fruits\",\n", | |||
|
446 | " options=['Apples', 'Oranges', 'Pears']\n", | |||
|
447 | ")\n", | |||
|
448 | "display(w)" | |||
|
449 | ] | |||
|
450 | }, | |||
|
451 | { | |||
|
452 | "cell_type": "code", | |||
|
453 | "execution_count": null, | |||
|
454 | "metadata": { | |||
|
455 | "collapsed": false | |||
|
456 | }, | |||
|
457 | "outputs": [], | |||
|
458 | "source": [ | |||
|
459 | "w.value" | |||
|
460 | ] | |||
|
461 | }, | |||
|
462 | { | |||
|
463 | "cell_type": "markdown", | |||
430 | "metadata": { |
|
464 | "metadata": { | |
431 | "slideshow": { |
|
465 | "slideshow": { | |
432 | "slide_type": "slide" |
|
466 | "slide_type": "slide" |
@@ -236,11 +236,11 b'' | |||||
236 | "outputs": [], |
|
236 | "outputs": [], | |
237 | "source": [ |
|
237 | "source": [ | |
238 | "name = widgets.Text(description='Name:')\n", |
|
238 | "name = widgets.Text(description='Name:')\n", | |
239 |
"color = widgets.Dropdown(description='Color:', |
|
239 | "color = widgets.Dropdown(description='Color:', options=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])\n", | |
240 | "page1 = widgets.Box(children=[name, color])\n", |
|
240 | "page1 = widgets.Box(children=[name, color])\n", | |
241 | "\n", |
|
241 | "\n", | |
242 | "age = widgets.IntSlider(description='Age:', min=0, max=120, value=50)\n", |
|
242 | "age = widgets.IntSlider(description='Age:', min=0, max=120, value=50)\n", | |
243 |
"gender = widgets.RadioButtons(description='Gender:', |
|
243 | "gender = widgets.RadioButtons(description='Gender:', options=['male', 'female'])\n", | |
244 | "page2 = widgets.Box(children=[age, gender])\n", |
|
244 | "page2 = widgets.Box(children=[age, gender])\n", | |
245 | "\n", |
|
245 | "\n", | |
246 | "tabs = widgets.Tab(children=[page1, page2])\n", |
|
246 | "tabs = widgets.Tab(children=[page1, page2])\n", |
General Comments 0
You need to be logged in to leave comments.
Login now