From 7b94a2e2cb6d9962b5063fcbfab7654831285b2f 2014-02-04 01:15:06 From: MinRK <benjaminrk@gmail.com> Date: 2014-02-04 01:15:06 Subject: [PATCH] Make `SelectionWidget.values` a dict rename 'labels' and '_value' to 'value_names' and 'value_name'. To specify a mapping of value names and values, use a dict. If you specify `values=[list]`, then an OrderedDict will be used. Assignment after construction only supports a dict. --- diff --git a/IPython/html/static/notebook/js/widgets/widget_selection.js b/IPython/html/static/notebook/js/widgets/widget_selection.js index 5d9791c..30c7ce4 100644 --- a/IPython/html/static/notebook/js/widgets/widget_selection.js +++ b/IPython/html/static/notebook/js/widgets/widget_selection.js @@ -57,14 +57,14 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // changed by another view or by a state update from the back-end. if (options === undefined || options.updated_view != this) { - var selected_item_text = this.model.get('_value'); + var selected_item_text = this.model.get('value_name'); if (selected_item_text.length === 0) { this.$droplabel.text(' '); } else { this.$droplabel.text(selected_item_text); } - var items = this.model.get('labels'); + var items = this.model.get('value_names'); var $replace_droplist = $('<ul />') .addClass('dropdown-menu'); var that = this; @@ -107,7 +107,7 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // Calling model.set will trigger all of the other views of the // model to update. - this.model.set('_value', $(e.target).text(), {updated_view: this}); + this.model.set('value_name', $(e.target).text(), {updated_view: this}); this.touch(); }, @@ -139,7 +139,7 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // changed by another view or by a state update from the back-end. if (options === undefined || options.updated_view != this) { // Add missing items to the DOM. - var items = this.model.get('labels'); + var items = this.model.get('value_names'); var disabled = this.model.get('disabled'); var that = this; _.each(items, function(item, index) { @@ -159,7 +159,7 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ } var $item_element = that.$container.find(item_query); - if (that.model.get('_value') == item) { + if (that.model.get('value_name') == item) { $item_element.prop('checked', true); } else { $item_element.prop('checked', false); @@ -199,14 +199,14 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // Calling model.set will trigger all of the other views of the // model to update. - this.model.set('_value', $(e.target).val(), {updated_view: this}); + this.model.set('value_name', $(e.target).val(), {updated_view: this}); this.touch(); }, }); WidgetManager.register_widget_view('RadioButtonsView', RadioButtonsView); - var ToggleButtonsView = IPython.DOMWidgetView.extend({ + var ToggleButtonsView = IPython.DOMWidgetView.extend({ render : function(){ // Called when view is rendered. this.$el @@ -230,7 +230,7 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // changed by another view or by a state update from the back-end. if (options === undefined || options.updated_view != this) { // Add missing items to the DOM. - var items = this.model.get('labels'); + var items = this.model.get('value_names'); var disabled = this.model.get('disabled'); var that = this; _.each(items, function(item, index) { @@ -245,7 +245,7 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ } var $item_element = that.$buttongroup.find(item_query); - if (that.model.get('_value') == item) { + if (that.model.get('value_name') == item) { $item_element.addClass('active'); } else { $item_element.removeClass('active'); @@ -285,7 +285,7 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // Calling model.set will trigger all of the other views of the // model to update. - this.model.set('_value', $(e.target).text(), {updated_view: this}); + this.model.set('value_name', $(e.target).text(), {updated_view: this}); this.touch(); }, }); @@ -316,21 +316,21 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // changed by another view or by a state update from the back-end. if (options === undefined || options.updated_view != this) { // Add missing items to the DOM. - var items = this.model.get('labels'); + var items = this.model.get('value_names'); var that = this; _.each(items, function(item, index) { var item_query = ' :contains("' + item + '")'; if (that.$listbox.find(item_query).length === 0) { $('<option />') .text(item) - .attr('_value', item) + .attr('value_name', item) .appendTo(that.$listbox) .on('click', $.proxy(that.handle_click, that)); } }); // Select the correct element - this.$listbox.val(this.model.get('_value')); + this.$listbox.val(this.model.get('value_name')); // Disable listbox if needed var disabled = this.model.get('disabled'); @@ -368,7 +368,7 @@ define(["notebook/js/widgets/widget"], function(WidgetManager){ // Calling model.set will trigger all of the other views of the // model to update. - this.model.set('_value', $(e.target).text(), {updated_view: this}); + this.model.set('value_name', $(e.target).text(), {updated_view: this}); this.touch(); }, }); diff --git a/IPython/html/tests/casperjs/test_cases/widgets_selection.js b/IPython/html/tests/casperjs/test_cases/widgets_selection.js index abf68a7..e21eced 100644 --- a/IPython/html/tests/casperjs/test_cases/widgets_selection.js +++ b/IPython/html/tests/casperjs/test_cases/widgets_selection.js @@ -123,7 +123,9 @@ casper.notebook_test(function () { index = this.append_cell( 'for widget in selection:\n' + - ' widget.values = list(widget.values) + ["z"]\n' + + ' d = widget.values.copy()\n' + + ' d["z"] = "z"\n' + + ' widget.values = d\n' + 'selection[0].value = "z"'); this.execute_cell_then(index, function(index){ diff --git a/IPython/html/widgets/widget_selection.py b/IPython/html/widgets/widget_selection.py index b167027..399b932 100644 --- a/IPython/html/widgets/widget_selection.py +++ b/IPython/html/widgets/widget_selection.py @@ -1,4 +1,4 @@ -"""SelectionWidget class. +"""SelectionWidget classes. Represents an enumeration using a widget. """ @@ -13,66 +13,84 @@ Represents an enumeration using a widget. #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- + +from collections import OrderedDict from threading import Lock from .widget import DOMWidget -from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict +from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict, TraitError +from IPython.utils.py3compat import unicode_type #----------------------------------------------------------------------------- # SelectionWidget #----------------------------------------------------------------------------- class _SelectionWidget(DOMWidget): - value = Any(help="Selected value") - values = List(help="List of values the user can select") - labels = List(help="""List of string representations for each value. - These string representations are used to display the values in the - front-end.""", sync=True) # Only synced to the back-end. + """Base class for Selection widgets + + ``values`` can be specified as a list or dict. If given as a list, + it will be transformed to a dict of the form ``{str(value):value}``. + """ + + value = Any(help="Selected value") + values = Dict(help="""Dictionary of {name: value} the user can select. + + The keys of this dictionary are the strings that will be displayed in the UI, + representing the actual Python choices. + + The keys of this dictionary are also available as value_names. + """) + value_name = Unicode(help="The name of the selected value", sync=True) + value_names = List(Unicode, help="""List of names for each value. + + If values is specified as a list, this is the string representation of each element. + Otherwise, it is the keys of the values dictionary. + + These strings are used to display the choices in the front-end.""", sync=True) disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode(help="Description of the value this widget represents", sync=True) + - _value = Unicode(sync=True) # Bi-directionally synced. - - def __init__(self, *pargs, **kwargs): - """Constructor""" + def __init__(self, *args, **kwargs): self.value_lock = Lock() - self.on_trait_change(self._string_value_set, ['_value']) - DOMWidget.__init__(self, *pargs, **kwargs) - - def _labels_changed(self, name=None, old=None, new=None): - """Handles when the value_names Dict has been changed. + if 'values' in kwargs: + values = kwargs['values'] + # convert list values to an dict of {str(v):v} + if isinstance(values, list): + # preserve list order with an OrderedDict + kwargs['values'] = OrderedDict((unicode_type(v), v) for v in values) + DOMWidget.__init__(self, *args, **kwargs) + + def _values_changed(self, name, old, new): + """Handles when the values dict has been changed. - This method sets the _reverse_value_names Dict to the inverse of the new - value for the value_names Dict.""" + Setting values implies setting value names from the keys of the dict. + """ + self.value_names = list(new.keys()) + + def _value_names_changed(self, name, old, new): if len(new) != len(self.values): - raise TypeError('Labels list must be the same size as the values list.') - - def _values_changed(self, name=None, old=None, new=None): - """Handles when the value_names Dict has been changed. - - This method sets the _reverse_value_names Dict to the inverse of the new - value for the value_names Dict.""" - if len(new) != len(self.labels): - self.labels = [(self.labels[i] if i < len(self.labels) else str(v)) for i, v in enumerate(new)] + raise TraitError("Expected %i value names, got %i." % (len(self.values), len(new))) def _value_changed(self, name, old, new): """Called when value has been changed""" if self.value_lock.acquire(False): try: - # Make sure the value is in the list of values. - if new in self.values: - # Set the string version of the value. - self._value = self.labels[self.values.index(new)] - else: - raise TypeError('Value must be a value in the values list.') + # Make sure the value is one of the options + for k,v in self.values.items(): + if new == v: + # set the selected value name + self.value_name = k + return + raise TraitError('Value not found: %r' % new) finally: self.value_lock.release() - def _string_value_set(self, name, old, new): - """Called when _value has been changed.""" + def _value_name_changed(self, name, old, new): + """Called when the value name has been changed (typically by the frontend).""" if self.value_lock.acquire(False): try: - if new in self.labels: - self.value = self.values[self.labels.index(new)] + if new in self.values: + self.value = self.values[new] else: self.value = None finally: