1/** 2 * Author and copyright: Stefan Haack (https://shaack.com) 3 * Repository: https://github.com/shaack/bootstrap-input-spinner 4 * License: MIT, see file 'LICENSE' 5 */ 6 7;(function ($) { 8 "use strict" 9 10 var triggerKeyPressed = false 11 var originalVal = $.fn.val 12 $.fn.val = function (value) { 13 if (arguments.length >= 1) { 14 if (this[0] && this[0]["bootstrap-input-spinner"] && this[0].setValue) { 15 var element = this[0] 16 setTimeout(function () { 17 element.setValue(value) 18 }) 19 } 20 } 21 return originalVal.apply(this, arguments) 22 } 23 24 $.fn.InputSpinner = $.fn.inputSpinner = function (options) { 25 26 var config = { 27 decrementButton: "<strong>-</strong>", // button text 28 incrementButton: "<strong>+</strong>", // .. 29 groupClass: "", // css class of the resulting input-group 30 buttonsClass: "btn-outline-secondary", 31 // buttonsWidth: "2.5rem", 32 buttonsWidth: "50px", 33 textAlign: "center", 34 autoDelay: 500, // ms holding before auto value change 35 autoInterval: 100, // speed of auto value change 36 boostThreshold: 10, // boost after these steps 37 boostMultiplier: "auto" // you can also set a constant number as multiplier 38 } 39 for (var option in options) { 40 config[option] = options[option] 41 } 42 43 var html = '<div class="input-group ' + config.groupClass + '">' + 44 '<div class="input-group-prepend">' + 45 '<button style="min-width: ' + config.buttonsWidth + '" class="btn btn-decrement ' + config.buttonsClass + '" type="button">' + config.decrementButton + '</button>' + 46 '</div>' + 47 '<input type="text" inputmode="decimal" style="background-color:white;min-width:35px;text-align: ' + config.textAlign + '" class="form-control"/>' + 48 '<div class="input-group-append">' + 49 '<button style="min-width: ' + config.buttonsWidth + '" class="btn btn-increment ' + config.buttonsClass + '" type="button">' + config.incrementButton + '</button>' + 50 '</div>' + 51 '</div>' 52 53 var locale = navigator.language || "en-US" 54 55 this.each(function () { 56 57 var $original = $(this) 58 $original[0]["bootstrap-input-spinner"] = true 59 $original.hide() 60 61 var autoDelayHandler = null 62 var autoIntervalHandler = null 63 var autoMultiplier = config.boostMultiplier === "auto" 64 var boostMultiplier = autoMultiplier ? 1 : config.boostMultiplier 65 66 var $inputGroup = $(html) 67 var $buttonDecrement = $inputGroup.find(".btn-decrement") 68 var $buttonIncrement = $inputGroup.find(".btn-increment") 69 var $input = $inputGroup.find("input") 70 71 var min = null 72 var max = null 73 var step = null 74 var stepMax = null 75 var decimals = null 76 var digitGrouping = null 77 var numberFormat = null 78 79 updateAttributes() 80 81 var value = parseFloat($original[0].value) 82 var boostStepsCount = 0 83 84 var prefix = $original.attr("data-prefix") || "" 85 var suffix = $original.attr("data-suffix") || "" 86 87 if (prefix) { 88 var prefixElement = $('<span class="input-group-text">' + prefix + '</span>') 89 $inputGroup.find(".input-group-prepend").append(prefixElement) 90 } 91 if (suffix) { 92 var suffixElement = $('<span class="input-group-text">' + suffix + '</span>') 93 $inputGroup.find(".input-group-append").prepend(suffixElement) 94 } 95 96 $original[0].setValue = function (newValue) { 97 setValue(newValue) 98 } 99 100 var observer = new MutationObserver(function () { 101 updateAttributes() 102 setValue(value, true) 103 }) 104 observer.observe($original[0], {attributes: true}) 105 106 $original.after($inputGroup) 107 108 setValue(value) 109 110 $input.on("paste input change focusout", function (event) { 111 var newValue = $input[0].value 112 var focusOut = event.type === "focusout" 113 newValue = parseLocaleNumber(newValue) 114 setValue(newValue, focusOut) 115 dispatchEvent($original, event.type) 116 }) 117 118 onPointerDown($buttonDecrement[0], function () { 119 stepHandling(-step) 120 }) 121 onPointerDown($buttonIncrement[0], function () { 122 stepHandling(step) 123 }) 124 onPointerUp(document.body, function () { 125 resetTimer() 126 }) 127 128 function setValue(newValue, updateInput) { 129 if (updateInput === undefined) { 130 updateInput = true 131 } 132 if (isNaN(newValue) || newValue === "") { 133 $original[0].value = "" 134 if (updateInput) { 135 $input[0].value = "" 136 } 137 value = NaN 138 } else { 139 newValue = parseFloat(newValue) 140 newValue = Math.min(Math.max(newValue, min), max) 141 newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals) 142 $original[0].value = newValue 143 if (updateInput) { 144 $input[0].value = numberFormat.format(newValue) 145 } 146 value = newValue 147 } 148 } 149 150 function dispatchEvent($element, type) { 151 if (type) { 152 setTimeout(function () { 153 var event 154 if (typeof (Event) === 'function') { 155 event = new Event(type, {bubbles: true}) 156 } else { // IE 157 event = document.createEvent('Event') 158 event.initEvent(type, true, true) 159 } 160 $element[0].dispatchEvent(event) 161 }) 162 } 163 } 164 165 function stepHandling(step) { 166 if (!$input[0].disabled && !$input[0].readOnly) { 167 calcStep(step) 168 resetTimer() 169 autoDelayHandler = setTimeout(function () { 170 autoIntervalHandler = setInterval(function () { 171 if (boostStepsCount > config.boostThreshold) { 172 if (autoMultiplier) { 173 calcStep(step * parseInt(boostMultiplier, 10)) 174 if (boostMultiplier < 100000000) { 175 boostMultiplier = boostMultiplier * 1.1 176 } 177 if (stepMax) { 178 boostMultiplier = Math.min(stepMax, boostMultiplier) 179 } 180 } else { 181 calcStep(step * boostMultiplier) 182 } 183 } else { 184 calcStep(step) 185 } 186 boostStepsCount++ 187 }, config.autoInterval) 188 }, config.autoDelay) 189 } 190 } 191 192 function calcStep(step) { 193 if (isNaN(value)) { 194 value = 0 195 } 196 setValue(Math.round(value / step) * step + step) 197 dispatchEvent($original, "input") 198 dispatchEvent($original, "change") 199 } 200 201 function resetTimer() { 202 boostStepsCount = 0 203 boostMultiplier = boostMultiplier = autoMultiplier ? 1 : config.boostMultiplier 204 clearTimeout(autoDelayHandler) 205 clearTimeout(autoIntervalHandler) 206 } 207 208 function updateAttributes() { 209 // copy properties from original to the new input 210 $input.prop("required", $original.prop("required")) 211 $input.prop("placeholder", $original.prop("placeholder")) 212 $input.attr("inputmode", $original.attr("inputmode") || "decimal") 213 var disabled = $original.prop("disabled") 214 var readonly = $original.prop("readonly") 215 $input.prop("disabled", disabled) 216 $input.prop("readonly", readonly) 217 $buttonIncrement.prop("disabled", disabled || readonly) 218 $buttonDecrement.prop("disabled", disabled || readonly) 219 if (disabled || readonly) { 220 resetTimer() 221 } 222 var originalClass = $original.prop("class") 223 var groupClass = "" 224 // sizing 225 if (/form-control-sm/g.test(originalClass)) { 226 groupClass = "input-group-sm" 227 } else if (/form-control-lg/g.test(originalClass)) { 228 groupClass = "input-group-lg" 229 } 230 var inputClass = originalClass.replace(/form-control(-(sm|lg))?/g, "") 231 $inputGroup.prop("class", "input-group " + groupClass + " " + config.groupClass) 232 $input.prop("class", "form-control " + inputClass) 233 234 // update the main attributes 235 min = parseFloat($original.prop("min")) || 0 236 max = isNaN($original.prop("max")) || $original.prop("max") === "" ? Infinity : parseFloat($original.prop("max")) 237 step = parseFloat($original.prop("step")) || 1 238 stepMax = parseInt($original.attr("data-step-max")) || 0 239 var newDecimals = parseInt($original.attr("data-decimals")) || 0 240 var newDigitGrouping = !($original.attr("data-digit-grouping") === "fal
240se") 241 if (decimals !== newDecimals || digitGrouping !== newDigitGrouping) { 242 decimals = newDecimals 243 digitGrouping = newDigitGrouping 244 numberFormat = new Intl.NumberFormat(locale, { 245 minimumFractionDigits: decimals, 246 maximumFractionDigits: decimals, 247 useGrouping: digitGrouping 248 }) 249 } 250 } 251 252 function parseLocaleNumber(stringNumber) { 253 var numberFormat = new Intl.NumberFormat(locale) 254 var thousandSeparator = numberFormat.format(1111).replace(/1/g, '') 255 var decimalSeparator = numberFormat.format(1.1).replace(/1/g, '') 256 return parseFloat(stringNumber 257 .replace(new RegExp('\\' + thousandSeparator, 'g'), '') 258 .replace(new RegExp('\\' + decimalSeparator), '.') 259 ) 260 } 261 }) 262 263 return this 264 } 265 266 function onPointerUp(element, callback) { 267 element.addEventListener("mouseup", function (e) { 268 callback(e) 269 }) 270 element.addEventListener("touchend", function (e) { 271 callback(e) 272 }) 273 element.addEventListener("keyup", function (e) { 274 if ((e.keyCode === 32 || e.keyCode === 13)) { 275 triggerKeyPressed = false 276 callback(e) 277 } 278 }) 279 } 280 281 function onPointerDown(element, callback) { 282 element.addEventListener("mousedown", function (e) { 283 e.preventDefault() 284 callback(e) 285 }) 286 element.addEventListener("touchstart", function (e) { 287 if (e.cancelable) { 288 e.preventDefault() 289 } 290 callback(e) 291 }) 292 element.addEventListener("keydown", function (e) { 293 if ((e.keyCode === 32 || e.keyCode === 13) && !triggerKeyPressed) { 294 triggerKeyPressed = true 295 callback(e) 296 } 297 }) 298 } 299 300}(jQuery))
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.