(function($) { 'use strict'; var _currentspinnerid = 0; function _scopedeventname(name, id) { return name + '.touchspin_' + id; } function _scopeeventnames(names, id) { return $.map(names, function(name) { return _scopedeventname(name, id); }); } $.fn.touchspin = function(options) { if (options === 'destroy') { this.each(function() { var originalinput = $(this), originalinput_data = originalinput.data(); $(document).off(_scopeeventnames([ 'mouseup', 'touchend', 'touchcancel', 'mousemove', 'touchmove', 'scroll', 'scrollstart'], originalinput_data.spinnerid).join(' ')); }); return; } var defaults = { min: 0, max: 100, initval: '', replacementval: '', step: 1, decimals: 0, stepinterval: 100, forcestepdivisibility: 'round', // none | floor | round | ceil stepintervaldelay: 500, verticalbuttons: false, verticalupclass: 'glyphicon glyphicon-chevron-up', verticaldownclass: 'glyphicon glyphicon-chevron-down', prefix: '', postfix: '', prefix_extraclass: '', postfix_extraclass: '', booster: true, boostat: 10, maxboostedstep: false, mousewheel: true, buttondown_class: 'btn btn-default', buttonup_class: 'btn btn-default', buttondown_txt: '-', buttonup_txt: '+' }; var attributemap = { min: 'min', max: 'max', initval: 'init-val', replacementval: 'replacement-val', step: 'step', decimals: 'decimals', stepinterval: 'step-interval', verticalbuttons: 'vertical-buttons', verticalupclass: 'vertical-up-class', verticaldownclass: 'vertical-down-class', forcestepdivisibility: 'force-step-divisibility', stepintervaldelay: 'step-interval-delay', prefix: 'prefix', postfix: 'postfix', prefix_extraclass: 'prefix-extra-class', postfix_extraclass: 'postfix-extra-class', booster: 'booster', boostat: 'boostat', maxboostedstep: 'max-boosted-step', mousewheel: 'mouse-wheel', buttondown_class: 'button-down-class', buttonup_class: 'button-up-class', buttondown_txt: 'button-down-txt', buttonup_txt: 'button-up-txt' }; return this.each(function() { var settings, originalinput = $(this), originalinput_data = originalinput.data(), container, elements, value, downspintimer, upspintimer, downdelaytimeout, updelaytimeout, spincount = 0, spinning = false; init(); function init() { if (originalinput.data('alreadyinitialized')) { return; } originalinput.data('alreadyinitialized', true); _currentspinnerid += 1; originalinput.data('spinnerid', _currentspinnerid); if (!originalinput.is('input')) { console.log('must be an input.'); return; } _initsettings(); _setinitval(); _checkvalue(); _buildhtml(); _initelements(); _hideemptyprefixpostfix(); _bindevents(); _bindeventsinterface(); elements.input.css('display', 'block'); } function _setinitval() { if (settings.initval !== '' && originalinput.val() === '') { originalinput.val(settings.initval); } } function changesettings(newsettings) { _updatesettings(newsettings); _checkvalue(); var value = elements.input.val(); if (value !== '') { value = number(elements.input.val()); elements.input.val(value.tofixed(settings.decimals)); } } function _initsettings() { settings = $.extend({}, defaults, originalinput_data, _parseattributes(), options); } function _parseattributes() { var data = {}; $.each(attributemap, function(key, value) { var attrname = 'bts-' + value + ''; if (originalinput.is('[data-' + attrname + ']')) { data[key] = originalinput.data(attrname); } }); return data; } function _updatesettings(newsettings) { settings = $.extend({}, settings, newsettings); } function _buildhtml() { var initval = originalinput.val(), parentelement = originalinput.parent(); if (initval !== '') { initval = number(initval).tofixed(settings.decimals); } originalinput.data('initvalue', initval).val(initval); originalinput.addclass('form-control'); if (parentelement.hasclass('input-group')) { _advanceinputgroup(parentelement); } else { _buildinputgroup(); } } function _advanceinputgroup(parentelement) { parentelement.addclass('bootstrap-touchspin'); var prev = originalinput.prev(), next = originalinput.next(); var downhtml, uphtml, prefixhtml = '' + settings.prefix + '', postfixhtml = '' + settings.postfix + ''; if (prev.hasclass('input-group-btn')) { downhtml = ''; prev.append(downhtml); } else { downhtml = ''; $(downhtml).insertbefore(originalinput); } if (next.hasclass('input-group-btn')) { uphtml = ''; next.prepend(uphtml); } else { uphtml = ''; $(uphtml).insertafter(originalinput); } $(prefixhtml).insertbefore(originalinput); $(postfixhtml).insertafter(originalinput); container = parentelement; } function _buildinputgroup() { var html; if (settings.verticalbuttons) { html = '
' + settings.prefix + '' + settings.postfix + '
'; } else { html = '
' + settings.prefix + '' + settings.postfix + '
'; } container = $(html).insertbefore(originalinput); $('.bootstrap-touchspin-prefix', container).after(originalinput); if (originalinput.hasclass('input-sm')) { container.addclass('input-group-sm'); } else if (originalinput.hasclass('input-lg')) { container.addclass('input-group-lg'); } } function _initelements() { elements = { down: $('.bootstrap-touchspin-down', container), up: $('.bootstrap-touchspin-up', container), input: $('input', container), prefix: $('.bootstrap-touchspin-prefix', container).addclass(settings.prefix_extraclass), postfix: $('.bootstrap-touchspin-postfix', container).addclass(settings.postfix_extraclass) }; } function _hideemptyprefixpostfix() { if (settings.prefix === '') { elements.prefix.hide(); } if (settings.postfix === '') { elements.postfix.hide(); } } function _bindevents() { originalinput.on('keydown', function(ev) { var code = ev.keycode || ev.which; if (code === 38) { if (spinning !== 'up') { uponce(); startupspin(); } ev.preventdefault(); } else if (code === 40) { if (spinning !== 'down') { downonce(); startdownspin(); } ev.preventdefault(); } }); originalinput.on('keyup', function(ev) { var code = ev.keycode || ev.which; if (code === 38) { stopspin(); } else if (code === 40) { stopspin(); } }); originalinput.on('blur', function() { _checkvalue(); }); elements.down.on('keydown', function(ev) { var code = ev.keycode || ev.which; if (code === 32 || code === 13) { if (spinning !== 'down') { downonce(); startdownspin(); } ev.preventdefault(); } }); elements.down.on('keyup', function(ev) { var code = ev.keycode || ev.which; if (code === 32 || code === 13) { stopspin(); } }); elements.up.on('keydown', function(ev) { var code = ev.keycode || ev.which; if (code === 32 || code === 13) { if (spinning !== 'up') { uponce(); startupspin(); } ev.preventdefault(); } }); elements.up.on('keyup', function(ev) { var code = ev.keycode || ev.which; if (code === 32 || code === 13) { stopspin(); } }); elements.down.on('mousedown.touchspin', function(ev) { elements.down.off('touchstart.touchspin'); // android 4 workaround if (originalinput.is(':disabled')) { return; } downonce(); startdownspin(); ev.preventdefault(); ev.stoppropagation(); }); elements.down.on('touchstart.touchspin', function(ev) { elements.down.off('mousedown.touchspin'); // android 4 workaround if (originalinput.is(':disabled')) { return; } downonce(); startdownspin(); ev.preventdefault(); ev.stoppropagation(); }); elements.up.on('mousedown.touchspin', function(ev) { elements.up.off('touchstart.touchspin'); // android 4 workaround if (originalinput.is(':disabled')) { return; } uponce(); startupspin(); ev.preventdefault(); ev.stoppropagation(); }); elements.up.on('touchstart.touchspin', function(ev) { elements.up.off('mousedown.touchspin'); // android 4 workaround if (originalinput.is(':disabled')) { return; } uponce(); startupspin(); ev.preventdefault(); ev.stoppropagation(); }); elements.up.on('mouseout touchleave touchend touchcancel', function(ev) { if (!spinning) { return; } ev.stoppropagation(); stopspin(); }); elements.down.on('mouseout touchleave touchend touchcancel', function(ev) { if (!spinning) { return; } ev.stoppropagation(); stopspin(); }); elements.down.on('mousemove touchmove', function(ev) { if (!spinning) { return; } ev.stoppropagation(); ev.preventdefault(); }); elements.up.on('mousemove touchmove', function(ev) { if (!spinning) { return; } ev.stoppropagation(); ev.preventdefault(); }); $(document).on(_scopeeventnames(['mouseup', 'touchend', 'touchcancel'], _currentspinnerid).join(' '), function(ev) { if (!spinning) { return; } ev.preventdefault(); stopspin(); }); $(document).on(_scopeeventnames(['mousemove', 'touchmove', 'scroll', 'scrollstart'], _currentspinnerid).join(' '), function(ev) { if (!spinning) { return; } ev.preventdefault(); stopspin(); }); originalinput.on('mousewheel dommousescroll', function(ev) { if (!settings.mousewheel || !originalinput.is(':focus')) { return; } var delta = ev.originalevent.wheeldelta || -ev.originalevent.deltay || -ev.originalevent.detail; ev.stoppropagation(); ev.preventdefault(); if (delta < 0) { downonce(); } else { uponce(); } }); } function _bindeventsinterface() { originalinput.on('touchspin.uponce', function() { stopspin(); uponce(); }); originalinput.on('touchspin.downonce', function() { stopspin(); downonce(); }); originalinput.on('touchspin.startupspin', function() { startupspin(); }); originalinput.on('touchspin.startdownspin', function() { startdownspin(); }); originalinput.on('touchspin.stopspin', function() { stopspin(); }); originalinput.on('touchspin.updatesettings', function(e, newsettings) { changesettings(newsettings); }); } function _forcestepdivisibility(value) { switch (settings.forcestepdivisibility) { case 'round': return (math.round(value / settings.step) * settings.step).tofixed(settings.decimals); case 'floor': return (math.floor(value / settings.step) * settings.step).tofixed(settings.decimals); case 'ceil': return (math.ceil(value / settings.step) * settings.step).tofixed(settings.decimals); default: return value; } } function _checkvalue() { var val, parsedval, returnval; val = originalinput.val(); if (val === '') { if (settings.replacementval !== '') { originalinput.val(settings.replacementval); originalinput.trigger('change'); } return; } if (settings.decimals > 0 && val === '.') { return; } parsedval = parsefloat(val); if (isnan(parsedval)) { if (settings.replacementval !== '') { parsedval = settings.replacementval; } else { parsedval = 0; } } returnval = parsedval; if (parsedval.tostring() !== val) { returnval = parsedval; } if (parsedval < settings.min) { returnval = settings.min; } if (parsedval > settings.max) { returnval = settings.max; } returnval = _forcestepdivisibility(returnval); if (number(val).tostring() !== returnval.tostring()) { originalinput.val(returnval); originalinput.trigger('change'); } } function _getboostedstep() { if (!settings.booster) { return settings.step; } else { var boosted = math.pow(2, math.floor(spincount / settings.boostat)) * settings.step; if (settings.maxboostedstep) { if (boosted > settings.maxboostedstep) { boosted = settings.maxboostedstep; value = math.round((value / boosted)) * boosted; } } return math.max(settings.step, boosted); } } function uponce() { _checkvalue(); value = parsefloat(elements.input.val()); if (isnan(value)) { value = 0; } var initvalue = value, boostedstep = _getboostedstep(); value = value + boostedstep; if (value > settings.max) { value = settings.max; originalinput.trigger('touchspin.on.max'); stopspin(); } elements.input.val(number(value).tofixed(settings.decimals)); if (initvalue !== value) { originalinput.trigger('change'); } } function downonce() { _checkvalue(); value = parsefloat(elements.input.val()); if (isnan(value)) { value = 0; } var initvalue = value, boostedstep = _getboostedstep(); value = value - boostedstep; if (value < settings.min) { value = settings.min; originalinput.trigger('touchspin.on.min'); stopspin(); } elements.input.val(value.tofixed(settings.decimals)); if (initvalue !== value) { originalinput.trigger('change'); } } function startdownspin() { stopspin(); spincount = 0; spinning = 'down'; originalinput.trigger('touchspin.on.startspin'); originalinput.trigger('touchspin.on.startdownspin'); downdelaytimeout = settimeout(function() { downspintimer = setinterval(function() { spincount++; downonce(); }, settings.stepinterval); }, settings.stepintervaldelay); } function startupspin() { stopspin(); spincount = 0; spinning = 'up'; originalinput.trigger('touchspin.on.startspin'); originalinput.trigger('touchspin.on.startupspin'); updelaytimeout = settimeout(function() { upspintimer = setinterval(function() { spincount++; uponce(); }, settings.stepinterval); }, settings.stepintervaldelay); } function stopspin() { cleartimeout(downdelaytimeout); cleartimeout(updelaytimeout); clearinterval(downspintimer); clearinterval(upspintimer); switch (spinning) { case 'up': originalinput.trigger('touchspin.on.stopupspin'); originalinput.trigger('touchspin.on.stopspin'); break; case 'down': originalinput.trigger('touchspin.on.stopdownspin'); originalinput.trigger('touchspin.on.stopspin'); break; } spincount = 0; spinning = false; } }); }; })(jquery);