690 lines
19 KiB
JavaScript
690 lines
19 KiB
JavaScript
(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 = '<span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span>',
|
|
postfixhtml = '<span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span>';
|
|
|
|
if (prev.hasClass('input-group-btn')) {
|
|
downhtml = '<button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button>';
|
|
prev.append(downhtml);
|
|
}
|
|
else {
|
|
downhtml = '<span class="input-group-btn"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button></span>';
|
|
$(downhtml).insertBefore(originalinput);
|
|
}
|
|
|
|
if (next.hasClass('input-group-btn')) {
|
|
uphtml = '<button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button>';
|
|
next.prepend(uphtml);
|
|
}
|
|
else {
|
|
uphtml = '<span class="input-group-btn"><button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button></span>';
|
|
$(uphtml).insertAfter(originalinput);
|
|
}
|
|
|
|
$(prefixhtml).insertBefore(originalinput);
|
|
$(postfixhtml).insertAfter(originalinput);
|
|
|
|
container = parentelement;
|
|
}
|
|
|
|
function _buildInputGroup() {
|
|
var html;
|
|
|
|
if (settings.verticalbuttons) {
|
|
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>';
|
|
}
|
|
else {
|
|
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>';
|
|
}
|
|
|
|
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);
|