(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);