-
-
Save chronitis/5bb76f913aa91eebc177 to your computer and use it in GitHub Desktop.
Allow text input on slider widgets (#6106) - alternative widget_int.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) IPython Development Team. | |
// Distributed under the terms of the Modified BSD License. | |
define([ | |
"widgets/js/widget", | |
"jqueryui", | |
"bootstrap", | |
], function(widget, $){ | |
//since both IntTextView and IntSliderView (with editable label) require | |
//very similar logic | |
var validateTextAndUpdate = function(text, that) { | |
var numericalValue = 0; | |
if (text !== '') { | |
var trimmed = text.trim(); | |
if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) { | |
numericalValue = that._parse_value(text); | |
} | |
} | |
if (!isNaN(numericalValue)) { | |
var maxv = that.model.get('max'); | |
var minv = that.model.get('min'); | |
if (maxv !== undefined) { | |
numericalValue = Math.min(maxv, numericalValue); | |
} | |
if (minv !== undefined) { | |
numericalValue = Math.max(minv, numericalValue); | |
} | |
// Apply the value if it has changed. | |
if (numericalValue != that.model.get('value')) { | |
// Calling model.set will trigger all of the other views of the | |
// model to update. | |
that.model.set('value', numericalValue, {updated_view: that}); | |
that.touch(); | |
} | |
} | |
}; | |
var IntSliderView = widget.DOMWidgetView.extend({ | |
render : function(){ | |
// Called when view is rendered. | |
this.$el | |
.addClass('widget-hbox-single'); | |
this.$label = $('<div />') | |
.appendTo(this.$el) | |
.addClass('widget-hlabel') | |
.hide(); | |
this.$slider = $('<div />') | |
.slider({}) | |
.addClass('slider'); | |
// Put the slider in a container | |
this.$slider_container = $('<div />') | |
.addClass('widget-hslider') | |
.append(this.$slider); | |
this.$el.append(this.$slider_container); | |
this.$readout = $('<div/>') | |
.appendTo(this.$el) | |
.addClass('widget-hreadout') | |
.attr('contentEditable', true) | |
.hide(); | |
// Set defaults. | |
this.update(); | |
}, | |
update : function(options){ | |
// 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. | |
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. | |
var jquery_slider_keys = ['step', 'max', 'min', 'disabled']; | |
var that = this; | |
that.$slider.slider({}); | |
_.each(jquery_slider_keys, function(key, i) { | |
var model_value = that.model.get(key); | |
if (model_value !== undefined) { | |
that.$slider.slider("option", key, model_value); | |
} | |
}); | |
// 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'); | |
var value = this.model.get('min'); | |
this.$slider.slider('option', 'value', value); | |
this.$slider.slider('option', 'orientation', orientation); | |
value = this.model.get('value'); | |
this.$slider.slider('option', 'value', value); | |
this.$readout.text(value); | |
// Use the right CSS classes for vertical & horizontal sliders | |
if (orientation=='vertical') { | |
this.$slider_container | |
.removeClass('widget-hslider') | |
.addClass('widget-vslider'); | |
this.$el | |
.removeClass('widget-hbox-single') | |
.addClass('widget-vbox-single'); | |
this.$label | |
.removeClass('widget-hlabel') | |
.addClass('widget-vlabel'); | |
this.$readout | |
.removeClass('widget-hreadout') | |
.addClass('widget-vreadout'); | |
} else { | |
this.$slider_container | |
.removeClass('widget-vslider') | |
.addClass('widget-hslider'); | |
this.$el | |
.removeClass('widget-vbox-single') | |
.addClass('widget-hbox-single'); | |
this.$label | |
.removeClass('widget-vlabel') | |
.addClass('widget-hlabel'); | |
this.$readout | |
.removeClass('widget-vreadout') | |
.addClass('widget-hreadout'); | |
} | |
var description = this.model.get('description'); | |
if (description.length === 0) { | |
this.$label.hide(); | |
} else { | |
this.$label.text(description); | |
MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); | |
this.$label.show(); | |
} | |
var readout = this.model.get('readout'); | |
if (readout) { | |
this.$readout.show(); | |
} else { | |
this.$readout.hide(); | |
} | |
} | |
return IntSliderView.__super__.update.apply(this); | |
}, | |
events: { | |
// Dictionary of events and their handlers. | |
"slide" : "handleSliderChange", | |
"blur [contentEditable=true]": "handleTextChange", | |
"keydown [contentEditable=true]": "handleKeyDown" | |
}, | |
handleKeyDown: function(e) { | |
if (e.keyCode == 13) { | |
e.preventDefault(); | |
this.handleTextChange(); | |
} | |
}, | |
handleTextChange: function() { | |
validateTextAndUpdate(this.$readout.text(), this); | |
this.$readout.text(this.model.get('value')); | |
}, | |
handleSliderChange: function(e, ui) { | |
// Called when the slider value is changed. | |
// Calling model.set will trigger all of the other views of the | |
// model to update. | |
var actual_value = this._validate_slide_value(ui.value); | |
this.model.set('value', actual_value, {updated_view: this}); | |
this.$readout.text(actual_value); | |
this.touch(); | |
}, | |
_parse_value: function(x) { | |
return parseInt(x); | |
}, | |
_validate_slide_value: function(x) { | |
// 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). | |
return ~~x; | |
}, | |
}); | |
var IntTextView = widget.DOMWidgetView.extend({ | |
render : function(){ | |
// Called when view is rendered. | |
this.$el | |
.addClass('widget-hbox-single'); | |
this.$label = $('<div />') | |
.appendTo(this.$el) | |
.addClass('widget-hlabel') | |
.hide(); | |
this.$textbox = $('<input type="text" />') | |
.addClass('form-control') | |
.addClass('widget-numeric-text') | |
.appendTo(this.$el); | |
this.update(); // Set defaults. | |
}, | |
update : function(options){ | |
// 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. | |
if (options === undefined || options.updated_view != this) { | |
var value = this.model.get('value'); | |
if (this._parse_value(this.$textbox.val()) != value) { | |
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 { | |
this.$label.text(description); | |
MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); | |
this.$label.show(); | |
} | |
} | |
return IntTextView.__super__.update.apply(this); | |
}, | |
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) { | |
validateTextAndUpdate(e.target.value, this); | |
}, | |
handleChanged: function(e) { | |
// Applies validated input. | |
if (this.model.get('value') != e.target.value) { | |
e.target.value = this.model.get('value'); | |
} | |
}, | |
_parse_value: function(value) { | |
// Parse the value stored in a string. | |
return parseInt(value); | |
}, | |
}); | |
var ProgressView = widget.DOMWidgetView.extend({ | |
render : function(){ | |
// Called when view is rendered. | |
this.$el | |
.addClass('widget-hbox-single'); | |
this.$label = $('<div />') | |
.appendTo(this.$el) | |
.addClass('widget-hlabel') | |
.hide(); | |
this.$progress = $('<div />') | |
.addClass('progress') | |
.addClass('widget-progress') | |
.appendTo(this.$el); | |
this.$bar = $('<div />') | |
.addClass('progress-bar') | |
.css('width', '50%') | |
.appendTo(this.$progress); | |
this.update(); // Set defaults. | |
}, | |
update : function(){ | |
// 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. | |
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 { | |
this.$label.text(description); | |
MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); | |
this.$label.show(); | |
} | |
return ProgressView.__super__.update.apply(this); | |
}, | |
}); | |
return { | |
'IntSliderView': IntSliderView, | |
'IntTextView': IntTextView, | |
'ProgressView': ProgressView, | |
}; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js | |
index 8edbd59..e9f5a59 100644 | |
--- a/IPython/html/static/widgets/js/widget_int.js | |
+++ b/IPython/html/static/widgets/js/widget_int.js | |
@@ -7,6 +7,38 @@ define([ | |
"bootstrap", | |
], function(widget, $){ | |
+ //since both IntTextView and IntSliderView (with editable label) require | |
+ //very similar logic | |
+ var validateTextAndUpdate = function(text, that) { | |
+ var numericalValue = 0; | |
+ if (text !== '') { | |
+ var trimmed = text.trim(); | |
+ if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) { | |
+ numericalValue = that._parse_value(text); | |
+ } | |
+ } | |
+ if (!isNaN(numericalValue)) { | |
+ var maxv = that.model.get('max'); | |
+ var minv = that.model.get('min'); | |
+ if (maxv !== undefined) { | |
+ numericalValue = Math.min(maxv, numericalValue); | |
+ } | |
+ if (minv !== undefined) { | |
+ numericalValue = Math.max(minv, numericalValue); | |
+ } | |
+ | |
+ // Apply the value if it has changed. | |
+ if (numericalValue != that.model.get('value')) { | |
+ | |
+ // Calling model.set will trigger all of the other views of the | |
+ // model to update. | |
+ that.model.set('value', numericalValue, {updated_view: that}); | |
+ that.touch(); | |
+ } | |
+ } | |
+ | |
+ }; | |
+ | |
var IntSliderView = widget.DOMWidgetView.extend({ | |
render : function(){ | |
// Called when view is rendered. | |
@@ -134,33 +166,8 @@ define([ | |
}, | |
handleTextChange: function() { | |
- var text = this.$readout.text(); | |
- var value = this._validate_text_input(text); | |
- if (isNaN(value)) { | |
- this.$readout.text(this.model.get('value')); | |
- } else { | |
- //check for outside range | |
- if (value > this.model.get('max')) value = this.model.get('max'); | |
- if (value < this.model.get('min')) value = this.model.get('min'); | |
- | |
- //update the readout unconditionally | |
- //this covers eg, entering a float value which rounds to the | |
- //existing int value, which will not trigger an update since the model | |
- //doesn't change, but we should update the text to reflect that | |
- //a float value isn't being used | |
- this.$readout.text(value); | |
- | |
- //note that the step size currently isn't enforced, so if an | |
- //off-step value is input it will be retained | |
- | |
- //update the model | |
- this.model.set('value', value, {updated_view: this}); | |
- this.touch(); | |
- } | |
- }, | |
- | |
- _validate_text_input: function(x) { | |
- return parseInt(x); | |
+ validateTextAndUpdate(this.$readout.text(), this); | |
+ this.$readout.text(this.model.get('value')); | |
}, | |
handleSliderChange: function(e, ui) { | |
@@ -174,6 +181,10 @@ define([ | |
this.touch(); | |
}, | |
+ _parse_value: function(x) { | |
+ return parseInt(x); | |
+ }, | |
+ | |
_validate_slide_value: function(x) { | |
// Validate the value of the slider before sending it to the back-end | |
// and applying it to the other views on the page. | |
@@ -239,38 +250,8 @@ define([ | |
"change input" : "handleChanged" | |
}, | |
- handleChanging: function(e) { | |
- // Handles and validates user input. | |
- | |
- // Try to parse value as a int. | |
- var numericalValue = 0; | |
- if (e.target.value !== '') { | |
- var trimmed = e.target.value.trim(); | |
- if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) { | |
- numericalValue = this._parse_value(e.target.value); | |
- } | |
- } | |
- | |
- // 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(); | |
- } | |
- } | |
+ handleChanging: function(e) { | |
+ validateTextAndUpdate(e.target.value, this); | |
}, | |
handleChanged: function(e) { |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment