##// END OF EJS Templates
Merge pull request #6890 from bollwyvl/widget-select-multiple...
Jonathan Frederic -
r20301:c8aa3bbf merge
parent child Browse files
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('value_name');
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('_value_names');
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('value_name', $(e.target).text(), {updated_view: this});
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('_value_names');
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('value_name') == item) {
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('value_name', $(e.target).val(), {updated_view: this});
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('_value_names');
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('value_name') == item) {
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('value_name', $(e.target).attr('value'), {updated_view: this});
414 this.model.set('selected_label', $(e.target).attr('value'), {updated_view: this});
414 this.touch();
415 this.touch();
415 },
416 },
416 });
417 });
@@ -430,7 +431,8 b' define(['
430 this.$listbox = $('<select />')
431 this.$listbox = $('<select />')
431 .addClass('widget-listbox form-control')
432 .addClass('widget-listbox form-control')
432 .attr('size', 6)
433 .attr('size', 6)
433 .appendTo(this.$el);
434 .appendTo(this.$el)
435 .on('change', $.proxy(this.handle_change, this));
434 this.update();
436 this.update();
435 },
437 },
436
438
@@ -443,7 +445,7 b' define(['
443 */
445 */
444 if (options === undefined || options.updated_view != this) {
446 if (options === undefined || options.updated_view != this) {
445 // Add missing items to the DOM.
447 // Add missing items to the DOM.
446 var items = this.model.get('_value_names');
448 var items = this.model.get('_options_labels');
447 var that = this;
449 var that = this;
448 _.each(items, function(item, index) {
450 _.each(items, function(item, index) {
449 var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]';
451 var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]';
@@ -451,14 +453,14 b' define(['
451 $('<option />')
453 $('<option />')
452 .text(item)
454 .text(item)
453 .attr('data-value', encodeURIComponent(item))
455 .attr('data-value', encodeURIComponent(item))
454 .attr('value_name', item)
456 .attr('selected_label', item)
455 .appendTo(that.$listbox)
457 .on("click", $.proxy(that.handle_click, that))
456 .on('click', $.proxy(that.handle_click, that));
458 .appendTo(that.$listbox);
457 }
459 }
458 });
460 });
459
461
460 // Select the correct element
462 // Select the correct element
461 this.$listbox.val(this.model.get('value_name'));
463 this.$listbox.val(this.model.get('selected_label'));
462
464
463 // Disable listbox if needed
465 // Disable listbox if needed
464 var disabled = this.model.get('disabled');
466 var disabled = this.model.get('disabled');
@@ -504,20 +506,68 b' define(['
504
506
505 handle_click: function (e) {
507 handle_click: function (e) {
506 /**
508 /**
507 * Handle when a value is clicked.
509 * Handle when a new value is clicked.
510 */
511 this.$listbox.val($(e.target).val()).change();
512 },
513
514 handle_change: function (e) {
515 /**
516 * Handle when a new value is selected.
508 *
517 *
509 * Calling model.set will trigger all of the other views of the
518 * Calling model.set will trigger all of the other views of the
510 * model to update.
519 * model to update.
511 */
520 */
512 this.model.set('value_name', $(e.target).text(), {updated_view: this});
521 this.model.set('selected_label', this.$listbox.val(), {updated_view: this});
513 this.touch();
522 this.touch();
514 },
523 },
524 });
525
526
527 var SelectMultipleView = SelectView.extend({
528 render: function(){
529 /**
530 * Called when view is rendered.
531 */
532 SelectMultipleView.__super__.render.apply(this);
533 this.$el.removeClass('widget-select')
534 .addClass('widget-select-multiple');
535 this.$listbox.attr('multiple', true)
536 .on('change', $.proxy(this.handle_change, this));
537 return this;
538 },
539
540 update: function(){
541 /**
542 * Update the contents of this view
543 *
544 * Called when the model is changed. The model may have been
545 * changed by another view or by a state update from the back-end.
546 */
547 SelectMultipleView.__super__.update.apply(this, arguments);
548 this.$listbox.val(this.model.get('selected_labels'));
549 },
550
551 handle_change: function (e) {
552 /**
553 * Handle when a new value is selected.
554 *
555 * Calling model.set will trigger all of the other views of the
556 * model to update.
557 */
558 this.model.set('selected_labels',
559 (this.$listbox.val() || []).slice(),
560 {updated_view: this});
561 this.touch();
562 },
515 });
563 });
516
564
565
517 return {
566 return {
518 'DropdownView': DropdownView,
567 'DropdownView': DropdownView,
519 'RadioButtonsView': RadioButtonsView,
568 'RadioButtonsView': RadioButtonsView,
520 'ToggleButtonsView': ToggleButtonsView,
569 'ToggleButtonsView': ToggleButtonsView,
521 'SelectView': SelectView,
570 'SelectView': SelectView,
571 'SelectMultipleView': SelectMultipleView,
522 };
572 };
523 });
573 });
@@ -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 'values=["' + selection_values + '"[i] for i in range(4)]\n' +
46 'options=["' + selection_values + '"[i] for i in range(4)]\n' +
47 'selection = [widgets.Dropdown(values=values),\n' +
47 'selection = [widgets.Dropdown(options=options),\n' +
48 ' widgets.ToggleButtons(values=values),\n' +
48 ' widgets.ToggleButtons(options=options),\n' +
49 ' widgets.RadioButtons(values=values),\n' +
49 ' widgets.RadioButtons(options=options),\n' +
50 ' widgets.Select(values=values)]\n' +
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.values)\n' +
139 ' d = copy(widget.options)\n' +
140 ' d.append("z")\n' +
140 ' d.append("z")\n' +
141 ' widget.values = d\n' +
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(values=o)
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(values=[unicode_type(k) for k in o])
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 values=d,
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 values=values
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 values={'a': 1, 'b': 2},
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 values=['hi', 'there'],
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 values={'a': 1},
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 values=['hi', 'there'],
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 ``values`` can be specified as a list or dict. If given as a list,
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 value_name = Unicode(help="The name of the selected value", sync=True)
38 selected_label = Unicode(help="The label of the selected value", sync=True)
39 values = Any(help="""List of (key, value) tuples or dict of values that the
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 _value_names.
45 The keys of this list are also available as _options_labels.
46 """)
46 """)
47
47
48 _values_dict = Dict()
48 _options_dict = Dict()
49 _value_names = Tuple(sync=True)
49 _options_labels = Tuple(sync=True)
50 _value_values = Tuple()
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.values_lock = Lock()
57 self.options_lock = Lock()
58 self.on_trait_change(self._values_readonly_changed, ['_values_dict', '_value_names', '_value_values', '_values'])
58 self.on_trait_change(self._options_readonly_changed, ['_options_dict', '_options_labels', '_options_values', '_options'])
59 if 'values' in kwargs:
59 if 'options' in kwargs:
60 self.values = kwargs.pop('values')
60 self.options = kwargs.pop('options')
61 DOMWidget.__init__(self, *args, **kwargs)
61 DOMWidget.__init__(self, *args, **kwargs)
62 self._value_in_values()
62 self._value_in_options()
63
63
64 def _make_values(self, x):
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 _values_changed(self, name, old, new):
81 def _options_changed(self, name, old, new):
82 """Handles when the values tuple has been changed.
82 """Handles when the options tuple has been changed.
83
83
84 Setting values implies setting value names from the keys of the dict.
84 Setting options implies setting option labels from the keys of the dict.
85 """
85 """
86 if self.values_lock.acquire(False):
86 if self.options_lock.acquire(False):
87 try:
87 try:
88 self.values = new
88 self.options = new
89
89
90 values = self._make_values(new)
90 options = self._make_options(new)
91 self._values_dict = {i[0]: i[1] for i in values}
91 self._options_dict = {i[0]: i[1] for i in options}
92 self._value_names = [i[0] for i in values]
92 self._options_labels = [i[0] for i in options]
93 self._value_values = [i[1] for i in values]
93 self._options_values = [i[1] for i in options]
94 self._value_in_values()
94 self._value_in_options()
95 finally:
95 finally:
96 self.values_lock.release()
96 self.options_lock.release()
97
97
98 def _value_in_values(self):
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._values_dict.items():
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.value_name = k
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 _value_name_changed(self, name, old, new):
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._values_dict[new]
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(values=get_export_names(), value='html')\n",
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 four inherit from the same base class. You can specify the **enumeration of selectables 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."
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 " values=['1', '2', '3'],\n",
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 " values={'One': 1, 'Two': 2, 'Three': 3},\n",
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 " values=['pepperoni', 'pineapple', 'anchovies'],\n",
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 " values=['Linux', 'Windows', 'OSX'],\n",
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 " values=['Slow', 'Regular', 'Fast'],\n",
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:', values=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])\n",
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:', values=['male', 'female'])\n",
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