Skip to content

Instantly share code, notes, and snippets.

@chronitis
Created August 7, 2014 13:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chronitis/5bb76f913aa91eebc177 to your computer and use it in GitHub Desktop.
Save chronitis/5bb76f913aa91eebc177 to your computer and use it in GitHub Desktop.
Allow text input on slider widgets (#6106) - alternative widget_int.js
// 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,
};
});
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