##// END OF EJS Templates
Font-awesome icons for toggle buttons
Sylvain Corlay -
Show More
@@ -1,575 +1,583 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "base/js/utils",
6 "base/js/utils",
7 "jquery",
7 "jquery",
8 "underscore",
8 "underscore",
9 "bootstrap",
9 "bootstrap",
10 ], function(widget, utils, $, _){
10 ], function(widget, utils, $, _){
11
11
12 var DropdownView = widget.DOMWidgetView.extend({
12 var DropdownView = widget.DOMWidgetView.extend({
13 render : function(){
13 render : function(){
14 /**
14 /**
15 * Called when view is rendered.
15 * Called when view is rendered.
16 */
16 */
17 this.$el
17 this.$el
18 .addClass('widget-hbox widget-dropdown');
18 .addClass('widget-hbox widget-dropdown');
19 this.$label = $('<div />')
19 this.$label = $('<div />')
20 .appendTo(this.$el)
20 .appendTo(this.$el)
21 .addClass('widget-label')
21 .addClass('widget-label')
22 .hide();
22 .hide();
23 this.$buttongroup = $('<div />')
23 this.$buttongroup = $('<div />')
24 .addClass('widget_item')
24 .addClass('widget_item')
25 .addClass('btn-group')
25 .addClass('btn-group')
26 .appendTo(this.$el);
26 .appendTo(this.$el);
27 this.$droplabel = $('<button />')
27 this.$droplabel = $('<button />')
28 .addClass('btn btn-default')
28 .addClass('btn btn-default')
29 .addClass('widget-combo-btn')
29 .addClass('widget-combo-btn')
30 .html("&nbsp;")
30 .html("&nbsp;")
31 .appendTo(this.$buttongroup);
31 .appendTo(this.$buttongroup);
32 this.$dropbutton = $('<button />')
32 this.$dropbutton = $('<button />')
33 .addClass('btn btn-default')
33 .addClass('btn btn-default')
34 .addClass('dropdown-toggle')
34 .addClass('dropdown-toggle')
35 .addClass('widget-combo-carrot-btn')
35 .addClass('widget-combo-carrot-btn')
36 .attr('data-toggle', 'dropdown')
36 .attr('data-toggle', 'dropdown')
37 .append($('<span />').addClass("caret"))
37 .append($('<span />').addClass("caret"))
38 .appendTo(this.$buttongroup);
38 .appendTo(this.$buttongroup);
39 this.$droplist = $('<ul />')
39 this.$droplist = $('<ul />')
40 .addClass('dropdown-menu')
40 .addClass('dropdown-menu')
41 .appendTo(this.$buttongroup);
41 .appendTo(this.$buttongroup);
42
42
43 this.model.on('change:button_style', function(model, value) {
43 this.model.on('change:button_style', function(model, value) {
44 this.update_button_style();
44 this.update_button_style();
45 }, this);
45 }, this);
46 this.update_button_style('');
46 this.update_button_style('');
47
47
48 // Set defaults.
48 // Set defaults.
49 this.update();
49 this.update();
50 },
50 },
51
51
52 update : function(options){
52 update : function(options){
53 /**
53 /**
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
59
60 if (options === undefined || options.updated_view != this) {
60 if (options === undefined || options.updated_view != this) {
61 var selected_item_text = this.model.get('selected_label');
61 var selected_item_text = this.model.get('selected_label');
62 if (selected_item_text.trim().length === 0) {
62 if (selected_item_text.trim().length === 0) {
63 this.$droplabel.html("&nbsp;");
63 this.$droplabel.html("&nbsp;");
64 } else {
64 } else {
65 this.$droplabel.text(selected_item_text);
65 this.$droplabel.text(selected_item_text);
66 }
66 }
67
67
68 var items = this.model.get('_options_labels');
68 var items = this.model.get('_options_labels');
69 var $replace_droplist = $('<ul />')
69 var $replace_droplist = $('<ul />')
70 .addClass('dropdown-menu');
70 .addClass('dropdown-menu');
71 // Copy the style
71 // Copy the style
72 $replace_droplist.attr('style', this.$droplist.attr('style'));
72 $replace_droplist.attr('style', this.$droplist.attr('style'));
73 var that = this;
73 var that = this;
74 _.each(items, function(item, i) {
74 _.each(items, function(item, i) {
75 var item_button = $('<a href="#"/>')
75 var item_button = $('<a href="#"/>')
76 .text(item)
76 .text(item)
77 .on('click', $.proxy(that.handle_click, that));
77 .on('click', $.proxy(that.handle_click, that));
78 $replace_droplist.append($('<li />').append(item_button));
78 $replace_droplist.append($('<li />').append(item_button));
79 });
79 });
80
80
81 this.$droplist.replaceWith($replace_droplist);
81 this.$droplist.replaceWith($replace_droplist);
82 this.$droplist.remove();
82 this.$droplist.remove();
83 this.$droplist = $replace_droplist;
83 this.$droplist = $replace_droplist;
84
84
85 if (this.model.get('disabled')) {
85 if (this.model.get('disabled')) {
86 this.$buttongroup.attr('disabled','disabled');
86 this.$buttongroup.attr('disabled','disabled');
87 this.$droplabel.attr('disabled','disabled');
87 this.$droplabel.attr('disabled','disabled');
88 this.$dropbutton.attr('disabled','disabled');
88 this.$dropbutton.attr('disabled','disabled');
89 this.$droplist.attr('disabled','disabled');
89 this.$droplist.attr('disabled','disabled');
90 } else {
90 } else {
91 this.$buttongroup.removeAttr('disabled');
91 this.$buttongroup.removeAttr('disabled');
92 this.$droplabel.removeAttr('disabled');
92 this.$droplabel.removeAttr('disabled');
93 this.$dropbutton.removeAttr('disabled');
93 this.$dropbutton.removeAttr('disabled');
94 this.$droplist.removeAttr('disabled');
94 this.$droplist.removeAttr('disabled');
95 }
95 }
96
96
97 var description = this.model.get('description');
97 var description = this.model.get('description');
98 if (description.length === 0) {
98 if (description.length === 0) {
99 this.$label.hide();
99 this.$label.hide();
100 } else {
100 } else {
101 this.typeset(this.$label, description);
101 this.typeset(this.$label, description);
102 this.$label.show();
102 this.$label.show();
103 }
103 }
104 }
104 }
105 return DropdownView.__super__.update.apply(this);
105 return DropdownView.__super__.update.apply(this);
106 },
106 },
107
107
108 update_button_style: function(previous_trait_value) {
108 update_button_style: function(previous_trait_value) {
109 var class_map = {
109 var class_map = {
110 primary: ['btn-primary'],
110 primary: ['btn-primary'],
111 success: ['btn-success'],
111 success: ['btn-success'],
112 info: ['btn-info'],
112 info: ['btn-info'],
113 warning: ['btn-warning'],
113 warning: ['btn-warning'],
114 danger: ['btn-danger']
114 danger: ['btn-danger']
115 };
115 };
116 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$droplabel);
116 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$droplabel);
117 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$dropbutton);
117 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$dropbutton);
118 },
118 },
119
119
120 update_attr: function(name, value) {
120 update_attr: function(name, value) {
121 /**
121 /**
122 * Set a css attr of the widget view.
122 * Set a css attr of the widget view.
123 */
123 */
124 if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') {
124 if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') {
125 this.$droplabel.css(name, value);
125 this.$droplabel.css(name, value);
126 this.$dropbutton.css(name, value);
126 this.$dropbutton.css(name, value);
127 this.$droplist.css(name, value);
127 this.$droplist.css(name, value);
128 } else if (name == 'width') {
128 } else if (name == 'width') {
129 this.$droplist.css(name, value);
129 this.$droplist.css(name, value);
130 this.$droplabel.css(name, value);
130 this.$droplabel.css(name, value);
131 } else if (name == 'padding') {
131 } else if (name == 'padding') {
132 this.$droplist.css(name, value);
132 this.$droplist.css(name, value);
133 this.$buttongroup.css(name, value);
133 this.$buttongroup.css(name, value);
134 } else if (name == 'margin') {
134 } else if (name == 'margin') {
135 this.$buttongroup.css(name, value);
135 this.$buttongroup.css(name, value);
136 } else if (name == 'height') {
136 } else if (name == 'height') {
137 this.$droplabel.css(name, value);
137 this.$droplabel.css(name, value);
138 this.$dropbutton.css(name, value);
138 this.$dropbutton.css(name, value);
139 } else if (name == 'padding' || name == 'margin') {
139 } else if (name == 'padding' || name == 'margin') {
140 this.$el.css(name, value);
140 this.$el.css(name, value);
141 } else {
141 } else {
142 this.$droplist.css(name, value);
142 this.$droplist.css(name, value);
143 this.$droplabel.css(name, value);
143 this.$droplabel.css(name, value);
144 }
144 }
145 },
145 },
146
146
147 handle_click: function (e) {
147 handle_click: function (e) {
148 /**
148 /**
149 * Handle when a value is clicked.
149 * Handle when a value is clicked.
150 *
150 *
151 * 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
152 * model to update.
152 * model to update.
153 */
153 */
154 this.model.set('selected_label', $(e.target).text(), {updated_view: this});
154 this.model.set('selected_label', $(e.target).text(), {updated_view: this});
155 this.touch();
155 this.touch();
156
156
157 // Manually hide the droplist.
157 // Manually hide the droplist.
158 e.stopPropagation();
158 e.stopPropagation();
159 e.preventDefault();
159 e.preventDefault();
160 this.$buttongroup.removeClass('open');
160 this.$buttongroup.removeClass('open');
161 },
161 },
162
162
163 });
163 });
164
164
165
165
166 var RadioButtonsView = widget.DOMWidgetView.extend({
166 var RadioButtonsView = widget.DOMWidgetView.extend({
167 render : function(){
167 render : function(){
168 /**
168 /**
169 * Called when view is rendered.
169 * Called when view is rendered.
170 */
170 */
171 this.$el
171 this.$el
172 .addClass('widget-hbox widget-radio');
172 .addClass('widget-hbox widget-radio');
173 this.$label = $('<div />')
173 this.$label = $('<div />')
174 .appendTo(this.$el)
174 .appendTo(this.$el)
175 .addClass('widget-label')
175 .addClass('widget-label')
176 .hide();
176 .hide();
177 this.$container = $('<div />')
177 this.$container = $('<div />')
178 .appendTo(this.$el)
178 .appendTo(this.$el)
179 .addClass('widget-radio-box');
179 .addClass('widget-radio-box');
180 this.update();
180 this.update();
181 },
181 },
182
182
183 update : function(options){
183 update : function(options){
184 /**
184 /**
185 * Update the contents of this view
185 * Update the contents of this view
186 *
186 *
187 * Called when the model is changed. The model may have been
187 * Called when the model is changed. The model may have been
188 * changed by another view or by a state update from the back-end.
188 * changed by another view or by a state update from the back-end.
189 */
189 */
190 if (options === undefined || options.updated_view != this) {
190 if (options === undefined || options.updated_view != this) {
191 // Add missing items to the DOM.
191 // Add missing items to the DOM.
192 var items = this.model.get('_options_labels');
192 var items = this.model.get('_options_labels');
193 var disabled = this.model.get('disabled');
193 var disabled = this.model.get('disabled');
194 var that = this;
194 var that = this;
195 _.each(items, function(item, index) {
195 _.each(items, function(item, index) {
196 var item_query = ' :input[data-value="' + encodeURIComponent(item) + '"]';
196 var item_query = ' :input[data-value="' + encodeURIComponent(item) + '"]';
197 if (that.$el.find(item_query).length === 0) {
197 if (that.$el.find(item_query).length === 0) {
198 var $label = $('<label />')
198 var $label = $('<label />')
199 .addClass('radio')
199 .addClass('radio')
200 .text(item)
200 .text(item)
201 .appendTo(that.$container);
201 .appendTo(that.$container);
202
202
203 $('<input />')
203 $('<input />')
204 .attr('type', 'radio')
204 .attr('type', 'radio')
205 .addClass(that.model)
205 .addClass(that.model)
206 .val(item)
206 .val(item)
207 .attr('data-value', encodeURIComponent(item))
207 .attr('data-value', encodeURIComponent(item))
208 .prependTo($label)
208 .prependTo($label)
209 .on('click', $.proxy(that.handle_click, that));
209 .on('click', $.proxy(that.handle_click, that));
210 }
210 }
211
211
212 var $item_element = that.$container.find(item_query);
212 var $item_element = that.$container.find(item_query);
213 if (that.model.get('selected_label') == item) {
213 if (that.model.get('selected_label') == item) {
214 $item_element.prop('checked', true);
214 $item_element.prop('checked', true);
215 } else {
215 } else {
216 $item_element.prop('checked', false);
216 $item_element.prop('checked', false);
217 }
217 }
218 $item_element.prop('disabled', disabled);
218 $item_element.prop('disabled', disabled);
219 });
219 });
220
220
221 // Remove items that no longer exist.
221 // Remove items that no longer exist.
222 this.$container.find('input').each(function(i, obj) {
222 this.$container.find('input').each(function(i, obj) {
223 var value = $(obj).val();
223 var value = $(obj).val();
224 var found = false;
224 var found = false;
225 _.each(items, function(item, index) {
225 _.each(items, function(item, index) {
226 if (item == value) {
226 if (item == value) {
227 found = true;
227 found = true;
228 return false;
228 return false;
229 }
229 }
230 });
230 });
231
231
232 if (!found) {
232 if (!found) {
233 $(obj).parent().remove();
233 $(obj).parent().remove();
234 }
234 }
235 });
235 });
236
236
237 var description = this.model.get('description');
237 var description = this.model.get('description');
238 if (description.length === 0) {
238 if (description.length === 0) {
239 this.$label.hide();
239 this.$label.hide();
240 } else {
240 } else {
241 this.$label.text(description);
241 this.$label.text(description);
242 this.typeset(this.$label, description);
242 this.typeset(this.$label, description);
243 this.$label.show();
243 this.$label.show();
244 }
244 }
245 }
245 }
246 return RadioButtonsView.__super__.update.apply(this);
246 return RadioButtonsView.__super__.update.apply(this);
247 },
247 },
248
248
249 update_attr: function(name, value) {
249 update_attr: function(name, value) {
250 /**
250 /**
251 * Set a css attr of the widget view.
251 * Set a css attr of the widget view.
252 */
252 */
253 if (name == 'padding' || name == 'margin') {
253 if (name == 'padding' || name == 'margin') {
254 this.$el.css(name, value);
254 this.$el.css(name, value);
255 } else {
255 } else {
256 this.$container.css(name, value);
256 this.$container.css(name, value);
257 }
257 }
258 },
258 },
259
259
260 handle_click: function (e) {
260 handle_click: function (e) {
261 /**
261 /**
262 * Handle when a value is clicked.
262 * Handle when a value is clicked.
263 *
263 *
264 * 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
265 * model to update.
265 * model to update.
266 */
266 */
267 this.model.set('selected_label', $(e.target).val(), {updated_view: this});
267 this.model.set('selected_label', $(e.target).val(), {updated_view: this});
268 this.touch();
268 this.touch();
269 },
269 },
270 });
270 });
271
271
272
272
273 var ToggleButtonsView = widget.DOMWidgetView.extend({
273 var ToggleButtonsView = widget.DOMWidgetView.extend({
274 initialize: function() {
274 initialize: function() {
275 this._css_state = {};
275 this._css_state = {};
276 ToggleButtonsView.__super__.initialize.apply(this, arguments);
276 ToggleButtonsView.__super__.initialize.apply(this, arguments);
277 },
277 },
278
278
279 render: function() {
279 render: function() {
280 /**
280 /**
281 * Called when view is rendered.
281 * Called when view is rendered.
282 */
282 */
283 this.$el
283 this.$el
284 .addClass('widget-hbox widget-toggle-buttons');
284 .addClass('widget-hbox widget-toggle-buttons');
285 this.$label = $('<div />')
285 this.$label = $('<div />')
286 .appendTo(this.$el)
286 .appendTo(this.$el)
287 .addClass('widget-label')
287 .addClass('widget-label')
288 .hide();
288 .hide();
289 this.$buttongroup = $('<div />')
289 this.$buttongroup = $('<div />')
290 .addClass('btn-group')
290 .addClass('btn-group')
291 .appendTo(this.$el);
291 .appendTo(this.$el);
292
292
293 this.model.on('change:button_style', function(model, value) {
293 this.model.on('change:button_style', function(model, value) {
294 this.update_button_style();
294 this.update_button_style();
295 }, this);
295 }, this);
296 this.update_button_style('');
296 this.update_button_style('');
297 this.update();
297 this.update();
298 },
298 },
299
299
300 update : function(options){
300 update : function(options){
301 /**
301 /**
302 * Update the contents of this view
302 * Update the contents of this view
303 *
303 *
304 * Called when the model is changed. The model may have been
304 * Called when the model is changed. The model may have been
305 * changed by another view or by a state update from the back-end.
305 * changed by another view or by a state update from the back-end.
306 */
306 */
307 if (options === undefined || options.updated_view != this) {
307 if (options === undefined || options.updated_view != this) {
308 // Add missing items to the DOM.
308 // Add missing items to the DOM.
309 var items = this.model.get('_options_labels');
309 var items = this.model.get('_options_labels');
310 var icons = this.model.get('icons');
311 var previous_icons = this.model.previous('icons') || [];
310 var disabled = this.model.get('disabled');
312 var disabled = this.model.get('disabled');
311 var that = this;
313 var that = this;
312 var item_html;
314 var item_html;
313 _.each(items, function(item, index) {
315 _.each(items, function(item, index) {
314 if (item.trim().length === 0) {
316 if (item.trim().length === 0 && (!icons[index] ||
317 icons[index].trim().length === 0)) {
315 item_html = "&nbsp;";
318 item_html = "&nbsp;";
316 } else {
319 } else {
317 item_html = utils.escape_html(item);
320 item_html = utils.escape_html(item);
318 }
321 }
319 var item_query = '[data-value="' + encodeURIComponent(item) + '"]';
322 var item_query = '[data-value="' + encodeURIComponent(item) + '"]';
320 var $item_element = that.$buttongroup.find(item_query);
323 var $item_element = that.$buttongroup.find(item_query);
324 var $icon_element = $item_element.find('.fa');
321 if (!$item_element.length) {
325 if (!$item_element.length) {
322 $item_element = $('<button/>')
326 $item_element = $('<button/>')
323 .attr('type', 'button')
327 .attr('type', 'button')
324 .addClass('btn btn-default')
328 .addClass('btn btn-default')
325 .html(item_html)
329 .html(item_html)
326 .appendTo(that.$buttongroup)
330 .appendTo(that.$buttongroup)
327 .attr('data-value', encodeURIComponent(item))
331 .attr('data-value', encodeURIComponent(item))
328 .attr('data-toggle', 'tooltip')
332 .attr('data-toggle', 'tooltip')
329 .attr('value', item)
333 .attr('value', item)
330 .on('click', $.proxy(that.handle_click, that));
334 .on('click', $.proxy(that.handle_click, that));
331 that.update_style_traits($item_element);
335 that.update_style_traits($item_element);
336 $icon_element = $('<i class="fa"></i>').prependTo($item_element);
332 }
337 }
333 if (that.model.get('selected_label') == item) {
338 if (that.model.get('selected_label') == item) {
334 $item_element.addClass('active');
339 $item_element.addClass('active');
335 } else {
340 } else {
336 $item_element.removeClass('active');
341 $item_element.removeClass('active');
337 }
342 }
338 $item_element.prop('disabled', disabled);
343 $item_element.prop('disabled', disabled);
339 $item_element.attr('title', that.model.get('tooltips')[index]);
344 $item_element.attr('title', that.model.get('tooltips')[index]);
345 $icon_element
346 .removeClass(previous_icons[index])
347 .addClass(icons[index]);
340 });
348 });
341
349
342 // Remove items that no longer exist.
350 // Remove items that no longer exist.
343 this.$buttongroup.find('button').each(function(i, obj) {
351 this.$buttongroup.find('button').each(function(i, obj) {
344 var value = $(obj).attr('value');
352 var value = $(obj).attr('value');
345 var found = false;
353 var found = false;
346 _.each(items, function(item, index) {
354 _.each(items, function(item, index) {
347 if (item == value) {
355 if (item == value) {
348 found = true;
356 found = true;
349 return false;
357 return false;
350 }
358 }
351 });
359 });
352
360
353 if (!found) {
361 if (!found) {
354 $(obj).remove();
362 $(obj).remove();
355 }
363 }
356 });
364 });
357
365
358 var description = this.model.get('description');
366 var description = this.model.get('description');
359 if (description.length === 0) {
367 if (description.length === 0) {
360 this.$label.hide();
368 this.$label.hide();
361 } else {
369 } else {
362 this.$label.text();
370 this.$label.text();
363 this.typeset(this.$label, description);
371 this.typeset(this.$label, description);
364 this.$label.show();
372 this.$label.show();
365 }
373 }
366 }
374 }
367 return ToggleButtonsView.__super__.update.apply(this);
375 return ToggleButtonsView.__super__.update.apply(this);
368 },
376 },
369
377
370 update_attr: function(name, value) {
378 update_attr: function(name, value) {
371 /**
379 /**
372 * Set a css attr of the widget view.
380 * Set a css attr of the widget view.
373 */
381 */
374 if (name == 'padding' || name == 'margin') {
382 if (name == 'padding' || name == 'margin') {
375 this.$el.css(name, value);
383 this.$el.css(name, value);
376 } else {
384 } else {
377 this._css_state[name] = value;
385 this._css_state[name] = value;
378 this.update_style_traits();
386 this.update_style_traits();
379 }
387 }
380 },
388 },
381
389
382 update_style_traits: function(button) {
390 update_style_traits: function(button) {
383 for (var name in this._css_state) {
391 for (var name in this._css_state) {
384 if (this._css_state.hasOwnProperty(name)) {
392 if (this._css_state.hasOwnProperty(name)) {
385 if (name == 'margin') {
393 if (name == 'margin') {
386 this.$buttongroup.css(name, this._css_state[name]);
394 this.$buttongroup.css(name, this._css_state[name]);
387 } else if (name != 'width') {
395 } else if (name != 'width') {
388 if (button) {
396 if (button) {
389 button.css(name, this._css_state[name]);
397 button.css(name, this._css_state[name]);
390 } else {
398 } else {
391 this.$buttongroup.find('button').css(name, this._css_state[name]);
399 this.$buttongroup.find('button').css(name, this._css_state[name]);
392 }
400 }
393 }
401 }
394 }
402 }
395 }
403 }
396 },
404 },
397
405
398 update_button_style: function(previous_trait_value) {
406 update_button_style: function(previous_trait_value) {
399 var class_map = {
407 var class_map = {
400 primary: ['btn-primary'],
408 primary: ['btn-primary'],
401 success: ['btn-success'],
409 success: ['btn-success'],
402 info: ['btn-info'],
410 info: ['btn-info'],
403 warning: ['btn-warning'],
411 warning: ['btn-warning'],
404 danger: ['btn-danger']
412 danger: ['btn-danger']
405 };
413 };
406 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$buttongroup.find('button'));
414 this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$buttongroup.find('button'));
407 },
415 },
408
416
409 handle_click: function (e) {
417 handle_click: function (e) {
410 /**
418 /**
411 * Handle when a value is clicked.
419 * Handle when a value is clicked.
412 *
420 *
413 * Calling model.set will trigger all of the other views of the
421 * Calling model.set will trigger all of the other views of the
414 * model to update.
422 * model to update.
415 */
423 */
416 this.model.set('selected_label', $(e.target).attr('value'), {updated_view: this});
424 this.model.set('selected_label', $(e.target).attr('value'), {updated_view: this});
417 this.touch();
425 this.touch();
418 },
426 },
419 });
427 });
420
428
421
429
422 var SelectView = widget.DOMWidgetView.extend({
430 var SelectView = widget.DOMWidgetView.extend({
423 render : function(){
431 render : function(){
424 /**
432 /**
425 * Called when view is rendered.
433 * Called when view is rendered.
426 */
434 */
427 this.$el
435 this.$el
428 .addClass('widget-hbox widget-select');
436 .addClass('widget-hbox widget-select');
429 this.$label = $('<div />')
437 this.$label = $('<div />')
430 .appendTo(this.$el)
438 .appendTo(this.$el)
431 .addClass('widget-label')
439 .addClass('widget-label')
432 .hide();
440 .hide();
433 this.$listbox = $('<select />')
441 this.$listbox = $('<select />')
434 .addClass('widget-listbox form-control')
442 .addClass('widget-listbox form-control')
435 .attr('size', 6)
443 .attr('size', 6)
436 .appendTo(this.$el)
444 .appendTo(this.$el)
437 .on('change', $.proxy(this.handle_change, this));
445 .on('change', $.proxy(this.handle_change, this));
438 this.update();
446 this.update();
439 },
447 },
440
448
441 update : function(options){
449 update : function(options){
442 /**
450 /**
443 * Update the contents of this view
451 * Update the contents of this view
444 *
452 *
445 * Called when the model is changed. The model may have been
453 * Called when the model is changed. The model may have been
446 * changed by another view or by a state update from the back-end.
454 * changed by another view or by a state update from the back-end.
447 */
455 */
448 if (options === undefined || options.updated_view != this) {
456 if (options === undefined || options.updated_view != this) {
449 // Add missing items to the DOM.
457 // Add missing items to the DOM.
450 var items = this.model.get('_options_labels');
458 var items = this.model.get('_options_labels');
451 var that = this;
459 var that = this;
452 _.each(items, function(item, index) {
460 _.each(items, function(item, index) {
453 var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]';
461 var item_query = 'option[data-value="' + encodeURIComponent(item) + '"]';
454 if (that.$listbox.find(item_query).length === 0) {
462 if (that.$listbox.find(item_query).length === 0) {
455 $('<option />')
463 $('<option />')
456 .text(item)
464 .text(item)
457 .attr('data-value', encodeURIComponent(item))
465 .attr('data-value', encodeURIComponent(item))
458 .attr('selected_label', item)
466 .attr('selected_label', item)
459 .on("click", $.proxy(that.handle_click, that))
467 .on("click", $.proxy(that.handle_click, that))
460 .appendTo(that.$listbox);
468 .appendTo(that.$listbox);
461 }
469 }
462 });
470 });
463
471
464 // Select the correct element
472 // Select the correct element
465 this.$listbox.val(this.model.get('selected_label'));
473 this.$listbox.val(this.model.get('selected_label'));
466
474
467 // Disable listbox if needed
475 // Disable listbox if needed
468 var disabled = this.model.get('disabled');
476 var disabled = this.model.get('disabled');
469 this.$listbox.prop('disabled', disabled);
477 this.$listbox.prop('disabled', disabled);
470
478
471 // Remove items that no longer exist.
479 // Remove items that no longer exist.
472 this.$listbox.find('option').each(function(i, obj) {
480 this.$listbox.find('option').each(function(i, obj) {
473 var value = $(obj).text();
481 var value = $(obj).text();
474 var found = false;
482 var found = false;
475 _.each(items, function(item, index) {
483 _.each(items, function(item, index) {
476 if (item == value) {
484 if (item == value) {
477 found = true;
485 found = true;
478 return false;
486 return false;
479 }
487 }
480 });
488 });
481
489
482 if (!found) {
490 if (!found) {
483 $(obj).remove();
491 $(obj).remove();
484 }
492 }
485 });
493 });
486
494
487 var description = this.model.get('description');
495 var description = this.model.get('description');
488 if (description.length === 0) {
496 if (description.length === 0) {
489 this.$label.hide();
497 this.$label.hide();
490 } else {
498 } else {
491 this.typeset(this.$label, description);
499 this.typeset(this.$label, description);
492 this.$label.show();
500 this.$label.show();
493 }
501 }
494 }
502 }
495 return SelectView.__super__.update.apply(this);
503 return SelectView.__super__.update.apply(this);
496 },
504 },
497
505
498 update_attr: function(name, value) {
506 update_attr: function(name, value) {
499 /**
507 /**
500 * Set a css attr of the widget view.
508 * Set a css attr of the widget view.
501 */
509 */
502 if (name == 'padding' || name == 'margin') {
510 if (name == 'padding' || name == 'margin') {
503 this.$el.css(name, value);
511 this.$el.css(name, value);
504 } else {
512 } else {
505 this.$listbox.css(name, value);
513 this.$listbox.css(name, value);
506 }
514 }
507 },
515 },
508
516
509 handle_click: function (e) {
517 handle_click: function (e) {
510 /**
518 /**
511 * Handle when a new value is clicked.
519 * Handle when a new value is clicked.
512 */
520 */
513 this.$listbox.val($(e.target).val()).change();
521 this.$listbox.val($(e.target).val()).change();
514 },
522 },
515
523
516 handle_change: function (e) {
524 handle_change: function (e) {
517 /**
525 /**
518 * Handle when a new value is selected.
526 * Handle when a new value is selected.
519 *
527 *
520 * Calling model.set will trigger all of the other views of the
528 * Calling model.set will trigger all of the other views of the
521 * model to update.
529 * model to update.
522 */
530 */
523 this.model.set('selected_label', this.$listbox.val(), {updated_view: this});
531 this.model.set('selected_label', this.$listbox.val(), {updated_view: this});
524 this.touch();
532 this.touch();
525 },
533 },
526 });
534 });
527
535
528
536
529 var SelectMultipleView = SelectView.extend({
537 var SelectMultipleView = SelectView.extend({
530 render: function(){
538 render: function(){
531 /**
539 /**
532 * Called when view is rendered.
540 * Called when view is rendered.
533 */
541 */
534 SelectMultipleView.__super__.render.apply(this);
542 SelectMultipleView.__super__.render.apply(this);
535 this.$el.removeClass('widget-select')
543 this.$el.removeClass('widget-select')
536 .addClass('widget-select-multiple');
544 .addClass('widget-select-multiple');
537 this.$listbox.attr('multiple', true)
545 this.$listbox.attr('multiple', true)
538 .on('change', $.proxy(this.handle_change, this));
546 .on('change', $.proxy(this.handle_change, this));
539 return this;
547 return this;
540 },
548 },
541
549
542 update: function(){
550 update: function(){
543 /**
551 /**
544 * Update the contents of this view
552 * Update the contents of this view
545 *
553 *
546 * Called when the model is changed. The model may have been
554 * Called when the model is changed. The model may have been
547 * changed by another view or by a state update from the back-end.
555 * changed by another view or by a state update from the back-end.
548 */
556 */
549 SelectMultipleView.__super__.update.apply(this, arguments);
557 SelectMultipleView.__super__.update.apply(this, arguments);
550 this.$listbox.val(this.model.get('selected_labels'));
558 this.$listbox.val(this.model.get('selected_labels'));
551 },
559 },
552
560
553 handle_change: function (e) {
561 handle_change: function (e) {
554 /**
562 /**
555 * Handle when a new value is selected.
563 * Handle when a new value is selected.
556 *
564 *
557 * Calling model.set will trigger all of the other views of the
565 * Calling model.set will trigger all of the other views of the
558 * model to update.
566 * model to update.
559 */
567 */
560 this.model.set('selected_labels',
568 this.model.set('selected_labels',
561 (this.$listbox.val() || []).slice(),
569 (this.$listbox.val() || []).slice(),
562 {updated_view: this});
570 {updated_view: this});
563 this.touch();
571 this.touch();
564 },
572 },
565 });
573 });
566
574
567
575
568 return {
576 return {
569 'DropdownView': DropdownView,
577 'DropdownView': DropdownView,
570 'RadioButtonsView': RadioButtonsView,
578 'RadioButtonsView': RadioButtonsView,
571 'ToggleButtonsView': ToggleButtonsView,
579 'ToggleButtonsView': ToggleButtonsView,
572 'SelectView': SelectView,
580 'SelectView': SelectView,
573 'SelectMultipleView': SelectMultipleView,
581 'SelectMultipleView': SelectMultipleView,
574 };
582 };
575 });
583 });
@@ -1,247 +1,248 b''
1 """Selection classes.
1 """Selection 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
16
17 from collections import OrderedDict
17 from collections import OrderedDict
18 from threading import Lock
18 from threading import Lock
19
19
20 from .widget import DOMWidget, register
20 from .widget import DOMWidget, register
21 from IPython.utils.traitlets import (
21 from IPython.utils.traitlets import (
22 Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple, List
22 Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple, List
23 )
23 )
24 from IPython.utils.py3compat import unicode_type
24 from IPython.utils.py3compat import unicode_type
25 from IPython.utils.warn import DeprecatedClass
25 from IPython.utils.warn import DeprecatedClass
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # SelectionWidget
28 # SelectionWidget
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 class _Selection(DOMWidget):
30 class _Selection(DOMWidget):
31 """Base class for Selection widgets
31 """Base class for Selection widgets
32
32
33 ``options`` 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 When programmatically setting the value, a reverse lookup is performed
36 When programmatically setting the value, a reverse lookup is performed
37 among the options to set the value of ``selected_label`` accordingly. The
37 among the options to set the value of ``selected_label`` accordingly. The
38 reverse lookup uses the equality operator by default, but an other
38 reverse lookup uses the equality operator by default, but an other
39 predicate may be provided via the ``equals`` argument. For example, when
39 predicate may be provided via the ``equals`` argument. For example, when
40 dealing with numpy arrays, one may set equals=np.array_equal.
40 dealing with numpy arrays, one may set equals=np.array_equal.
41 """
41 """
42
42
43 value = Any(help="Selected value")
43 value = Any(help="Selected value")
44 selected_label = Unicode(help="The label of the selected value", sync=True)
44 selected_label = Unicode(help="The label of the selected value", sync=True)
45 options = Any(help="""List of (key, value) tuples or dict of values that the
45 options = Any(help="""List of (key, value) tuples or dict of values that the
46 user can select.
46 user can select.
47
47
48 The keys of this list are the strings that will be displayed in the UI,
48 The keys of this list are the strings that will be displayed in the UI,
49 representing the actual Python choices.
49 representing the actual Python choices.
50
50
51 The keys of this list are also available as _options_labels.
51 The keys of this list are also available as _options_labels.
52 """)
52 """)
53
53
54 _options_dict = Dict()
54 _options_dict = Dict()
55 _options_labels = Tuple(sync=True)
55 _options_labels = Tuple(sync=True)
56 _options_values = Tuple()
56 _options_values = Tuple()
57
57
58 disabled = Bool(False, help="Enable or disable user changes", sync=True)
58 disabled = Bool(False, help="Enable or disable user changes", sync=True)
59 description = Unicode(help="Description of the value this widget represents", sync=True)
59 description = Unicode(help="Description of the value this widget represents", sync=True)
60
60
61 def __init__(self, *args, **kwargs):
61 def __init__(self, *args, **kwargs):
62 self.value_lock = Lock()
62 self.value_lock = Lock()
63 self.options_lock = Lock()
63 self.options_lock = Lock()
64 self.equals = kwargs.pop('equals', lambda x, y: x == y)
64 self.equals = kwargs.pop('equals', lambda x, y: x == y)
65 self.on_trait_change(self._options_readonly_changed, ['_options_dict', '_options_labels', '_options_values', '_options'])
65 self.on_trait_change(self._options_readonly_changed, ['_options_dict', '_options_labels', '_options_values', '_options'])
66 if 'options' in kwargs:
66 if 'options' in kwargs:
67 self.options = kwargs.pop('options')
67 self.options = kwargs.pop('options')
68 DOMWidget.__init__(self, *args, **kwargs)
68 DOMWidget.__init__(self, *args, **kwargs)
69 self._value_in_options()
69 self._value_in_options()
70
70
71 def _make_options(self, x):
71 def _make_options(self, x):
72 # If x is a dict, convert it to list format.
72 # If x is a dict, convert it to list format.
73 if isinstance(x, (OrderedDict, dict)):
73 if isinstance(x, (OrderedDict, dict)):
74 return [(k, v) for k, v in x.items()]
74 return [(k, v) for k, v in x.items()]
75
75
76 # Make sure x is a list or tuple.
76 # Make sure x is a list or tuple.
77 if not isinstance(x, (list, tuple)):
77 if not isinstance(x, (list, tuple)):
78 raise ValueError('x')
78 raise ValueError('x')
79
79
80 # If x is an ordinary list, use the option values as names.
80 # If x is an ordinary list, use the option values as names.
81 for y in x:
81 for y in x:
82 if not isinstance(y, (list, tuple)) or len(y) < 2:
82 if not isinstance(y, (list, tuple)) or len(y) < 2:
83 return [(i, i) for i in x]
83 return [(i, i) for i in x]
84
84
85 # Value is already in the correct format.
85 # Value is already in the correct format.
86 return x
86 return x
87
87
88 def _options_changed(self, name, old, new):
88 def _options_changed(self, name, old, new):
89 """Handles when the options tuple has been changed.
89 """Handles when the options tuple has been changed.
90
90
91 Setting options implies setting option labels from the keys of the dict.
91 Setting options implies setting option labels from the keys of the dict.
92 """
92 """
93 if self.options_lock.acquire(False):
93 if self.options_lock.acquire(False):
94 try:
94 try:
95 self.options = new
95 self.options = new
96
96
97 options = self._make_options(new)
97 options = self._make_options(new)
98 self._options_dict = {i[0]: i[1] for i in options}
98 self._options_dict = {i[0]: i[1] for i in options}
99 self._options_labels = [i[0] for i in options]
99 self._options_labels = [i[0] for i in options]
100 self._options_values = [i[1] for i in options]
100 self._options_values = [i[1] for i in options]
101 self._value_in_options()
101 self._value_in_options()
102 finally:
102 finally:
103 self.options_lock.release()
103 self.options_lock.release()
104
104
105 def _value_in_options(self):
105 def _value_in_options(self):
106 # ensure that the chosen value is one of the choices
106 # ensure that the chosen value is one of the choices
107
107
108 if self._options_values:
108 if self._options_values:
109 if self.value not in self._options_values:
109 if self.value not in self._options_values:
110 self.value = next(iter(self._options_values))
110 self.value = next(iter(self._options_values))
111
111
112 def _options_readonly_changed(self, name, old, new):
112 def _options_readonly_changed(self, name, old, new):
113 if not self.options_lock.locked():
113 if not self.options_lock.locked():
114 raise TraitError("`.%s` is a read-only trait. Use the `.options` tuple instead." % name)
114 raise TraitError("`.%s` is a read-only trait. Use the `.options` tuple instead." % name)
115
115
116 def _value_changed(self, name, old, new):
116 def _value_changed(self, name, old, new):
117 """Called when value has been changed"""
117 """Called when value has been changed"""
118 if self.value_lock.acquire(False):
118 if self.value_lock.acquire(False):
119 try:
119 try:
120 # Reverse dictionary lookup for the value name
120 # Reverse dictionary lookup for the value name
121 for k, v in self._options_dict.items():
121 for k, v in self._options_dict.items():
122 if self.equals(new, v):
122 if self.equals(new, v):
123 # set the selected value name
123 # set the selected value name
124 self.selected_label = k
124 self.selected_label = k
125 return
125 return
126 # undo the change, and raise KeyError
126 # undo the change, and raise KeyError
127 self.value = old
127 self.value = old
128 raise KeyError(new)
128 raise KeyError(new)
129 finally:
129 finally:
130 self.value_lock.release()
130 self.value_lock.release()
131
131
132 def _selected_label_changed(self, name, old, new):
132 def _selected_label_changed(self, name, old, new):
133 """Called when the value name has been changed (typically by the frontend)."""
133 """Called when the value name has been changed (typically by the frontend)."""
134 if self.value_lock.acquire(False):
134 if self.value_lock.acquire(False):
135 try:
135 try:
136 self.value = self._options_dict[new]
136 self.value = self._options_dict[new]
137 finally:
137 finally:
138 self.value_lock.release()
138 self.value_lock.release()
139
139
140
140
141 class _MultipleSelection(_Selection):
141 class _MultipleSelection(_Selection):
142 """Base class for MultipleSelection widgets.
142 """Base class for MultipleSelection widgets.
143
143
144 As with ``_Selection``, ``options`` can be specified as a list or dict. If
144 As with ``_Selection``, ``options`` can be specified as a list or dict. If
145 given as a list, it will be transformed to a dict of the form
145 given as a list, it will be transformed to a dict of the form
146 ``{str(value): value}``.
146 ``{str(value): value}``.
147
147
148 Despite their names, ``value`` (and ``selected_label``) will be tuples, even
148 Despite their names, ``value`` (and ``selected_label``) will be tuples, even
149 if only a single option is selected.
149 if only a single option is selected.
150 """
150 """
151
151
152 value = Tuple(help="Selected values")
152 value = Tuple(help="Selected values")
153 selected_labels = Tuple(help="The labels of the selected options",
153 selected_labels = Tuple(help="The labels of the selected options",
154 sync=True)
154 sync=True)
155
155
156 @property
156 @property
157 def selected_label(self):
157 def selected_label(self):
158 raise AttributeError(
158 raise AttributeError(
159 "Does not support selected_label, use selected_labels")
159 "Does not support selected_label, use selected_labels")
160
160
161 def _value_in_options(self):
161 def _value_in_options(self):
162 # ensure that the chosen value is one of the choices
162 # ensure that the chosen value is one of the choices
163 if self.options:
163 if self.options:
164 old_value = self.value or []
164 old_value = self.value or []
165 new_value = []
165 new_value = []
166 for value in old_value:
166 for value in old_value:
167 if value in self._options_dict.values():
167 if value in self._options_dict.values():
168 new_value.append(value)
168 new_value.append(value)
169 if new_value:
169 if new_value:
170 self.value = new_value
170 self.value = new_value
171 else:
171 else:
172 self.value = [next(iter(self._options_dict.values()))]
172 self.value = [next(iter(self._options_dict.values()))]
173
173
174 def _value_changed(self, name, old, new):
174 def _value_changed(self, name, old, new):
175 """Called when value has been changed"""
175 """Called when value has been changed"""
176 if self.value_lock.acquire(False):
176 if self.value_lock.acquire(False):
177 try:
177 try:
178 self.selected_labels = [
178 self.selected_labels = [
179 self._options_labels[self._options_values.index(v)]
179 self._options_labels[self._options_values.index(v)]
180 for v in new
180 for v in new
181 ]
181 ]
182 except:
182 except:
183 self.value = old
183 self.value = old
184 raise KeyError(new)
184 raise KeyError(new)
185 finally:
185 finally:
186 self.value_lock.release()
186 self.value_lock.release()
187
187
188 def _selected_labels_changed(self, name, old, new):
188 def _selected_labels_changed(self, name, old, new):
189 """Called when the selected label has been changed (typically by the
189 """Called when the selected label has been changed (typically by the
190 frontend)."""
190 frontend)."""
191 if self.value_lock.acquire(False):
191 if self.value_lock.acquire(False):
192 try:
192 try:
193 self.value = [self._options_dict[name] for name in new]
193 self.value = [self._options_dict[name] for name in new]
194 finally:
194 finally:
195 self.value_lock.release()
195 self.value_lock.release()
196
196
197
197
198 @register('IPython.ToggleButtons')
198 @register('IPython.ToggleButtons')
199 class ToggleButtons(_Selection):
199 class ToggleButtons(_Selection):
200 """Group of toggle buttons that represent an enumeration. Only one toggle
200 """Group of toggle buttons that represent an enumeration. Only one toggle
201 button can be toggled at any point in time."""
201 button can be toggled at any point in time."""
202 _view_name = Unicode('ToggleButtonsView', sync=True)
202 _view_name = Unicode('ToggleButtonsView', sync=True)
203 tooltips = List(Unicode(), sync=True)
203 tooltips = List(Unicode(), sync=True)
204 icons = List(Unicode(), sync=True)
204
205
205 button_style = CaselessStrEnum(
206 button_style = CaselessStrEnum(
206 values=['primary', 'success', 'info', 'warning', 'danger', ''],
207 values=['primary', 'success', 'info', 'warning', 'danger', ''],
207 default_value='', allow_none=True, sync=True, help="""Use a
208 default_value='', allow_none=True, sync=True, help="""Use a
208 predefined styling for the buttons.""")
209 predefined styling for the buttons.""")
209
210
210 @register('IPython.Dropdown')
211 @register('IPython.Dropdown')
211 class Dropdown(_Selection):
212 class Dropdown(_Selection):
212 """Allows you to select a single item from a dropdown."""
213 """Allows you to select a single item from a dropdown."""
213 _view_name = Unicode('DropdownView', sync=True)
214 _view_name = Unicode('DropdownView', sync=True)
214
215
215 button_style = CaselessStrEnum(
216 button_style = CaselessStrEnum(
216 values=['primary', 'success', 'info', 'warning', 'danger', ''],
217 values=['primary', 'success', 'info', 'warning', 'danger', ''],
217 default_value='', allow_none=True, sync=True, help="""Use a
218 default_value='', allow_none=True, sync=True, help="""Use a
218 predefined styling for the buttons.""")
219 predefined styling for the buttons.""")
219
220
220 @register('IPython.RadioButtons')
221 @register('IPython.RadioButtons')
221 class RadioButtons(_Selection):
222 class RadioButtons(_Selection):
222 """Group of radio buttons that represent an enumeration. Only one radio
223 """Group of radio buttons that represent an enumeration. Only one radio
223 button can be toggled at any point in time."""
224 button can be toggled at any point in time."""
224 _view_name = Unicode('RadioButtonsView', sync=True)
225 _view_name = Unicode('RadioButtonsView', sync=True)
225
226
226
227
227
228
228 @register('IPython.Select')
229 @register('IPython.Select')
229 class Select(_Selection):
230 class Select(_Selection):
230 """Listbox that only allows one item to be selected at any given time."""
231 """Listbox that only allows one item to be selected at any given time."""
231 _view_name = Unicode('SelectView', sync=True)
232 _view_name = Unicode('SelectView', sync=True)
232
233
233
234
234 @register('IPython.SelectMultiple')
235 @register('IPython.SelectMultiple')
235 class SelectMultiple(_MultipleSelection):
236 class SelectMultiple(_MultipleSelection):
236 """Listbox that allows many items to be selected at any given time.
237 """Listbox that allows many items to be selected at any given time.
237 Despite their names, inherited from ``_Selection``, the currently chosen
238 Despite their names, inherited from ``_Selection``, the currently chosen
238 option values, ``value``, or their labels, ``selected_labels`` must both be
239 option values, ``value``, or their labels, ``selected_labels`` must both be
239 updated with a list-like object."""
240 updated with a list-like object."""
240 _view_name = Unicode('SelectMultipleView', sync=True)
241 _view_name = Unicode('SelectMultipleView', sync=True)
241
242
242
243
243 # Remove in IPython 4.0
244 # Remove in IPython 4.0
244 ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget')
245 ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget')
245 DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget')
246 DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget')
246 RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget')
247 RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget')
247 SelectWidget = DeprecatedClass(Select, 'SelectWidget')
248 SelectWidget = DeprecatedClass(Select, 'SelectWidget')
General Comments 0
You need to be logged in to leave comments. Login now