PageSourceSearch

https://www.anmonm.org/global/javascript/ui.spinner.js

js anmonm.org collected 2026-09-25 06:09:19 UTC 18,550 bytes, 655 lines download raw bytes

1/** 
2 * @license jQuery UI Spinner 1.20
3 *
4 * Copyright (c) 2009-2010 Brant Burnett
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 */
7 (function($, undefined) {
8
9var 
10	// constants
11	active = 'ui-state-active',
12	hover = 'ui-state-hover',
13	disabled = 'ui-state-disabled',
14	
15	keyCode = $.ui.keyCode,
16	up = keyCode.UP,
17	down = keyCode.DOWN,
18	right = keyCode.RIGHT,
19	left = keyCode.LEFT,
20	pageUp = keyCode.PAGE_UP,
21	pageDown = keyCode.PAGE_DOWN,
22	home = keyCode.HOME,
23	end = keyCode.END,
24	
25	msie = $.browser.msie,
26	mouseWheelEventName = $.browser.mozilla ? 'DOMMouseScroll' : 'mousewheel',
27	
28	// namespace for events on input
29	eventNamespace = '.uispinner', 
30	
31	// only these special keys will be accepted, all others will be ignored unless CTRL or ALT are pressed
32	validKeys = [up, down, right, left, pageUp, pageDown, home, end, keyCode.BACKSPACE, keyCode.DELETE, keyCode.TAB],
33	
34	// stores the currently focused spinner
35	// Note: due to oddities in the focus/blur events, this is part of a two-part system for confirming focus
36	// this must set to the control, and the focus variable must be true
37	// this is because hitting up/down arrows with mouse causes focus to change, but blur event for previous control doesn't fire
38	focusCtrl;
39	
40$.widget('ui.spinner', {
41	options: {
42		min: null,
43		max: null,
44		allowNull: false,
45		
46		group: '',
47		point: '.',
48		prefix: '',
49		suffix: '',
50		places: null, // null causes it to detect the number of places in step
51		
52		defaultStep: 1, // real value is 'step', and should be passed as such.  This value is used to detect if passed value should override HTML5 attribute
53		largeStep: 10,
54		mouseWheel: true,
55		increment: 'slow',		
56		className: null,
57		showOn: 'always',
58		width: 16,
59		upIconClass: "ui-icon-triangle-1-n",
60		downIconClass: "ui-icon-triangle-1-s",
61		
62		format: function(num, places) {
63			var options = this,
64				regex = /(\d+)(\d{3})/,
65				result = ((isNaN(num) ? 0 : Math.abs(num)).toFixed(places)) + '';
66				
67			for (result = result.replace('.', options.point); regex.test(result) && options.group; result=result.replace(regex, '$1'+options.group+'$2')) {};
68			return (num < 0 ? '-' : '') + options.prefix + result + options.suffix;
69		},
70		
71		parse: function(val) {
72			var options = this;
73			
74			if (options.group == '.')
75				val = val.replace('.', '');
76			if (options.point != '.')
77				val = val.replace(options.point, '.');
78			return parseFloat(val.replace(/[^0-9\-\.]/g, ''));
79		}
80	},
81	
82	// * Widget fields *
83	// curvalue - current value
84	// places - currently effective number of decimal places
85	// oWidth - original input width (used for destroy)
86	// oMargin - original input right margin (used for destroy)
87	// counter - number of spins at the current spin speed
88	// incCounter - index within options.increment of the current spin speed
89	// selfChange - indicates that change event is being fired by the widget, so don't reprocess input value
90	// inputMaxLength - initial maxLength value on the input
91	// focused - this spinner currently has the focus
92
93	_create: function() {
94		// shortcuts
95		var self = this,
96			input = self.element,
97			type = input.attr('type');
98			
99		if (!input.is('input') || ((type != 'text') && (type != 'number'))) {
100			console.error('Invalid target for ui.spinner');
101			return;
102		}
103		
104		self._procOptions(true);
105		self._createButtons(input);
106
107		if (!input.is(':enabled'))
108			self.disable();
109	},
110	
111	_createButtons: function(input) {
112		function getMargin(margin) {
113			// IE8 returns auto if no margin specified
114			return margin == 'auto' ? 0 : parseInt(margin);
115		}
116
117		var self = this,
118			options = self.options,
119			className = options.className,
120			buttonWidth = options.width,
121			showOn = options.showOn,
122			box = $.support.boxModel,
123			height = input.outerHeight(),
124			rightMargin = self.oMargin = getMargin(input.css('margin-right')), // store original width and right margin for later destroy
125			wrapper = self.wrapper = input.css({ width: (self.oWidth = (box ? input.width() : input.outerWidth())) - buttonWidth, 
126												 marginRight: rightMargin + buttonWidth, textAlign: options.align && typeof options.align !== 'undefined' ? options.align : 'right' })
127				.after('<span class="ui-spinner ui-widget"></span>').next(),
128			btnContainer = self.btnContainer = $(
129				'<div class="ui-spinner-buttons">' + 
130					'<div class="ui-spinner-up ui-spinner-button ui-state-default ui-corner-tr"><span class="ui-icon '+options.upIconClass+'">&nbsp;</span></div>' + 
131					'<div class="ui-spinner-down ui-spinner-button ui-state-default ui-corner-br"><span class="ui-icon '+options.downIconClass+'">&nbsp;</span></div>' + 
132				'</div>'),
133
134			// object shortcuts
135			upButton, downButton, buttons, icons,
136
137			hoverDelay,
138			hoverDelayCallback,
139			
140			// current state booleans
141			hovered, inKeyDown, inSpecialKey, inMouseDown,
142						
143			// used to reverse left/right key directions
144			rtl = input[0].dir == 'rtl';
145		
146		// apply className before doing any calculations because it could affect them
147		if (className) wrapper.addClass(className);
148		
149		wrapper.append(btnContainer.css({ height: height, left: -buttonWidth-rightMargin,
150			// use offset calculation to fix vertical position in Firefox
151			top: (input.offset().top - wrapper.offset().top) + 'px' }));
152		
153		buttons = self.buttons = btnContainer.find('.ui-spinner-button');
154		buttons.css({ width: buttonWidth - (box ? buttons.outerWidth() - buttons.width() : 0), height: height/2 - (box ? buttons.outerHeight() - buttons.height() : 0) });
155		upButton = buttons[0];
156		downButton = buttons[1];
157
158		// fix icon centering
159		icons = buttons.find('.ui-icon');
160		icons.css({ marginLeft: (buttons.innerWidth() - icons.width()) / 2, marginTop:  (buttons.innerHeight() - icons.height()) / 2 });
161		
162		// set width of btnContainer to be the same as the buttons
163		btnContainer.width(buttons.outerWidth());
164		if (showOn != 'always')
165			btnContainer.css('opacity', 0);
166		
167		/* Event Bindings */
168
169		// bind hover events to show/hide buttons
170		if (showOn == 'hover' || showOn == 'both')
171			buttons.add(input)
172				.bind('mouseenter' + eventNamespace, function() {
173					setHoverDelay(function() {
174						hovered = true;
175						if (!self.focused || (showOn == 'hover')) // ignore focus flag if show on hover only
176							self.showButtons();
177					});
178				})
179				
180				.bind('mouseleave' + eventNamespace, function hoverOut() {
181					setHoverDelay(function() {
182						hovered = false;
183						if (!self.focused || (showOn == 'hover')) // ignore focus flag if show on hover only
184							self.hideButtons();
185					});
186				});
187
188	
189		buttons.hover(function() {
190					// ensure that both buttons have hover removed, sometimes they get left on
191					self.buttons.removeClass(hover);
192					
193					if (!options.disabled)
194						$(this).addClass(hover);
195				}, function() {
196					$(this).removeClass(hover);
197				})
198			.mousedown(mouseDown)
199			.mouseup(mouseUp)
200			.mouseout(mouseUp);
201			
202		if (msie)
203			// fixes dbl click not firing second mouse down in IE
204			buttons.dblclick(function() {
205					if (!options.disabled) {
206						// make sure any changes are posted
207						self._change();
208						self._doSpin((this === upButton ? 1 : -1) * options.step);
209					}
210					
211					return false;
212				}) 
213				
214				// fixes IE8 dbl click selection highlight
215				.bind('selectstart', function() {return false;});
216				
217		input.bind('keydown' + eventNamespace, function(e) {
218					var dir, large, limit,
219						keyCode = e.keyCode; // shortcut for minimization
220					if (e.ctrl || e.alt) return true; // ignore these events
221					
222					if (isSpecialKey(keyCode))
223						inSpecialKey = true;
224					
225					if (inKeyDown) return false; // only one direction at a time, and suppress invalid keys
226					
227					switch (keyCode) {
228						case up:
229						case pageUp:
230							dir = 1;
231							large = keyCode == pageUp;
232							break;
233							
234						case down:
235						case pageDown:
236							dir = -1;
237							large = keyCode == pageDown;
238							break;
239							
240						case right:
241						case left:
242							dir = (keyCode == right) ^ rtl ? 1 : -1;
243							break;
244							
245						case home:
246							limit = self.options.min;
247							if (limit != null) self._setValue(limit);
248							return false;
249							
250						case end:
251							limit = self.options.max;
252							limit = self.options.max;
253							if (limit != null) self._setValue(limit);
254							return false;
255					}
256					
257					if (dir) { // only process if dir was set above
258						if (!inKeyDown && !options.disabled) {
259							keyDir = dir;
260							
261							$(dir > 0 ? upButton : downButton).addClass(active);
262							inKeyDown = true;
263							self._startSpin(dir, large);
264						}
265						
266						return false;
267					}
268				})
269				
270			.bind('keyup' + eventNamespace, function(e) {
271					if (e.ctrl || e.alt) return true; // ignore these events
272					
273					if (isSpecialKey(keyCode))
274						inSpecialKey = false;
275					
276					switch (e.keyCode) {
277						case up:
278						case right:
279						case pageUp:
280						case down:
281						case left:
282						case pageDown:
283							buttons.removeClass(active)
284							self._stopSpin();
285							inKeyDown = false;
286							return false;
287					}
288				})
289				
290			.bind('keypress' + eventNamespace, function(e) {
291					if (invalidKey(e.keyCode, e.charCode)) return false;
292				})
293				
294			.bind('change' + eventNamespace, function() { self._change(); })
295			
296			.bind('focus' + eventNamespace, function() {
297					function selectAll() {
298						self.element.select();
299					}
300
301					msie ? selectAll() : setTimeout(selectAll, 0); // add delay for Chrome, but breaks IE8
302					self.focused = true;
303					focusCtrl = self;
304					if (!hovered && (showOn == 'focus' || showOn == 'both')) // hovered will only be set if hover affects show
305						self.showButtons();
306				})
307				
308			.bind('blur' + eventNamespace, function() {
309					self.focused = false;
310					if (!hovered && (showOn == 'focus' || showOn == 'both')) // hovered will only be set if hover affects show
311						self.hideButtons();
312				});
313				
314		function isSpecialKey(keyCode) {
315			for (var i=0; i<validKeys.length; i++) // predefined list of special keys
316				if (validKeys[i] == keyCode) return true;
317				
318			return false;
319		}
320			
321		function invalidKey(keyCode, charCode) {
322			if (inSpecialKey) return false;				
323			
324			var ch = String.fromCharCode(charCode || keyCode),
325				options = self.options;
326				
327			if ((ch >= '0') && (ch <= '9') || (ch == '-')) return false;
328			if (((self.places > 0) && (ch == options.point))
329				|| (ch == options.group)) return false;
330						
331			return true;
332		}
333		
334		// used to delay start of hover show/hide by 100 milliseconds
335		function setHoverDelay(callback) {
336			if (hoverDelay) {
337				// don't do anything if trying to set the same callback again
338				if (callback === hoverDelayCallback) return;
339				
340				clearTimeout(hoverDelay);
341			}
342			
343			hoverDelayCallback = callback;
344			hoverDelay = setTimeout(execute, 100);
345			
346			function execute() {
347				hoverDelay = 0;
348				callback();
349			}
350		}
351			
352		function mouseDown() {
353			if (!options.disabled) {
354				var input = self.element[0],
355					dir = (this === upButton ? 1 : -1);
356					
357				input.focus();
358				input.select();
359				$(this).addClass(active);
360				
361				inMouseDown = true;
362				self._startSpin(dir);
363			}
364
365			return false;
366		}
367		
368		function mouseUp() {
369			if (inMouseDown) {
370				$(this).removeClass(active);
371				self._stopSpin();
372				inMouseDown = false;
373			}
374			return false;
375		}
376	},
377	
378	_procOptions: function(init) {
379		var self = this,
380			input = self.element,
381			options = self.options,
382			min = options.min,
383			max = options.max,
384			step = options.step,
385			places = options.places,
386			maxlength = -1, temp;
387			
388		// setup increment based on speed string
389		if (options.increment == 'slow')
390			options.increment = [{count: 1, mult: 1, delay: 250},
391								 {count: 3, mult: 1, delay: 100},
392								 {count: 0, mult: 1, delay: 50}];
393		else if (options.increment == 'fast')
394			options.increment = [{count: 1, mult: 1, delay: 250},
395								 {count: 19, mult: 1, delay: 100},
396								 {count: 80, mult: 1, delay: 20},
397								 {count: 100, mult: 10, delay: 20},
398								 {count: 0, mult: 100, delay: 20}];
399
400		if ((min == null) && ((temp = input.attr('min')) != null))
401			min = parseFloat(temp);
402		
403		if ((max == null) && ((temp = input.attr('max')) != null))
404			max = parseFloat(temp);
405		
406		if (!step && ((temp = input.attr('step')) != null))
407			if (temp != 'any') {
408				step = parseFloat(temp);
409				options.largeStep *= step;
410			}
411		options.step = step = step || options.defaultStep;
412
413		// Process step for decimal places if none are specified
414		if ((places == null) && ((temp = step + '').indexOf('.') != -1))
415			places = temp.length - temp.indexOf('.') - 1;
416		self.places = places;
417		
418		if ((max != null) && (min != null)) {
419			// ensure that min is less than or equal to max
420			if (min > max) min = max;
421			
422			// set maxlength based on min/max
423			maxlength = Math.max(Math.max(maxlength, options.format(max, places, input).length), options.format(min, places, input).length);
424		}
425		
426		// only lookup input maxLength on init
427		if (init) self.inputMaxLength = input[0].maxLength;
428		temp = self.inputMaxLength;
429			
430		if (temp > 0) {
431			maxlength = maxlength > 0 ? Math.min(temp, maxlength) : temp;
432			temp = Math.pow(10, maxlength) - 1;
433			if ((max == null) || (max > temp))
434				max = temp;
435			temp = -(temp + 1) / 10 + 1;
436			if ((min == null) || (min < temp))
437				min = temp;
438		}
439		
440		if (maxlength > 0)
441			input.attr('maxlength', maxlength);
442					
443		options.min = min;
444		options.max = max;
445		
446		// ensures that current value meets constraints
447		self._change();
448		
449		input.unbind(mouseWheelEventName + eventNamespace);
450		if (options.mouseWheel)
451			input.bind(mouseWheelEventName + eventNamespace, self._mouseWheel);
452	},
453		
454	_mouseWheel: function(e) {
455		var self = $.data(this, 'spinner');
456		if (!self.options.disabled && self.focused && (focusCtrl === self)) {
457			// make sure changes are posted
458			self._change();
459			self._doSpin(((e.wheelDelta || -e.detail) > 0 ? 1 : -1) * self.options.step);
460			return false;
461		}
462	},
463	
464	// sets an interval to call the _spin function
465	_setTimer: function(delay, dir, large) {
466		var self = this;
467		self._stopSpin();
468		self.timer = setInterval(fire, delay);
469		
470		function fire() {
471			self._spin(dir, large);
472		}
473	},
474	
475	// stops the spin timer
476	_stopSpin: function() {
477		if (this.timer) {
478			clearInterval(this.timer);
479			this.timer = 0;
480		}
481	},
482	
483	// performs first step, and starts the spin timer if increment is set
484	_startSpin: function(dir, large) {
485		// shortcuts
486		var self = this,
487			options = self.options,
488			increment = options.increment;
489			
490		// make sure any changes are posted
491		self._change();
492		self._doSpin(dir * (large ? self.options.largeStep : self.options.step));
493		
494		if (increment && increment.length > 0) {		
495			self.counter = 0;
496			self.incCounter = 0;
497			self._setTimer(increment[0].delay, dir, large);
498		}
499	},
500	
501	// called by timer for each step in the spin
502	_spin: function(dir, large) {
503		// shortcuts
504		var self = this,
505			increment = self.options.increment,
506			curIncrement = increment[self.incCounter];
507		
508		self._doSpin(dir * curIncrement.mult * (large ? self.options.largeStep : self.options.step));
509		self.counter++;
510
511		if ((self.counter > curIncrement.count) && (self.incCounter < increment.length-1)) {
512			self.counter = 0;
513			curIncrement = increment[++self.incCounter];
514			self._setTimer(curIncrement.delay, dir, large);
515		}
516	},
517	
518	// actually spins the timer by a step
519	_doSpin: function(step) {
520		// shortcut
521		var self = this,
522			value = self.curvalue;
523			
524		if (value == null)
525			value = (step > 0 ? self.options.min : self.options.max) || 0;
526		
527		self._setValue(value + step);
528	},
529	
530	// Parse the value currently in the field
531	_parseValue: function() {
532		var value = this.element.val();
533		return value ? this.options.parse(value, this.element) : null;
534	},
535	
536	_validate: function(value) {
537		var options = this.options,
538			min = options.min,
539			max = options.max;
540
541		if ((value == null) && !options.allowNull)
542			value = this.curvalue != null ? this.curvalue : min || max || 0; // must confirm not null in case just initializing and had blank value
543
544		if ((max != null) && (value > max))
545			return max;
546		else if ((min != null) && (value || !options.allowNull) && (value < min))
547			return min;
548		else
549			return value;
550	},
551	
552	_change: function() {
553		var self = this, // shortcut
554			value = self._parseValue(),
555			min = self.options.min,
556			max = self.options.max;
557
558		// don't reprocess if change was self triggered
559		if (!self.selfChange) {
560			if (isNaN(value))
561				value = self.curvalue;
562
563			self._setValue(value, true);
564		}
565	},
566	
567	// overrides _setData to force option parsing
568	_setOption: function(key, value) {
569		$.Widget.prototype._setOption.call(this, key, value);
570		this._procOptions();
571	},
572	
573	increment: function() {
574		this._doSpin(this.options.step);
575	},
576	
577	decrement: function() {
578		this._doSpin(-this.options.step);
579	},
580	
581	showButtons: function(immediate) {
582		var btnContainer = this.btnContainer.stop();
583		if (immediate)
584			btnContainer.css('opacity', 1);
585		else
586			btnContainer.fadeTo('fast', 1);
587	},
588	
589	hideButtons: function(immediate) {
590		var btnContainer = this.btnContainer.stop();
591		if (immediate)
592			btnContainer.css('opacity', 0);
593		else
594			btnContainer.fadeTo('fast', 0);
595		this.buttons.removeClass(hover);
596	},
597	
598	// Set the value directly
599	_setValue: function(value, suppressFireEvent) {
600		var self = this;
601
602		var old_val = value;
603		self.curvalue = value = self._validate(value);
604		self.element.val(value != null ? 
605			self.options.format(value, self.places, self.element) :
606			'');
607
608		// value changed because < min : we set the old data in data-oldval
609		self.element.data('oldval', value);
610		if (old_val < value)
611			self.element.data('oldval', old_val);
612
613		if (!suppressFireEvent) {
614			self.selfChange = true;
615			self.element.change();
616			self.selfChange = false;
617		}
618	},
619
620	// Set or retrieve the value
621	value: function(newValue) {
622		if (arguments.length) {
623			this._setValue(newValue);
624			
625			// maintains chaining
626			return this.element;
627		}
628
629		return this.curvalue;
630	},
631
632	enable: function() {
633		this.buttons.removeClass(disabled);
634		this.element[0].disabled = false;
635		$.Widget.prototype.enable.call(this);
636	},
637	
638	disable: function() {
639		this.buttons.addClass(disabled)
640			// in case hover class got left on
641			.removeClass(hover);
642			
643		this.element[0].disabled = true;
644		$.Widget.prototype.disable.call(this);
645	},
646	
647	destroy: function(target) {
648		this.wrapper.remove();
649		this.element.unbind(eventNamespace).css({ width: this.oWidth, marginRight: this.oMargin });
650		
651		$.Widget.prototype.destroy.call(this);
652	}	
653});
654
655})( jQuery );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.