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