##// END OF EJS Templates
Merge pull request #5012 from minrk/selection-dict...
Brian E. Granger -
r15066:a4c95005 merge
parent child Browse files
Show More
@@ -1,376 +1,376 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // SelectionWidget
9 // SelectionWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
17 define(["notebook/js/widgets/widget"], function(WidgetManager){
18
18
19 var DropdownView = IPython.DOMWidgetView.extend({
19 var DropdownView = IPython.DOMWidgetView.extend({
20 render : function(){
20 render : function(){
21 // Called when view is rendered.
21 // Called when view is rendered.
22 this.$el
22 this.$el
23 .addClass('widget-hbox-single');
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
24 this.$label = $('<div />')
25 .appendTo(this.$el)
25 .appendTo(this.$el)
26 .addClass('widget-hlabel')
26 .addClass('widget-hlabel')
27 .hide();
27 .hide();
28 this.$buttongroup = $('<div />')
28 this.$buttongroup = $('<div />')
29 .addClass('widget_item')
29 .addClass('widget_item')
30 .addClass('btn-group')
30 .addClass('btn-group')
31 .appendTo(this.$el);
31 .appendTo(this.$el);
32 this.$el_to_style = this.$buttongroup; // Set default element to style
32 this.$el_to_style = this.$buttongroup; // Set default element to style
33 this.$droplabel = $('<button />')
33 this.$droplabel = $('<button />')
34 .addClass('btn')
34 .addClass('btn')
35 .addClass('widget-combo-btn')
35 .addClass('widget-combo-btn')
36 .text(' ')
36 .text(' ')
37 .appendTo(this.$buttongroup);
37 .appendTo(this.$buttongroup);
38 this.$dropbutton = $('<button />')
38 this.$dropbutton = $('<button />')
39 .addClass('btn')
39 .addClass('btn')
40 .addClass('dropdown-toggle')
40 .addClass('dropdown-toggle')
41 .addClass('widget-combo-carrot-btn')
41 .addClass('widget-combo-carrot-btn')
42 .attr('data-toggle', 'dropdown')
42 .attr('data-toggle', 'dropdown')
43 .append($('<span />').addClass("caret"))
43 .append($('<span />').addClass("caret"))
44 .appendTo(this.$buttongroup);
44 .appendTo(this.$buttongroup);
45 this.$droplist = $('<ul />')
45 this.$droplist = $('<ul />')
46 .addClass('dropdown-menu')
46 .addClass('dropdown-menu')
47 .appendTo(this.$buttongroup);
47 .appendTo(this.$buttongroup);
48
48
49 // Set defaults.
49 // Set defaults.
50 this.update();
50 this.update();
51 },
51 },
52
52
53 update : function(options){
53 update : function(options){
54 // Update the contents of this view
54 // Update the contents of this view
55 //
55 //
56 // Called when the model is changed. The model may have been
56 // Called when the model is changed. The model may have been
57 // 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.
58
58
59 if (options === undefined || options.updated_view != this) {
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 if (selected_item_text.length === 0) {
61 if (selected_item_text.length === 0) {
62 this.$droplabel.text(' ');
62 this.$droplabel.text(' ');
63 } else {
63 } else {
64 this.$droplabel.text(selected_item_text);
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 var $replace_droplist = $('<ul />')
68 var $replace_droplist = $('<ul />')
69 .addClass('dropdown-menu');
69 .addClass('dropdown-menu');
70 var that = this;
70 var that = this;
71 _.each(items, function(item, i) {
71 _.each(items, function(item, i) {
72 var item_button = $('<a href="#"/>')
72 var item_button = $('<a href="#"/>')
73 .text(item)
73 .text(item)
74 .on('click', $.proxy(that.handle_click, that));
74 .on('click', $.proxy(that.handle_click, that));
75 $replace_droplist.append($('<li />').append(item_button));
75 $replace_droplist.append($('<li />').append(item_button));
76 });
76 });
77
77
78 this.$droplist.replaceWith($replace_droplist);
78 this.$droplist.replaceWith($replace_droplist);
79 this.$droplist.remove();
79 this.$droplist.remove();
80 this.$droplist = $replace_droplist;
80 this.$droplist = $replace_droplist;
81
81
82 if (this.model.get('disabled')) {
82 if (this.model.get('disabled')) {
83 this.$buttongroup.attr('disabled','disabled');
83 this.$buttongroup.attr('disabled','disabled');
84 this.$droplabel.attr('disabled','disabled');
84 this.$droplabel.attr('disabled','disabled');
85 this.$dropbutton.attr('disabled','disabled');
85 this.$dropbutton.attr('disabled','disabled');
86 this.$droplist.attr('disabled','disabled');
86 this.$droplist.attr('disabled','disabled');
87 } else {
87 } else {
88 this.$buttongroup.removeAttr('disabled');
88 this.$buttongroup.removeAttr('disabled');
89 this.$droplabel.removeAttr('disabled');
89 this.$droplabel.removeAttr('disabled');
90 this.$dropbutton.removeAttr('disabled');
90 this.$dropbutton.removeAttr('disabled');
91 this.$droplist.removeAttr('disabled');
91 this.$droplist.removeAttr('disabled');
92 }
92 }
93
93
94 var description = this.model.get('description');
94 var description = this.model.get('description');
95 if (description.length === 0) {
95 if (description.length === 0) {
96 this.$label.hide();
96 this.$label.hide();
97 } else {
97 } else {
98 this.$label.text(description);
98 this.$label.text(description);
99 this.$label.show();
99 this.$label.show();
100 }
100 }
101 }
101 }
102 return DropdownView.__super__.update.apply(this);
102 return DropdownView.__super__.update.apply(this);
103 },
103 },
104
104
105 handle_click: function (e) {
105 handle_click: function (e) {
106 // Handle when a value is clicked.
106 // Handle when a value is clicked.
107
107
108 // Calling model.set will trigger all of the other views of the
108 // Calling model.set will trigger all of the other views of the
109 // model to update.
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 this.touch();
111 this.touch();
112 },
112 },
113
113
114 });
114 });
115 WidgetManager.register_widget_view('DropdownView', DropdownView);
115 WidgetManager.register_widget_view('DropdownView', DropdownView);
116
116
117
117
118 var RadioButtonsView = IPython.DOMWidgetView.extend({
118 var RadioButtonsView = IPython.DOMWidgetView.extend({
119 render : function(){
119 render : function(){
120 // Called when view is rendered.
120 // Called when view is rendered.
121 this.$el
121 this.$el
122 .addClass('widget-hbox');
122 .addClass('widget-hbox');
123 this.$label = $('<div />')
123 this.$label = $('<div />')
124 .appendTo(this.$el)
124 .appendTo(this.$el)
125 .addClass('widget-hlabel')
125 .addClass('widget-hlabel')
126 .hide();
126 .hide();
127 this.$container = $('<div />')
127 this.$container = $('<div />')
128 .appendTo(this.$el)
128 .appendTo(this.$el)
129 .addClass('widget-container')
129 .addClass('widget-container')
130 .addClass('vbox');
130 .addClass('vbox');
131 this.$el_to_style = this.$container; // Set default element to style
131 this.$el_to_style = this.$container; // Set default element to style
132 this.update();
132 this.update();
133 },
133 },
134
134
135 update : function(options){
135 update : function(options){
136 // Update the contents of this view
136 // Update the contents of this view
137 //
137 //
138 // Called when the model is changed. The model may have been
138 // Called when the model is changed. The model may have been
139 // changed by another view or by a state update from the back-end.
139 // changed by another view or by a state update from the back-end.
140 if (options === undefined || options.updated_view != this) {
140 if (options === undefined || options.updated_view != this) {
141 // Add missing items to the DOM.
141 // Add missing items to the DOM.
142 var items = this.model.get('labels');
142 var items = this.model.get('value_names');
143 var disabled = this.model.get('disabled');
143 var disabled = this.model.get('disabled');
144 var that = this;
144 var that = this;
145 _.each(items, function(item, index) {
145 _.each(items, function(item, index) {
146 var item_query = ' :input[value="' + item + '"]';
146 var item_query = ' :input[value="' + item + '"]';
147 if (that.$el.find(item_query).length === 0) {
147 if (that.$el.find(item_query).length === 0) {
148 var $label = $('<label />')
148 var $label = $('<label />')
149 .addClass('radio')
149 .addClass('radio')
150 .text(item)
150 .text(item)
151 .appendTo(that.$container);
151 .appendTo(that.$container);
152
152
153 $('<input />')
153 $('<input />')
154 .attr('type', 'radio')
154 .attr('type', 'radio')
155 .addClass(that.model)
155 .addClass(that.model)
156 .val(item)
156 .val(item)
157 .prependTo($label)
157 .prependTo($label)
158 .on('click', $.proxy(that.handle_click, that));
158 .on('click', $.proxy(that.handle_click, that));
159 }
159 }
160
160
161 var $item_element = that.$container.find(item_query);
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 $item_element.prop('checked', true);
163 $item_element.prop('checked', true);
164 } else {
164 } else {
165 $item_element.prop('checked', false);
165 $item_element.prop('checked', false);
166 }
166 }
167 $item_element.prop('disabled', disabled);
167 $item_element.prop('disabled', disabled);
168 });
168 });
169
169
170 // Remove items that no longer exist.
170 // Remove items that no longer exist.
171 this.$container.find('input').each(function(i, obj) {
171 this.$container.find('input').each(function(i, obj) {
172 var value = $(obj).val();
172 var value = $(obj).val();
173 var found = false;
173 var found = false;
174 _.each(items, function(item, index) {
174 _.each(items, function(item, index) {
175 if (item == value) {
175 if (item == value) {
176 found = true;
176 found = true;
177 return false;
177 return false;
178 }
178 }
179 });
179 });
180
180
181 if (!found) {
181 if (!found) {
182 $(obj).parent().remove();
182 $(obj).parent().remove();
183 }
183 }
184 });
184 });
185
185
186 var description = this.model.get('description');
186 var description = this.model.get('description');
187 if (description.length === 0) {
187 if (description.length === 0) {
188 this.$label.hide();
188 this.$label.hide();
189 } else {
189 } else {
190 this.$label.text(description);
190 this.$label.text(description);
191 this.$label.show();
191 this.$label.show();
192 }
192 }
193 }
193 }
194 return RadioButtonsView.__super__.update.apply(this);
194 return RadioButtonsView.__super__.update.apply(this);
195 },
195 },
196
196
197 handle_click: function (e) {
197 handle_click: function (e) {
198 // Handle when a value is clicked.
198 // Handle when a value is clicked.
199
199
200 // Calling model.set will trigger all of the other views of the
200 // Calling model.set will trigger all of the other views of the
201 // model to update.
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 this.touch();
203 this.touch();
204 },
204 },
205 });
205 });
206 WidgetManager.register_widget_view('RadioButtonsView', RadioButtonsView);
206 WidgetManager.register_widget_view('RadioButtonsView', RadioButtonsView);
207
207
208
208
209 var ToggleButtonsView = IPython.DOMWidgetView.extend({
209 var ToggleButtonsView = IPython.DOMWidgetView.extend({
210 render : function(){
210 render : function(){
211 // Called when view is rendered.
211 // Called when view is rendered.
212 this.$el
212 this.$el
213 .addClass('widget-hbox-single');
213 .addClass('widget-hbox-single');
214 this.$label = $('<div />')
214 this.$label = $('<div />')
215 .appendTo(this.$el)
215 .appendTo(this.$el)
216 .addClass('widget-hlabel')
216 .addClass('widget-hlabel')
217 .hide();
217 .hide();
218 this.$buttongroup = $('<div />')
218 this.$buttongroup = $('<div />')
219 .addClass('btn-group')
219 .addClass('btn-group')
220 .attr('data-toggle', 'buttons-radio')
220 .attr('data-toggle', 'buttons-radio')
221 .appendTo(this.$el);
221 .appendTo(this.$el);
222 this.$el_to_style = this.$buttongroup; // Set default element to style
222 this.$el_to_style = this.$buttongroup; // Set default element to style
223 this.update();
223 this.update();
224 },
224 },
225
225
226 update : function(options){
226 update : function(options){
227 // Update the contents of this view
227 // Update the contents of this view
228 //
228 //
229 // Called when the model is changed. The model may have been
229 // Called when the model is changed. The model may have been
230 // changed by another view or by a state update from the back-end.
230 // changed by another view or by a state update from the back-end.
231 if (options === undefined || options.updated_view != this) {
231 if (options === undefined || options.updated_view != this) {
232 // Add missing items to the DOM.
232 // Add missing items to the DOM.
233 var items = this.model.get('labels');
233 var items = this.model.get('value_names');
234 var disabled = this.model.get('disabled');
234 var disabled = this.model.get('disabled');
235 var that = this;
235 var that = this;
236 _.each(items, function(item, index) {
236 _.each(items, function(item, index) {
237 var item_query = ' :contains("' + item + '")';
237 var item_query = ' :contains("' + item + '")';
238 if (that.$buttongroup.find(item_query).length === 0) {
238 if (that.$buttongroup.find(item_query).length === 0) {
239 $('<button />')
239 $('<button />')
240 .attr('type', 'button')
240 .attr('type', 'button')
241 .addClass('btn')
241 .addClass('btn')
242 .text(item)
242 .text(item)
243 .appendTo(that.$buttongroup)
243 .appendTo(that.$buttongroup)
244 .on('click', $.proxy(that.handle_click, that));
244 .on('click', $.proxy(that.handle_click, that));
245 }
245 }
246
246
247 var $item_element = that.$buttongroup.find(item_query);
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 $item_element.addClass('active');
249 $item_element.addClass('active');
250 } else {
250 } else {
251 $item_element.removeClass('active');
251 $item_element.removeClass('active');
252 }
252 }
253 $item_element.prop('disabled', disabled);
253 $item_element.prop('disabled', disabled);
254 });
254 });
255
255
256 // Remove items that no longer exist.
256 // Remove items that no longer exist.
257 this.$buttongroup.find('button').each(function(i, obj) {
257 this.$buttongroup.find('button').each(function(i, obj) {
258 var value = $(obj).text();
258 var value = $(obj).text();
259 var found = false;
259 var found = false;
260 _.each(items, function(item, index) {
260 _.each(items, function(item, index) {
261 if (item == value) {
261 if (item == value) {
262 found = true;
262 found = true;
263 return false;
263 return false;
264 }
264 }
265 });
265 });
266
266
267 if (!found) {
267 if (!found) {
268 $(obj).remove();
268 $(obj).remove();
269 }
269 }
270 });
270 });
271
271
272 var description = this.model.get('description');
272 var description = this.model.get('description');
273 if (description.length === 0) {
273 if (description.length === 0) {
274 this.$label.hide();
274 this.$label.hide();
275 } else {
275 } else {
276 this.$label.text(description);
276 this.$label.text(description);
277 this.$label.show();
277 this.$label.show();
278 }
278 }
279 }
279 }
280 return ToggleButtonsView.__super__.update.apply(this);
280 return ToggleButtonsView.__super__.update.apply(this);
281 },
281 },
282
282
283 handle_click: function (e) {
283 handle_click: function (e) {
284 // Handle when a value is clicked.
284 // Handle when a value is clicked.
285
285
286 // Calling model.set will trigger all of the other views of the
286 // Calling model.set will trigger all of the other views of the
287 // model to update.
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 this.touch();
289 this.touch();
290 },
290 },
291 });
291 });
292 WidgetManager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
292 WidgetManager.register_widget_view('ToggleButtonsView', ToggleButtonsView);
293
293
294
294
295 var SelectView = IPython.DOMWidgetView.extend({
295 var SelectView = IPython.DOMWidgetView.extend({
296 render : function(){
296 render : function(){
297 // Called when view is rendered.
297 // Called when view is rendered.
298 this.$el
298 this.$el
299 .addClass('widget-hbox');
299 .addClass('widget-hbox');
300 this.$label = $('<div />')
300 this.$label = $('<div />')
301 .appendTo(this.$el)
301 .appendTo(this.$el)
302 .addClass('widget-hlabel')
302 .addClass('widget-hlabel')
303 .hide();
303 .hide();
304 this.$listbox = $('<select />')
304 this.$listbox = $('<select />')
305 .addClass('widget-listbox')
305 .addClass('widget-listbox')
306 .attr('size', 6)
306 .attr('size', 6)
307 .appendTo(this.$el);
307 .appendTo(this.$el);
308 this.$el_to_style = this.$listbox; // Set default element to style
308 this.$el_to_style = this.$listbox; // Set default element to style
309 this.update();
309 this.update();
310 },
310 },
311
311
312 update : function(options){
312 update : function(options){
313 // Update the contents of this view
313 // Update the contents of this view
314 //
314 //
315 // Called when the model is changed. The model may have been
315 // Called when the model is changed. The model may have been
316 // changed by another view or by a state update from the back-end.
316 // changed by another view or by a state update from the back-end.
317 if (options === undefined || options.updated_view != this) {
317 if (options === undefined || options.updated_view != this) {
318 // Add missing items to the DOM.
318 // Add missing items to the DOM.
319 var items = this.model.get('labels');
319 var items = this.model.get('value_names');
320 var that = this;
320 var that = this;
321 _.each(items, function(item, index) {
321 _.each(items, function(item, index) {
322 var item_query = ' :contains("' + item + '")';
322 var item_query = ' :contains("' + item + '")';
323 if (that.$listbox.find(item_query).length === 0) {
323 if (that.$listbox.find(item_query).length === 0) {
324 $('<option />')
324 $('<option />')
325 .text(item)
325 .text(item)
326 .attr('_value', item)
326 .attr('value_name', item)
327 .appendTo(that.$listbox)
327 .appendTo(that.$listbox)
328 .on('click', $.proxy(that.handle_click, that));
328 .on('click', $.proxy(that.handle_click, that));
329 }
329 }
330 });
330 });
331
331
332 // Select the correct element
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 // Disable listbox if needed
335 // Disable listbox if needed
336 var disabled = this.model.get('disabled');
336 var disabled = this.model.get('disabled');
337 this.$listbox.prop('disabled', disabled);
337 this.$listbox.prop('disabled', disabled);
338
338
339 // Remove items that no longer exist.
339 // Remove items that no longer exist.
340 this.$listbox.find('option').each(function(i, obj) {
340 this.$listbox.find('option').each(function(i, obj) {
341 var value = $(obj).text();
341 var value = $(obj).text();
342 var found = false;
342 var found = false;
343 _.each(items, function(item, index) {
343 _.each(items, function(item, index) {
344 if (item == value) {
344 if (item == value) {
345 found = true;
345 found = true;
346 return false;
346 return false;
347 }
347 }
348 });
348 });
349
349
350 if (!found) {
350 if (!found) {
351 $(obj).remove();
351 $(obj).remove();
352 }
352 }
353 });
353 });
354
354
355 var description = this.model.get('description');
355 var description = this.model.get('description');
356 if (description.length === 0) {
356 if (description.length === 0) {
357 this.$label.hide();
357 this.$label.hide();
358 } else {
358 } else {
359 this.$label.text(description);
359 this.$label.text(description);
360 this.$label.show();
360 this.$label.show();
361 }
361 }
362 }
362 }
363 return SelectView.__super__.update.apply(this);
363 return SelectView.__super__.update.apply(this);
364 },
364 },
365
365
366 handle_click: function (e) {
366 handle_click: function (e) {
367 // Handle when a value is clicked.
367 // Handle when a value is clicked.
368
368
369 // Calling model.set will trigger all of the other views of the
369 // Calling model.set will trigger all of the other views of the
370 // model to update.
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 this.touch();
372 this.touch();
373 },
373 },
374 });
374 });
375 WidgetManager.register_widget_view('SelectView', SelectView);
375 WidgetManager.register_widget_view('SelectView', SelectView);
376 });
376 });
@@ -1,133 +1,135 b''
1 // Test selection class
1 // Test selection class
2 casper.notebook_test(function () {
2 casper.notebook_test(function () {
3 index = this.append_cell(
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
6 'print("Success")');
7 this.execute_cell_then(index);
7 this.execute_cell_then(index);
8
8
9 var combo_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group .widget-combo-btn';
9 var combo_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group .widget-combo-btn';
10 var multibtn_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group[data-toggle="buttons-radio"]';
10 var multibtn_selector = '.widget-area .widget-subarea .widget-hbox-single .btn-group[data-toggle="buttons-radio"]';
11 var radio_selector = '.widget-area .widget-subarea .widget-hbox .vbox';
11 var radio_selector = '.widget-area .widget-subarea .widget-hbox .vbox';
12 var list_selector = '.widget-area .widget-subarea .widget-hbox .widget-listbox';
12 var list_selector = '.widget-area .widget-subarea .widget-hbox .widget-listbox';
13
13
14 var selection_index;
14 var selection_index;
15 var selection_values = 'abcd';
15 var selection_values = 'abcd';
16 var check_state = function(context, index, state){
16 var check_state = function(context, index, state){
17 if (0 <= index && index < selection_values.length) {
17 if (0 <= index && index < selection_values.length) {
18 var multibtn_state = context.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(' + (index + 1) + ')', 'hasClass', ['active']);
18 var multibtn_state = context.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(' + (index + 1) + ')', 'hasClass', ['active']);
19 var radio_state = context.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(' + (index + 1) + ') input', 'prop', ['checked']);
19 var radio_state = context.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(' + (index + 1) + ') input', 'prop', ['checked']);
20 var list_val = context.cell_element_function(selection_index, list_selector, 'val');
20 var list_val = context.cell_element_function(selection_index, list_selector, 'val');
21 var combo_val = context.cell_element_function(selection_index, combo_selector, 'html');
21 var combo_val = context.cell_element_function(selection_index, combo_selector, 'html');
22
22
23 var val = selection_values.charAt(index);
23 var val = selection_values.charAt(index);
24 var list_state = (val == list_val);
24 var list_state = (val == list_val);
25 var combo_state = (val == combo_val);
25 var combo_state = (val == combo_val);
26
26
27 return multibtn_state == state &&
27 return multibtn_state == state &&
28 radio_state == state &&
28 radio_state == state &&
29 list_state == state &&
29 list_state == state &&
30 combo_state == state;
30 combo_state == state;
31 }
31 }
32 return true;
32 return true;
33 };
33 };
34
34
35 var verify_selection = function(context, index){
35 var verify_selection = function(context, index){
36 for (var i = 0; i < selection_values.length; i++) {
36 for (var i = 0; i < selection_values.length; i++) {
37 if (!check_state(context, i, i==index)) {
37 if (!check_state(context, i, i==index)) {
38 return false;
38 return false;
39 }
39 }
40 }
40 }
41 return true;
41 return true;
42 };
42 };
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 'values=["' + selection_values + '"[i] for i in range(4)]\n' +
47 'selection = [widgets.DropdownWidget(values=values),\n' +
47 'selection = [widgets.DropdownWidget(values=values),\n' +
48 ' widgets.ToggleButtonsWidget(values=values),\n' +
48 ' widgets.ToggleButtonsWidget(values=values),\n' +
49 ' widgets.RadioButtonsWidget(values=values),\n' +
49 ' widgets.RadioButtonsWidget(values=values),\n' +
50 ' widgets.SelectWidget(values=values)]\n' +
50 ' widgets.SelectWidget(values=values)]\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' +
54 ' for other_widget in selection:\n' +
54 ' for other_widget in selection:\n' +
55 ' other_widget.value = new\n' +
55 ' other_widget.value = new\n' +
56 ' widget.on_trait_change(handle_change, "value")\n' +
56 ' widget.on_trait_change(handle_change, "value")\n' +
57 'print("Success")\n');
57 'print("Success")\n');
58 this.execute_cell_then(selection_index, function(index){
58 this.execute_cell_then(selection_index, function(index){
59 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
59 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
60 'Create selection cell executed with correct output.');
60 'Create selection cell executed with correct output.');
61
61
62 this.test.assert(this.cell_element_exists(index,
62 this.test.assert(this.cell_element_exists(index,
63 '.widget-area .widget-subarea'),
63 '.widget-area .widget-subarea'),
64 'Widget subarea exists.');
64 'Widget subarea exists.');
65
65
66 this.test.assert(this.cell_element_exists(index, combo_selector),
66 this.test.assert(this.cell_element_exists(index, combo_selector),
67 'Widget combobox exists.');
67 'Widget combobox exists.');
68
68
69 this.test.assert(this.cell_element_exists(index, multibtn_selector),
69 this.test.assert(this.cell_element_exists(index, multibtn_selector),
70 'Widget multibutton exists.');
70 'Widget multibutton exists.');
71
71
72 this.test.assert(this.cell_element_exists(index, radio_selector),
72 this.test.assert(this.cell_element_exists(index, radio_selector),
73 'Widget radio buttons exists.');
73 'Widget radio buttons exists.');
74
74
75 this.test.assert(this.cell_element_exists(index, list_selector),
75 this.test.assert(this.cell_element_exists(index, list_selector),
76 'Widget list exists.');
76 'Widget list exists.');
77
77
78 // Verify that no items are selected.
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 index = this.append_cell(
82 index = this.append_cell(
83 'for widget in selection:\n' +
83 'for widget in selection:\n' +
84 ' widget.value = "a"\n' +
84 ' widget.value = "a"\n' +
85 'print("Success")\n');
85 'print("Success")\n');
86 this.execute_cell_then(index, function(index){
86 this.execute_cell_then(index, function(index){
87 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
87 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
88 'Python select item executed with correct output.');
88 'Python select item executed with correct output.');
89
89
90 // Verify that the first item is selected.
90 // Verify that the first item is selected.
91 this.test.assert(verify_selection(this, 0), 'Python selected');
91 this.test.assert(verify_selection(this, 0), 'Python selected');
92
92
93 // Verify that selecting a radio button updates all of the others.
93 // Verify that selecting a radio button updates all of the others.
94 this.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(2) input', 'click');
94 this.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(2) input', 'click');
95 });
95 });
96 this.wait_for_idle();
96 this.wait_for_idle();
97 this.then(function () {
97 this.then(function () {
98 this.test.assert(verify_selection(this, 1), 'Radio button selection updated view states correctly.');
98 this.test.assert(verify_selection(this, 1), 'Radio button selection updated view states correctly.');
99
99
100 // Verify that selecting a list option updates all of the others.
100 // Verify that selecting a list option updates all of the others.
101 this.cell_element_function(selection_index, list_selector + ' option:nth-child(3)', 'click');
101 this.cell_element_function(selection_index, list_selector + ' option:nth-child(3)', 'click');
102 });
102 });
103 this.wait_for_idle();
103 this.wait_for_idle();
104 this.then(function () {
104 this.then(function () {
105 this.test.assert(verify_selection(this, 2), 'List selection updated view states correctly.');
105 this.test.assert(verify_selection(this, 2), 'List selection updated view states correctly.');
106
106
107 // Verify that selecting a multibutton option updates all of the others.
107 // Verify that selecting a multibutton option updates all of the others.
108 this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click');
108 this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click');
109 });
109 });
110 this.wait_for_idle();
110 this.wait_for_idle();
111 this.then(function () {
111 this.then(function () {
112 this.test.assert(verify_selection(this, 3), 'Multibutton selection updated view states correctly.');
112 this.test.assert(verify_selection(this, 3), 'Multibutton selection updated view states correctly.');
113
113
114 // Verify that selecting a combobox option updates all of the others.
114 // Verify that selecting a combobox option updates all of the others.
115 this.cell_element_function(selection_index, '.widget-area .widget-subarea .widget-hbox-single .btn-group ul.dropdown-menu li:nth-child(3) a', 'click');
115 this.cell_element_function(selection_index, '.widget-area .widget-subarea .widget-hbox-single .btn-group ul.dropdown-menu li:nth-child(3) a', 'click');
116 });
116 });
117 this.wait_for_idle();
117 this.wait_for_idle();
118 this.then(function () {
118 this.then(function () {
119 this.test.assert(verify_selection(this, 2), 'Combobox selection updated view states correctly.');
119 this.test.assert(verify_selection(this, 2), 'Combobox selection updated view states correctly.');
120 });
120 });
121
121
122 this.wait_for_idle();
122 this.wait_for_idle();
123
123
124 index = this.append_cell(
124 index = this.append_cell(
125 'for widget in selection:\n' +
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 'selection[0].value = "z"');
129 'selection[0].value = "z"');
128 this.execute_cell_then(index, function(index){
130 this.execute_cell_then(index, function(index){
129
131
130 // Verify that selecting a combobox option updates all of the others.
132 // Verify that selecting a combobox option updates all of the others.
131 this.test.assert(verify_selection(this, 4), 'Item added to selection widget.');
133 this.test.assert(verify_selection(this, 4), 'Item added to selection widget.');
132 });
134 });
133 }); No newline at end of file
135 });
@@ -1,95 +1,119 b''
1 """SelectionWidget class.
1 """SelectionWidget classes.
2
2
3 Represents an enumeration using a widget.
3 Represents an enumeration using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
17 from collections import OrderedDict
16 from threading import Lock
18 from threading import Lock
17
19
18 from .widget import DOMWidget
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 # SelectionWidget
25 # SelectionWidget
23 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
24 class _SelectionWidget(DOMWidget):
27 class _SelectionWidget(DOMWidget):
25 value = Any(help="Selected value")
28 """Base class for Selection widgets
26 values = List(help="List of values the user can select")
29
27 labels = List(help="""List of string representations for each value.
30 ``values`` can be specified as a list or dict. If given as a list,
28 These string representations are used to display the values in the
31 it will be transformed to a dict of the form ``{str(value):value}``.
29 front-end.""", sync=True) # Only synced to the back-end.
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 disabled = Bool(False, help="Enable or disable user changes", sync=True)
49 disabled = Bool(False, help="Enable or disable user changes", sync=True)
31 description = Unicode(help="Description of the value this widget represents", sync=True)
50 description = Unicode(help="Description of the value this widget represents", sync=True)
51
32
52
33 _value = Unicode(sync=True) # Bi-directionally synced.
53 def __init__(self, *args, **kwargs):
34
35 def __init__(self, *pargs, **kwargs):
36 """Constructor"""
37 self.value_lock = Lock()
54 self.value_lock = Lock()
38 self.on_trait_change(self._string_value_set, ['_value'])
55 self._in_values_changed = False
39 DOMWidget.__init__(self, *pargs, **kwargs)
56 if 'values' in kwargs:
40
57 values = kwargs['values']
41 def _labels_changed(self, name=None, old=None, new=None):
58 # convert list values to an dict of {str(v):v}
42 """Handles when the value_names Dict has been changed.
59 if isinstance(values, list):
43
60 # preserve list order with an OrderedDict
44 This method sets the _reverse_value_names Dict to the inverse of the new
61 kwargs['values'] = OrderedDict((unicode_type(v), v) for v in values)
45 value for the value_names Dict."""
62 DOMWidget.__init__(self, *args, **kwargs)
46 if len(new) != len(self.values):
63
47 raise TypeError('Labels list must be the same size as the values list.')
64 def _values_changed(self, name, old, new):
48
65 """Handles when the values dict has been changed.
49 def _values_changed(self, name=None, old=None, new=None):
66
50 """Handles when the value_names Dict has been changed.
67 Setting values implies setting value names from the keys of the dict.
51
68 """
52 This method sets the _reverse_value_names Dict to the inverse of the new
69 self._in_values_changed = True
53 value for the value_names Dict."""
70 try:
54 if len(new) != len(self.labels):
71 self.value_names = list(new.keys())
55 self.labels = [(self.labels[i] if i < len(self.labels) else str(v)) for i, v in enumerate(new)]
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 def _value_changed(self, name, old, new):
83 def _value_changed(self, name, old, new):
58 """Called when value has been changed"""
84 """Called when value has been changed"""
59 if self.value_lock.acquire(False):
85 if self.value_lock.acquire(False):
60 try:
86 try:
61 # Make sure the value is in the list of values.
87 # Reverse dictionary lookup for the value name
62 if new in self.values:
88 for k,v in self.values.items():
63 # Set the string version of the value.
89 if new == v:
64 self._value = self.labels[self.values.index(new)]
90 # set the selected value name
65 else:
91 self.value_name = k
66 raise TypeError('Value must be a value in the values list.')
92 return
93 raise KeyError(new)
67 finally:
94 finally:
68 self.value_lock.release()
95 self.value_lock.release()
69
96
70 def _string_value_set(self, name, old, new):
97 def _value_name_changed(self, name, old, new):
71 """Called when _value has been changed."""
98 """Called when the value name has been changed (typically by the frontend)."""
72 if self.value_lock.acquire(False):
99 if self.value_lock.acquire(False):
73 try:
100 try:
74 if new in self.labels:
101 self.value = self.values[new]
75 self.value = self.values[self.labels.index(new)]
76 else:
77 self.value = None
78 finally:
102 finally:
79 self.value_lock.release()
103 self.value_lock.release()
80
104
81
105
82 class ToggleButtonsWidget(_SelectionWidget):
106 class ToggleButtonsWidget(_SelectionWidget):
83 _view_name = Unicode('ToggleButtonsView', sync=True)
107 _view_name = Unicode('ToggleButtonsView', sync=True)
84
108
85
109
86 class DropdownWidget(_SelectionWidget):
110 class DropdownWidget(_SelectionWidget):
87 _view_name = Unicode('DropdownView', sync=True)
111 _view_name = Unicode('DropdownView', sync=True)
88
112
89
113
90 class RadioButtonsWidget(_SelectionWidget):
114 class RadioButtonsWidget(_SelectionWidget):
91 _view_name = Unicode('RadioButtonsView', sync=True)
115 _view_name = Unicode('RadioButtonsView', sync=True)
92
116
93
117
94 class SelectWidget(_SelectionWidget):
118 class SelectWidget(_SelectionWidget):
95 _view_name = Unicode('SelectView', sync=True)
119 _view_name = Unicode('SelectView', sync=True)
General Comments 0
You need to be logged in to leave comments. Login now