<!DOCTYPE html> <!--suppress HtmlFormInputWithoutLabel --> <html lang="en"> <head> <meta charset="UTF-8"> <title>bootstrap-input-spinner</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> <link rel="stylesheet" href="./node_modules/prismjs/themes/prism-tomorrow.css"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <style> h2 { margin-top: 3rem; margin-bottom: 1rem; border-bottom: 1px solid rgba(0, 0, 0, 0.5); } h3, h4 { margin-top: 2rem; } .input-group, input.test-value-input { max-width: 250px; } </style> <!-- TODO remove jQuery --> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script> <script src="src/custom-editors.js"></script> <script src="./node_modules/prismjs/prism.js"></script> </head> <body> <section class="container py-5"> <h1>bootstrap-input-spinner</h1> <p> A Bootstrap / jQuery plugin to create input spinner elements for number input, by <a href="https://shaack.com/en">shaack.com</a> engineering. For now it needs jQuery, but I am working on it. </p> <p>This version is compatible with Bootstrap 5, but we remain a Bootstrap 4 compatible version with the branch <a href="https://github.com/shaack/bootstrap-input-spinner/tree/bootstrap4-compatible">bootstrap4-compatible</a>. npm versions 3.x are Bootstrap 5 compatible, versions 2.x Bootstrap 4 compatible.</p> <p> License: <a href="https://github.com/shaack/bootstrap-input-spinner/blob/master/LICENSE">MIT</a> </p> <h2>Features</h2> <p>The Bootstrap InputSpinner</p> <ul> <li> is <b>mobile friendly</b> and <b>responsive</b>, </li> <li> has <b>internationalized</b> number formatting, </li> <li> automatically changes the value when <b>holding a button</b>, </li> <li> allows setting a <b>prefix</b> or <b>suffix</b> text in the input, </li> <li> handles <code>val()</code> like the native element, </li> <li> <b>dynamically handles</b> changing <b>attribute values</b> like <code>disabled</code> oder <code>class</code>, </li> <li> dispatches <code>change</code> and <code>input</code> <b>events on value change</b> like the native element, </li> <li> <b>needs no extra css</b>, just Bootstrap 5. </li> </ul> <h2>Usage</h2> <p> This script enables the InputSpinner for all inputs with <code>type='number'</code>. <b>No extra css needed</b>, just Bootstrap 5. </p> <pre><code class="language-html"><script src="./src/InputSpinner.js"></script> <script> $("input[type='number']").inputSpinner() </script></code></pre> <h2>Repository, documentation and npm package</h2> <p>Find the source code, more documentation and the npm package at</p> <ul> <li><a href="https://github.com/shaack/bootstrap-input-spinner">GitHub repository and documentation</a></li> <li><a href="https://www.npmjs.com/package/bootstrap-input-spinner">npm package</a></li> </ul> <h2>Examples</h2> <p>The following contains examples of the InputSpinner's main features</p> <h3>No attributes</h3> <p> <input type="number"/> </p> <pre><code class="language-html"><input type="number"/></code></pre> <h3>Simple Integer</h3> <p> <input type="number" value="500" min="0" max="1000" step="10"/> </p> <pre><code class="language-html"><input type="number" value="500" min="0" max="1000" step="10"/></code></pre> <h3>Floating Point</h3> <p> <input type="number" value="4.5" data-decimals="2" min="0" max="9" step="0.1"/> </p> <pre><code class="language-html"><input type="number" value="4.5" data-decimals="2" min="0" max="9" step="0.1"/></code></pre> <h3>Handle <code>change</code> and <code>input</code> events and read the value from JavaScript with <code>val()</code></h3> <p> Type in a number to see the difference between <code>change</code> and <code>input</code> events. </p> <p> <input type="number" id="changedInput" value="2500" min="0" max="5000000" data-decimals="2"/> </p> <p> Value on input: <span id="valueOnInput"></span><br/> Value on change: <span id="valueOnChange"></span> </p> <script> var $changedInput = $("#changedInput") var $valueOnInput = $("#valueOnInput") var $valueOnChange = $("#valueOnChange") $changedInput.on("input", function (event) { console.log("on input", event) $valueOnInput.html($(event.target).val()) // or $valueOnInput.html(event.target.value) // in vanilla js // or $valueOnInput.html($changedInput.val()) }) $changedInput.on("change", function (event) { console.log("on change", event) $valueOnChange.html($(event.target).val()) }) </script> <pre><code class="language-javascript">var $changedInput = $("#changedInput") var $valueOnInput = $("#valueOnInput") var $valueOnChange = $("#valueOnChange") $changedInput.on("input", function (event) { $valueOnInput.html($(event.target).val()) // or $valueOnInput.html(event.target.value) // in vanilla js // or $valueOnInput.html($changedInput.val()) }) $changedInput.on("change", function (event) { $valueOnChange.html($(event.target).val()) })</code></pre> <h3>Programmatic changing the value with <code>val()</code></h3> <p> <label for="inputNet">Net</label> <input type="number" id="inputNet" value="100" min="0" max="10000" step="0.01" data-decimals="2"/> </p> <p> <label for="inputGross">Gross (+19%)</label> <input type="number" id="inputGross" value="100" min="0" max="11900" step="0.01" data-decimals="2"/> </p> <script> var $inputNet = $("#inputNet") var $inputGross = $("#inputGross") $inputNet.on("input", function (event) { $inputGross.val($(event.target).val() * 1.19) }) $inputGross.on("input", function (event) { $inputNet.val($(event.target).val() / 1.19) }) $inputGross.val($inputNet.val() * 1.19) </script> <pre><code class="language-javascript">$inputNet.on("input", function (event) { $inputGross.val($(event.target).val() * 1.19) // or $inputGross[0].setValue(event.target.value * 1.19) // in vanilla js // or $inputGross.val($inputNet.val() * 1.19) // do all the same }) $inputGross.on("input", function (event) { $inputNet.val($(event.target).val() / 1.19) })</code></pre> <h3>Attributes <code>placeholder</code> and <code>required</code></h3> <form> <p> <input placeholder="Enter a number" required type="number" value="" min="-100" max="100"/> </p> <pre><code class="language-html"><input <strong>placeholder</strong>="Enter a number" <strong>required</strong> type="number" value="" min="-100" max="100"/></code></pre> <input type="submit" id="submitButton" class="btn btn-primary mb-4" value="Submit to test empty input"/> </form> <h3>Attribute <code>disabled</code>, dynamically changing</h3> <p>Attributes are handled dynamically.</p> <form> <p> <input id="inputDisabled" disabled type="number" value="50"/> </p> <div class="form-check"> <input type="checkbox" checked class="form-check-input" id="disabledSwitch"> <label class="form-check-label" for="disabledSwitch">Disabled</label> </div> <script> var $inputDisabled = $("#inputDisabled") var $disabledSwitch = $("#disabledSwitch") $disabledSwitch.on("change", function () { $inputDisabled.prop("disabled", $(this).prop("checked")) }) </script> <pre><code class="language-html"><input id="inputDisabled" disabled type="number" value="50"/> <div class="form-check"> <input type="checkbox" checked class="form-check-input" id="disabledSwitch"/> <label class="form-check-label" for="disabledSwitch">Disabled</label> </div> <script> var $inputDisabled = $("#inputDisabled") var $disabledSwitch = $("#disabledSwitch") $disabledSwitch.on("change", function () { $inputDisabled.prop("disabled", $(this).prop("checked")) }) </script></code></pre> </form> <h3><code>buttonsOnly</code> mode and disabled <code>autoInterval</code></h3> <p> In <code>buttonsOnly</code> mode no direct text input is allowed, the text-input gets the attribute <code>readonly</code>. But the plus and minus buttons still allow to change the value. <br/><code>autoInterval: undefined</code> additionally disables the auto increase/decrease, when you hold the button. </p> <p> <input id="buttons-only" value="5" min="1" max="10"/> </p> <script type="module"> import {InputSpinner} from "./src/InputSpinner.js" const element = document.getElementById("buttons-only") new InputSpinner(element, {buttonsOnly: true, autoInterval: undefined}) </script> <pre><code class="language-js">$(".buttons-only").inputSpinner({buttonsOnly: true, autoInterval: undefined})</code></pre> <h3>Dynamically handling of the <code>class</code> attribute</h3> <p> <input id="inputChangeClass" class="is-valid" type="number" value="50"/> </p> <p> <label for="classInput">CSS Class</label> <input id="classInput" type="text" class="form-control test-value-input" value="is-valid"/> Try to change the class to "is-invalid" or "text-info". </p> <script> var $inputChangeClass = $("#inputChangeClass") var $classInput = $("#classInput") $classInput.on("input", function () { $inputChangeClass.prop("class", this.value) }) </script> <pre><code class="language-html"><input id="inputChangeClass" class="is-valid" type="number" value="50"/> <label for="classInput">CSS Class</label> <input id="classInput" type="text" class="form-control" value="is-valid"/> <script> var $inputChangeClass = $("#inputChangeClass") var $classInput = $("#classInput") $classInput.on("input", function() { $inputChangeClass.prop("class", this.value); }) </script></code></pre> <h3>Sizing</h3> <p>Sizing works out of the box. Just set the original inputs class to <code>form-control-sm</code> or <code>form-control-lg</code>, and the resulting group gets the class <code>input-group-sm</code> or <code>input-group-lg</code>.</p> <p> <label for="inputSmall">Small</label> <input id="inputSmall" class="form-control-sm" type="number" value="0.0" data-decimals="4" min="-1" max="1" step="0.0001"/> </p> <pre><code class="language-html"><input class="form-control-sm" type="number" value="0.0" data-decimals="4" min="-1" max="1" step="0.0001"/></code></pre> <p> <label for="inputLarge">Large</label> <input id="inputLarge" class="form-control-lg" type="number" value="1000000" data-decimals="0" min="0" max="2000000" step="1"/> </p> <pre><code class="language-html"><input class="form-control-lg" type="number" value="1000000" data-decimals="0" min="0" max="2000000" step="1"/></code></pre> <h3>Dynamically handling of <code>min</code>, <code>max</code>, <code>step</code> and <code>data-decimals</code></h3> <div class="row"> <div class="col-lg-3"> <p> <label for="minInput">min</label> <input id="minInput" type="text" class="form-control test-value-input" value="0"/> </p> </div> <div class="col-lg-3"> <p> <label for="maxInput">max</label> <input id="maxInput" type="text" class="form-control test-value-input" value="100"/> </p> </div> <div class="col-lg-3"> <p> <label for="stepInput">step</label> <input id="stepInput" type="text" class="form-control test-value-input" value="0.05"/> </p> </div> <div class="col-lg-3"> <p> <label for="dataDecimalsInput">data-decimals</label> <input id="dataDecimalsInput" type="text" class="form-control test-value-input" value="2"/> </p> </div> </div> <p> <label for="minMaxTester">Try here</label> <input id="minMaxTester" type="number" value="50" min="0" max="100" step="0.05" data-decimals="2"/> </p> <script> var $minInput = $("#minInput") var $maxInput = $("#maxInput") var $stepInput = $("#stepInput") var $dataDecimalsInput = $("#dataDecimalsInput") var $minMaxTester = $("#minMaxTester") $minInput.on("change", function (event) { console.log("on change", event) $minMaxTester.attr("min", $minInput.val()) }) $maxInput.on("change", function (event) { console.log("on change", event) $minMaxTester.attr("max", $maxInput.val()) }) $stepInput.on("change", function (event) { console.log("on change", event) $minMaxTester.attr("step", $stepInput.val()) }) $dataDecimalsInput.on("change", function (event) { console.log("on change", event) $minMaxTester.attr("data-decimals", $dataDecimalsInput.val()) }) </script> <pre><code class="language-javascript">var $minInput = $("#minInput") var $maxInput = $("#maxInput") var $stepInput = $("#stepInput") var $dataDecimalsInput = $("#dataDecimalsInput") var $minMaxTester = $("#minMaxTester") $minInput.on("change", function (event) { $minMaxTester.attr("min", $minInput.val()) }) $maxInput.on("change", function (event) { $minMaxTester.attr("max", $maxInput.val()) }) $stepInput.on("change", function (event) { $minMaxTester.attr("step", $stepInput.val()) }) $dataDecimalsInput.on("change", function (event) { $minMaxTester.attr("data-decimals", $dataDecimalsInput.val()) }) </code></pre> <h3>Prefix and Suffix</h3> <p> <label for="inputPrefix">Prefix</label> <input id="inputPrefix" data-prefix="$" value="100.0" data-decimals="2" min="0" max="1000" step="0.1" type="number"/> </p> <pre><code class="language-html"><input data-prefix="$" value="100.0" data-decimals="2" min="0" max="1000" step="0.1" type="number" /></code></pre> <p> <label for="inputSuffix">Suffix</label> <input id="inputSuffix" data-suffix="°C" value="50" min="0" max="100" type="number"/> </p> <pre><code class="language-html"><input data-suffix="°C" value="50" min="0" max="100" type="number" /></code></pre> <h3>Looping the value</h3> <p>This input starts from 0 when reaching 360.</p> <p> <input type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360" step="10"/> </p> <script> var $inputLoop = $("#inputLoop") $inputLoop.on("input", function (ignored) { var value = $inputLoop.val() value = (value < 0) ? 360 + parseInt(value, 10) : value % 360 $inputLoop.val(value) }) </script> <pre><code class="language-html"><input step="10" type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360"/></code></pre> <p>"Loop" the value between 0 and 360 with the <code>change</code> event in JavaScript.</p> <pre><code class="language-javascript">var $inputLoop = $("#inputLoop") $inputLoop.on("input", function(event) { var value = $inputLoop.val() value = (value < 0) ? 360 + parseInt(value, 10) : value % 360 $inputLoop.val(value) })</code></pre> <h3>Custom Editors</h3> <p>An Editor defines, how the input is parsed and rendered. The inputSpinner is shipped with some custom Editors in <code>/src/custom-editors.js</code>.</p> <h4>RawEditor</h4> <p>The simplest custom Editor is the <code>RawEditor</code>, it renders just the value und parses just the value, without any changes, like a native number input. No internationalization, no digit grouping.</p> <p> <input id="rawEditor" value="1000"/> </p> <script type="module"> import {InputSpinner} from "./src/InputSpinner.js" new InputSpinner(document.getElementById("rawEditor"), {editor: customEditors.RawEditor}) </script> <pre><code class="language-js">$("#rawEditor").inputSpinner({editor: customEditors.RawEditor})</code></pre> <h4>TimeEditor</h4> <p>The <code>TimeEditor</code> renders the number as time in hours and minutes, separated by a colon.</p> <input id="timeEditor" value="60" step="5"/> <div class="mt-1">value: <span id="timeValue"></span></div> <script type="module"> import {InputSpinner} from "./src/InputSpinner.js" const element = document.getElementById("timeEditor") new InputSpinner(element, {editor: customEditors.TimeEditor}) element.addEventListener("input", () => { document.getElementById("timeValue").textContent = element.value }) document.getElementById("timeValue").textContent = element.value </script> <pre><code class="language-js">$("#rawEditor").inputSpinner({editor: customEditors.TimeEditor})</code></pre> <h3>Styling with templates (<i>new!</i>)</h3> <p>With the new templating feature, you can almost do <b>anything, when it comes to layout</b>.</p> <h5>How about... buttons right</h5> <p> <input data-prefix="¥" id="templateButtonsRight" value="1000"/> </p> <p> This is the template for "buttons right": </p> <script type="module"> import {InputSpinner} from "./src/InputSpinner.js" const element = document.getElementById("templateButtonsRight") new InputSpinner(element, { template: '<div class="input-group ${groupClass}">' + '<input type="text" inputmode="decimal" style="text-align: ${textAlign}" class="form-control"/>' + '<button style="min-width: ${buttonsWidth}" class="btn btn-decrement ${buttonsClass}" type="button">${decrementButton}</button>' + '<button style="min-width: ${buttonsWidth}" class="btn btn-increment ${buttonsClass}" type="button">${incrementButton}</button>' + '</div>' }) </script> <pre><code class="language-html"><div class="input-group ${groupClass}"> <input type="text" inputmode="decimal" style="text-align: ${textAlign}" class="form-control"/> <button style="min-width: ${buttonsWidth}" class="btn btn-decrement ${buttonsClass}" type="button">${decrementButton}</button> <button style="min-width: ${buttonsWidth}" class="btn btn-increment ${buttonsClass}" type="button">${incrementButton}</button> </div></code></pre> <p>You can... or must use the following variables in your template:</p> <ul> <li>${groupClass}</li> <li>${textAlign}</li> <li>${buttonsWidth}</li> <li>${buttonsClass}</li> <li>${decrementButton}</li> <li>${incrementButton}</li> </ul> <p>Provide the template as configuration parameter:</p> <pre><code class="language-js">$(element).inputSpinner({template: '<div class...'})</code></pre> <h3>Destroying the spinner</h3> <p>To Remove the InputSpinner and show the original input element, use</p> <pre><code class="language-javascript">$(element).inputSpinner("destroy")</code></pre> <div class="mb-3"> <label for="inputDestroyCreate">Label `for` switches dynamically:</label> <input type="number" id="inputDestroyCreate" value="50"/> </div> <button id="buttonDestroy" class="btn btn-primary">destroy</button> <button id="buttonCreate" disabled="disabled" class="btn btn-primary">re-create</button> <script type="module"> import {InputSpinner} from "./src/InputSpinner.js" var $buttonDestroy = $("#buttonDestroy") var $buttonCreate = $("#buttonCreate") var $inputDestroyCreate = $("#inputDestroyCreate") $buttonDestroy.click(function () { $inputDestroyCreate[0].destroyInputSpinner() $buttonDestroy.attr("disabled", true) $buttonCreate.attr("disabled", false) }) $buttonCreate.click(function () { new InputSpinner($inputDestroyCreate[0]) $buttonDestroy.attr("disabled", false) $buttonCreate.attr("disabled", true) }) </script> <div class="card my-5 border-info"> <a href="https://shaack.com/works"> <div class="card-body"> <h4 class="mb-2 mt-0">More Bootstrap components (from shaack.com)</h4> You may want to check out our further Bootstrap extensions, <b>bootstrap-show-modal</b> and <b>bootstrap-detect-breakpoint</b>. </div> </a> </div> <p>If you find bugs or have suggestions, you may write an <a href="https://github.com/shaack/bootstrap-input-spinner/issues">issue</a>.</p> <br/><br/> </section> <!-- bootstrap needs jQuery --> <script type="module"> import {InputSpinner} from "./src/InputSpinner.js" const inputSpinnerElements = document.querySelectorAll("input[type='number']") for (const inputSpinnerElement of inputSpinnerElements) { new InputSpinner(inputSpinnerElement) } </script> </body> </html>