source: trunk/themes/default/js/ui/jquery.ui.spinner.js @ 18953

Last change on this file since 18953 was 18953, checked in by rvelices, 11 years ago

feature 2771 upgrade jquery ui - forgot to add non minimified files (not used anywhere in the code, but for the sake of coherence)

File size: 11.9 KB
Line 
1/*!
2 * jQuery UI Spinner 1.9.0
3 * http://jqueryui.com
4 *
5 * Copyright 2012 jQuery Foundation and other contributors
6 * Released under the MIT license.
7 * http://jquery.org/license
8 *
9 * http://api.jqueryui.com/spinner/
10 *
11 * Depends:
12 *  jquery.ui.core.js
13 *  jquery.ui.widget.js
14 *  jquery.ui.button.js
15 */
16(function( $ ) {
17
18function modifier( fn ) {
19        return function() {
20                var previous = this.element.val();
21                fn.apply( this, arguments );
22                this._refresh();
23                if ( previous !== this.element.val() ) {
24                        this._trigger( "change" );
25                }
26        };
27}
28
29$.widget( "ui.spinner", {
30        version: "1.9.0",
31        defaultElement: "<input>",
32        widgetEventPrefix: "spin",
33        options: {
34                culture: null,
35                icons: {
36                        down: "ui-icon-triangle-1-s",
37                        up: "ui-icon-triangle-1-n"
38                },
39                incremental: true,
40                max: null,
41                min: null,
42                numberFormat: null,
43                page: 10,
44                step: 1,
45
46                change: null,
47                spin: null,
48                start: null,
49                stop: null
50        },
51
52        _create: function() {
53                // handle string values that need to be parsed
54                this._setOption( "max", this.options.max );
55                this._setOption( "min", this.options.min );
56                this._setOption( "step", this.options.step );
57
58                // format the value, but don't constrain
59                this._value( this.element.val(), true );
60
61                this._draw();
62                this._on( this._events );
63                this._refresh();
64
65                // turning off autocomplete prevents the browser from remembering the
66                // value when navigating through history, so we re-enable autocomplete
67                // if the page is unloaded before the widget is destroyed. #7790
68                this._on( this.window, {
69                        beforeunload: function() {
70                                this.element.removeAttr( "autocomplete" );
71                        }
72                });
73        },
74
75        _getCreateOptions: function() {
76                var options = {},
77                        element = this.element;
78
79                $.each( [ "min", "max", "step" ], function( i, option ) {
80                        var value = element.attr( option );
81                        if ( value !== undefined && value.length ) {
82                                options[ option ] = value;
83                        }
84                });
85
86                return options;
87        },
88
89        _events: {
90                keydown: function( event ) {
91                        if ( this._start( event ) && this._keydown( event ) ) {
92                                event.preventDefault();
93                        }
94                },
95                keyup: "_stop",
96                focus: function() {
97                        this.uiSpinner.addClass( "ui-state-active" );
98                        this.previous = this.element.val();
99                },
100                blur: function( event ) {
101                        if ( this.cancelBlur ) {
102                                delete this.cancelBlur;
103                                return;
104                        }
105
106                        this._refresh();
107                        this.uiSpinner.removeClass( "ui-state-active" );
108                        if ( this.previous !== this.element.val() ) {
109                                this._trigger( "change", event );
110                        }
111                },
112                mousewheel: function( event, delta ) {
113                        if ( !delta ) {
114                                return;
115                        }
116                        if ( !this.spinning && !this._start( event ) ) {
117                                return false;
118                        }
119
120                        this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
121                        clearTimeout( this.mousewheelTimer );
122                        this.mousewheelTimer = this._delay(function() {
123                                if ( this.spinning ) {
124                                        this._stop( event );
125                                }
126                        }, 100 );
127                        event.preventDefault();
128                },
129                "mousedown .ui-spinner-button": function( event ) {
130                        var previous;
131
132                        // We never want the buttons to have focus; whenever the user is
133                        // interacting with the spinner, the focus should be on the input.
134                        // If the input is focused then this.previous is properly set from
135                        // when the input first received focus. If the input is not focused
136                        // then we need to set this.previous based on the value before spinning.
137                        previous = this.element[0] === this.document[0].activeElement ?
138                                this.previous : this.element.val();
139                        function checkFocus() {
140                                var isActive = this.element[0] === this.document[0].activeElement;
141                                if ( !isActive ) {
142                                        this.element.focus();
143                                        this.previous = previous;
144                                        // support: IE
145                                        // IE sets focus asynchronously, so we need to check if focus
146                                        // moved off of the input because the user clicked on the button.
147                                        this._delay(function() {
148                                                this.previous = previous;
149                                        });
150                                }
151                        }
152
153                        // ensure focus is on (or stays on) the text field
154                        event.preventDefault();
155                        checkFocus.call( this );
156
157                        // support: IE
158                        // IE doesn't prevent moving focus even with event.preventDefault()
159                        // so we set a flag to know when we should ignore the blur event
160                        // and check (again) if focus moved off of the input.
161                        this.cancelBlur = true;
162                        this._delay(function() {
163                                delete this.cancelBlur;
164                                checkFocus.call( this );
165                        });
166
167                        if ( this._start( event ) === false ) {
168                                return;
169                        }
170
171                        this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
172                },
173                "mouseup .ui-spinner-button": "_stop",
174                "mouseenter .ui-spinner-button": function( event ) {
175                        // button will add ui-state-active if mouse was down while mouseleave and kept down
176                        if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
177                                return;
178                        }
179
180                        if ( this._start( event ) === false ) {
181                                return false;
182                        }
183                        this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
184                },
185                // TODO: do we really want to consider this a stop?
186                // shouldn't we just stop the repeater and wait until mouseup before
187                // we trigger the stop event?
188                "mouseleave .ui-spinner-button": "_stop"
189        },
190
191        _draw: function() {
192                var uiSpinner = this.uiSpinner = this.element
193                        .addClass( "ui-spinner-input" )
194                        .attr( "autocomplete", "off" )
195                        .wrap( this._uiSpinnerHtml() )
196                        .parent()
197                                // add buttons
198                                .append( this._buttonHtml() );
199                this._hoverable( uiSpinner );
200
201                this.element.attr( "role", "spinbutton" );
202
203                // button bindings
204                this.buttons = uiSpinner.find( ".ui-spinner-button" )
205                        .attr( "tabIndex", -1 )
206                        .button()
207                        .removeClass( "ui-corner-all" );
208
209                // IE 6 doesn't understand height: 50% for the buttons
210                // unless the wrapper has an explicit height
211                if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
212                                uiSpinner.height() > 0 ) {
213                        uiSpinner.height( uiSpinner.height() );
214                }
215
216                // disable spinner if element was already disabled
217                if ( this.options.disabled ) {
218                        this.disable();
219                }
220        },
221
222        _keydown: function( event ) {
223                var options = this.options,
224                        keyCode = $.ui.keyCode;
225
226                switch ( event.keyCode ) {
227                case keyCode.UP:
228                        this._repeat( null, 1, event );
229                        return true;
230                case keyCode.DOWN:
231                        this._repeat( null, -1, event );
232                        return true;
233                case keyCode.PAGE_UP:
234                        this._repeat( null, options.page, event );
235                        return true;
236                case keyCode.PAGE_DOWN:
237                        this._repeat( null, -options.page, event );
238                        return true;
239                }
240
241                return false;
242        },
243
244        _uiSpinnerHtml: function() {
245                return "<span class='ui-spinner ui-state-default ui-widget ui-widget-content ui-corner-all'></span>";
246        },
247
248        _buttonHtml: function() {
249                return "" +
250                        "<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
251                                "<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
252                        "</a>" +
253                        "<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
254                                "<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
255                        "</a>";
256        },
257
258        _start: function( event ) {
259                if ( !this.spinning && this._trigger( "start", event ) === false ) {
260                        return false;
261                }
262
263                if ( !this.counter ) {
264                        this.counter = 1;
265                }
266                this.spinning = true;
267                return true;
268        },
269
270        _repeat: function( i, steps, event ) {
271                i = i || 500;
272
273                clearTimeout( this.timer );
274                this.timer = this._delay(function() {
275                        this._repeat( 40, steps, event );
276                }, i );
277
278                this._spin( steps * this.options.step, event );
279        },
280
281        _spin: function( step, event ) {
282                var value = this.value() || 0;
283
284                if ( !this.counter ) {
285                        this.counter = 1;
286                }
287
288                value = this._adjustValue( value + step * this._increment( this.counter ) );
289
290                if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
291                        this._value( value );
292                        this.counter++;
293                }
294        },
295
296        _increment: function( i ) {
297                var incremental = this.options.incremental;
298
299                if ( incremental ) {
300                        return $.isFunction( incremental ) ?
301                                incremental( i ) :
302                                Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 );
303                }
304
305                return 1;
306        },
307
308        _precision: function() {
309                var precision = this._precisionOf( this.options.step );
310                if ( this.options.min !== null ) {
311                        precision = Math.max( precision, this._precisionOf( this.options.min ) );
312                }
313                return precision;
314        },
315
316        _precisionOf: function( num ) {
317                var str = num.toString(),
318                        decimal = str.indexOf( "." );
319                return decimal === -1 ? 0 : str.length - decimal - 1;
320        },
321
322        _adjustValue: function( value ) {
323                var base, aboveMin,
324                        options = this.options;
325
326                // make sure we're at a valid step
327                // - find out where we are relative to the base (min or 0)
328                base = options.min !== null ? options.min : 0;
329                aboveMin = value - base;
330                // - round to the nearest step
331                aboveMin = Math.round(aboveMin / options.step) * options.step;
332                // - rounding is based on 0, so adjust back to our base
333                value = base + aboveMin;
334
335                // fix precision from bad JS floating point math
336                value = parseFloat( value.toFixed( this._precision() ) );
337
338                // clamp the value
339                if ( options.max !== null && value > options.max) {
340                        return options.max;
341                }
342                if ( options.min !== null && value < options.min ) {
343                        return options.min;
344                }
345
346                return value;
347        },
348
349        _stop: function( event ) {
350                if ( !this.spinning ) {
351                        return;
352                }
353
354                clearTimeout( this.timer );
355                clearTimeout( this.mousewheelTimer );
356                this.counter = 0;
357                this.spinning = false;
358                this._trigger( "stop", event );
359        },
360
361        _setOption: function( key, value ) {
362                if ( key === "culture" || key === "numberFormat" ) {
363                        var prevValue = this._parse( this.element.val() );
364                        this.options[ key ] = value;
365                        this.element.val( this._format( prevValue ) );
366                        return;
367                }
368
369                if ( key === "max" || key === "min" || key === "step" ) {
370                        if ( typeof value === "string" ) {
371                                value = this._parse( value );
372                        }
373                }
374
375                this._super( key, value );
376
377                if ( key === "disabled" ) {
378                        if ( value ) {
379                                this.element.prop( "disabled", true );
380                                this.buttons.button( "disable" );
381                        } else {
382                                this.element.prop( "disabled", false );
383                                this.buttons.button( "enable" );
384                        }
385                }
386        },
387
388        _setOptions: modifier(function( options ) {
389                this._super( options );
390                this._value( this.element.val() );
391        }),
392
393        _parse: function( val ) {
394                if ( typeof val === "string" && val !== "" ) {
395                        val = window.Globalize && this.options.numberFormat ?
396                                Globalize.parseFloat( val, 10, this.options.culture ) : +val;
397                }
398                return val === "" || isNaN( val ) ? null : val;
399        },
400
401        _format: function( value ) {
402                if ( value === "" ) {
403                        return "";
404                }
405                return window.Globalize && this.options.numberFormat ?
406                        Globalize.format( value, this.options.numberFormat, this.options.culture ) :
407                        value;
408        },
409
410        _refresh: function() {
411                this.element.attr({
412                        "aria-valuemin": this.options.min,
413                        "aria-valuemax": this.options.max,
414                        // TODO: what should we do with values that can't be parsed?
415                        "aria-valuenow": this._parse( this.element.val() )
416                });
417        },
418
419        // update the value without triggering change
420        _value: function( value, allowAny ) {
421                var parsed;
422                if ( value !== "" ) {
423                        parsed = this._parse( value );
424                        if ( parsed !== null ) {
425                                if ( !allowAny ) {
426                                        parsed = this._adjustValue( parsed );
427                                }
428                                value = this._format( parsed );
429                        }
430                }
431                this.element.val( value );
432                this._refresh();
433        },
434
435        _destroy: function() {
436                this.element
437                        .removeClass( "ui-spinner-input" )
438                        .prop( "disabled", false )
439                        .removeAttr( "autocomplete" )
440                        .removeAttr( "role" )
441                        .removeAttr( "aria-valuemin" )
442                        .removeAttr( "aria-valuemax" )
443                        .removeAttr( "aria-valuenow" );
444                this.uiSpinner.replaceWith( this.element );
445        },
446
447        stepUp: modifier(function( steps ) {
448                this._stepUp( steps );
449        }),
450        _stepUp: function( steps ) {
451                this._spin( (steps || 1) * this.options.step );
452        },
453
454        stepDown: modifier(function( steps ) {
455                this._stepDown( steps );
456        }),
457        _stepDown: function( steps ) {
458                this._spin( (steps || 1) * -this.options.step );
459        },
460
461        pageUp: modifier(function( pages ) {
462                this._stepUp( (pages || 1) * this.options.page );
463        }),
464
465        pageDown: modifier(function( pages ) {
466                this._stepDown( (pages || 1) * this.options.page );
467        }),
468
469        value: function( newVal ) {
470                if ( !arguments.length ) {
471                        return this._parse( this.element.val() );
472                }
473                modifier( this._value ).call( this, newVal );
474        },
475
476        widget: function() {
477                return this.uiSpinner;
478        }
479});
480
481}( jQuery ) );
Note: See TracBrowser for help on using the repository browser.