aboutsummaryrefslogtreecommitdiffstats
path: root/ui/jquery.ui.spinner.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/jquery.ui.spinner.js')
-rw-r--r--ui/jquery.ui.spinner.js306
1 files changed, 175 insertions, 131 deletions
diff --git a/ui/jquery.ui.spinner.js b/ui/jquery.ui.spinner.js
index 9f4a6b921..5623722db 100644
--- a/ui/jquery.ui.spinner.js
+++ b/ui/jquery.ui.spinner.js
@@ -10,15 +10,16 @@
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
+ * jquery.ui.button.js
*/
(function( $ ) {
function modifier( fn ) {
return function() {
- var previous = this.options.value;
+ var previous = this.element.val();
fn.apply( this, arguments );
this._refresh();
- if ( previous !== this.options.value ) {
+ if ( previous !== this.element.val() ) {
this._trigger( "change" );
}
};
@@ -29,13 +30,13 @@ $.widget( "ui.spinner", {
defaultElement: "<input>",
widgetEventPrefix: "spin",
options: {
+ culture: null,
incremental: true,
- max: Number.MAX_VALUE,
- min: -Number.MAX_VALUE,
+ max: null,
+ min: null,
numberFormat: null,
page: 10,
step: 1,
- value: 0,
change: null,
spin: null,
@@ -44,17 +45,26 @@ $.widget( "ui.spinner", {
},
_create: function() {
- this._value( this.options.value );
+ this._value( this.element.val(), true );
this._draw();
- this._mousewheel();
+ this._bind( this._events );
this._refresh();
+
+ // turning off autocomplete prevents the browser from remembering the
+ // value when navigating through history, so we re-enable autocomplete
+ // if the page is unloaded before the widget is destroyed. #7790
+ this._bind( this.window, {
+ beforeunload: function() {
+ this.element.removeAttr( "autocomplete" );
+ }
+ });
},
_getCreateOptions: function() {
var options = {},
element = this.element;
- $.each( [ "min", "max", "step", "value" ], function( i, option ) {
+ $.each( [ "min", "max", "step" ], function( i, option ) {
var value = element.attr( option );
if ( value !== undefined && value.length ) {
options[ option ] = value;
@@ -64,6 +74,72 @@ $.widget( "ui.spinner", {
return options;
},
+ _events: {
+ keydown: function( event ) {
+ if ( this._start( event ) && this._keydown( event ) ) {
+ event.preventDefault();
+ }
+ },
+ keyup: "_stop",
+ focus: function() {
+ this.uiSpinner.addClass( "ui-state-active" );
+ this.previous = this.element.val();
+ },
+ blur: function( event ) {
+ this._refresh();
+ this.uiSpinner.removeClass( "ui-state-active" );
+ if ( this.previous !== this.element.val() ) {
+ this._trigger( "change", event );
+ }
+ },
+ mousewheel: function( event, delta ) {
+ if ( !delta ) {
+ return;
+ }
+ if ( !this.spinning && !this._start( event ) ) {
+ return false;
+ }
+
+ this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
+ clearTimeout( this.mousewheelTimer );
+ this.mousewheelTimer = this._delay(function() {
+ if ( this.spinning ) {
+ this._stop( event );
+ }
+ }, 100 );
+ event.preventDefault();
+ },
+ "mousedown .ui-spinner-button": function( event ) {
+ // ensure focus is on (or stays on) the text field
+ event.preventDefault();
+ if ( this.document[0].activeElement !== this.element[ 0 ] ) {
+ this.element.focus();
+ }
+
+ if ( this._start( event ) === false ) {
+ return;
+ }
+
+ this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+ },
+ "mouseup .ui-spinner-button": "_stop",
+ "mouseenter .ui-spinner-button": function( event ) {
+ // button will add ui-state-active if mouse was down while mouseleave and kept down
+ if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
+ return;
+ }
+
+ if ( this._start( event ) === false ) {
+ return false;
+ }
+ this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+ },
+ // TODO: do we really want to consider this a stop?
+ // shouldn't we just stop the repeater and wait until mouseup before
+ // we trigger the stop event?
+ "mouseleave .ui-spinner-button": "_stop"
+ },
+
_draw: function() {
var uiSpinner = this.uiSpinner = this.element
.addClass( "ui-spinner-input" )
@@ -75,69 +151,19 @@ $.widget( "ui.spinner", {
this._hoverable( uiSpinner );
this.element.attr( "role", "spinbutton" );
- this._bind({
- keydown: function( event ) {
- if ( this._start( event ) && this._keydown( event ) ) {
- event.preventDefault();
- }
- },
- keyup: "_stop",
- focus: function() {
- uiSpinner.addClass( "ui-state-active" );
- this.previous = this.element.val();
- },
- blur: function( event ) {
- // don't clear invalid values on blur
- var value = this.element.val();
- this._value( value );
- if ( this.element.val() === "" ) {
- this.element.val( value );
- }
- uiSpinner.removeClass( "ui-state-active" );
- // TODO: what should trigger change?
- // element.val() or options.value?
- if ( this.previous !== this.element.val() ) {
- this._trigger( "change", event );
- }
- }
- });
// button bindings
this.buttons = uiSpinner.find( ".ui-spinner-button" )
.attr( "tabIndex", -1 )
.button()
.removeClass( "ui-corner-all" );
- this._bind( this.buttons, {
- mousedown: function( event ) {
- // ensure focus is on (or stays on) the text field
- event.preventDefault();
- if ( document.activeElement !== this.element[ 0 ] ) {
- this.element.focus();
- }
-
- if ( this._start( event ) === false ) {
- return;
- }
- this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
- },
- mouseup: "_stop",
- mouseenter: function( event ) {
- // button will add ui-state-active if mouse was down while mouseleave and kept down
- if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
- return;
- }
-
- if ( this._start( event ) === false ) {
- return false;
- }
- this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
- },
- // TODO: do we really want to consider this a stop?
- // shouldn't we just stop the repeater and wait until mouseup before
- // we trigger the stop event?
- mouseleave: "_stop"
- });
+ // IE 6 doesn't understand height: 50% for the buttons
+ // unless the wrapper has an explicit height
+ if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
+ uiSpinner.height() > 0 ) {
+ uiSpinner.height( uiSpinner.height() );
+ }
// disable spinner if element was already disabled
if ( this.options.disabled ) {
@@ -162,39 +188,11 @@ $.widget( "ui.spinner", {
case keyCode.PAGE_DOWN:
this._repeat( null, -options.page, event );
return true;
- case keyCode.ENTER:
- this._value( this.element.val() );
}
return false;
},
- _mousewheel: function() {
- // need the delta normalization that mousewheel plugin provides
- if ( !$.fn.mousewheel ) {
- return;
- }
- this._bind({
- mousewheel: function( event, delta ) {
- if ( !delta ) {
- return;
- }
- if ( !this.spinning && !this._start( event ) ) {
- return false;
- }
-
- this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
- clearTimeout( this.mousewheelTimer );
- this.mousewheelTimer = setTimeout(function() {
- if ( this.spinning ) {
- this._stop( event );
- }
- }, 100 );
- event.preventDefault();
- }
- });
- },
-
_uiSpinnerHtml: function() {
return "<span class='ui-spinner ui-state-default ui-widget ui-widget-content ui-corner-all'></span>";
},
@@ -222,47 +220,78 @@ $.widget( "ui.spinner", {
},
_repeat: function( i, steps, event ) {
- var that = this;
i = i || 500;
clearTimeout( this.timer );
- this.timer = setTimeout(function() {
- that._repeat( 40, steps, event );
+ this.timer = this._delay(function() {
+ this._repeat( 40, steps, event );
}, i );
this._spin( steps * this.options.step, event );
},
_spin: function( step, event ) {
+ var value = this.value() || 0;
+
if ( !this.counter ) {
this.counter = 1;
}
- var newVal = this.value() + step * this._increment( this.counter );
+ value = this._adjustValue( value + step * this._increment( this.counter ) );
- // clamp the new value
- newVal = this._trimValue( newVal );
-
- if ( !this.spinning || this._trigger( "spin", event, { value: newVal } ) !== false) {
- this._value( newVal );
+ if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
+ this._value( value );
this.counter++;
}
},
_increment: function( i ) {
- return this.options.incremental ?
- Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 ) :
- 1;
+ var incremental = this.options.incremental;
+
+ if ( incremental ) {
+ return $.isFunction( incremental ) ?
+ incremental( i ) :
+ Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 );
+ }
+
+ return 1;
},
- _trimValue: function( value ) {
- var options = this.options;
+ _precision: function() {
+ var precision = this._precisionOf( this.options.step );
+ if ( this.options.min !== null ) {
+ precision = Math.max( precision, this._precisionOf( this.options.min ) );
+ }
+ return precision;
+ },
+
+ _precisionOf: function( num ) {
+ var str = num.toString(),
+ decimal = str.indexOf( "." );
+ return decimal === -1 ? 0 : str.length - decimal - 1;
+ },
- if ( value > options.max) {
+ _adjustValue: function( value ) {
+ var base, aboveMin,
+ options = this.options;
+
+ // make sure we're at a valid step
+ // - find out where we are relative to the base (min or 0)
+ base = options.min !== null ? options.min : 0;
+ aboveMin = value - base;
+ // - round to the nearest step
+ aboveMin = Math.round(aboveMin / options.step) * options.step;
+ // - rounding is based on 0, so adjust back to our base
+ value = base + aboveMin;
+
+ // fix precision from bad JS floating point math
+ value = parseFloat( value.toFixed( this._precision() ) );
+
+ // clamp the value
+ if ( options.max !== null && value > options.max) {
return options.max;
}
-
- if ( value < options.min ) {
+ if ( options.min !== null && value < options.min ) {
return options.min;
}
@@ -282,8 +311,11 @@ $.widget( "ui.spinner", {
},
_setOption: function( key, value ) {
- if ( key === "value" ) {
- return this._value( value );
+ if ( key === "culture" || key === "numberFormat" ) {
+ var prevValue = this._parse( this.element.val() );
+ this.options[ key ] = value;
+ this.element.val( this._format( prevValue ) );
+ return;
}
this._super( "_setOption", key, value );
@@ -301,36 +333,48 @@ $.widget( "ui.spinner", {
_setOptions: modifier(function( options ) {
this._super( "_setOptions", options );
-
- // handle any options that might cause value to change, e.g., min
- this._value( this._trimValue( this.options.value ) );
+ this._value( this.element.val() );
}),
_parse: function( val ) {
- if ( typeof val === "string" ) {
- val = window.Globalize && this.options.numberFormat ? Globalize.parseFloat( val ) : +val;
+ if ( typeof val === "string" && val !== "" ) {
+ val = window.Globalize && this.options.numberFormat ?
+ Globalize.parseFloat( val, 10, this.options.culture ) : +val;
}
- return isNaN( val ) ? null : val;
+ return val === "" || isNaN( val ) ? null : val;
},
- _format: function() {
- var num = this.options.value;
- return window.Globalize && this.options.numberFormat ? Globalize.format( num, this.options.numberFormat ) : num;
+ _format: function( value ) {
+ if ( value === "" ) {
+ return "";
+ }
+ return window.Globalize && this.options.numberFormat ?
+ Globalize.format( value, this.options.numberFormat, this.options.culture ) :
+ value;
},
_refresh: function() {
- this.element
- .val( this._format() )
- .attr({
- "aria-valuemin": this.options.min,
- "aria-valuemax": this.options.max,
- "aria-valuenow": this.options.value
- });
+ this.element.attr({
+ "aria-valuemin": this.options.min,
+ "aria-valuemax": this.options.max,
+ // TODO: what should we do with values that can't be parsed?
+ "aria-valuenow": this._parse( this.element.val() )
+ });
},
// update the value without triggering change
- _value: function( value ) {
- this.options.value = this._trimValue( this._parse(value) );
+ _value: function( value, allowAny ) {
+ var parsed;
+ if ( value !== "" ) {
+ parsed = this._parse( value );
+ if ( parsed !== null ) {
+ if ( !allowAny ) {
+ parsed = this._adjustValue( parsed );
+ }
+ value = this._format( parsed );
+ }
+ }
+ this.element.val( value );
this._refresh();
},
@@ -371,9 +415,9 @@ $.widget( "ui.spinner", {
value: function( newVal ) {
if ( !arguments.length ) {
- return this.options.value;
+ return this._parse( this.element.val() );
}
- this.option( "value", newVal );
+ modifier( this._value ).call( this, newVal );
},
widget: function() {