/* * jQuery UI Spinner @VERSION * * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Spinner * * Depends: * jquery.ui.core.js * jquery.ui.widget.js */ (function( $ ) { function modifier( fn ) { return function() { var previous = this.element.val(); fn.apply( this, arguments ); this._refresh(); if ( previous !== this.element.val() ) { this._trigger( "change" ); } }; } $.widget( "ui.spinner", { version: "@VERSION", defaultElement: "", widgetEventPrefix: "spin", options: { incremental: true, max: null, min: null, numberFormat: null, page: 10, step: 1, change: null, spin: null, start: null, stop: null }, _create: function() { this._value( this.element.val(), true ); this._draw(); this._bind( this._events ); this._refresh(); }, _getCreateOptions: function() { var options = {}, element = this.element; $.each( [ "min", "max", "step" ], function( i, option ) { var value = element.attr( option ); if ( value !== undefined && value.length ) { options[ option ] = value; } }); 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 = setTimeout(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 ( 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 .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" ) .attr( "autocomplete", "off" ) .wrap( this._uiSpinnerHtml() ) .parent() // add buttons .append( this._buttonHtml() ); this._hoverable( uiSpinner ); this.element.attr( "role", "spinbutton" ); // button bindings this.buttons = uiSpinner.find( ".ui-spinner-button" ) .attr( "tabIndex", -1 ) .button() .removeClass( "ui-corner-all" ); // disable spinner if element was already disabled if ( this.options.disabled ) { this.disable(); } }, _keydown: function( event ) { var options = this.options, keyCode = $.ui.keyCode; switch ( event.keyCode ) { case keyCode.UP: this._repeat( null, 1, event ); return true; case keyCode.DOWN: this._repeat( null, -1, event ); return true; case keyCode.PAGE_UP: this._repeat( null, options.page, event ); return true; case keyCode.PAGE_DOWN: this._repeat( null, -options.page, event ); return true; } return false; }, _uiSpinnerHtml: function() { return ""; }, _buttonHtml: function() { return "" + "" + "" + "" + "" + "" + ""; }, _start: function( event ) { if ( !this.spinning && this._trigger( "start", event ) === false ) { return false; } if ( !this.counter ) { this.counter = 1; } this.spinning = true; return true; }, _repeat: function( i, steps, event ) { var that = this; i = i || 500; clearTimeout( this.timer ); this.timer = setTimeout(function() { that._repeat( 40, steps, event ); }, i ); this._spin( steps * this.options.step, event ); }, _spin: function( step, event ) { if ( !this.counter ) { this.counter = 1; } var value = this.value(), newVal = value + step * this._increment( this.counter ), // fix precision from bad JS floating point math precision = Math.max( this._precision( value ), this._precision( this.options.step ) ); // clamp the new value newVal = this._trimValue( newVal.toFixed( precision ) ); if ( !this.spinning || this._trigger( "spin", event, { value: newVal } ) !== false) { this._value( newVal ); this.counter++; } }, _increment: function( i ) { return this.options.incremental ? Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 ) : 1; }, _precision: function( num ) { var str = num.toString(), decimal = str.indexOf( "." ); return decimal === -1 ? 0 : str.length - decimal - 1; }, _trimValue: function( value ) { var options = this.options; if ( options.max != null && value > options.max) { return options.max; } if ( options.min != null && value < options.min ) { return options.min; } return value; }, _stop: function( event ) { if ( !this.spinning ) { return; } clearTimeout( this.timer ); clearTimeout( this.mousewheelTimer ); this.counter = 0; this.spinning = false; this._trigger( "stop", event ); }, _setOption: function( key, value ) { this._super( "_setOption", key, value ); if ( key === "disabled" ) { if ( value ) { this.element.prop( "disabled", true ); this.buttons.button( "disable" ); } else { this.element.prop( "disabled", false ); this.buttons.button( "enable" ); } } }, _setOptions: modifier(function( options ) { this._super( "_setOptions", options ); this._value( this.element.val() ); }), _parse: function( val ) { if ( typeof val === "string" ) { val = window.Globalize && this.options.numberFormat ? Globalize.parseFloat( val ) : +val; } return isNaN( val ) ? null : val; }, _format: function( value ) { if ( value === "" ) { return ""; } return window.Globalize && this.options.numberFormat ? Globalize.format( value, this.options.numberFormat ) : value; }, _refresh: function() { 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, ignoreRange ) { var parsed; if ( value !== "" ) { parsed = this._parse( value ); if ( parsed !== null ) { if ( !ignoreRange ) { parsed = this._trimValue( parsed ); } value = this._format( parsed ); } } this.element.val( value ); this._refresh(); }, destroy: function() { this.element .removeClass( "ui-spinner-input" ) .prop( "disabled", false ) .removeAttr( "autocomplete" ) .removeAttr( "role" ) .removeAttr( "aria-valuemin" ) .removeAttr( "aria-valuemax" ) .removeAttr( "aria-valuenow" ); this._super( "destroy" ); this.uiSpinner.replaceWith( this.element ); }, stepUp: modifier(function( steps ) { this._stepUp( steps ); }), _stepUp: function( steps ) { this._spin( (steps || 1) * this.options.step ); }, stepDown: modifier(function( steps ) { this._stepDown( steps ); }), _stepDown: function( steps ) { this._spin( (steps || 1) * -this.options.step ); }, pageUp: modifier(function( pages ) { this._stepUp( (pages || 1) * this.options.page ); }), pageDown: modifier(function( pages ) { this._stepDown( (pages || 1) * this.options.page ); }), value: function( newVal ) { if ( !arguments.length ) { return this._parse( this.element.val() ); } modifier( this._value ).call( this, newVal ); }, widget: function() { return this.uiSpinner; } }); }( jQuery ) );