diff options
Diffstat (limited to 'ui/jquery.ui.spinner.js')
-rw-r--r-- | ui/jquery.ui.spinner.js | 306 |
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() { |