##// END OF EJS Templates
Merge pull request #5012 from minrk/selection-dict...
Brian E. Granger -
r15066:a4c95005 merge
parent child Browse files
Show More
@@ -57,14 +57,14 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
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 var selected_item_text = this.model.get('_value');
60 var selected_item_text = this.model.get('value_name');
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 var items = this.model.get('labels');
67 var items = this.model.get('value_names');
68 68 var $replace_droplist = $('<ul />')
69 69 .addClass('dropdown-menu');
70 70 var that = this;
@@ -107,7 +107,7 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
107 107
108 108 // Calling model.set will trigger all of the other views of the
109 109 // model to update.
110 this.model.set('_value', $(e.target).text(), {updated_view: this});
110 this.model.set('value_name', $(e.target).text(), {updated_view: this});
111 111 this.touch();
112 112 },
113 113
@@ -139,7 +139,7 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
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 var items = this.model.get('labels');
142 var items = this.model.get('value_names');
143 143 var disabled = this.model.get('disabled');
144 144 var that = this;
145 145 _.each(items, function(item, index) {
@@ -159,7 +159,7 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
159 159 }
160 160
161 161 var $item_element = that.$container.find(item_query);
162 if (that.model.get('_value') == item) {
162 if (that.model.get('value_name') == item) {
163 163 $item_element.prop('checked', true);
164 164 } else {
165 165 $item_element.prop('checked', false);
@@ -199,14 +199,14 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
199 199
200 200 // Calling model.set will trigger all of the other views of the
201 201 // model to update.
202 this.model.set('_value', $(e.target).val(), {updated_view: this});
202 this.model.set('value_name', $(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 var ToggleButtonsView = IPython.DOMWidgetView.extend({
209 var ToggleButtonsView = IPython.DOMWidgetView.extend({
210 210 render : function(){
211 211 // Called when view is rendered.
212 212 this.$el
@@ -230,7 +230,7 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
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 var items = this.model.get('labels');
233 var items = this.model.get('value_names');
234 234 var disabled = this.model.get('disabled');
235 235 var that = this;
236 236 _.each(items, function(item, index) {
@@ -245,7 +245,7 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
245 245 }
246 246
247 247 var $item_element = that.$buttongroup.find(item_query);
248 if (that.model.get('_value') == item) {
248 if (that.model.get('value_name') == item) {
249 249 $item_element.addClass('active');
250 250 } else {
251 251 $item_element.removeClass('active');
@@ -285,7 +285,7 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
285 285
286 286 // Calling model.set will trigger all of the other views of the
287 287 // model to update.
288 this.model.set('_value', $(e.target).text(), {updated_view: this});
288 this.model.set('value_name', $(e.target).text(), {updated_view: this});
289 289 this.touch();
290 290 },
291 291 });
@@ -316,21 +316,21 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
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 var items = this.model.get('labels');
319 var items = this.model.get('value_names');
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 .attr('_value', item)
326 .attr('value_name', 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 this.$listbox.val(this.model.get('_value'));
333 this.$listbox.val(this.model.get('value_name'));
334 334
335 335 // Disable listbox if needed
336 336 var disabled = this.model.get('disabled');
@@ -368,7 +368,7 b' define(["notebook/js/widgets/widget"], function(WidgetManager){'
368 368
369 369 // Calling model.set will trigger all of the other views of the
370 370 // model to update.
371 this.model.set('_value', $(e.target).text(), {updated_view: this});
371 this.model.set('value_name', $(e.target).text(), {updated_view: this});
372 372 this.touch();
373 373 },
374 374 });
@@ -76,7 +76,7 b' casper.notebook_test(function () {'
76 76 'Widget list exists.');
77 77
78 78 // Verify that no items are selected.
79 this.test.assert(verify_selection(this, -1), 'No items selected.');
79 this.test.assert(verify_selection(this, 0), 'Default first item selected.');
80 80 });
81 81
82 82 index = this.append_cell(
@@ -123,7 +123,9 b' casper.notebook_test(function () {'
123 123
124 124 index = this.append_cell(
125 125 'for widget in selection:\n' +
126 ' widget.values = list(widget.values) + ["z"]\n' +
126 ' d = widget.values.copy()\n' +
127 ' d["z"] = "z"\n' +
128 ' widget.values = d\n' +
127 129 'selection[0].value = "z"');
128 130 this.execute_cell_then(index, function(index){
129 131
@@ -1,4 +1,4 b''
1 """SelectionWidget class.
1 """SelectionWidget classes.
2 2
3 3 Represents an enumeration using a widget.
4 4 """
@@ -13,68 +13,92 b' Represents an enumeration using a widget.'
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16
17 from collections import OrderedDict
16 18 from threading import Lock
17 19
18 20 from .widget import DOMWidget
19 from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict
21 from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict, TraitError
22 from IPython.utils.py3compat import unicode_type
20 23
21 24 #-----------------------------------------------------------------------------
22 25 # SelectionWidget
23 26 #-----------------------------------------------------------------------------
24 27 class _SelectionWidget(DOMWidget):
25 value = Any(help="Selected value")
26 values = List(help="List of values the user can select")
27 labels = List(help="""List of string representations for each value.
28 These string representations are used to display the values in the
29 front-end.""", sync=True) # Only synced to the back-end.
28 """Base class for Selection widgets
29
30 ``values`` can be specified as a list or dict. If given as a list,
31 it will be transformed to a dict of the form ``{str(value):value}``.
32 """
33
34 value = Any(help="Selected value")
35 values = Dict(help="""Dictionary of {name: value} the user can select.
36
37 The keys of this dictionary are the strings that will be displayed in the UI,
38 representing the actual Python choices.
39
40 The keys of this dictionary are also available as value_names.
41 """)
42 value_name = Unicode(help="The name of the selected value", sync=True)
43 value_names = List(Unicode, help="""Read-only list of names for each value.
44
45 If values is specified as a list, this is the string representation of each element.
46 Otherwise, it is the keys of the values dictionary.
47
48 These strings are used to display the choices in the front-end.""", sync=True)
30 49 disabled = Bool(False, help="Enable or disable user changes", sync=True)
31 50 description = Unicode(help="Description of the value this widget represents", sync=True)
51
32 52
33 _value = Unicode(sync=True) # Bi-directionally synced.
34
35 def __init__(self, *pargs, **kwargs):
36 """Constructor"""
53 def __init__(self, *args, **kwargs):
37 54 self.value_lock = Lock()
38 self.on_trait_change(self._string_value_set, ['_value'])
39 DOMWidget.__init__(self, *pargs, **kwargs)
40
41 def _labels_changed(self, name=None, old=None, new=None):
42 """Handles when the value_names Dict has been changed.
43
44 This method sets the _reverse_value_names Dict to the inverse of the new
45 value for the value_names Dict."""
46 if len(new) != len(self.values):
47 raise TypeError('Labels list must be the same size as the values list.')
48
49 def _values_changed(self, name=None, old=None, new=None):
50 """Handles when the value_names Dict has been changed.
51
52 This method sets the _reverse_value_names Dict to the inverse of the new
53 value for the value_names Dict."""
54 if len(new) != len(self.labels):
55 self.labels = [(self.labels[i] if i < len(self.labels) else str(v)) for i, v in enumerate(new)]
55 self._in_values_changed = False
56 if 'values' in kwargs:
57 values = kwargs['values']
58 # convert list values to an dict of {str(v):v}
59 if isinstance(values, list):
60 # preserve list order with an OrderedDict
61 kwargs['values'] = OrderedDict((unicode_type(v), v) for v in values)
62 DOMWidget.__init__(self, *args, **kwargs)
63
64 def _values_changed(self, name, old, new):
65 """Handles when the values dict has been changed.
66
67 Setting values implies setting value names from the keys of the dict.
68 """
69 self._in_values_changed = True
70 try:
71 self.value_names = list(new.keys())
72 finally:
73 self._in_values_changed = False
74
75 # ensure that the chosen value is one of the choices
76 if self.value not in new.values():
77 self.value = next(iter(new.values()))
78
79 def _value_names_changed(self, name, old, new):
80 if not self._in_values_changed:
81 raise TraitError("value_names is a read-only proxy to values.keys(). Use the values dict instead.")
56 82
57 83 def _value_changed(self, name, old, new):
58 84 """Called when value has been changed"""
59 85 if self.value_lock.acquire(False):
60 86 try:
61 # Make sure the value is in the list of values.
62 if new in self.values:
63 # Set the string version of the value.
64 self._value = self.labels[self.values.index(new)]
65 else:
66 raise TypeError('Value must be a value in the values list.')
87 # Reverse dictionary lookup for the value name
88 for k,v in self.values.items():
89 if new == v:
90 # set the selected value name
91 self.value_name = k
92 return
93 raise KeyError(new)
67 94 finally:
68 95 self.value_lock.release()
69 96
70 def _string_value_set(self, name, old, new):
71 """Called when _value has been changed."""
97 def _value_name_changed(self, name, old, new):
98 """Called when the value name has been changed (typically by the frontend)."""
72 99 if self.value_lock.acquire(False):
73 100 try:
74 if new in self.labels:
75 self.value = self.values[self.labels.index(new)]
76 else:
77 self.value = None
101 self.value = self.values[new]
78 102 finally:
79 103 self.value_lock.release()
80 104
General Comments 0
You need to be logged in to leave comments. Login now