/** * Author and copyright: Stefan Haack (https://shaack.com) * Repository: https://github.com/shaack/bootstrap-input-spinner * License: MIT, see file 'LICENSE' */ //** The file has been modified (function ($) { "use strict"; var triggerKeyPressed = false; var originalVal = $.fn.val; $.fn.val = function (value) { if (arguments.length >= 1) { if (this[0] && this[0]["bootstrap-input-spinner"] && this[0].setValue) { var element = this[0]; setTimeout(function () { element.setValue(value); }); } } return originalVal.apply(this, arguments); }; $.fn.InputSpinner = $.fn.inputSpinner = function (options) { var config = { decrementButton: "-", // button text incrementButton: "+", // .. groupClass: "", // css class of the resulting input-group buttonsClass: "btn-outline-secondary", buttonsWidth: "2.5rem", textAlign: "center", autoDelay: 500, // ms holding before auto value change autoInterval: 100, // speed of auto value change boostThreshold: 10, // boost after these steps boostMultiplier: "auto", // you can also set a constant number as multiplier }; for (var option in options) { config[option] = options[option]; } var html = '
' + '
' + '" + "
" + '' + '
' + '" + "
" + "
"; var locale = navigator.language || "en-US"; this.each(function () { var $original = $(this); $original[0]["bootstrap-input-spinner"] = true; $original.hide(); var autoDelayHandler = null; var autoIntervalHandler = null; var autoMultiplier = config.boostMultiplier === "auto"; var boostMultiplier = autoMultiplier ? 1 : config.boostMultiplier; var $inputGroup = $(html); var $buttonDecrement = $inputGroup.find(".btn-decrement"); var $buttonIncrement = $inputGroup.find(".btn-increment"); var $input = $inputGroup.find("input"); var min = null; var max = null; var step = null; var stepMax = null; var decimals = null; var digitGrouping = null; var numberFormat = null; updateAttributes(); var value = parseFloat($original[0].value); var boostStepsCount = 0; var prefix = $original.attr("data-prefix") || ""; var suffix = $original.attr("data-suffix") || ""; if (prefix) { var prefixElement = $( '' + prefix + "" ); $inputGroup.find(".input-group-prepend").append(prefixElement); } if (suffix) { var suffixElement = $( '' + suffix + "" ); $inputGroup.find(".input-group-append").prepend(suffixElement); } $original[0].setValue = function (newValue) { setValue(newValue); }; var observer = new MutationObserver(function () { updateAttributes(); setValue(value, true); }); observer.observe($original[0], { attributes: true }); $original.after($inputGroup); setValue(value); $input.on("paste input change focusout", function (event) { var newValue = $input[0].value; var focusOut = event.type === "focusout"; newValue = parseLocaleNumber(newValue); setValue(newValue, focusOut); dispatchEvent($original, event.type); }); onPointerDown($buttonDecrement[0], function () { stepHandling(-step); }); onPointerDown($buttonIncrement[0], function () { stepHandling(step); }); onPointerUp(document.body, function () { resetTimer(); }); function setValue(newValue, updateInput) { if (updateInput === undefined) { updateInput = true; } if (isNaN(newValue) || newValue === "") { $original[0].value = ""; if (updateInput) { $input[0].value = ""; } value = NaN; } else { newValue = parseFloat(newValue); newValue = Math.min(Math.max(newValue, min), max); newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals); $original[0].value = newValue; if (updateInput) { $input[0].value = numberFormat.format(newValue); } value = newValue; } } function dispatchEvent($element, type) { if (type) { setTimeout(function () { var event; if (typeof Event === "function") { event = new Event(type, { bubbles: true }); } else { // IE event = document.createEvent("Event"); event.initEvent(type, true, true); } $element[0].dispatchEvent(event); }); } } function stepHandling(step) { if (!$input[0].disabled && !$input[0].readOnly) { calcStep(step); resetTimer(); autoDelayHandler = setTimeout(function () { autoIntervalHandler = setInterval(function () { if (boostStepsCount > config.boostThreshold) { if (autoMultiplier) { calcStep(step * parseInt(boostMultiplier, 10)); if (boostMultiplier < 100000000) { boostMultiplier = boostMultiplier * 1.1; } if (stepMax) { boostMultiplier = Math.min(stepMax, boostMultiplier); } } else { calcStep(step * boostMultiplier); } } else { calcStep(step); } boostStepsCount++; }, config.autoInterval); }, config.autoDelay); } } function calcStep(step) { if (isNaN(value)) { value = 0; } setValue(Math.round(value / step) * step + step); dispatchEvent($original, "input"); dispatchEvent($original, "change"); } function resetTimer() { boostStepsCount = 0; boostMultiplier = boostMultiplier = autoMultiplier ? 1 : config.boostMultiplier; clearTimeout(autoDelayHandler); clearTimeout(autoIntervalHandler); } function updateAttributes() { // copy properties from original to the new input $input.prop("required", $original.prop("required")); $input.prop("placeholder", $original.prop("placeholder")); $input.attr("inputmode", $original.attr("inputmode") || "decimal"); var disabled = $original.prop("disabled"); var readonly = $original.prop("readonly"); $input.prop("disabled", disabled); $input.prop("readonly", readonly); $buttonIncrement.prop("disabled", disabled || readonly); $buttonDecrement.prop("disabled", disabled || readonly); if (disabled || readonly) { resetTimer(); } var originalClass = $original.prop("class"); var groupClass = ""; // sizing if (/form-control-sm/g.test(originalClass)) { groupClass = "input-group-sm"; } else if (/form-control-lg/g.test(originalClass)) { groupClass = "input-group-lg"; } var inputClass = originalClass.replace(/form-control(-(sm|lg))?/g, ""); $inputGroup.prop( "class", "input-group " + groupClass + " " + config.groupClass ); $input.prop("class", "form-control " + inputClass); // update the main attributes min = parseFloat($original.prop("min")) || 0; max = isNaN($original.prop("max")) || $original.prop("max") === "" ? Infinity : parseFloat($original.prop("max")); step = parseFloat($original.prop("step")) || 1; stepMax = parseInt($original.attr("data-step-max")) || 0; var newDecimals = parseInt($original.attr("data-decimals")) || 0; var newDigitGrouping = !( $original.attr("data-digit-grouping") === "false" ); if (decimals !== newDecimals || digitGrouping !== newDigitGrouping) { decimals = newDecimals; digitGrouping = newDigitGrouping; numberFormat = new Intl.NumberFormat(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals, useGrouping: digitGrouping, }); } } function parseLocaleNumber(stringNumber) { var numberFormat = new Intl.NumberFormat(locale); var thousandSeparator = numberFormat.format(1111).replace(/1/g, ""); var decimalSeparator = numberFormat.format(1.1).replace(/1/g, ""); return parseFloat( stringNumber .replace(new RegExp("\\" + thousandSeparator, "g"), "") .replace(new RegExp("\\" + decimalSeparator), ".") ); } }); return this; }; function onPointerUp(element, callback) { element.addEventListener("mouseup", function (e) { callback(e); }); element.addEventListener("touchend", function (e) { callback(e); }); element.addEventListener("keyup", function (e) { if (e.keyCode === 32 || e.keyCode === 13) { triggerKeyPressed = false; callback(e); } }); } function onPointerDown(element, callback) { element.addEventListener("mousedown", function (e) { e.preventDefault(); callback(e); }); element.addEventListener("touchstart", function (e) { if (e.cancelable) { e.preventDefault(); } callback(e); }); element.addEventListener("keydown", function (e) { if ((e.keyCode === 32 || e.keyCode === 13) && !triggerKeyPressed) { triggerKeyPressed = true; callback(e); } }); } })(jQuery);