You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

jquery.bootstrap-touchspin.js 19KB

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