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+'"> </span></div>' + 131 '<div class="ui-spinner-down ui-spinner-button ui-state-default ui-corner-br"><span class="ui-icon '+options.downIconClass+'"> </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.