1 /* SpinButton control
  2  *
  3  * Adds bells and whistles to any ordinary textbox to
  4  * make it look and feel like a SpinButton Control.
  5  *
  6  * Originally written by George Adamson, Software Unity (george.jquery@softwareunity.com) August 2006.
  7  * - Added min/max options
  8  * - Added step size option
  9  * - Added bigStep (page up/down) option
 10  *
 11  * Modifications made by Mark Gibson, (mgibson@designlinks.net) September 2006:
 12  * - Converted to jQuery plugin
 13  * - Allow limited or unlimited min/max values
 14  * - Allow custom class names, and add class to input element
 15  * - Removed global vars
 16  * - Reset (to original or through config) when invalid value entered
 17  * - Repeat whilst holding mouse button down (with initial pause, like keyboard repeat)
 18  * - Support mouse wheel in Firefox
 19  * - Fix double click in IE
 20  * - Refactored some code and renamed some vars
 21  *
 22  * Tested in IE6, Opera9, Firefox 1.5
 23  * v1.0  11 Aug 2006 - George Adamson	- First release
 24  * v1.1     Aug 2006 - George Adamson	- Minor enhancements
 25  * v1.2  27 Sep 2006 - Mark Gibson		- Major enhancements
 26  * v1.3a 28 Sep 2006 - George Adamson	- Minor enhancements
 27  * rf1.3a 15 Nov 2007 - Pavel Yaschenko - some changes
 28  
 29  Sample usage:
 30  
 31 	// Create group of settings to initialise spinbutton(s). (Optional)
 32 	var myOptions = {
 33 					min: 0,						// Set lower limit.
 34 					max: 100,					// Set upper limit.
 35 					step: 1,					// Set increment size.
 36 					spinClass: mySpinBtnClass,	// CSS class to style the spinbutton. (Class also specifies url of the up/down button image.)
 37 					upClass: mySpinUpClass,		// CSS class for style when mouse over up button.
 38 					downClass: mySpinDnClass	// CSS class for style when mouse over down button.
 39 					}
 40  
 41 	$(document).ready(function(){
 42 
 43 		// Initialise INPUT element(s) as SpinButtons: (passing options if desired)
 44 		$("#myInputElement").SpinButton(myOptions);
 45 
 46 	});
 47  
 48  */
 49 var sbjQuery = jQuery;
 50 sbjQuery.fn.SpinButton = function(cfg){
 51 	return this.each(function(){
 52 
 53 		// Apply specified options or defaults:
 54 		// (Ought to refactor this some day to use $.extend() instead)
 55 		this.spinCfg = {
 56 			//min: cfg && cfg.min ? Number(cfg.min) : null,
 57 			//max: cfg && cfg.max ? Number(cfg.max) : null,
 58 			min: cfg && !isNaN(parseFloat(cfg.min)) ? Number(cfg.min) : null,	// Fixes bug with min:0
 59 			max: cfg && !isNaN(parseFloat(cfg.max)) ? Number(cfg.max) : null,
 60 			step: cfg && cfg.step ? Number(cfg.step) : 1,
 61 			page: cfg && cfg.page ? Number(cfg.page) : 10,
 62 			upClass: cfg && cfg.upClass ? cfg.upClass : 'up',
 63 			downClass: cfg && cfg.downClass ? cfg.downClass : 'down',
 64 			reset: cfg && cfg.reset ? cfg.reset : this.value,
 65 			delay: cfg && cfg.delay ? Number(cfg.delay) : 500,
 66 			interval: cfg && cfg.interval ? Number(cfg.interval) : 100,
 67 			_btn_width: 20,
 68 			_btn_height: 12,
 69 			_direction: null,
 70 			_delay: null,
 71 			_repeat: null,
 72 			
 73 			digits: cfg && cfg.digits ? Number(cfg.digits) : 1			
 74 		};
 75 		
 76 		this.adjustValue = function(i){
 77 			var v = this.value.toLowerCase();
 78 			if (v=="am") 
 79 			{
 80 				this.value="PM";
 81 				return; 
 82 			}
 83 			else if (v=="pm") {
 84 				this.value="AM";
 85 				return;
 86 			} 
 87 			v = (isNaN(this.value) ? this.spinCfg.reset : Number(this.value)) + Number(i);
 88 			if (this.spinCfg.min !== null) v = (v<this.spinCfg.min ? (this.spinCfg.max != null ? this.spinCfg.max : this.spinCfg.min) : v);
 89 			if (this.spinCfg.max !== null) v = (v>this.spinCfg.max ? (this.spinCfg.min != null ? this.spinCfg.min : this.spinCfg.max) : v);
 90 
 91 			var value = String(v);
 92 			while (value.length<this.spinCfg.digits) value="0"+value;
 93 			
 94 			this.value = value;
 95 		};
 96 		
 97 		sbjQuery(this)
 98 //		.addClass(cfg && cfg.spinClass ? cfg.spinClass : 'spin-button')
 99 //		
100 //		.mousemove(function(e){
101 //			// Determine which button mouse is over, or not (spin direction):
102 //			var x = e.pageX || e.x;
103 //			var y = e.pageY || e.y;
104 //			var el = e.target || e.srcElement;
105 //			var direction = 
106 //				(x > coord(el,'offsetLeft') + el.offsetWidth - this.spinCfg._btn_width)
107 //				? ((y < coord(el,'offsetTop') + this.spinCfg._btn_height) ? 1 : -1) : 0;
108 //			
109 //			if (direction !== this.spinCfg._direction) {
110 //				// Style up/down buttons:
111 //				switch(direction){
112 //					case 1: // Up arrow:
113 //						sbjQuery(this).removeClass(this.spinCfg.downClass).addClass(this.spinCfg.upClass);
114 //						break;
115 //					case -1: // Down arrow:
116 //						sbjQuery(this).removeClass(this.spinCfg.upClass).addClass(this.spinCfg.downClass);
117 //						break;
118 //					default: // Mouse is elsewhere in the textbox
119 //						sbjQuery(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
120 //				}
121 //				
122 //				// Set spin direction:
123 //				this.spinCfg._direction = direction;
124 //			}
125 //		})
126 //		
127 //		.mouseout(function(){
128 //			// Reset up/down buttons to their normal appearance when mouse moves away:
129 //			sbjQuery(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
130 //			this.spinCfg._direction = null;
131 //		})
132 		
133 //		.mousedown(function(e){
134 //			if (this.spinCfg._direction != 0) {
135 //				// Respond to click on one of the buttons:
136 //				var self = this;
137 //				var adjust = function() {
138 //					self.adjustValue(self.spinCfg._direction * self.spinCfg.step);
139 //				};
140 //			
141 //				adjust();
142 //				
143 //				// Initial delay before repeating adjustment
144 //				self.spinCfg._delay = window.setTimeout(function() {
145 //					adjust();
146 //					// Repeat adjust at regular intervals
147 //					self.spinCfg._repeat = window.setInterval(adjust, self.spinCfg.interval);
148 //				}, self.spinCfg.delay);
149 //			}
150 //		})
151 //		
152 //		.mouseup(function(e){
153 //			// Cancel repeating adjustment
154 //			window.clearInterval(this.spinCfg._repeat);
155 //			window.clearTimeout(this.spinCfg._delay);
156 //		})
157 //		
158 //		.dblclick(function(e) {
159 //			if (sbjQuery.browser.msie)
160 //				this.adjustValue(this.spinCfg._direction * this.spinCfg.step);
161 //		})
162 		
163 		.keydown(function(e){
164 			// Respond to up/down arrow keys.
165 			switch(e.keyCode){
166 				case 38: this.adjustValue(this.spinCfg.step);  break; // Up
167 				case 40: this.adjustValue(-this.spinCfg.step); break; // Down
168 				case 33: this.adjustValue(this.spinCfg.page);  break; // PageUp
169 				case 34: this.adjustValue(-this.spinCfg.page); break; // PageDown
170 			}
171 		})
172 
173 		.bind("mousewheel", function(e){
174 			// Respond to mouse wheel in IE. (It returns up/dn motion in multiples of 120)
175 			if (e.wheelDelta >= 120)
176 				this.adjustValue(this.spinCfg.step);
177 			else if (e.wheelDelta <= -120)
178 				this.adjustValue(-this.spinCfg.step);
179 			
180 			e.preventDefault();
181 		})
182 		
183 		.change(function(e){
184 			this.adjustValue(0);
185 		});
186 		
187 		var self = this;
188 		
189 		var btnUp = document.getElementById(this.id + 'BtnUp');
190 		sbjQuery(btnUp)
191 			.mousedown(function(e){
192 				// Respond to click on one of the buttons:
193 				var adjust = function() {
194 					self.adjustValue(self.spinCfg.step);
195 				};
196 			
197 				adjust();
198 				
199 				// Initial delay before repeating adjustment
200 				self.spinCfg._delay = window.setTimeout(function() {
201 					adjust();
202 					// Repeat adjust at regular intervals
203 					self.spinCfg._repeat = window.setInterval(adjust, self.spinCfg.interval);
204 				}, self.spinCfg.delay);
205 				self.spinCfg._repeater = true;
206 				return false;
207 			})
208 			
209 			.mouseup(function(e){
210 				// Cancel repeating adjustment
211 				self.spinCfg._repeater = false;
212 				window.clearInterval(self.spinCfg._repeat);
213 				window.clearTimeout(self.spinCfg._delay);
214 			})
215 			
216 			.dblclick(function(e) {
217 				if (RichFaces.browser.msie)
218 					self.adjustValue(self.spinCfg.step);
219 			})
220 			.mouseout(function(e){
221 				// Cancel repeating adjustment
222 				if (self.spinCfg._repeater)
223 				{
224 					self.spinCfg._repeater = false
225 					window.clearInterval(self.spinCfg._repeat);
226 					window.clearTimeout(self.spinCfg._delay);
227 				}
228 			});
229 		
230 		var btnDown = document.getElementById(this.id + 'BtnDown');
231 		sbjQuery(btnDown)
232 			.mousedown(function(e){
233 				// Respond to click on one of the buttons:
234 				var adjust = function() {
235 					self.adjustValue(-self.spinCfg.step);
236 				};
237 			
238 				adjust();
239 				
240 				// Initial delay before repeating adjustment
241 				self.spinCfg._delay = window.setTimeout(function() {
242 					adjust();
243 					// Repeat adjust at regular intervals
244 					self.spinCfg._repeat = window.setInterval(adjust, self.spinCfg.interval);
245 				}, self.spinCfg.delay);
246 				self.spinCfg._repeater = true;
247 				return false;
248 			})
249 			
250 			.mouseup(function(e){
251 				// Cancel repeating adjustment
252 				self.spinCfg._repeater = false;
253 				window.clearInterval(self.spinCfg._repeat);
254 				window.clearTimeout(self.spinCfg._delay);
255 			})
256 			
257 			.dblclick(function(e) {
258 				if (RichFaces.browser.msie)
259 					self.adjustValue(-self.spinCfg.step);
260 			})
261 			.mouseout(function(e){
262 				// Cancel repeating adjustment
263 				if (self.spinCfg._repeater)
264 				{
265 					self.spinCfg._repeater = false
266 					window.clearInterval(self.spinCfg._repeat);
267 					window.clearTimeout(self.spinCfg._delay);
268 				}
269 			});
270 			
271 		
272 		if (this.addEventListener) {
273 			// Respond to mouse wheel in Firefox
274 			this.addEventListener('DOMMouseScroll', function(e) {
275 				if (e.detail > 0)
276 					this.adjustValue(-this.spinCfg.step);
277 				else if (e.detail < 0)
278 					this.adjustValue(this.spinCfg.step);
279 				
280 				e.preventDefault();
281 			}, false);
282 		}
283 	});
284 	
285 	function coord(el,prop) {
286 		var c = el[prop], b = document.body;
287 		
288 		while ((el = el.offsetParent) && (el != b)) {
289 			if (!RichFaces.browser.msie || (el.currentStyle.position != 'relative'))
290 				c += el[prop];
291 		}
292 		
293 		return c;
294 	}
295 };
296