##// END OF EJS Templates
Ignore the event object for handleTextChange
Gordon Ball -
Show More
@@ -1,413 +1,413
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 "jqueryui",
6 "jqueryui",
7 "bootstrap",
7 "bootstrap",
8 ], function(widget, $){
8 ], function(widget, $){
9
9
10 var IntSliderView = widget.DOMWidgetView.extend({
10 var IntSliderView = widget.DOMWidgetView.extend({
11 render : function(){
11 render : function(){
12 // Called when view is rendered.
12 // Called when view is rendered.
13 this.$el
13 this.$el
14 .addClass('widget-hbox');
14 .addClass('widget-hbox');
15 this.$label = $('<div />')
15 this.$label = $('<div />')
16 .appendTo(this.$el)
16 .appendTo(this.$el)
17 .addClass('widget-label')
17 .addClass('widget-label')
18 .hide();
18 .hide();
19
19
20 this.$slider = $('<div />')
20 this.$slider = $('<div />')
21 .slider({})
21 .slider({})
22 .addClass('slider');
22 .addClass('slider');
23 // Put the slider in a container
23 // Put the slider in a container
24 this.$slider_container = $('<div />')
24 this.$slider_container = $('<div />')
25 .addClass('widget-hslider')
25 .addClass('widget-hslider')
26 .append(this.$slider);
26 .append(this.$slider);
27 this.$el.append(this.$slider_container);
27 this.$el.append(this.$slider_container);
28
28
29 this.$readout = $('<div/>')
29 this.$readout = $('<div/>')
30 .appendTo(this.$el)
30 .appendTo(this.$el)
31 .addClass('widget-readout')
31 .addClass('widget-readout')
32 .attr('contentEditable', true)
32 .attr('contentEditable', true)
33 .hide();
33 .hide();
34
34
35 this.model.on('change:slider_color', function(sender, value) {
35 this.model.on('change:slider_color', function(sender, value) {
36 this.$slider.find('a').css('background', value);
36 this.$slider.find('a').css('background', value);
37 }, this);
37 }, this);
38 this.$slider.find('a').css('background', this.model.get('slider_color'));
38 this.$slider.find('a').css('background', this.model.get('slider_color'));
39
39
40 // Set defaults.
40 // Set defaults.
41 this.update();
41 this.update();
42 },
42 },
43
43
44 update_attr: function(name, value) {
44 update_attr: function(name, value) {
45 // Set a css attr of the widget view.
45 // Set a css attr of the widget view.
46 if (name == 'color') {
46 if (name == 'color') {
47 this.$readout.css(name, value);
47 this.$readout.css(name, value);
48 } else if (name.substring(0, 4) == 'font') {
48 } else if (name.substring(0, 4) == 'font') {
49 this.$readout.css(name, value);
49 this.$readout.css(name, value);
50 } else if (name.substring(0, 6) == 'border') {
50 } else if (name.substring(0, 6) == 'border') {
51 this.$slider.find('a').css(name, value);
51 this.$slider.find('a').css(name, value);
52 this.$slider_container.css(name, value);
52 this.$slider_container.css(name, value);
53 } else if (name == 'width' || name == 'height' || name == 'background') {
53 } else if (name == 'width' || name == 'height' || name == 'background') {
54 this.$slider_container.css(name, value);
54 this.$slider_container.css(name, value);
55 } else {
55 } else {
56 this.$slider.css(name, value);
56 this.$slider.css(name, value);
57 }
57 }
58 },
58 },
59
59
60 update : function(options){
60 update : function(options){
61 // Update the contents of this view
61 // Update the contents of this view
62 //
62 //
63 // Called when the model is changed. The model may have been
63 // Called when the model is changed. The model may have been
64 // changed by another view or by a state update from the back-end.
64 // changed by another view or by a state update from the back-end.
65 if (options === undefined || options.updated_view != this) {
65 if (options === undefined || options.updated_view != this) {
66 // JQuery slider option keys. These keys happen to have a
66 // JQuery slider option keys. These keys happen to have a
67 // one-to-one mapping with the corrosponding keys of the model.
67 // one-to-one mapping with the corrosponding keys of the model.
68 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
68 var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
69 var that = this;
69 var that = this;
70 that.$slider.slider({});
70 that.$slider.slider({});
71 _.each(jquery_slider_keys, function(key, i) {
71 _.each(jquery_slider_keys, function(key, i) {
72 var model_value = that.model.get(key);
72 var model_value = that.model.get(key);
73 if (model_value !== undefined) {
73 if (model_value !== undefined) {
74 that.$slider.slider("option", key, model_value);
74 that.$slider.slider("option", key, model_value);
75 }
75 }
76 });
76 });
77 var range_value = this.model.get("_range");
77 var range_value = this.model.get("_range");
78 if (range_value !== undefined) {
78 if (range_value !== undefined) {
79 this.$slider.slider("option", "range", range_value);
79 this.$slider.slider("option", "range", range_value);
80 }
80 }
81
81
82 // WORKAROUND FOR JQUERY SLIDER BUG.
82 // WORKAROUND FOR JQUERY SLIDER BUG.
83 // The horizontal position of the slider handle
83 // The horizontal position of the slider handle
84 // depends on the value of the slider at the time
84 // depends on the value of the slider at the time
85 // of orientation change. Before applying the new
85 // of orientation change. Before applying the new
86 // workaround, we set the value to the minimum to
86 // workaround, we set the value to the minimum to
87 // make sure that the horizontal placement of the
87 // make sure that the horizontal placement of the
88 // handle in the vertical slider is always
88 // handle in the vertical slider is always
89 // consistent.
89 // consistent.
90 var orientation = this.model.get('orientation');
90 var orientation = this.model.get('orientation');
91 var min = this.model.get('min');
91 var min = this.model.get('min');
92 var max = this.model.get('max');
92 var max = this.model.get('max');
93 if (this.model.get('_range')) {
93 if (this.model.get('_range')) {
94 this.$slider.slider('option', 'values', [min, min]);
94 this.$slider.slider('option', 'values', [min, min]);
95 } else {
95 } else {
96 this.$slider.slider('option', 'value', min);
96 this.$slider.slider('option', 'value', min);
97 }
97 }
98 this.$slider.slider('option', 'orientation', orientation);
98 this.$slider.slider('option', 'orientation', orientation);
99 var value = this.model.get('value');
99 var value = this.model.get('value');
100 if (this.model.get('_range')) {
100 if (this.model.get('_range')) {
101 // values for the range case are validated python-side in
101 // values for the range case are validated python-side in
102 // _Bounded{Int,Float}RangeWidget._validate
102 // _Bounded{Int,Float}RangeWidget._validate
103 this.$slider.slider('option', 'values', value);
103 this.$slider.slider('option', 'values', value);
104 this.$readout.text(value.join("-"));
104 this.$readout.text(value.join("-"));
105 } else {
105 } else {
106 if(value > max) {
106 if(value > max) {
107 value = max;
107 value = max;
108 }
108 }
109 else if(value < min){
109 else if(value < min){
110 value = min;
110 value = min;
111 }
111 }
112 this.$slider.slider('option', 'value', value);
112 this.$slider.slider('option', 'value', value);
113 this.$readout.text(value);
113 this.$readout.text(value);
114 }
114 }
115
115
116 if(this.model.get('value')!=value) {
116 if(this.model.get('value')!=value) {
117 this.model.set('value', value, {updated_view: this});
117 this.model.set('value', value, {updated_view: this});
118 this.touch();
118 this.touch();
119 }
119 }
120
120
121 // Use the right CSS classes for vertical & horizontal sliders
121 // Use the right CSS classes for vertical & horizontal sliders
122 if (orientation=='vertical') {
122 if (orientation=='vertical') {
123 this.$slider_container
123 this.$slider_container
124 .removeClass('widget-hslider')
124 .removeClass('widget-hslider')
125 .addClass('widget-vslider');
125 .addClass('widget-vslider');
126 this.$el
126 this.$el
127 .removeClass('widget-hbox')
127 .removeClass('widget-hbox')
128 .addClass('widget-vbox');
128 .addClass('widget-vbox');
129
129
130 } else {
130 } else {
131 this.$slider_container
131 this.$slider_container
132 .removeClass('widget-vslider')
132 .removeClass('widget-vslider')
133 .addClass('widget-hslider');
133 .addClass('widget-hslider');
134 this.$el
134 this.$el
135 .removeClass('widget-vbox')
135 .removeClass('widget-vbox')
136 .addClass('widget-hbox');
136 .addClass('widget-hbox');
137 }
137 }
138
138
139 var description = this.model.get('description');
139 var description = this.model.get('description');
140 if (description.length === 0) {
140 if (description.length === 0) {
141 this.$label.hide();
141 this.$label.hide();
142 } else {
142 } else {
143 this.$label.text(description);
143 this.$label.text(description);
144 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
144 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
145 this.$label.show();
145 this.$label.show();
146 }
146 }
147
147
148 var readout = this.model.get('readout');
148 var readout = this.model.get('readout');
149 if (readout) {
149 if (readout) {
150 this.$readout.show();
150 this.$readout.show();
151 } else {
151 } else {
152 this.$readout.hide();
152 this.$readout.hide();
153 }
153 }
154 }
154 }
155 return IntSliderView.__super__.update.apply(this);
155 return IntSliderView.__super__.update.apply(this);
156 },
156 },
157
157
158 events: {
158 events: {
159 // Dictionary of events and their handlers.
159 // Dictionary of events and their handlers.
160 "slide" : "handleSliderChange",
160 "slide" : "handleSliderChange",
161 "blur [contentEditable=true]": "handleTextChange",
161 "blur [contentEditable=true]": "handleTextChange",
162 "keydown [contentEditable=true]": "handleKeyDown"
162 "keydown [contentEditable=true]": "handleKeyDown"
163 },
163 },
164
164
165 handleKeyDown: function(e) {
165 handleKeyDown: function(e) {
166 if (e.keyCode == 13) {
166 if (e.keyCode == 13) {
167 e.preventDefault();
167 e.preventDefault();
168 this.handleTextChange(e);
168 this.handleTextChange();
169 }
169 }
170 },
170 },
171
171
172 handleTextChange: function(e) {
172 handleTextChange: function() {
173 var text = $(e.target).text().trim();
173 var text = this.$readout.text();
174 var value = this._validate_text_input(text);
174 var value = this._validate_text_input(text);
175 if (isNaN(value)) {
175 if (isNaN(value)) {
176 this.$readout.text(this.model.get('value'));
176 this.$readout.text(this.model.get('value'));
177 } else {
177 } else {
178 //check for outside range
178 //check for outside range
179 if (value > this.model.get('max')) value = this.model.get('max');
179 if (value > this.model.get('max')) value = this.model.get('max');
180 if (value < this.model.get('min')) value = this.model.get('min');
180 if (value < this.model.get('min')) value = this.model.get('min');
181
181
182 //update the readout unconditionally
182 //update the readout unconditionally
183 //this covers eg, entering a float value which rounds to the
183 //this covers eg, entering a float value which rounds to the
184 //existing int value, which will not trigger an update since the model
184 //existing int value, which will not trigger an update since the model
185 //doesn't change, but we should update the text to reflect that
185 //doesn't change, but we should update the text to reflect that
186 //a float value isn't being used
186 //a float value isn't being used
187 this.$readout.text(value);
187 this.$readout.text(value);
188
188
189 //note that the step size currently isn't enforced, so if an
189 //note that the step size currently isn't enforced, so if an
190 //off-step value is input it will be retained
190 //off-step value is input it will be retained
191
191
192 //update the model
192 //update the model
193 this.model.set('value', value, {updated_view: this});
193 this.model.set('value', value, {updated_view: this});
194 this.touch();
194 this.touch();
195 }
195 }
196 },
196 },
197
197
198 _validate_text_input: function(x) {
198 _validate_text_input: function(x) {
199 return parseInt(x);
199 return parseInt(x);
200 },
200 },
201
201
202 handleSliderChange: function(e, ui) {
202 handleSliderChange: function(e, ui) {
203 // Called when the slider value is changed.
203 // Called when the slider value is changed.
204
204
205 // Calling model.set will trigger all of the other views of the
205 // Calling model.set will trigger all of the other views of the
206 // model to update.
206 // model to update.
207 if (this.model.get("_range")) {
207 if (this.model.get("_range")) {
208 var actual_value = ui.values.map(this._validate_slide_value);
208 var actual_value = ui.values.map(this._validate_slide_value);
209 this.$readout.text(actual_value.join("-"));
209 this.$readout.text(actual_value.join("-"));
210 } else {
210 } else {
211 var actual_value = this._validate_slide_value(ui.value);
211 var actual_value = this._validate_slide_value(ui.value);
212 this.$readout.text(actual_value);
212 this.$readout.text(actual_value);
213 }
213 }
214 this.model.set('value', actual_value, {updated_view: this});
214 this.model.set('value', actual_value, {updated_view: this});
215 this.touch();
215 this.touch();
216 },
216 },
217
217
218 _validate_slide_value: function(x) {
218 _validate_slide_value: function(x) {
219 // Validate the value of the slider before sending it to the back-end
219 // Validate the value of the slider before sending it to the back-end
220 // and applying it to the other views on the page.
220 // and applying it to the other views on the page.
221
221
222 // Double bit-wise not truncates the decimel (int cast).
222 // Double bit-wise not truncates the decimel (int cast).
223 return ~~x;
223 return ~~x;
224 },
224 },
225 });
225 });
226
226
227
227
228 var IntTextView = widget.DOMWidgetView.extend({
228 var IntTextView = widget.DOMWidgetView.extend({
229 render : function(){
229 render : function(){
230 // Called when view is rendered.
230 // Called when view is rendered.
231 this.$el
231 this.$el
232 .addClass('widget-hbox');
232 .addClass('widget-hbox');
233 this.$label = $('<div />')
233 this.$label = $('<div />')
234 .appendTo(this.$el)
234 .appendTo(this.$el)
235 .addClass('widget-label')
235 .addClass('widget-label')
236 .hide();
236 .hide();
237 this.$textbox = $('<input type="text" />')
237 this.$textbox = $('<input type="text" />')
238 .addClass('form-control')
238 .addClass('form-control')
239 .addClass('widget-numeric-text')
239 .addClass('widget-numeric-text')
240 .appendTo(this.$el);
240 .appendTo(this.$el);
241 this.update(); // Set defaults.
241 this.update(); // Set defaults.
242 },
242 },
243
243
244 update : function(options){
244 update : function(options){
245 // Update the contents of this view
245 // Update the contents of this view
246 //
246 //
247 // Called when the model is changed. The model may have been
247 // Called when the model is changed. The model may have been
248 // changed by another view or by a state update from the back-end.
248 // changed by another view or by a state update from the back-end.
249 if (options === undefined || options.updated_view != this) {
249 if (options === undefined || options.updated_view != this) {
250 var value = this.model.get('value');
250 var value = this.model.get('value');
251 if (this._parse_value(this.$textbox.val()) != value) {
251 if (this._parse_value(this.$textbox.val()) != value) {
252 this.$textbox.val(value);
252 this.$textbox.val(value);
253 }
253 }
254
254
255 if (this.model.get('disabled')) {
255 if (this.model.get('disabled')) {
256 this.$textbox.attr('disabled','disabled');
256 this.$textbox.attr('disabled','disabled');
257 } else {
257 } else {
258 this.$textbox.removeAttr('disabled');
258 this.$textbox.removeAttr('disabled');
259 }
259 }
260
260
261 var description = this.model.get('description');
261 var description = this.model.get('description');
262 if (description.length === 0) {
262 if (description.length === 0) {
263 this.$label.hide();
263 this.$label.hide();
264 } else {
264 } else {
265 this.$label.text(description);
265 this.$label.text(description);
266 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
266 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
267 this.$label.show();
267 this.$label.show();
268 }
268 }
269 }
269 }
270 return IntTextView.__super__.update.apply(this);
270 return IntTextView.__super__.update.apply(this);
271 },
271 },
272
272
273 update_attr: function(name, value) {
273 update_attr: function(name, value) {
274 // Set a css attr of the widget view.
274 // Set a css attr of the widget view.
275 this.$textbox.css(name, value);
275 this.$textbox.css(name, value);
276 },
276 },
277
277
278 events: {
278 events: {
279 // Dictionary of events and their handlers.
279 // Dictionary of events and their handlers.
280 "keyup input" : "handleChanging",
280 "keyup input" : "handleChanging",
281 "paste input" : "handleChanging",
281 "paste input" : "handleChanging",
282 "cut input" : "handleChanging",
282 "cut input" : "handleChanging",
283
283
284 // Fires only when control is validated or looses focus.
284 // Fires only when control is validated or looses focus.
285 "change input" : "handleChanged"
285 "change input" : "handleChanged"
286 },
286 },
287
287
288 handleChanging: function(e) {
288 handleChanging: function(e) {
289 // Handles and validates user input.
289 // Handles and validates user input.
290
290
291 // Try to parse value as a int.
291 // Try to parse value as a int.
292 var numericalValue = 0;
292 var numericalValue = 0;
293 if (e.target.value !== '') {
293 if (e.target.value !== '') {
294 var trimmed = e.target.value.trim();
294 var trimmed = e.target.value.trim();
295 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
295 if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
296 numericalValue = this._parse_value(e.target.value);
296 numericalValue = this._parse_value(e.target.value);
297 }
297 }
298 }
298 }
299
299
300 // If parse failed, reset value to value stored in model.
300 // If parse failed, reset value to value stored in model.
301 if (isNaN(numericalValue)) {
301 if (isNaN(numericalValue)) {
302 e.target.value = this.model.get('value');
302 e.target.value = this.model.get('value');
303 } else if (!isNaN(numericalValue)) {
303 } else if (!isNaN(numericalValue)) {
304 if (this.model.get('max') !== undefined) {
304 if (this.model.get('max') !== undefined) {
305 numericalValue = Math.min(this.model.get('max'), numericalValue);
305 numericalValue = Math.min(this.model.get('max'), numericalValue);
306 }
306 }
307 if (this.model.get('min') !== undefined) {
307 if (this.model.get('min') !== undefined) {
308 numericalValue = Math.max(this.model.get('min'), numericalValue);
308 numericalValue = Math.max(this.model.get('min'), numericalValue);
309 }
309 }
310
310
311 // Apply the value if it has changed.
311 // Apply the value if it has changed.
312 if (numericalValue != this.model.get('value')) {
312 if (numericalValue != this.model.get('value')) {
313
313
314 // Calling model.set will trigger all of the other views of the
314 // Calling model.set will trigger all of the other views of the
315 // model to update.
315 // model to update.
316 this.model.set('value', numericalValue, {updated_view: this});
316 this.model.set('value', numericalValue, {updated_view: this});
317 this.touch();
317 this.touch();
318 }
318 }
319 }
319 }
320 },
320 },
321
321
322 handleChanged: function(e) {
322 handleChanged: function(e) {
323 // Applies validated input.
323 // Applies validated input.
324 if (this.model.get('value') != e.target.value) {
324 if (this.model.get('value') != e.target.value) {
325 e.target.value = this.model.get('value');
325 e.target.value = this.model.get('value');
326 }
326 }
327 },
327 },
328
328
329 _parse_value: function(value) {
329 _parse_value: function(value) {
330 // Parse the value stored in a string.
330 // Parse the value stored in a string.
331 return parseInt(value);
331 return parseInt(value);
332 },
332 },
333 });
333 });
334
334
335
335
336 var ProgressView = widget.DOMWidgetView.extend({
336 var ProgressView = widget.DOMWidgetView.extend({
337 render : function(){
337 render : function(){
338 // Called when view is rendered.
338 // Called when view is rendered.
339 this.$el
339 this.$el
340 .addClass('widget-hbox');
340 .addClass('widget-hbox');
341 this.$label = $('<div />')
341 this.$label = $('<div />')
342 .appendTo(this.$el)
342 .appendTo(this.$el)
343 .addClass('widget-label')
343 .addClass('widget-label')
344 .hide();
344 .hide();
345 this.$progress = $('<div />')
345 this.$progress = $('<div />')
346 .addClass('progress')
346 .addClass('progress')
347 .addClass('widget-progress')
347 .addClass('widget-progress')
348 .appendTo(this.$el);
348 .appendTo(this.$el);
349 this.$bar = $('<div />')
349 this.$bar = $('<div />')
350 .addClass('progress-bar')
350 .addClass('progress-bar')
351 .css('width', '50%')
351 .css('width', '50%')
352 .appendTo(this.$progress);
352 .appendTo(this.$progress);
353 this.update(); // Set defaults.
353 this.update(); // Set defaults.
354
354
355 this.model.on('change:bar_style', function(model, value) {
355 this.model.on('change:bar_style', function(model, value) {
356 this.update_bar_style();
356 this.update_bar_style();
357 }, this);
357 }, this);
358 this.update_bar_style('');
358 this.update_bar_style('');
359 },
359 },
360
360
361 update : function(){
361 update : function(){
362 // Update the contents of this view
362 // Update the contents of this view
363 //
363 //
364 // Called when the model is changed. The model may have been
364 // Called when the model is changed. The model may have been
365 // changed by another view or by a state update from the back-end.
365 // changed by another view or by a state update from the back-end.
366 var value = this.model.get('value');
366 var value = this.model.get('value');
367 var max = this.model.get('max');
367 var max = this.model.get('max');
368 var min = this.model.get('min');
368 var min = this.model.get('min');
369 var percent = 100.0 * (value - min) / (max - min);
369 var percent = 100.0 * (value - min) / (max - min);
370 this.$bar.css('width', percent + '%');
370 this.$bar.css('width', percent + '%');
371
371
372 var description = this.model.get('description');
372 var description = this.model.get('description');
373 if (description.length === 0) {
373 if (description.length === 0) {
374 this.$label.hide();
374 this.$label.hide();
375 } else {
375 } else {
376 this.$label.text(description);
376 this.$label.text(description);
377 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
377 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
378 this.$label.show();
378 this.$label.show();
379 }
379 }
380 return ProgressView.__super__.update.apply(this);
380 return ProgressView.__super__.update.apply(this);
381 },
381 },
382
382
383 update_bar_style: function(previous_trait_value) {
383 update_bar_style: function(previous_trait_value) {
384 var class_map = {
384 var class_map = {
385 success: ['progress-bar-success'],
385 success: ['progress-bar-success'],
386 info: ['progress-bar-info'],
386 info: ['progress-bar-info'],
387 warning: ['progress-bar-warning'],
387 warning: ['progress-bar-warning'],
388 danger: ['progress-bar-danger']
388 danger: ['progress-bar-danger']
389 };
389 };
390 this.update_mapped_classes(class_map, 'bar_style', previous_trait_value, this.$bar);
390 this.update_mapped_classes(class_map, 'bar_style', previous_trait_value, this.$bar);
391 },
391 },
392
392
393 update_attr: function(name, value) {
393 update_attr: function(name, value) {
394 // Set a css attr of the widget view.
394 // Set a css attr of the widget view.
395 if (name.substring(0, 6) == 'border' || name == 'width' ||
395 if (name.substring(0, 6) == 'border' || name == 'width' ||
396 name == 'height' || name == 'background' || name == 'margin' ||
396 name == 'height' || name == 'background' || name == 'margin' ||
397 name == 'padding') {
397 name == 'padding') {
398
398
399 this.$progress.css(name, value);
399 this.$progress.css(name, value);
400 } else if (name == 'color') {
400 } else if (name == 'color') {
401 this.$bar.css('background', value);
401 this.$bar.css('background', value);
402 } else {
402 } else {
403 this.$bar.css(name, value);
403 this.$bar.css(name, value);
404 }
404 }
405 },
405 },
406 });
406 });
407
407
408 return {
408 return {
409 'IntSliderView': IntSliderView,
409 'IntSliderView': IntSliderView,
410 'IntTextView': IntTextView,
410 'IntTextView': IntTextView,
411 'ProgressView': ProgressView,
411 'ProgressView': ProgressView,
412 };
412 };
413 });
413 });
General Comments 0
You need to be logged in to leave comments. Login now