jquery.bootstrap-touchspin.js (19540B)
1 (function($) { 2 'use strict'; 3 4 var _currentSpinnerId = 0; 5 6 function _scopedEventName(name, id) { 7 return name + '.touchspin_' + id; 8 } 9 10 function _scopeEventNames(names, id) { 11 return $.map(names, function(name) { 12 return _scopedEventName(name, id); 13 }); 14 } 15 16 $.fn.TouchSpin = function(options) { 17 18 if (options === 'destroy') { 19 this.each(function() { 20 var originalinput = $(this), 21 originalinput_data = originalinput.data(); 22 $(document).off(_scopeEventNames([ 23 'mouseup', 24 'touchend', 25 'touchcancel', 26 'mousemove', 27 'touchmove', 28 'scroll', 29 'scrollstart'], originalinput_data.spinnerid).join(' ')); 30 }); 31 return; 32 } 33 34 var defaults = { 35 min: 0, 36 max: 100, 37 initval: '', 38 replacementval: '', 39 step: 1, 40 decimals: 0, 41 stepinterval: 100, 42 forcestepdivisibility: 'round', // none | floor | round | ceil 43 stepintervaldelay: 500, 44 verticalbuttons: false, 45 verticalupclass: 'glyphicon glyphicon-chevron-up', 46 verticaldownclass: 'glyphicon glyphicon-chevron-down', 47 prefix: '', 48 postfix: '', 49 prefix_extraclass: '', 50 postfix_extraclass: '', 51 booster: true, 52 boostat: 10, 53 maxboostedstep: false, 54 mousewheel: true, 55 buttondown_class: 'btn btn-default', 56 buttonup_class: 'btn btn-default', 57 buttondown_txt: '-', 58 buttonup_txt: '+' 59 }; 60 61 var attributeMap = { 62 min: 'min', 63 max: 'max', 64 initval: 'init-val', 65 replacementval: 'replacement-val', 66 step: 'step', 67 decimals: 'decimals', 68 stepinterval: 'step-interval', 69 verticalbuttons: 'vertical-buttons', 70 verticalupclass: 'vertical-up-class', 71 verticaldownclass: 'vertical-down-class', 72 forcestepdivisibility: 'force-step-divisibility', 73 stepintervaldelay: 'step-interval-delay', 74 prefix: 'prefix', 75 postfix: 'postfix', 76 prefix_extraclass: 'prefix-extra-class', 77 postfix_extraclass: 'postfix-extra-class', 78 booster: 'booster', 79 boostat: 'boostat', 80 maxboostedstep: 'max-boosted-step', 81 mousewheel: 'mouse-wheel', 82 buttondown_class: 'button-down-class', 83 buttonup_class: 'button-up-class', 84 buttondown_txt: 'button-down-txt', 85 buttonup_txt: 'button-up-txt' 86 }; 87 88 return this.each(function() { 89 90 var settings, 91 originalinput = $(this), 92 originalinput_data = originalinput.data(), 93 container, 94 elements, 95 value, 96 downSpinTimer, 97 upSpinTimer, 98 downDelayTimeout, 99 upDelayTimeout, 100 spincount = 0, 101 spinning = false; 102 103 init(); 104 105 106 function init() { 107 if (originalinput.data('alreadyinitialized')) { 108 return; 109 } 110 111 originalinput.data('alreadyinitialized', true); 112 _currentSpinnerId += 1; 113 originalinput.data('spinnerid', _currentSpinnerId); 114 115 116 if (!originalinput.is('input')) { 117 console.log('Must be an input.'); 118 return; 119 } 120 121 _initSettings(); 122 _setInitval(); 123 _checkValue(); 124 _buildHtml(); 125 _initElements(); 126 _hideEmptyPrefixPostfix(); 127 _bindEvents(); 128 _bindEventsInterface(); 129 elements.input.css('display', 'block'); 130 } 131 132 function _setInitval() { 133 if (settings.initval !== '' && originalinput.val() === '') { 134 originalinput.val(settings.initval); 135 } 136 } 137 138 function changeSettings(newsettings) { 139 _updateSettings(newsettings); 140 _checkValue(); 141 142 var value = elements.input.val(); 143 144 if (value !== '') { 145 value = Number(elements.input.val()); 146 elements.input.val(value.toFixed(settings.decimals)); 147 } 148 } 149 150 function _initSettings() { 151 settings = $.extend({}, defaults, originalinput_data, _parseAttributes(), options); 152 } 153 154 function _parseAttributes() { 155 var data = {}; 156 $.each(attributeMap, function(key, value) { 157 var attrName = 'bts-' + value + ''; 158 if (originalinput.is('[data-' + attrName + ']')) { 159 data[key] = originalinput.data(attrName); 160 } 161 }); 162 return data; 163 } 164 165 function _updateSettings(newsettings) { 166 settings = $.extend({}, settings, newsettings); 167 } 168 169 function _buildHtml() { 170 var initval = originalinput.val(), 171 parentelement = originalinput.parent(); 172 173 if (initval !== '') { 174 initval = Number(initval).toFixed(settings.decimals); 175 } 176 177 originalinput.data('initvalue', initval).val(initval); 178 originalinput.addClass('form-control'); 179 180 if (parentelement.hasClass('input-group')) { 181 _advanceInputGroup(parentelement); 182 } 183 else { 184 _buildInputGroup(); 185 } 186 } 187 188 function _advanceInputGroup(parentelement) { 189 parentelement.addClass('bootstrap-touchspin'); 190 191 var prev = originalinput.prev(), 192 next = originalinput.next(); 193 194 var downhtml, 195 uphtml, 196 prefixhtml = '<span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span>', 197 postfixhtml = '<span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span>'; 198 199 if (prev.hasClass('input-group-btn')) { 200 downhtml = '<button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button>'; 201 prev.append(downhtml); 202 } 203 else { 204 downhtml = '<span class="input-group-btn"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button></span>'; 205 $(downhtml).insertBefore(originalinput); 206 } 207 208 if (next.hasClass('input-group-btn')) { 209 uphtml = '<button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button>'; 210 next.prepend(uphtml); 211 } 212 else { 213 uphtml = '<span class="input-group-btn"><button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button></span>'; 214 $(uphtml).insertAfter(originalinput); 215 } 216 217 $(prefixhtml).insertBefore(originalinput); 218 $(postfixhtml).insertAfter(originalinput); 219 220 container = parentelement; 221 } 222 223 function _buildInputGroup() { 224 var html; 225 226 if (settings.verticalbuttons) { 227 html = '<div class="input-group bootstrap-touchspin"><span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span><span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span><span class="input-group-btn-vertical"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-up" type="button"><i class="' + settings.verticalupclass + '"></i></button><button class="' + settings.buttonup_class + ' bootstrap-touchspin-down" type="button"><i class="' + settings.verticaldownclass + '"></i></button></span></div>'; 228 } 229 else { 230 html = '<div class="input-group bootstrap-touchspin"><span class="input-group-btn"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button></span><span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span><span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span><span class="input-group-btn"><button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button></span></div>'; 231 } 232 233 container = $(html).insertBefore(originalinput); 234 235 $('.bootstrap-touchspin-prefix', container).after(originalinput); 236 237 if (originalinput.hasClass('input-sm')) { 238 container.addClass('input-group-sm'); 239 } 240 else if (originalinput.hasClass('input-lg')) { 241 container.addClass('input-group-lg'); 242 } 243 } 244 245 function _initElements() { 246 elements = { 247 down: $('.bootstrap-touchspin-down', container), 248 up: $('.bootstrap-touchspin-up', container), 249 input: $('input', container), 250 prefix: $('.bootstrap-touchspin-prefix', container).addClass(settings.prefix_extraclass), 251 postfix: $('.bootstrap-touchspin-postfix', container).addClass(settings.postfix_extraclass) 252 }; 253 } 254 255 function _hideEmptyPrefixPostfix() { 256 if (settings.prefix === '') { 257 elements.prefix.hide(); 258 } 259 260 if (settings.postfix === '') { 261 elements.postfix.hide(); 262 } 263 } 264 265 function _bindEvents() { 266 originalinput.on('keydown', function(ev) { 267 var code = ev.keyCode || ev.which; 268 269 if (code === 38) { 270 if (spinning !== 'up') { 271 upOnce(); 272 startUpSpin(); 273 } 274 ev.preventDefault(); 275 } 276 else if (code === 40) { 277 if (spinning !== 'down') { 278 downOnce(); 279 startDownSpin(); 280 } 281 ev.preventDefault(); 282 } 283 }); 284 285 originalinput.on('keyup', function(ev) { 286 var code = ev.keyCode || ev.which; 287 288 if (code === 38) { 289 stopSpin(); 290 } 291 else if (code === 40) { 292 stopSpin(); 293 } 294 }); 295 296 originalinput.on('blur', function() { 297 _checkValue(); 298 }); 299 300 elements.down.on('keydown', function(ev) { 301 var code = ev.keyCode || ev.which; 302 303 if (code === 32 || code === 13) { 304 if (spinning !== 'down') { 305 downOnce(); 306 startDownSpin(); 307 } 308 ev.preventDefault(); 309 } 310 }); 311 312 elements.down.on('keyup', function(ev) { 313 var code = ev.keyCode || ev.which; 314 315 if (code === 32 || code === 13) { 316 stopSpin(); 317 } 318 }); 319 320 elements.up.on('keydown', function(ev) { 321 var code = ev.keyCode || ev.which; 322 323 if (code === 32 || code === 13) { 324 if (spinning !== 'up') { 325 upOnce(); 326 startUpSpin(); 327 } 328 ev.preventDefault(); 329 } 330 }); 331 332 elements.up.on('keyup', function(ev) { 333 var code = ev.keyCode || ev.which; 334 335 if (code === 32 || code === 13) { 336 stopSpin(); 337 } 338 }); 339 340 elements.down.on('mousedown.touchspin', function(ev) { 341 elements.down.off('touchstart.touchspin'); // android 4 workaround 342 343 if (originalinput.is(':disabled')) { 344 return; 345 } 346 347 downOnce(); 348 startDownSpin(); 349 350 ev.preventDefault(); 351 ev.stopPropagation(); 352 }); 353 354 elements.down.on('touchstart.touchspin', function(ev) { 355 elements.down.off('mousedown.touchspin'); // android 4 workaround 356 357 if (originalinput.is(':disabled')) { 358 return; 359 } 360 361 downOnce(); 362 startDownSpin(); 363 364 ev.preventDefault(); 365 ev.stopPropagation(); 366 }); 367 368 elements.up.on('mousedown.touchspin', function(ev) { 369 elements.up.off('touchstart.touchspin'); // android 4 workaround 370 371 if (originalinput.is(':disabled')) { 372 return; 373 } 374 375 upOnce(); 376 startUpSpin(); 377 378 ev.preventDefault(); 379 ev.stopPropagation(); 380 }); 381 382 elements.up.on('touchstart.touchspin', function(ev) { 383 elements.up.off('mousedown.touchspin'); // android 4 workaround 384 385 if (originalinput.is(':disabled')) { 386 return; 387 } 388 389 upOnce(); 390 startUpSpin(); 391 392 ev.preventDefault(); 393 ev.stopPropagation(); 394 }); 395 396 elements.up.on('mouseout touchleave touchend touchcancel', function(ev) { 397 if (!spinning) { 398 return; 399 } 400 401 ev.stopPropagation(); 402 stopSpin(); 403 }); 404 405 elements.down.on('mouseout touchleave touchend touchcancel', function(ev) { 406 if (!spinning) { 407 return; 408 } 409 410 ev.stopPropagation(); 411 stopSpin(); 412 }); 413 414 elements.down.on('mousemove touchmove', function(ev) { 415 if (!spinning) { 416 return; 417 } 418 419 ev.stopPropagation(); 420 ev.preventDefault(); 421 }); 422 423 elements.up.on('mousemove touchmove', function(ev) { 424 if (!spinning) { 425 return; 426 } 427 428 ev.stopPropagation(); 429 ev.preventDefault(); 430 }); 431 432 $(document).on(_scopeEventNames(['mouseup', 'touchend', 'touchcancel'], _currentSpinnerId).join(' '), function(ev) { 433 if (!spinning) { 434 return; 435 } 436 437 ev.preventDefault(); 438 stopSpin(); 439 }); 440 441 $(document).on(_scopeEventNames(['mousemove', 'touchmove', 'scroll', 'scrollstart'], _currentSpinnerId).join(' '), function(ev) { 442 if (!spinning) { 443 return; 444 } 445 446 ev.preventDefault(); 447 stopSpin(); 448 }); 449 450 originalinput.on('mousewheel DOMMouseScroll', function(ev) { 451 if (!settings.mousewheel || !originalinput.is(':focus')) { 452 return; 453 } 454 455 var delta = ev.originalEvent.wheelDelta || -ev.originalEvent.deltaY || -ev.originalEvent.detail; 456 457 ev.stopPropagation(); 458 ev.preventDefault(); 459 460 if (delta < 0) { 461 downOnce(); 462 } 463 else { 464 upOnce(); 465 } 466 }); 467 } 468 469 function _bindEventsInterface() { 470 originalinput.on('touchspin.uponce', function() { 471 stopSpin(); 472 upOnce(); 473 }); 474 475 originalinput.on('touchspin.downonce', function() { 476 stopSpin(); 477 downOnce(); 478 }); 479 480 originalinput.on('touchspin.startupspin', function() { 481 startUpSpin(); 482 }); 483 484 originalinput.on('touchspin.startdownspin', function() { 485 startDownSpin(); 486 }); 487 488 originalinput.on('touchspin.stopspin', function() { 489 stopSpin(); 490 }); 491 492 originalinput.on('touchspin.updatesettings', function(e, newsettings) { 493 changeSettings(newsettings); 494 }); 495 } 496 497 function _forcestepdivisibility(value) { 498 switch (settings.forcestepdivisibility) { 499 case 'round': 500 return (Math.round(value / settings.step) * settings.step).toFixed(settings.decimals); 501 case 'floor': 502 return (Math.floor(value / settings.step) * settings.step).toFixed(settings.decimals); 503 case 'ceil': 504 return (Math.ceil(value / settings.step) * settings.step).toFixed(settings.decimals); 505 default: 506 return value; 507 } 508 } 509 510 function _checkValue() { 511 var val, parsedval, returnval; 512 513 val = originalinput.val(); 514 515 if (val === '') { 516 if (settings.replacementval !== '') { 517 originalinput.val(settings.replacementval); 518 originalinput.trigger('change'); 519 } 520 return; 521 } 522 523 if (settings.decimals > 0 && val === '.') { 524 return; 525 } 526 527 parsedval = parseFloat(val); 528 529 if (isNaN(parsedval)) { 530 if (settings.replacementval !== '') { 531 parsedval = settings.replacementval; 532 } 533 else { 534 parsedval = 0; 535 } 536 } 537 538 returnval = parsedval; 539 540 if (parsedval.toString() !== val) { 541 returnval = parsedval; 542 } 543 544 if (parsedval < settings.min) { 545 returnval = settings.min; 546 } 547 548 if (parsedval > settings.max) { 549 returnval = settings.max; 550 } 551 552 returnval = _forcestepdivisibility(returnval); 553 554 if (Number(val).toString() !== returnval.toString()) { 555 originalinput.val(returnval); 556 originalinput.trigger('change'); 557 } 558 } 559 560 function _getBoostedStep() { 561 if (!settings.booster) { 562 return settings.step; 563 } 564 else { 565 var boosted = Math.pow(2, Math.floor(spincount / settings.boostat)) * settings.step; 566 567 if (settings.maxboostedstep) { 568 if (boosted > settings.maxboostedstep) { 569 boosted = settings.maxboostedstep; 570 value = Math.round((value / boosted)) * boosted; 571 } 572 } 573 574 return Math.max(settings.step, boosted); 575 } 576 } 577 578 function upOnce() { 579 _checkValue(); 580 581 value = parseFloat(elements.input.val()); 582 if (isNaN(value)) { 583 value = 0; 584 } 585 586 var initvalue = value, 587 boostedstep = _getBoostedStep(); 588 589 value = value + boostedstep; 590 591 if (value > settings.max) { 592 value = settings.max; 593 originalinput.trigger('touchspin.on.max'); 594 stopSpin(); 595 } 596 597 elements.input.val(Number(value).toFixed(settings.decimals)); 598 599 if (initvalue !== value) { 600 originalinput.trigger('change'); 601 } 602 } 603 604 function downOnce() { 605 _checkValue(); 606 607 value = parseFloat(elements.input.val()); 608 if (isNaN(value)) { 609 value = 0; 610 } 611 612 var initvalue = value, 613 boostedstep = _getBoostedStep(); 614 615 value = value - boostedstep; 616 617 if (value < settings.min) { 618 value = settings.min; 619 originalinput.trigger('touchspin.on.min'); 620 stopSpin(); 621 } 622 623 elements.input.val(value.toFixed(settings.decimals)); 624 625 if (initvalue !== value) { 626 originalinput.trigger('change'); 627 } 628 } 629 630 function startDownSpin() { 631 stopSpin(); 632 633 spincount = 0; 634 spinning = 'down'; 635 636 originalinput.trigger('touchspin.on.startspin'); 637 originalinput.trigger('touchspin.on.startdownspin'); 638 639 downDelayTimeout = setTimeout(function() { 640 downSpinTimer = setInterval(function() { 641 spincount++; 642 downOnce(); 643 }, settings.stepinterval); 644 }, settings.stepintervaldelay); 645 } 646 647 function startUpSpin() { 648 stopSpin(); 649 650 spincount = 0; 651 spinning = 'up'; 652 653 originalinput.trigger('touchspin.on.startspin'); 654 originalinput.trigger('touchspin.on.startupspin'); 655 656 upDelayTimeout = setTimeout(function() { 657 upSpinTimer = setInterval(function() { 658 spincount++; 659 upOnce(); 660 }, settings.stepinterval); 661 }, settings.stepintervaldelay); 662 } 663 664 function stopSpin() { 665 clearTimeout(downDelayTimeout); 666 clearTimeout(upDelayTimeout); 667 clearInterval(downSpinTimer); 668 clearInterval(upSpinTimer); 669 670 switch (spinning) { 671 case 'up': 672 originalinput.trigger('touchspin.on.stopupspin'); 673 originalinput.trigger('touchspin.on.stopspin'); 674 break; 675 case 'down': 676 originalinput.trigger('touchspin.on.stopdownspin'); 677 originalinput.trigger('touchspin.on.stopspin'); 678 break; 679 } 680 681 spincount = 0; 682 spinning = false; 683 } 684 685 }); 686 687 }; 688 689 })(jQuery);