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 // the default editor for parsing and rendering 11 var I18nEditor = function (props, element) { 12 var locale = props.locale || "en-US" 13 14 this.parse = function (customFormat) { 15 var numberFormat = new Intl.NumberFormat(locale) 16 var thousandSeparator = numberFormat.format(11111).replace(/1/g, '') || '.' 17 var decimalSeparator = numberFormat.format(1.1).replace(/1/g, '') 18 return parseFloat(customFormat 19 .replace(new RegExp(' ', 'g'), '') 20 .replace(new RegExp('\\' + thousandSeparator, 'g'), '') 21 .replace(new RegExp('\\' + decimalSeparator), '.') 22 ) 23 } 24 25 this.render = function (number) { 26 var decimals = parseInt(element.getAttribute("data-decimals")) || 0 27 var digitGrouping = !(element.getAttribute("data-digit-grouping") === "false") 28 var numberFormat = new Intl.NumberFormat(locale, { 29 minimumFractionDigits: decimals, 30 maximumFractionDigits: decimals, 31 useGrouping: digitGrouping 32 }) 33 return numberFormat.format(number) 34 } 35 } 36 37 var triggerKeyPressed = false 38 var originalVal = $.fn.val 39 $.fn.val = function (value) { 40 if (arguments.length >= 1) { 41 for (var i = 0; i < this.length; i++) { 42 if (this[i]["bootstrap-input-spinner"] && this[i].setValue) { 43 this[i].setValue(value) 44 } 45 } 46 } 47 return originalVal.apply(this, arguments) 48 } 49 50 $.fn.inputSpinner = function (methodOrProps) { 51 52 if (methodOrProps === "destroy") { 53 this.each(function () { 54 if (this["bootstrap-input-spinner"]) { 55 this.destroyInputSpinner() 56 } else { 57 console.warn("element", this, "is no bootstrap-input-spinner") 58 } 59 }) 60 return this 61 } 62 63 var props = { 64 decrementButton: "<strong>−</strong>", // button text 65 incrementButton: "<strong>+</strong>", // .. 66 groupClass: "", // css class of the resulting input-group 67 buttonsClass: "btn-outline-secondary", 68 buttonsWidth: "2.5rem", 69 textAlign: "center", // alignment of the entered number 70 autoDelay: 500, // ms threshold before auto value change 71 autoInterval: 50, // speed of auto value change 72 buttonsOnly: false, // set this `true` to disable the possibility to enter or paste the number via keyboard 73 keyboardStepping: true, // set this to `false` to disallow the use of the up and down arrow keys to step 74 locale: navigator.language, // the locale, per default detected automatically from the browser 75 editor: I18nEditor, // the editor (parsing and rendering of the input) 76 template: // the template of the input 77 '<div class="input-group ${groupClass}">' + 78 '<div class="input-group-prepend"><button style="min-width: ${buttonsWidth}" class="btn btn-decrement ${buttonsClass} btn-minus" type="button">${decrementButton}</button></div>' + 79 '<input type="text" inputmode="decimal" style="text-align: ${textAlign}" class="form-control form-control-text-input"/>' + 80 '<div class="input-group-append"><button style="min-width: ${buttonsWidth}" class="btn btn-increment ${buttonsClass} btn-plus" type="button">${incrementButton}</button></div>' + 81 '</div>' 82 } 83 84 for (var option in methodOrProps) { 85 // noinspection JSUnfilteredForInLoop 86 props[option] = methodOrProps[option] 87 } 88 89 var html = props.template 90 .replace(/\${groupClass}/g, props.groupClass) 91 .replace(/\${buttonsWidth}/g, props.buttonsWidth) 92 .replace(/\${buttonsClass}/g, props.buttonsClass) 93 .replace(/\${decrementButton}/g, props.decrementButton) 94 .replace(/\${incrementButton}/g, props.incrementButton) 95 .replace(/\${textAlign}/g, props.textAlign) 96 97 this.each(function () { 98 99 if (this["bootstrap-input-spinner"]) { 100 console.warn("element", this, "is already a bootstrap-input-spinner") 101 } else { 102 103 var $original = $(this) 104 $original[0]["bootstrap-input-spinner"] = true 105 $original.hide() 106 $original[0].inputSpinnerEditor = new props.editor(props, this) 107 108 var autoDelayHandler = null 109 var autoIntervalHandler = null 110 111 var $inputGroup = $(html) 112 var $buttonDecrement = $inputGroup.find(".btn-decrement") 113 var $buttonIncrement = $inputGroup.find(".btn-increment") 114 var $input = $inputGroup.find("input") 115 var $label = $("label[for='" + $original.attr("id") + "']") 116 if (!$label[0]) { 117 $label = $original.closest("label") 118 } 119 120 var min = null 121 var max = null 122 var step = null 123 124 updateAttributes() 125 126 var value = parseFloat($original[0].value) 127 128 var prefix = $original.attr("data-prefix") || "" 129 var suffix = $original.attr("data-suffix") || "" 130 131 if (prefix) { 132 var prefixElement = $('<span class="input-group-text">' + prefix + '</span>') 133 $inputGroup.find(".input-group-prepend").append(prefixElement) 134 } 135 if (suffix) { 136 var suffixElement = $('<span class="input-group-text">' + suffix + '</span>') 137 $inputGroup.find(".input-group-append").prepend(suffixElement) 138 } 139 140 $original[0].setValue = function (newValue) { 141 setValue(newValue) 142 } 143 $original[0].destroyInputSpinner = function () { 144 destroy() 145 } 146 147 var observer = new MutationObserver(function () { 148 updateAttributes() 149 setValue(value, true) 150 }) 151 observer.observe($original[0], {attributes: true}) 152 153 $original.after($inputGroup) 154 155 setValue(value) 156 157 $input.on("paste input change focusout", function (event) { 158 var newValue = $input[0].value 159 var focusOut = event.type === "focusout" 160 newValue = $original[0].inputSpinnerEditor.parse(newValue) 161 setValue(newValue, focusOut) 162 dispatchEvent($original, event.type) 163 }).on("keydown", function (event) { 164 if (props.keyboardStepping) { 165 if (event.which === 38) { // up arrow pressed 166 event.preventDefault() 167 if (!$buttonDecrement.prop("disabled")) { 168 stepHandling(step) 169 } 170 } else if (event.which === 40) { // down arrow pressed 171 event.preventDefault() 172 if (!$buttonIncrement.prop("disabled")) { 173 stepHandling(-step) 174 } 175 } 176 } 177 }).on("keyup", function (event) { 178 // up/down arrow released 179 if (props.keyboardStepping && (event.which === 38 || event.which === 40)) { 180 event.preventDefault() 181 resetTimer() 182 } 183 }) 184 185 onPointerDown($buttonDecrement[0], function () { 186 if (!$buttonDecrement.prop("disabled")) { 187 stepHandling(-step) 188 } 189 }) 190 onPointerDown($buttonIncrement[0], function () { 191 if (!$buttonIncrement.prop("disabled")) { 192 stepHandling(step) 193 } 194 }) 195 onPointerUp(document.body, function () { 196 resetTimer() 197 dispatchEvent($original, "change") 198 }) 199 } 200 201 function setValue(newValue, updateInput) { 202 if (updateInput === undefined) { 203 updateInput = true 204 } 205 if (isNaN(newValue) || newValue === "") { 206 $original[0].value = "" 207 if (updateInput) { 208 $input[0].value = "" 209 } 210 value = NaN 211 } else { 212 newValue = parseFloat(newValue) 213 newValue = Math.min(Math.max(newValue, min), max) 214 // newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals) 215 $original[0].value = newValue 216 if (updateInput) { 217 // $input[0].value = numberFormat.format(newValue) 218 $input[0].value = $original[0].inputSpinnerEditor.render(newValue) 219 } 220 value = newValue 221 } 222 } 223 224 function destroy() { 225 $original.prop("required", $input.prop("required")) 226 observer.disconnect() 227 resetTimer() 228 $input.off("paste input change focusout") 229 $inputGroup.remove() 230 $original.show() 231 $original[0]["bootstrap-input-spinner"] = undefined 232 if ($label[0]) { 233 $label.attr("for", $original.attr("id")) 234 } 235 } 236 237 function dispatchEvent($element, type) { 238 if (type) { 239 setTimeout(function () { 240 var event 241 if (typeof (Event) === 'function') { 242 event = new Event(type, {bubbles: true}) 243 } else { // IE 244 event = document.createEvent('Event') 245 event.initEvent(type, true, true) 246 } 247 $element[0].dispatchEvent(event) 248 }) 249 } 250 } 251 252 function stepHandling(step) { 253 calcStep(step) 254 resetTimer() 255 autoDelayHandler = setTimeout(function () { 256 autoIntervalHandler = setInterval(function () { 257 calcStep(step) 258 }, props.autoInterval) 259 }, props.autoDelay) 260 } 261 262 function calcStep(step) { 263 if (isNaN(value)) { 264 value = 0 265 } 266 setValue(Math.round(value / step) * step + step) 267 dispatchEvent($original, "input") 268 } 269 270 function resetTimer() { 271 clearTimeout(autoDelayHandler) 272 clearTimeout(autoIntervalHandler) 273 } 274 275 function updateAttributes() { 276 // copy properties from original to the new input 277 if ($original.prop("required")) { 278 $input.prop("required", $original.prop("required")) 279 $original.removeAttr('required') 280 } 281 $input.prop("placeholder", $original.prop("placeholder")) 282 $input.attr("inputmode", $original.attr("inputmode") || "decimal") 283 var disabled = $original.prop("disabled") 284 var readonly = $original.prop("readonly") 285 $input.prop("disabled", disabled) 286 $input.prop("readonly", readonly || props.buttonsOnly) 287 $buttonIncrement.prop("disabled", disabled || readonly) 288 $buttonDecrement.prop("disabled", disabled || readonly) 289 if (disabled || readonly) { 290 resetTimer() 291 } 292 var originalClass = $original.prop("class") 293 var groupClass = "" 294 // sizing 295 if (/form-control-sm/g.test(originalClass)) { 296 groupClass = "input-group-sm" 297 } else if (/form-control-lg/g.test(originalClass)) { 298 groupClass = "input-group-lg" 299 } 300 var inputClass = originalClass.replace(/form-control(-(sm|lg))?/g, "") 301 $inputGroup.prop("class", "input-group " + groupClass + " " + props.groupClass) 302 $input.prop("class", "form-control " + inputClass) 303 304 // update the main attributes 305 min = isNaN($original.prop("min")) || $original.prop("min") === "" ? -Infinity : parseFloat($original.prop("min")) 306 max = isNaN($original.prop("max")) || $original.prop("max") === "" ? Infinity : parseFloat($original.prop("max")) 307 step = parseFloat($original.prop("step")) || 1 308 if ($original.attr("hidden")) { 309 $inputGroup.attr("hidden", $original.attr("hidden")) 310 } else { 311 $inputGroup.removeAttr("hidden") 312 } 313 if ($original.attr("id")) { 314 $input.attr("id", $original.attr("id") + "_MP_cBdLN29i2") 315 if ($label[0]) { 316 $label.attr("for", $input.attr("id")) 317 } 318 } 319 } 320 }) 321 322 return this 323 } 324 325 function onPointerUp(element, callback) { 326 element.addEventListener("mouseup", function (e) { 327 callback(e) 328 }) 329 element.addEventListener("touchend", function (e) { 330 callback(e) 331 }) 332 element.addEventListener("keyup", function (e) { 333 if ((e.keyCode === 32 || e.keyCode === 13)) { 334 triggerKeyPressed = false 335 callback(e) 336 } 337 }) 338 } 339 340 function onPointerDown(element, callback) { 341 element.addEventListener("mousedown", function (e) { 342 if (e.button === 0) { 343 e.preventDefault() 344 callback(e) 345 } 346 }) 347 element.addEventListener("touchstart", function (e) { 348 if (e.cancelable) { 349 e.preventDefault() 350 } 351 callback(e) 352 }) 353 element.addEventListener("keydown", function (e) { 354 if ((e.keyCode === 32 || e.keyCode === 13) && !triggerKeyPressed) { 355 triggerKeyPressed = true 356 callback(e) 357 } 358 }) 359 } 360 361}(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.