widget_int.js
482 lines
| 18.1 KiB
| application/javascript
|
JavascriptLexer
Jonathan Frederic
|
r17198 | // Copyright (c) IPython Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||||
Jonathan Frederic
|
r14366 | |||
Jonathan Frederic
|
r17198 | define([ | ||
"widgets/js/widget", | ||||
Jonathan Frederic
|
r17216 | "jqueryui", | ||
Gordon Ball
|
r18079 | "base/js/keyboard", | ||
"bootstrap" | ||||
], function(widget, $, keyboard){ | ||||
Jonathan Frederic
|
r17198 | |||
var IntSliderView = widget.DOMWidgetView.extend({ | ||||
Jonathan Frederic
|
r14672 | render : function(){ | ||
Jonathan Frederic
|
r19176 | /** | ||
* Called when view is rendered. | ||||
*/ | ||||
Jonathan Frederic
|
r14672 | this.$el | ||
Jason Grout
|
r18120 | .addClass('widget-hbox widget-slider'); | ||
Jonathan Frederic
|
r14672 | this.$label = $('<div />') | ||
.appendTo(this.$el) | ||||
Jonathan Frederic
|
r17929 | .addClass('widget-label') | ||
Jonathan Frederic
|
r14672 | .hide(); | ||
Brian E. Granger
|
r14982 | |||
Jonathan Frederic
|
r14672 | this.$slider = $('<div />') | ||
.slider({}) | ||||
.addClass('slider'); | ||||
// Put the slider in a container | ||||
this.$slider_container = $('<div />') | ||||
.addClass('widget-hslider') | ||||
Jonathan Frederic
|
r16817 | .append(this.$slider); | ||
Jonathan Frederic
|
r14672 | this.$el.append(this.$slider_container); | ||
Brian E. Granger
|
r14982 | this.$readout = $('<div/>') | ||
.appendTo(this.$el) | ||||
Jonathan Frederic
|
r17932 | .addClass('widget-readout') | ||
Gordon Ball
|
r17952 | .attr('contentEditable', true) | ||
Brian E. Granger
|
r14982 | .hide(); | ||
Jonathan Frederic
|
r17723 | |||
this.model.on('change:slider_color', function(sender, value) { | ||||
this.$slider.find('a').css('background', value); | ||||
}, this); | ||||
Jonathan Frederic
|
r17948 | this.$slider.find('a').css('background', this.model.get('slider_color')); | ||
Brian E. Granger
|
r14982 | |||
Jonathan Frederic
|
r14672 | // Set defaults. | ||
this.update(); | ||||
}, | ||||
Jonathan Frederic
|
r17723 | |||
update_attr: function(name, value) { | ||||
Jonathan Frederic
|
r19176 | /** | ||
* Set a css attr of the widget view. | ||||
*/ | ||||
Jonathan Frederic
|
r17723 | if (name == 'color') { | ||
this.$readout.css(name, value); | ||||
} else if (name.substring(0, 4) == 'font') { | ||||
this.$readout.css(name, value); | ||||
} else if (name.substring(0, 6) == 'border') { | ||||
this.$slider.find('a').css(name, value); | ||||
this.$slider_container.css(name, value); | ||||
} else if (name == 'width' || name == 'height' || name == 'background') { | ||||
this.$slider_container.css(name, value); | ||||
} else { | ||||
this.$slider.css(name, value); | ||||
} | ||||
}, | ||||
Jonathan Frederic
|
r14672 | |||
update : function(options){ | ||||
Jonathan Frederic
|
r19176 | /** | ||
* Update the contents of this view | ||||
* | ||||
* Called when the model is changed. The model may have been | ||||
* changed by another view or by a state update from the back-end. | ||||
*/ | ||||
Jonathan Frederic
|
r14672 | if (options === undefined || options.updated_view != this) { | ||
// JQuery slider option keys. These keys happen to have a | ||||
// one-to-one mapping with the corrosponding keys of the model. | ||||
Jonathan Frederic
|
r17967 | var jquery_slider_keys = ['step', 'disabled']; | ||
Jonathan Frederic
|
r14686 | var that = this; | ||
Jonathan Frederic
|
r17175 | that.$slider.slider({}); | ||
Jonathan Frederic
|
r14672 | _.each(jquery_slider_keys, function(key, i) { | ||
Jonathan Frederic
|
r14686 | var model_value = that.model.get(key); | ||
Jonathan Frederic
|
r14672 | if (model_value !== undefined) { | ||
Jonathan Frederic
|
r14686 | that.$slider.slider("option", key, model_value); | ||
Jonathan Frederic
|
r14672 | } | ||
}); | ||||
Jonathan Frederic
|
r17967 | |||
var max = this.model.get('max'); | ||||
var min = this.model.get('min'); | ||||
if (min <= max) { | ||||
if (max !== undefined) this.$slider.slider('option', 'max', max); | ||||
if (min !== undefined) this.$slider.slider('option', 'min', min); | ||||
} | ||||
Gordon Ball
|
r17703 | var range_value = this.model.get("_range"); | ||
if (range_value !== undefined) { | ||||
this.$slider.slider("option", "range", range_value); | ||||
} | ||||
Jonathan Frederic
|
r14672 | |||
// WORKAROUND FOR JQUERY SLIDER BUG. | ||||
// The horizontal position of the slider handle | ||||
// depends on the value of the slider at the time | ||||
// of orientation change. Before applying the new | ||||
// workaround, we set the value to the minimum to | ||||
// make sure that the horizontal placement of the | ||||
// handle in the vertical slider is always | ||||
// consistent. | ||||
var orientation = this.model.get('orientation'); | ||||
Tarun Gaba
|
r17274 | var min = this.model.get('min'); | ||
var max = this.model.get('max'); | ||||
Gordon Ball
|
r17703 | if (this.model.get('_range')) { | ||
Gordon Ball
|
r17684 | this.$slider.slider('option', 'values', [min, min]); | ||
Gordon Ball
|
r17059 | } else { | ||
Gordon Ball
|
r17684 | this.$slider.slider('option', 'value', min); | ||
Gordon Ball
|
r17059 | } | ||
Jonathan Frederic
|
r14672 | this.$slider.slider('option', 'orientation', orientation); | ||
Tarun Gaba
|
r17657 | var value = this.model.get('value'); | ||
Gordon Ball
|
r17703 | if (this.model.get('_range')) { | ||
Gordon Ball
|
r17702 | // values for the range case are validated python-side in | ||
// _Bounded{Int,Float}RangeWidget._validate | ||||
Gordon Ball
|
r17059 | this.$slider.slider('option', 'values', value); | ||
this.$readout.text(value.join("-")); | ||||
} else { | ||||
Gordon Ball
|
r17702 | if(value > max) { | ||
value = max; | ||||
} | ||||
else if(value < min){ | ||||
value = min; | ||||
} | ||||
Gordon Ball
|
r17059 | this.$slider.slider('option', 'value', value); | ||
this.$readout.text(value); | ||||
} | ||||
Tarun Gaba
|
r17654 | if(this.model.get('value')!=value) { | ||
this.model.set('value', value, {updated_view: this}); | ||||
this.touch(); | ||||
} | ||||
Jonathan Frederic
|
r14672 | |||
// Use the right CSS classes for vertical & horizontal sliders | ||||
if (orientation=='vertical') { | ||||
this.$slider_container | ||||
.removeClass('widget-hslider') | ||||
.addClass('widget-vslider'); | ||||
this.$el | ||||
Jonathan Frederic
|
r17929 | .removeClass('widget-hbox') | ||
.addClass('widget-vbox'); | ||||
Jonathan Frederic
|
r14672 | |||
} else { | ||||
this.$slider_container | ||||
.removeClass('widget-vslider') | ||||
.addClass('widget-hslider'); | ||||
this.$el | ||||
Jonathan Frederic
|
r17929 | .removeClass('widget-vbox') | ||
.addClass('widget-hbox'); | ||||
Jonathan Frederic
|
r14672 | } | ||
var description = this.model.get('description'); | ||||
if (description.length === 0) { | ||||
this.$label.hide(); | ||||
} else { | ||||
Nicholas Bollweg (Nick)
|
r19196 | this.typeset(this.$label, description); | ||
Jonathan Frederic
|
r14672 | this.$label.show(); | ||
} | ||||
Brian E. Granger
|
r14982 | |||
var readout = this.model.get('readout'); | ||||
if (readout) { | ||||
this.$readout.show(); | ||||
} else { | ||||
this.$readout.hide(); | ||||
} | ||||
Jonathan Frederic
|
r14672 | } | ||
return IntSliderView.__super__.update.apply(this); | ||||
}, | ||||
events: { | ||||
// Dictionary of events and their handlers. | ||||
Gordon Ball
|
r17952 | "slide" : "handleSliderChange", | ||
Gordon Ball
|
r17954 | "blur [contentEditable=true]": "handleTextChange", | ||
"keydown [contentEditable=true]": "handleKeyDown" | ||||
Jonathan Frederic
|
r14672 | }, | ||
Gordon Ball
|
r17954 | handleKeyDown: function(e) { | ||
Gordon Ball
|
r18079 | if (e.keyCode == keyboard.keycodes.enter) { | ||
Gordon Ball
|
r17954 | e.preventDefault(); | ||
Gordon Ball
|
r17955 | this.handleTextChange(); | ||
Gordon Ball
|
r17954 | } | ||
}, | ||||
Gordon Ball
|
r17955 | handleTextChange: function() { | ||
Jonathan Frederic
|
r19176 | /** | ||
* this handles the entry of text into the contentEditable label | ||||
* first, the value is checked if it contains a parseable number | ||||
* (or pair of numbers, for the _range case) | ||||
* then it is clamped within the min-max range of the slider | ||||
* finally, the model is updated if the value is to be changed | ||||
* | ||||
* if any of these conditions are not met, the text is reset | ||||
* | ||||
* the step size is not enforced | ||||
*/ | ||||
Gordon Ball
|
r17956 | |||
Gordon Ball
|
r17955 | var text = this.$readout.text(); | ||
Gordon Ball
|
r17956 | var vmin = this.model.get('min'); | ||
var vmax = this.model.get('max'); | ||||
if (this.model.get("_range")) { | ||||
// range case | ||||
// ranges can be expressed either "val-val" or "val:val" (+spaces) | ||||
var match = this._range_regex.exec(text); | ||||
if (match) { | ||||
Gordon Ball
|
r17957 | var values = [this._parse_value(match[1]), | ||
this._parse_value(match[2])]; | ||||
Gordon Ball
|
r17956 | // reject input where NaN or lower > upper | ||
if (isNaN(values[0]) || | ||||
isNaN(values[1]) || | ||||
(values[0] > values[1])) { | ||||
this.$readout.text(this.model.get('value').join('-')); | ||||
} else { | ||||
// clamp to range | ||||
values = [Math.max(Math.min(values[0], vmax), vmin), | ||||
Math.max(Math.min(values[1], vmax), vmin)]; | ||||
if ((values[0] != this.model.get('value')[0]) || | ||||
(values[1] != this.model.get('value')[1])) { | ||||
this.$readout.text(values.join('-')); | ||||
this.model.set('value', values, {updated_view: this}); | ||||
this.touch(); | ||||
} else { | ||||
this.$readout.text(this.model.get('value').join('-')); | ||||
} | ||||
} | ||||
} else { | ||||
this.$readout.text(this.model.get('value').join('-')); | ||||
} | ||||
Gordon Ball
|
r17952 | } else { | ||
Gordon Ball
|
r17956 | // single value case | ||
Gordon Ball
|
r17957 | var value = this._parse_value(text); | ||
Gordon Ball
|
r17956 | if (isNaN(value)) { | ||
this.$readout.text(this.model.get('value')); | ||||
} else { | ||||
value = Math.max(Math.min(value, vmax), vmin); | ||||
if (value != this.model.get('value')) { | ||||
this.$readout.text(value); | ||||
this.model.set('value', value, {updated_view: this}); | ||||
this.touch(); | ||||
} else { | ||||
this.$readout.text(this.model.get('value')); | ||||
} | ||||
} | ||||
Gordon Ball
|
r17952 | } | ||
}, | ||||
Gordon Ball
|
r17957 | _parse_value: parseInt, | ||
Gordon Ball
|
r17956 | |||
_range_regex: /^\s*([+-]?\d+)\s*[-:]\s*([+-]?\d+)/, | ||||
Gordon Ball
|
r17952 | |||
Jonathan Frederic
|
r14672 | handleSliderChange: function(e, ui) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* Called when the slider value is changed. | ||||
* | ||||
* Calling model.set will trigger all of the other views of the | ||||
* model to update. | ||||
*/ | ||||
Gordon Ball
|
r17703 | if (this.model.get("_range")) { | ||
Gordon Ball
|
r17059 | var actual_value = ui.values.map(this._validate_slide_value); | ||
this.$readout.text(actual_value.join("-")); | ||||
} else { | ||||
var actual_value = this._validate_slide_value(ui.value); | ||||
this.$readout.text(actual_value); | ||||
} | ||||
Brian E. Granger
|
r14982 | this.model.set('value', actual_value, {updated_view: this}); | ||
Jonathan Frederic
|
r14672 | this.touch(); | ||
}, | ||||
Jonathan Frederic
|
r14694 | |||
_validate_slide_value: function(x) { | ||||
Jonathan Frederic
|
r19176 | /** | ||
* Validate the value of the slider before sending it to the back-end | ||||
* and applying it to the other views on the page. | ||||
* | ||||
* Double bit-wise not truncates the decimel (int cast). | ||||
*/ | ||||
Jonathan Frederic
|
r14694 | return ~~x; | ||
}, | ||||
Jonathan Frederic
|
r14672 | }); | ||
Jonathan Frederic
|
r17198 | var IntTextView = widget.DOMWidgetView.extend({ | ||
Jonathan Frederic
|
r14672 | render : function(){ | ||
Jonathan Frederic
|
r19176 | /** | ||
* Called when view is rendered. | ||||
*/ | ||||
Jonathan Frederic
|
r14672 | this.$el | ||
Jason Grout
|
r18120 | .addClass('widget-hbox widget-text'); | ||
Jonathan Frederic
|
r14672 | this.$label = $('<div />') | ||
.appendTo(this.$el) | ||||
Jonathan Frederic
|
r17929 | .addClass('widget-label') | ||
Jonathan Frederic
|
r14672 | .hide(); | ||
this.$textbox = $('<input type="text" />') | ||||
Jonathan Frederic
|
r16952 | .addClass('form-control') | ||
Jonathan Frederic
|
r14672 | .addClass('widget-numeric-text') | ||
.appendTo(this.$el); | ||||
this.update(); // Set defaults. | ||||
}, | ||||
update : function(options){ | ||||
Jonathan Frederic
|
r19176 | /** | ||
* Update the contents of this view | ||||
* | ||||
* Called when the model is changed. The model may have been | ||||
* changed by another view or by a state update from the back-end. | ||||
*/ | ||||
Jonathan Frederic
|
r14672 | if (options === undefined || options.updated_view != this) { | ||
var value = this.model.get('value'); | ||||
Jonathan Frederic
|
r14694 | if (this._parse_value(this.$textbox.val()) != value) { | ||
Jonathan Frederic
|
r14672 | this.$textbox.val(value); | ||
} | ||||
if (this.model.get('disabled')) { | ||||
this.$textbox.attr('disabled','disabled'); | ||||
} else { | ||||
this.$textbox.removeAttr('disabled'); | ||||
} | ||||
var description = this.model.get('description'); | ||||
if (description.length === 0) { | ||||
this.$label.hide(); | ||||
} else { | ||||
Nicholas Bollweg (Nick)
|
r19196 | this.typeset(this.$label, description); | ||
Jonathan Frederic
|
r14672 | this.$label.show(); | ||
} | ||||
} | ||||
return IntTextView.__super__.update.apply(this); | ||||
}, | ||||
Jonathan Frederic
|
r17723 | update_attr: function(name, value) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* Set a css attr of the widget view. | ||||
*/ | ||||
Jonathan Frederic
|
r17723 | this.$textbox.css(name, value); | ||
}, | ||||
Jonathan Frederic
|
r14672 | events: { | ||
// Dictionary of events and their handlers. | ||||
"keyup input" : "handleChanging", | ||||
"paste input" : "handleChanging", | ||||
"cut input" : "handleChanging", | ||||
// Fires only when control is validated or looses focus. | ||||
"change input" : "handleChanged" | ||||
}, | ||||
handleChanging: function(e) { | ||||
Jonathan Frederic
|
r19176 | /** | ||
* Handles and validates user input. | ||||
* | ||||
* Try to parse value as a int. | ||||
*/ | ||||
Jonathan Frederic
|
r14672 | var numericalValue = 0; | ||
Jonathan Frederic
|
r18877 | var trimmed = e.target.value.trim(); | ||
if (trimmed === '') { | ||||
return; | ||||
} else { | ||||
jon
|
r16019 | if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) { | ||
numericalValue = this._parse_value(e.target.value); | ||||
} | ||||
Jonathan Frederic
|
r14672 | } | ||
// If parse failed, reset value to value stored in model. | ||||
if (isNaN(numericalValue)) { | ||||
e.target.value = this.model.get('value'); | ||||
} else if (!isNaN(numericalValue)) { | ||||
if (this.model.get('max') !== undefined) { | ||||
numericalValue = Math.min(this.model.get('max'), numericalValue); | ||||
} | ||||
if (this.model.get('min') !== undefined) { | ||||
numericalValue = Math.max(this.model.get('min'), numericalValue); | ||||
} | ||||
// Apply the value if it has changed. | ||||
if (numericalValue != this.model.get('value')) { | ||||
// Calling model.set will trigger all of the other views of the | ||||
// model to update. | ||||
this.model.set('value', numericalValue, {updated_view: this}); | ||||
this.touch(); | ||||
} | ||||
} | ||||
}, | ||||
MinRK
|
r14792 | handleChanged: function(e) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* Applies validated input. | ||||
*/ | ||||
Jonathan Frederic
|
r18877 | if (e.target.value.trim() === '' || e.target.value !== this.model.get('value')) { | ||
Jonathan Frederic
|
r14672 | e.target.value = this.model.get('value'); | ||
} | ||||
Jonathan Frederic
|
r14694 | }, | ||
Gordon Ball
|
r17957 | _parse_value: parseInt | ||
Jonathan Frederic
|
r14672 | }); | ||
Jonathan Frederic
|
r14694 | |||
Jonathan Frederic
|
r17198 | var ProgressView = widget.DOMWidgetView.extend({ | ||
Jonathan Frederic
|
r14694 | render : function(){ | ||
Jonathan Frederic
|
r19176 | /** | ||
* Called when view is rendered. | ||||
*/ | ||||
Jonathan Frederic
|
r14694 | this.$el | ||
Jason Grout
|
r18120 | .addClass('widget-hbox widget-progress'); | ||
Jonathan Frederic
|
r14694 | this.$label = $('<div />') | ||
.appendTo(this.$el) | ||||
Jonathan Frederic
|
r17929 | .addClass('widget-label') | ||
Jonathan Frederic
|
r14694 | .hide(); | ||
this.$progress = $('<div />') | ||||
.addClass('progress') | ||||
.addClass('widget-progress') | ||||
.appendTo(this.$el); | ||||
this.$bar = $('<div />') | ||||
Jonathan Frederic
|
r16913 | .addClass('progress-bar') | ||
Jonathan Frederic
|
r14694 | .css('width', '50%') | ||
.appendTo(this.$progress); | ||||
this.update(); // Set defaults. | ||||
Jonathan Frederic
|
r17729 | |||
this.model.on('change:bar_style', function(model, value) { | ||||
this.update_bar_style(); | ||||
}, this); | ||||
this.update_bar_style(''); | ||||
Jonathan Frederic
|
r14694 | }, | ||
update : function(){ | ||||
Jonathan Frederic
|
r19176 | /** | ||
* Update the contents of this view | ||||
* | ||||
* Called when the model is changed. The model may have been | ||||
* changed by another view or by a state update from the back-end. | ||||
*/ | ||||
Jonathan Frederic
|
r14694 | var value = this.model.get('value'); | ||
var max = this.model.get('max'); | ||||
var min = this.model.get('min'); | ||||
var percent = 100.0 * (value - min) / (max - min); | ||||
this.$bar.css('width', percent + '%'); | ||||
var description = this.model.get('description'); | ||||
if (description.length === 0) { | ||||
this.$label.hide(); | ||||
} else { | ||||
Nicholas Bollweg (Nick)
|
r19196 | this.typeset(this.$label, description); | ||
Jonathan Frederic
|
r14694 | this.$label.show(); | ||
} | ||||
return ProgressView.__super__.update.apply(this); | ||||
}, | ||||
Jonathan Frederic
|
r17723 | |||
Jonathan Frederic
|
r17729 | update_bar_style: function(previous_trait_value) { | ||
var class_map = { | ||||
success: ['progress-bar-success'], | ||||
info: ['progress-bar-info'], | ||||
warning: ['progress-bar-warning'], | ||||
danger: ['progress-bar-danger'] | ||||
}; | ||||
this.update_mapped_classes(class_map, 'bar_style', previous_trait_value, this.$bar); | ||||
}, | ||||
Jonathan Frederic
|
r17723 | update_attr: function(name, value) { | ||
Jonathan Frederic
|
r19176 | /** | ||
* Set a css attr of the widget view. | ||||
*/ | ||||
Jonathan Frederic
|
r17728 | if (name.substring(0, 6) == 'border' || name == 'width' || | ||
name == 'height' || name == 'background' || name == 'margin' || | ||||
name == 'padding') { | ||||
Jonathan Frederic
|
r17723 | this.$progress.css(name, value); | ||
} else if (name == 'color') { | ||||
this.$bar.css('background', value); | ||||
} else { | ||||
this.$bar.css(name, value); | ||||
} | ||||
}, | ||||
Jonathan Frederic
|
r14694 | }); | ||
Jonathan Frederic
|
r17198 | return { | ||
'IntSliderView': IntSliderView, | ||||
'IntTextView': IntTextView, | ||||
'ProgressView': ProgressView, | ||||
}; | ||||
Jonathan Frederic
|
r14672 | }); | ||