##// END OF EJS Templates
Support multiple types in selection widget.
Jonathan Frederic -
Show More
@@ -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 var selected_item_text = this.model.get('value');
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 var items = this.model.get('values');
67 var items = this.model.get('_values');
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 this.model.set('value', $(e.target).text(), {updated_view: this});
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 var items = this.model.get('values');
142 var items = this.model.get('_values');
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 if (that.model.get('value') == item) {
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 this.model.set('value', $(e.target).val(), {updated_view: this});
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 var items = this.model.get('values');
233 var items = this.model.get('_values');
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 if (that.model.get('value') == item) {
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 this.model.set('value', $(e.target).text(), {updated_view: this});
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 295 var ListBoxView = 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 var items = this.model.get('values');
319 var items = this.model.get('_values');
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', 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'));
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 363 return ListBoxView.__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 this.model.set('value', $(e.target).text(), {updated_view: this});
371 this.model.set('_value', $(e.target).text(), {updated_view: this});
372 372 this.touch();
373 373 },
374 374 });
375 375 WidgetManager.register_widget_view('ListBoxView', ListBoxView);
376 376 });
@@ -1,42 +1,100 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 from threading import Lock
17
16 18 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, List, Bool
19 from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict
18 20
19 21 #-----------------------------------------------------------------------------
20 22 # SelectionWidget
21 23 #-----------------------------------------------------------------------------
22 24 class _SelectionWidget(DOMWidget):
23 value = Unicode(help="Selected value", sync=True) # TODO: Any support
24 values = List(help="List of values the user can select", sync=True)
25 value = Any(help="Selected value")
26 values = List(help="List of values the user can select")
27 value_names = Dict(help="""List of string representations for each value.
28 These string representations are used to display the values in the
29 front-end.""")
25 30 disabled = Bool(False, help="Enable or disable user changes", sync=True)
26 31 description = Unicode(help="Description of the value this widget represents", sync=True)
27 32
33 _value = Unicode(sync=True) # Bi-directionally synced.
34 _values = List(sync=True) # Only back-end to front-end synced.
35 _reverse_value_names = Dict()
36
37 def __init__(self, *pargs, **kwargs):
38 """Constructor"""
39 DOMWidget.__init__(self, *pargs, **kwargs)
40 self.value_lock = Lock()
41 self.on_trait_change(self._string_value_set, ['_value'])
42
43 def _value_names_changed(self, name=None, old=None, new=None):
44 """Handles when the value_names Dict has been changed.
45
46 This method sets the _reverse_value_names Dict to the inverse of the new
47 value for the value_names Dict."""
48 self._reverse_value_names = {v:k for k, v in self.value_names.items()}
49 self._values_changed()
50
51 def _values_changed(self, name=None, old=None, new=None):
52 """Called when values has been changed"""
53 self._values = [self._get_string_repr(v) for v in self.values]
54
55 def _value_changed(self, name, old, new):
56 """Called when value has been changed"""
57 if self.value_lock.acquire(False):
58 try:
59 # Make sure the value is in the list of values.
60 if new in self.values:
61 # Set the string version of the value.
62 self._value = self._get_string_repr(new)
63 else:
64 raise TypeError('Value must be a value in the values list.')
65 finally:
66 self.value_lock.release()
67
68 def _get_string_repr(self, value):
69 """Get the string repr of a value"""
70 if value not in self.value_names:
71 self.value_names[value] = str(value)
72 self._value_names_changed()
73 return self.value_names[value]
74
75 def _string_value_set(self, name, old, new):
76 """Called when _value has been changed."""
77 if self.value_lock.acquire(False):
78 try:
79 if new in self._reverse_value_names:
80 self.value = self._reverse_value_names[new]
81 else:
82 self.value = None
83 finally:
84 self.value_lock.release()
85
28 86
29 87 class ToggleButtonsWidget(_SelectionWidget):
30 88 view_name = Unicode('ToggleButtonsView', sync=True)
31 89
32 90
33 91 class DropdownWidget(_SelectionWidget):
34 92 view_name = Unicode('DropdownView', sync=True)
35 93
36 94
37 95 class RadioButtonsWidget(_SelectionWidget):
38 96 view_name = Unicode('RadioButtonsView', sync=True)
39 97
40 98
41 99 class ListBoxWidget(_SelectionWidget):
42 100 view_name = Unicode('ListBoxView', sync=True)
General Comments 0
You need to be logged in to leave comments. Login now