Built files from Bizgaze WebServer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bootstrap-input-spinner.js 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. (function ($) {
  7. "use strict"
  8. var spacePressed = false
  9. var originalVal = $.fn.val
  10. $.fn.val = function (value) {
  11. if (arguments.length >= 1) {
  12. if (this[0]["bootstrap-input-spinner"] && this[0].setValue) {
  13. this[0].setValue(value)
  14. }
  15. }
  16. return originalVal.apply(this, arguments)
  17. }
  18. $.fn.InputSpinner = $.fn.inputSpinner = function (options) {
  19. var config = {
  20. decrementButton: "<strong>-</strong>", // button text
  21. incrementButton: "<strong>+</strong>", // ..
  22. groupClass: "", // css class of the input-group (sizing with input-group-sm, input-group-lg)
  23. buttonsClass: "btn-outline-secondary",
  24. buttonsWidth: "2.5em",
  25. textAlign: "center",
  26. autoDelay: 500, // ms holding before auto value change
  27. autoInterval: 100, // speed of auto value change
  28. boostThreshold: 10, // boost after these steps
  29. boostMultiplier: "auto", // you can also set a constant number as multiplier
  30. locale: null // the locale for number rendering; if null, the browsers language is used
  31. }
  32. Object.assign(config, options)
  33. var html = '<div class="input-group ' + config.groupClass + '">' +
  34. '<div class="input-group-prepend">' +
  35. '<button style="min-width: ' + config.buttonsWidth + '" class="btn btn-decrement ' + config.buttonsClass + '" type="button">' + config.decrementButton + '</button>' +
  36. '</div>' +
  37. '<input type="text" style="text-align: ' + config.textAlign + '" class="form-control"/>' +
  38. '<div class="input-group-append">' +
  39. '<button style="min-width: ' + config.buttonsWidth + '" class="btn btn-increment ' + config.buttonsClass + '" type="button">' + config.incrementButton + '</button>' +
  40. '</div>' +
  41. '</div>'
  42. var locale = config.locale || navigator.language || "en-US"
  43. this.each(function () {
  44. var $original = $(this)
  45. $original[0]["bootstrap-input-spinner"] = true
  46. $original.hide()
  47. var autoDelayHandler = null
  48. var autoIntervalHandler = null
  49. var autoMultiplier = config.boostMultiplier === "auto"
  50. var boostMultiplier = autoMultiplier ? 1 : config.boostMultiplier
  51. var $inputGroup = $(html)
  52. var $buttonDecrement = $inputGroup.find(".btn-decrement")
  53. var $buttonIncrement = $inputGroup.find(".btn-increment")
  54. var $input = $inputGroup.find("input")
  55. var min = parseFloat($original.prop("min")) || 0
  56. var max = isNaN($original.prop("max")) || $original.prop("max") === "" ? Infinity : parseFloat($original.prop("max"))
  57. var step = parseFloat($original.prop("step")) || 1
  58. var decimals = parseInt($original.attr("data-decimals")) || 0
  59. var numberFormat = new Intl.NumberFormat(locale, {
  60. minimumFractionDigits: decimals,
  61. maximumFractionDigits: decimals
  62. })
  63. var value = parseFloat($original[0].value)
  64. var boostStepsCount = 0
  65. $original[0].setValue = function (newValue) {
  66. setValue(newValue)
  67. }
  68. if ($original.prop("class").indexOf("is-invalid") !== -1) { // TODO dynamically copy all classes
  69. $input.addClass("is-invalid")
  70. }
  71. if ($original.prop("class").indexOf("is-valid") !== -1) {
  72. $input.addClass("is-valid")
  73. }
  74. if ($original.prop("required")) {
  75. $input.prop("required", true)
  76. }
  77. if ($original.prop("placeholder")) {
  78. $input.prop("placeholder", $original.prop("placeholder"))
  79. }
  80. $original.after($inputGroup)
  81. if (isNaN(value)) {
  82. $original[0].value = ""
  83. $input[0].value = ""
  84. } else {
  85. $original[0].value = value
  86. $input[0].value = numberFormat.format(value)
  87. }
  88. $input.on("paste keyup change", function () {
  89. var inputValue = $input[0].value
  90. if (locale === "en-US" || locale === "en-GB" || locale === "th-TH") {
  91. value = parseFloat(inputValue)
  92. } else {
  93. value = parseFloat(inputValue.replace(/[. ]/g, '').replace(/,/g, '.')) // i18n
  94. }
  95. if (isNaN(value)) {
  96. $original[0].value = ""
  97. } else {
  98. $original[0].value = value
  99. }
  100. dispatchChangeEvents($original)
  101. })
  102. onPointerDown($buttonDecrement[0], function () {
  103. stepHandling(-step)
  104. })
  105. onPointerDown($buttonIncrement[0], function () {
  106. stepHandling(step)
  107. })
  108. onPointerUp(document.body, function () {
  109. resetTimer()
  110. })
  111. function setValue(newValue) {
  112. if (isNaN(newValue) || newValue === "") {
  113. $original[0].value = ""
  114. $input[0].value = ""
  115. value = 0.0
  116. } else {
  117. $original[0].value = newValue
  118. $input[0].value = numberFormat.format(newValue)
  119. value = parseFloat(newValue)
  120. }
  121. }
  122. function dispatchChangeEvents($element) {
  123. setTimeout(function () {
  124. var changeEvent = new Event("change", {bubbles: true})
  125. var inputEvent = new Event("input", {bubbles: true})
  126. $element[0].dispatchEvent(changeEvent)
  127. $element[0].dispatchEvent(inputEvent)
  128. })
  129. }
  130. function stepHandling(step) {
  131. calcStep(step)
  132. resetTimer()
  133. autoDelayHandler = setTimeout(function () {
  134. autoIntervalHandler = setInterval(function () {
  135. if (boostStepsCount > config.boostThreshold) {
  136. if (autoMultiplier) {
  137. calcStep(step * parseInt(boostMultiplier, 10))
  138. boostMultiplier = Math.min(1000000, boostMultiplier * 1.1)
  139. } else {
  140. calcStep(step * boostMultiplier)
  141. }
  142. } else {
  143. calcStep(step)
  144. }
  145. boostStepsCount++
  146. }, config.autoInterval)
  147. }, config.autoDelay)
  148. }
  149. function calcStep(step) {
  150. if (isNaN(value)) {
  151. value = 0
  152. }
  153. value = Math.round(value / step) * step
  154. value = Math.min(Math.max(value + step, min), max)
  155. $input[0].value = numberFormat.format(value)
  156. $original[0].value = Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals)
  157. dispatchChangeEvents($original)
  158. }
  159. function resetTimer() {
  160. boostStepsCount = 0
  161. boostMultiplier = boostMultiplier = autoMultiplier ? 1 : config.boostMultiplier
  162. clearTimeout(autoDelayHandler)
  163. clearTimeout(autoIntervalHandler)
  164. }
  165. })
  166. }
  167. function onPointerUp(element, callback) {
  168. element.addEventListener("mouseup", function (e) {
  169. callback(e)
  170. })
  171. element.addEventListener("touchend", function (e) {
  172. callback(e)
  173. })
  174. element.addEventListener("keyup", function (e) {
  175. if (e.keyCode === 32) {
  176. spacePressed = false
  177. callback(e)
  178. }
  179. })
  180. }
  181. function onPointerDown(element, callback) {
  182. element.addEventListener("mousedown", function (e) {
  183. e.preventDefault()
  184. callback(e)
  185. })
  186. element.addEventListener("touchstart", function (e) {
  187. e.preventDefault()
  188. callback(e)
  189. })
  190. element.addEventListener("keydown", function (e) {
  191. if (e.keyCode === 32 && !spacePressed) {
  192. spacePressed = true
  193. callback(e)
  194. }
  195. })
  196. }
  197. }(jQuery))