Related
How can I focus the next input once the previous input has reached its maxlength value?
a: <input type="text" maxlength="5" />
b: <input type="text" maxlength="5" />
c: <input type="text" maxlength="5" />
If a user pastes text that is greater than the maxlength, ideally it should spill into the next input.
jsFiddle: http://jsfiddle.net/4m5fg/1/
I must stress that I do not want to use a plugin, as I'd much rather learn the logic behind this, than use something that already exists. Thanks for understanding.
No jQuery used and is a very clean implementation:
Reads from the maxlength attribute.
Scales to any number of inputs inside of your container.
Automatically finds the next input to focus.
No jQuery.
http://jsfiddle.net/4m5fg/5/
<div class="container">
a: <input type="text" maxlength="5" />
b: <input type="text" maxlength="5" />
c: <input type="text" maxlength="5" />
</div>
..
var container = document.getElementsByClassName("container")[0];
container.onkeyup = function(e) {
var target = e.srcElement || e.target;
var maxLength = parseInt(target.attributes["maxlength"].value, 10);
var myLength = target.value.length;
if (myLength >= maxLength) {
var next = target;
while (next = next.nextElementSibling) {
if (next == null)
break;
if (next.tagName.toLowerCase() === "input") {
next.focus();
break;
}
}
}
// Move to previous field if empty (user pressed backspace)
else if (myLength === 0) {
var previous = target;
while (previous = previous.previousElementSibling) {
if (previous == null)
break;
if (previous.tagName.toLowerCase() === "input") {
previous.focus();
break;
}
}
}
}
You can watch for input in the fields and test its value:
$("input").bind("input", function() {
var $this = $(this);
setTimeout(function() {
if ( $this.val().length >= parseInt($this.attr("maxlength"),10) )
$this.next("input").focus();
},0);
});
Working demo.
The setTimeout is there to ensure the code will only run after the input is completed and the value updated. Binding input ensures most types of input will trigger the event, including key presses, copy/paste (even from mouse) and drag & drop (though in this case, the latter won't work, since the focus was on the draggable, not the droppable).
Note: on some older browsers, you might also need to bind propertychange.
If a user pastes text that is greater than the maxlength, ideally it should spill into the next input.
To do that, you might need to remove the maxlength attribute using JavaScript (to be able to capture the full input), and implement that functionality yourself. I made a small example, relevant parts below:
$("input").each(function() {
var $this = $(this);
$(this).data("maxlength", $this.prop("maxlength"));
$(this).removeAttr("maxlength");
})
This removes the attribute, but saves it in data, so you can access it later.
function spill($this, val) {
var maxlength = $this.data("maxlength");
if ( val.length >= maxlength ) {
$this.val(val.substring(0, maxlength));
var next = $this.next("input").focus();
spill(next, val.substring(maxlength));
}
else
$this.val(val);
}
Here the max length logic is reintroduced in JavaScript, as well as getting the "discarded" part and using it in a recursive call to spill. If there's no next element, the call to data will return undefined and the loop will stop, so the input will be truncated in the last field.
You can use plain JavaScript:
See DEMO.
Check the character length with el.value.length. If it is equal to the maximum value, move to the next field by using focus(). Bind this function to the keyup event with onkeyup so that the function fires every time after the user keys in a character.
var a = document.getElementById("a"),
b = document.getElementById("b"),
c = document.getElementById("c");
a.onkeyup = function() {
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
b.focus();
}
}
b.onkeyup = function() {
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
c.focus();
}
}
if you are going to have many fields you can do something like this.
basically on keyup get the length of the input and then compare it to the maxlength, if matches, then focus onto the next input field.
http://jsfiddle.net/btevfik/DVxDA/
$(document).ready(function(){
$('input').keyup(function(){
if(this.value.length==$(this).attr("maxlength")){
$(this).next().focus();
}
});
});
let otp = document.querySelector('#otp-screen');
for(let pin of otp.children) {
pin.onkeyup = function() {
if(pin.nextElementSibling) {
pin.nextElementSibling.focus();
}
}
}
<div class="otp-screen" id="otp-screen">
<input type="text" placeholder="0" maxlength="1"/>
<input type="text" placeholder="0" maxlength="1"/>
<input type="text" placeholder="0" maxlength="1"/>
<input type="text" placeholder="0" maxlength="1"/>
</div>
Updated btevfik code, Onkeyup or onkeydown will create issue as you won't be able to delete the previous input on tab navigation. It will be tough to edit or change the text inside the input box as it will be limited to maxlength. So we can use oninput event to achieve the task.
DEMO
HTML
<ul>
<li>a: <input type="text" maxlength="5" /></li>
<li>b: <input type="text" maxlength="3" /></li>
<li>c: <input type="text" maxlength="5" /></li>
<li>d: <input type="text" maxlength="3" /></li>
<li>e: <input type="text" maxlength="6" /></li>
<li>f: <input type="text" maxlength="10" /></li>
<li>g: <input type="text" maxlength="7" /></li>
</ul>
Javascript
$(document).ready(function(){
$('input').on("input", function(){
if($(this).val().length==$(this).attr("maxlength")){
$(this).next().focus();
}
});
});
CSS
ul {list-style-type:none;}
li {padding:5px 5px;}
Other answers do give some idea how this can be implemented, but I find that they do not consider some minor things among which are:
The fact, that you do not want to auto-focus any elements across whole page, but rather within specific form.
Input elements can be wrapped in some other elements (for example I wrap them in span or div to allow floating labels through CSS, and I've seen forms that use table to for structure).
Validity of the field, when spilling over or moving to next one automatically.
Input events when spilling over.
Cursor position when returning to previous field (it looks like it can be saved by browser, thus backspacing can focus not in the end of the field, but, for example, in the middle).
Below code is trying to account to all of this, at least. Most of it can be tested on codepen: paste-spilling does not work there, looks like because of Clipboard API (other codepens with it do not work for me either).
Let me know if anything is unclear in the code, I'll update my answer and the code. If you find some edge case that is not covered - let me know as well.
For paste-spilling test using form from codepen, you can use something like this: 123456789123456789012345678903454353434534
Video sample of how it works in a more "live" envitonment on youtube
//List of input types, that are "textual" by default, thus can be tracked through keypress and paste events. In essence,
// these are types, that support maxlength attribute
const textInputTypes = ['email', 'password', 'search', 'tel', 'text', 'url', ];
formInit();
//Add listeners
function formInit()
{
document.querySelectorAll('form input').forEach((item)=>{
if (textInputTypes.includes(item.type)) {
//Somehow backspace can be tracked only on keydown, not keypress
item.addEventListener('keydown', inputBackSpace);
if (item.getAttribute('maxlength')) {
item.addEventListener('input', autoNext);
item.addEventListener('change', autoNext);
item.addEventListener('paste', pasteSplit);
}
}
});
}
//Track backspace and focus previous input field, if input is empty, when it's pressed
function inputBackSpace(event)
{
let current = event.target;
if ((event.keyCode || event.charCode || 0) === 8 && !current.value) {
let moveTo = nextInput(current, true);
if (moveTo) {
moveTo.focus();
//Ensure, that cursor ends up at the end of the previous field
moveTo.selectionStart = moveTo.selectionEnd = moveTo.value.length;
}
}
}
//Focus next field, if current is filled to the brim and valid
function autoNext(event)
{
let current = event.target;
//Get length attribute
let maxLength = parseInt(current.getAttribute('maxlength'));
//Check it against value length
if (maxLength && current.value.length === maxLength && current.validity.valid) {
let moveTo = nextInput(current, false);
if (moveTo) {
moveTo.focus();
}
}
}
async function pasteSplit(event)
{
let permission = await navigator.permissions.query({ name: 'clipboard-read',});
//Check permission is granted or not
if (permission.state === 'denied') {
//It's explicitly denied, thus cancelling action
return false;
}
//Get buffer
navigator.clipboard.readText().then(result => {
let buffer = result.toString();
//Get initial element
let current = event.target;
//Get initial length attribute
let maxLength = parseInt(current.getAttribute('maxlength'));
//Loop while the buffer is too large
while (current && maxLength && buffer.length > maxLength) {
//Ensure input value is updated
current.value = buffer.substring(0, maxLength);
//Trigger input event to bubble any bound events
current.dispatchEvent(new Event('input', {
bubbles: true,
cancelable: true,
}));
//Do not spill over if a field is invalid
if (!current.validity.valid) {
return false;
}
//Update buffer value (not the buffer itself)
buffer = buffer.substring(maxLength);
//Get next node
current = nextInput(current);
if (current) {
//Focus to provide visual identification of a switch
current.focus();
//Update maxLength
maxLength = parseInt(current.getAttribute('maxlength'));
}
}
//Check if we still have a valid node
if (current) {
//Dump everything we can from leftovers
current.value = buffer;
//Trigger input event to bubble any bound events
current.dispatchEvent(new Event('input', {
bubbles: true,
cancelable: true,
}));
}
}).catch(err => {
//Most likely user denied request. Check status
navigator.permissions.query({ name: 'clipboard-read',}).then(newPerm => {
if (newPerm.state === 'granted') {
console.log('Failed to read clipboard', err);
} else {
console.log('Request denied by user. Show him some notification to explain why enabling permission may be useful');
}
}).catch(errPerm => {
console.log('Failed to read clipboard', errPerm);
});
});
}
//Find next/previous input
function nextInput(initial, reverse = false)
{
//Get form
let form = initial.form;
//Iterate inputs inside the form. Not using previousElementSibling, because next/previous input may not be a sibling on the same level
if (form) {
let previous;
for (let moveTo of form.querySelectorAll('input')) {
if (reverse) {
//Check if current element in loop is the initial one, meaning
if (moveTo === initial) {
//If previous is not empty - share it. Otherwise - false, since initial input is first in the form
if (previous) {
return previous;
} else {
return false;
}
}
} else {
//If we are moving forward and initial node is the previous one
if (previous === initial) {
return moveTo;
}
}
//Update previous input
previous = moveTo;
}
}
return false;
}
If you are adding input text fields dynamically then you can try this.
This will re-inject the script into the DOM and works Perfectly.
$('body').on('keyup', '#num_1',function(){
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
$('#num_2').focus();
}
})
$('body').on('keyup','#num_2', function(){
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
$('#num_3').focus();
}
})
<input type="text" class="form-control" name="number" maxlength="3" id="num_1">
<input type="text" class="form-control" name="number" maxlength="3" id="num_2">
<input type="text" class="form-control" name="number" maxlength="4" id="num_3">
If you're focused on creating card(debit/credit) number input type. Then clean an easily manageable jQuery version as follows:
/*..............................................................................................
* jQuery function for Credit card number input group
......................................................................................................*/
// make container label of input groups, responsible
$('.card-box').on('focus', function(e){
$(this).parent().addClass('focus-form-control');
});
$('.card-box').on('blur', function(e){
$(this).parent().removeClass('focus-form-control');
});
$('.card-box-1').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
$('.card-box-2').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
if(_length <= 0){
return;
}
});
$('.card-box-2').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
$('.card-box-3').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
if(_length <= 0){
$('.card-box-1').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
});
$('.card-box-3').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
$('.card-box-4').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
if(_length <= 0){
$('.card-box-2').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
});
$('.card-box-4').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
return;
}
if(_length <= 0){
$('.card-box-3').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
});
/*..............................................................................................
* End jQuery function for Credit card number input group
......................................................................................................*/
/* Hide HTML5 Up and Down arrows. */
input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none; margin: 0;
}
input[type="number"] { -moz-appearance: textfield; }
.card-box {
width: 20%; display: inline-block; height: 100%; border: none;
}
.focus-form-control {
border-color: #66afe9; outline: 0;-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="form-control" style="padding: 0; max-width: 300px; ">
<input class="card-box card-box-1" type="number" id="CreditCard_CardNumber1" required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;"/>
<input class="card-box card-box-2" type="number" id="CreditCard_CardNumber2" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
<input class="card-box card-box-3" type="number" id="CreditCard_CardNumber3" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
<input class="card-box card-box-4" type="number" id="CreditCard_CardNumber4" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
</div>
Verified Answer have one issue which focus previous field if previous field have valid length
I have Modified Above Answer to fix complete length of previous tag
var container = document.getElementsByClassName("container")[0];
container.onkeyup = function(e) {
var target = e.srcElement || e.target;
var maxLength = parseInt(target.attributes["maxlength"].value, 10);
var myLength = target.value.length;
if (myLength >= maxLength) {
var next = target;
while (next = next.nextElementSibling) {
if (next == null)
break;
if (next.tagName.toLowerCase() === "input") {
next.focus();
break;
}
}
}
// Move to previous field if empty (user pressed backspace)
else if (myLength === 0) {
var previous = target;
// Move to previous field if backspace is pressed
if (code == 8) {
previous = previous.previousElementSibling;
if (previous != null) {
if (previous.tagName.toLowerCase() === "input") {
previous.focus();
}
}
} else {
while (previous = previous.previousElementSibling) {
if (previous == null)
break;
if (previous.tagName.toLowerCase() === "input") {
var mLength = parseInt(previous.attributes["maxlength"].value, 10);
var pMyLength = previous.value.length;
// Move to previous field if it does not have required length
if (mLength == pMyLength) {
break;
} else {
previous.focus();
break;
}
}
}
}
}
}
I think this is a shorter way. As long as you use a specific structure in HTML
const inputHandler = (event) => {
if (event.target.nodeName == "INPUT" && event.target.nextElementSibling != null) {
event.target.nextElementSibling.focus();
}
}
<div class="enter-code">
<input type="text" maxlength="1" #keyup="inputHandler">
<input type="text" maxlength="1" #keyup="inputHandler">
<input type="text" maxlength="1" #keyup="inputHandler">
<input type="text" maxlength="1" #keyup="inputHandler">
</div>
I am working on a component in a vue application that takes a passcode that is 8 digits each digit has its own input when types a character in, it should move to the next input, if the user deletes the character if it can the focus should move to the previous sibling, if the user presses the left or right key, the focus should move with the key press. What is happening at the moment is that the #input and #keydown event are both firing, so if the the user presses left and the the previous sibling already has content, it moves the focus back to the right.
Here is my code,
<div class="code">
<input autofocus type="text" maxlength="1" class="passcode__box" #input="handleInput" #keydown="handleKeyDown"/>
<input type="text" class="passcode__box" maxlength="1" #input="handleInput" #keydown="handleKeyDown"/>
<input type="text" class="passcode__box" maxlength="1" #keyup="handleInput" #keydown="handleKeyDown"/>
<input type="text" class="passcode__box" maxlength="1" #keyup="handleInput" #keydown="handleKeyDown"/>
<input type="text" class="passcode__box" maxlength="1" #keyup="handleInput" #keydown="handleKeyDown"/>
<input type="text" class="passcode__box" maxlength="1" #keyup="handleInput" #keydown="handleKeyDown"/>
<input type="text" class="passcode__box" maxlength="1" #keyup="handleInput" #keydown="handleKeyDown"/>
<input type="text" class="passcode__box" maxlength="1" #keyup="handleInput" #keydown="handleKeyDown"/>
</div>
and the 2 methods,
handleInput(event) {
const value = event.target.value;
const nextElement = event.target.nextElementSibling;
if (value === "" || !nextElement) {
return;
}
nextElement.focus();
},
handleKeyDown(event) {
console.log('handleKeyDown');
//Right arrow key
if(event.keyCode == 39) {
event.target.nextElementSibling.focus();
}
//Left arrow key
if(event.keyCode == 37) {
event.target.previousElementSibling.focus();
}
//Backspace key - cmd
if(event.keyCode == 8) { //backspace
if(event.target.value === '') {
event.target.previousElementSibling.focus();
return;
}
event.target.value = '';
}
}
Here is an example of the component, that wierdly uses the same code enter link description here
I guess the problem is in the #keyup events, because #keyup emits after #input. You can read more about KeyboardEvents and their sequences in this MDN article.
Anyway I took the liberty of rewriting/refactoring the code you provided and introduced a couple of JS-shenanigans. The idea is the same though. There is a working example on CodeSandbox.
Hope this helped!
<template>
<div class="passcode">
<template v-for="index in inputs">
<input
type="text"
class="passcode__box"
:key="index"
:maxlength="maxlength"
#keydown="onKeydown"
#input="onInput"
>
</template>
</div>
</template>
<script>
export const KeyCodes = {
RIGHT_ARROW: 39,
LEFT_ARROW: 37,
BACKSPACE: 8
};
export default {
name: "Passcode",
props: {
inputs: {
type: Number,
default: 4
},
maxlength: {
type: Number,
default: 1
}
},
methods: {
/**
* Moves focus between siblings and handles `Backspace`.
*
* #param {KeyboardEvent} event
*/
onKeydown(event) {
const target = event.target;
const next = target.nextElementSibling;
const prev = target.previousElementSibling;
switch (event.keyCode) {
case KeyCodes.RIGHT_ARROW:
next && next.focus();
break;
case KeyCodes.LEFT_ARROW:
prev && prev.focus();
break;
case KeyCodes.BACKSPACE:
if (target.value.length) {
target.value = "";
return;
}
setTimeout(() => prev && prev.focus());
break;
default:
break;
}
},
/**
* Handles printable characters and moves focus
* to the next <input> sibling if it's present.
*
* #param {KeyboardEvent} event
*/
onInput(event) {
const value = event.target.value;
const next = event.target.nextElementSibling;
if (!value.length || !next) {
return;
}
next.focus();
}
}
};
</script>
I am basically trying to have these three text inputs representing 00h 00m 00s for my timer to get user input. I wanted to be able to make google timer (the one you can see if you just type timer in google) but I got stuck with getting navigation right - when user put two digits then focus into next input and when there is no "form" element break out of the loop. However, I keeping getting "Cannot read property 'firstElementChild' of null".
Also, what I hope to see is when user type the number (ex. 01 : 30: 00) and then hit enter I want to be able to get the user value so then I can work on building timer based on the user input however, the keypress event for listening "Enter" key to fire a submit doesn't seem to work and e.preventDefault() doesn't work either..
<div class="form-container">
<form class="1" action="">
<input class="1" type="text" maxlength="2" placeholder="00:">
</form>
<form class="2" action="">
<input class="2" type="text" maxlength="2" placeholder="00:">
</form>
<form class="3" action="">
<input class="3" type="text" maxlength="2" placeholder="00:">
</form>
</div>
formContainer.addEventListener('keyup', function(e){
let target = e.srcElement || e.target;
let targetValue = target.attributes["maxlength"].value
let maxLength = parseInt(targetValue, 10);
let currentLength = target.value.length;
if(currentLength >= maxLength) {
let next = target;
let nextInputParent = target.parentElement.nextElementSibling
let nextInputInNextSibling = nextInputParent.firstElementChild
while (next = nextInputInNextSibling){
if (next.parentElement == null){
break;
}
if (next.tagName.toLowerCase() === "input") {
next.focus();
break;
}
}
}
// Move to previous field if empty (user pressed backspace)
else if (currentLength === 0) {
let previous = target;
let previousInputInPreviousSibling = target.parentElement.previousElementSibling.children[0]
while (previous = previousInputInPreviousSibling) {
if (previous == null)
break;
if (previous.tagName.toLowerCase() === "input") {
previous.focus();
break;
}
}
}
})
form.addEventListener('keydown', function(e){
if(e.key == "Enter" || e.keyCode == 13){
console.log("submit");
e.preventDefault()
form.submit()
let userInput = inputEl.value
let countdown = 60 * userInput
timer = new Timer(countdown, userInput)
timer.start()
toggleStartTimer.textContent = "stop"
timer.isOn = true
}
})
Why using three separate forms for each input element? You can use one form, with an oninput event handler for each input element. Basically, an oninput event fires whenever the user presses a key/writes anything inside the input.
Then, you can use the form's onsubmit handler to handle hitting Enter, like so:
//event handler that will fire whenever the user types something in one of the inputs
$("#timer input[type='text']").on("input", function() {
//inside the event handler, `this` refers to the input element that fired the event
if(this.value.length == 2) {
//move to the next input, if any
if(next = $(this).next("#timer input[type='text']")[0]) {
$(this).blur(); //lose focus from the element
next.focus();
}
}
});
//handling case when user presses 'Enter'
//(note that using this approach, one of the inputs should be in focus when Enter is hit)
$("#timer").on("submit", function(e) {
//here e is your event object
e.preventDefault();
//build your timer and start it
console.log("Timer started!");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="form-container">
<form id="timer" action="">
<input class="1" type="text" maxlength="2" placeholder="00:">
<input class="2" type="text" maxlength="2" placeholder="00:">
<input class="3" type="text" maxlength="2" placeholder="00:">
<!-- An input type='submit' is needed, otherwise the form will never submit when Enter is hit.-->
<input type='submit' value='submit' style='display:none'/>
</form>
</div>
Or, using plain Javascript:
//event handler that will fire whenever the user types something in one of the inputs
var inputs = document.querySelectorAll('#timer input[type="text"]');
for(let input of inputs) {
input.oninput = function() {
if(this.value.length == 2) {
//move to the next input, if any
next = this.nextElementSibling;
if(next && next.type == 'text') {
this.blur(); //lose focus from the element
next.focus();
}
}
}
}
//handling case when user presses 'Enter'
//(note that using this approach, one of the inputs should be in focus when Enter is hit)
$("#timer").on("submit", function(e) {
//here e is your event object
e.preventDefault();
//build your timer and start it
console.log("Timer started!");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="form-container">
<form id="timer" action="">
<input class="1" type="text" maxlength="2" placeholder="00:">
<input class="2" type="text" maxlength="2" placeholder="00:">
<input class="3" type="text" maxlength="2" placeholder="00:">
<!-- An input type='submit' is needed, otherwise the form will never submit when Enter is hit.-->
<input type='submit' value='submit' style='display:none'/>
</form>
</div>
I think the better way is rely on some max constrains, not the input length. I've created a simple snippet for you. There are 3 inputs and a timer which starts when you hit "Enter".
const form = document.querySelector(".timer");
const time = document.querySelector(".time");
const limits = {
hours: 23,
minutes: 59,
seconds: 59
};
const multipliers = {
hours: val => (parseInt(val, 10) || 0) * 60 * 60,
minutes: val => (parseInt(val, 10) || 0) * 60,
seconds: val => parseInt(val, 10) || 0
};
let interval = null;
let totalTime = 0;
form.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
totalTime = Array.from(form.querySelectorAll("input")).reduce(function(
sum,
input
) {
const { name, value } = input;
return (sum += multipliers[name](value));
},
0);
if (totalTime > 0) {
if (interval) {
clearInterval(interval);
}
interval = setInterval(() => {
totalTime--;
time.innerHTML = totalTime;
}, 1000);
}
}
const { name, value } = event.target;
const parsedValue = parseInt(value, 10);
const newValue = Math.min(limits[name], parsedValue);
if (parsedValue > newValue) {
const sibling = event.target.nextElementSibling;
if (sibling && "focus" in sibling) {
sibling.focus();
}
event.target.value = newValue;
}
});
<form class="timer">
<input name="hours" placeholder="00"/>
<input name="minutes" placeholder="00"/>
<input name="seconds" placeholder="00"/>
</form>
<h1 class="time"></h1>
I am looking for a javascript function that when using onblur it validates that the text input is a number only with no decimal points or special characters. I have been able to find a few things, but none of them have worked thus far.
Here's what I have for the input fields:
<tr>
<td width="312"><strong>Cash on Hand</strong></td>
<td width="188">$
<input type="text" onchange="updateassets()" value="0" maxlength="11" name="CashOnHand" /></td>
Any help would be greatly appreciated.
Use below method onKeyUp event, this will not allow any characters on input field
function numericOnly(e)
{
var val = e.value.replace(/[^\d]/g, "");
if(val != e.value)
e.value = val;
}
Input field code
<input type="text" onchange="updateassets()" onKeyUp="numericOnly(this)" value="0" maxlength="11" name="CashOnHand" />
I like to separate the structure (HTML) from the function (JS). That's why there's no "onchange" attribute in the input element.
HTML
<input type="number" name="cashOnHand" value="0" maxlength="11" />
JS
function checkInputInteger() {
// Check if the input value is an integer
if (this.value == parseInt(this.value)) {
// The value is an integer
console.log('Input ' + this.name + ' is an integer');
}
else {
// The value is not an integer
console.log('Input ' + this.name + ' is not an integer');
}
}
// Get the input from DOM (getElementsByName returns a list)
input = document.getElementsByName('cashOnHand')[0];
// Bind the blur event to checkInputInteger
input.addEventListener('blur', checkInputInteger, false);
HTML
<form>
<input type="text" id="txt" />
</form>
JS
(function(a) {
a.onkeypress = function(e) {
if (e.keyCode >= 49 && e.keyCode <= 57) {}
else {
if (e.keyCode >= 97 && e.keyCode <= 122) {
alert('Error');
// return false;
} else return false;
}
};
})($('txt'));
function $(id) {
return document.getElementById(id);
}
Hope it helps you
Given:
<input onchange="updateassets()" value="0" ...>
you can make life easier by passing a reference to the element to the function using this:
<input onchange="updateassets(this)" value="0" ...>
and the validation function can be:
function validateIsInteger(element) {
if (/\D/.test(element.value)) {
// the element value contains non–digit values
} else {
// the element value is only digits
}
}
Using the change event is a good idea, as if the value hasn't changed, you don't need to check it.
How can I focus the next input once the previous input has reached its maxlength value?
a: <input type="text" maxlength="5" />
b: <input type="text" maxlength="5" />
c: <input type="text" maxlength="5" />
If a user pastes text that is greater than the maxlength, ideally it should spill into the next input.
jsFiddle: http://jsfiddle.net/4m5fg/1/
I must stress that I do not want to use a plugin, as I'd much rather learn the logic behind this, than use something that already exists. Thanks for understanding.
No jQuery used and is a very clean implementation:
Reads from the maxlength attribute.
Scales to any number of inputs inside of your container.
Automatically finds the next input to focus.
No jQuery.
http://jsfiddle.net/4m5fg/5/
<div class="container">
a: <input type="text" maxlength="5" />
b: <input type="text" maxlength="5" />
c: <input type="text" maxlength="5" />
</div>
..
var container = document.getElementsByClassName("container")[0];
container.onkeyup = function(e) {
var target = e.srcElement || e.target;
var maxLength = parseInt(target.attributes["maxlength"].value, 10);
var myLength = target.value.length;
if (myLength >= maxLength) {
var next = target;
while (next = next.nextElementSibling) {
if (next == null)
break;
if (next.tagName.toLowerCase() === "input") {
next.focus();
break;
}
}
}
// Move to previous field if empty (user pressed backspace)
else if (myLength === 0) {
var previous = target;
while (previous = previous.previousElementSibling) {
if (previous == null)
break;
if (previous.tagName.toLowerCase() === "input") {
previous.focus();
break;
}
}
}
}
You can watch for input in the fields and test its value:
$("input").bind("input", function() {
var $this = $(this);
setTimeout(function() {
if ( $this.val().length >= parseInt($this.attr("maxlength"),10) )
$this.next("input").focus();
},0);
});
Working demo.
The setTimeout is there to ensure the code will only run after the input is completed and the value updated. Binding input ensures most types of input will trigger the event, including key presses, copy/paste (even from mouse) and drag & drop (though in this case, the latter won't work, since the focus was on the draggable, not the droppable).
Note: on some older browsers, you might also need to bind propertychange.
If a user pastes text that is greater than the maxlength, ideally it should spill into the next input.
To do that, you might need to remove the maxlength attribute using JavaScript (to be able to capture the full input), and implement that functionality yourself. I made a small example, relevant parts below:
$("input").each(function() {
var $this = $(this);
$(this).data("maxlength", $this.prop("maxlength"));
$(this).removeAttr("maxlength");
})
This removes the attribute, but saves it in data, so you can access it later.
function spill($this, val) {
var maxlength = $this.data("maxlength");
if ( val.length >= maxlength ) {
$this.val(val.substring(0, maxlength));
var next = $this.next("input").focus();
spill(next, val.substring(maxlength));
}
else
$this.val(val);
}
Here the max length logic is reintroduced in JavaScript, as well as getting the "discarded" part and using it in a recursive call to spill. If there's no next element, the call to data will return undefined and the loop will stop, so the input will be truncated in the last field.
You can use plain JavaScript:
See DEMO.
Check the character length with el.value.length. If it is equal to the maximum value, move to the next field by using focus(). Bind this function to the keyup event with onkeyup so that the function fires every time after the user keys in a character.
var a = document.getElementById("a"),
b = document.getElementById("b"),
c = document.getElementById("c");
a.onkeyup = function() {
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
b.focus();
}
}
b.onkeyup = function() {
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
c.focus();
}
}
if you are going to have many fields you can do something like this.
basically on keyup get the length of the input and then compare it to the maxlength, if matches, then focus onto the next input field.
http://jsfiddle.net/btevfik/DVxDA/
$(document).ready(function(){
$('input').keyup(function(){
if(this.value.length==$(this).attr("maxlength")){
$(this).next().focus();
}
});
});
let otp = document.querySelector('#otp-screen');
for(let pin of otp.children) {
pin.onkeyup = function() {
if(pin.nextElementSibling) {
pin.nextElementSibling.focus();
}
}
}
<div class="otp-screen" id="otp-screen">
<input type="text" placeholder="0" maxlength="1"/>
<input type="text" placeholder="0" maxlength="1"/>
<input type="text" placeholder="0" maxlength="1"/>
<input type="text" placeholder="0" maxlength="1"/>
</div>
Updated btevfik code, Onkeyup or onkeydown will create issue as you won't be able to delete the previous input on tab navigation. It will be tough to edit or change the text inside the input box as it will be limited to maxlength. So we can use oninput event to achieve the task.
DEMO
HTML
<ul>
<li>a: <input type="text" maxlength="5" /></li>
<li>b: <input type="text" maxlength="3" /></li>
<li>c: <input type="text" maxlength="5" /></li>
<li>d: <input type="text" maxlength="3" /></li>
<li>e: <input type="text" maxlength="6" /></li>
<li>f: <input type="text" maxlength="10" /></li>
<li>g: <input type="text" maxlength="7" /></li>
</ul>
Javascript
$(document).ready(function(){
$('input').on("input", function(){
if($(this).val().length==$(this).attr("maxlength")){
$(this).next().focus();
}
});
});
CSS
ul {list-style-type:none;}
li {padding:5px 5px;}
Other answers do give some idea how this can be implemented, but I find that they do not consider some minor things among which are:
The fact, that you do not want to auto-focus any elements across whole page, but rather within specific form.
Input elements can be wrapped in some other elements (for example I wrap them in span or div to allow floating labels through CSS, and I've seen forms that use table to for structure).
Validity of the field, when spilling over or moving to next one automatically.
Input events when spilling over.
Cursor position when returning to previous field (it looks like it can be saved by browser, thus backspacing can focus not in the end of the field, but, for example, in the middle).
Below code is trying to account to all of this, at least. Most of it can be tested on codepen: paste-spilling does not work there, looks like because of Clipboard API (other codepens with it do not work for me either).
Let me know if anything is unclear in the code, I'll update my answer and the code. If you find some edge case that is not covered - let me know as well.
For paste-spilling test using form from codepen, you can use something like this: 123456789123456789012345678903454353434534
Video sample of how it works in a more "live" envitonment on youtube
//List of input types, that are "textual" by default, thus can be tracked through keypress and paste events. In essence,
// these are types, that support maxlength attribute
const textInputTypes = ['email', 'password', 'search', 'tel', 'text', 'url', ];
formInit();
//Add listeners
function formInit()
{
document.querySelectorAll('form input').forEach((item)=>{
if (textInputTypes.includes(item.type)) {
//Somehow backspace can be tracked only on keydown, not keypress
item.addEventListener('keydown', inputBackSpace);
if (item.getAttribute('maxlength')) {
item.addEventListener('input', autoNext);
item.addEventListener('change', autoNext);
item.addEventListener('paste', pasteSplit);
}
}
});
}
//Track backspace and focus previous input field, if input is empty, when it's pressed
function inputBackSpace(event)
{
let current = event.target;
if ((event.keyCode || event.charCode || 0) === 8 && !current.value) {
let moveTo = nextInput(current, true);
if (moveTo) {
moveTo.focus();
//Ensure, that cursor ends up at the end of the previous field
moveTo.selectionStart = moveTo.selectionEnd = moveTo.value.length;
}
}
}
//Focus next field, if current is filled to the brim and valid
function autoNext(event)
{
let current = event.target;
//Get length attribute
let maxLength = parseInt(current.getAttribute('maxlength'));
//Check it against value length
if (maxLength && current.value.length === maxLength && current.validity.valid) {
let moveTo = nextInput(current, false);
if (moveTo) {
moveTo.focus();
}
}
}
async function pasteSplit(event)
{
let permission = await navigator.permissions.query({ name: 'clipboard-read',});
//Check permission is granted or not
if (permission.state === 'denied') {
//It's explicitly denied, thus cancelling action
return false;
}
//Get buffer
navigator.clipboard.readText().then(result => {
let buffer = result.toString();
//Get initial element
let current = event.target;
//Get initial length attribute
let maxLength = parseInt(current.getAttribute('maxlength'));
//Loop while the buffer is too large
while (current && maxLength && buffer.length > maxLength) {
//Ensure input value is updated
current.value = buffer.substring(0, maxLength);
//Trigger input event to bubble any bound events
current.dispatchEvent(new Event('input', {
bubbles: true,
cancelable: true,
}));
//Do not spill over if a field is invalid
if (!current.validity.valid) {
return false;
}
//Update buffer value (not the buffer itself)
buffer = buffer.substring(maxLength);
//Get next node
current = nextInput(current);
if (current) {
//Focus to provide visual identification of a switch
current.focus();
//Update maxLength
maxLength = parseInt(current.getAttribute('maxlength'));
}
}
//Check if we still have a valid node
if (current) {
//Dump everything we can from leftovers
current.value = buffer;
//Trigger input event to bubble any bound events
current.dispatchEvent(new Event('input', {
bubbles: true,
cancelable: true,
}));
}
}).catch(err => {
//Most likely user denied request. Check status
navigator.permissions.query({ name: 'clipboard-read',}).then(newPerm => {
if (newPerm.state === 'granted') {
console.log('Failed to read clipboard', err);
} else {
console.log('Request denied by user. Show him some notification to explain why enabling permission may be useful');
}
}).catch(errPerm => {
console.log('Failed to read clipboard', errPerm);
});
});
}
//Find next/previous input
function nextInput(initial, reverse = false)
{
//Get form
let form = initial.form;
//Iterate inputs inside the form. Not using previousElementSibling, because next/previous input may not be a sibling on the same level
if (form) {
let previous;
for (let moveTo of form.querySelectorAll('input')) {
if (reverse) {
//Check if current element in loop is the initial one, meaning
if (moveTo === initial) {
//If previous is not empty - share it. Otherwise - false, since initial input is first in the form
if (previous) {
return previous;
} else {
return false;
}
}
} else {
//If we are moving forward and initial node is the previous one
if (previous === initial) {
return moveTo;
}
}
//Update previous input
previous = moveTo;
}
}
return false;
}
If you are adding input text fields dynamically then you can try this.
This will re-inject the script into the DOM and works Perfectly.
$('body').on('keyup', '#num_1',function(){
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
$('#num_2').focus();
}
})
$('body').on('keyup','#num_2', function(){
if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
$('#num_3').focus();
}
})
<input type="text" class="form-control" name="number" maxlength="3" id="num_1">
<input type="text" class="form-control" name="number" maxlength="3" id="num_2">
<input type="text" class="form-control" name="number" maxlength="4" id="num_3">
If you're focused on creating card(debit/credit) number input type. Then clean an easily manageable jQuery version as follows:
/*..............................................................................................
* jQuery function for Credit card number input group
......................................................................................................*/
// make container label of input groups, responsible
$('.card-box').on('focus', function(e){
$(this).parent().addClass('focus-form-control');
});
$('.card-box').on('blur', function(e){
$(this).parent().removeClass('focus-form-control');
});
$('.card-box-1').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
$('.card-box-2').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
if(_length <= 0){
return;
}
});
$('.card-box-2').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
$('.card-box-3').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
if(_length <= 0){
$('.card-box-1').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
});
$('.card-box-3').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
$('.card-box-4').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
if(_length <= 0){
$('.card-box-2').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
});
$('.card-box-4').on('keyup', function(e){
e.preventDefault();
var max_length = parseInt($(this).attr('maxLength'));
var _length = parseInt($(this).val().length);
if(_length >= max_length) {
return;
}
if(_length <= 0){
$('.card-box-3').focus().removeAttr('readonly');
$(this).attr('readonly', 'readonly');
}
});
/*..............................................................................................
* End jQuery function for Credit card number input group
......................................................................................................*/
/* Hide HTML5 Up and Down arrows. */
input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none; margin: 0;
}
input[type="number"] { -moz-appearance: textfield; }
.card-box {
width: 20%; display: inline-block; height: 100%; border: none;
}
.focus-form-control {
border-color: #66afe9; outline: 0;-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="form-control" style="padding: 0; max-width: 300px; ">
<input class="card-box card-box-1" type="number" id="CreditCard_CardNumber1" required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;"/>
<input class="card-box card-box-2" type="number" id="CreditCard_CardNumber2" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
<input class="card-box card-box-3" type="number" id="CreditCard_CardNumber3" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
<input class="card-box card-box-4" type="number" id="CreditCard_CardNumber4" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
</div>
Verified Answer have one issue which focus previous field if previous field have valid length
I have Modified Above Answer to fix complete length of previous tag
var container = document.getElementsByClassName("container")[0];
container.onkeyup = function(e) {
var target = e.srcElement || e.target;
var maxLength = parseInt(target.attributes["maxlength"].value, 10);
var myLength = target.value.length;
if (myLength >= maxLength) {
var next = target;
while (next = next.nextElementSibling) {
if (next == null)
break;
if (next.tagName.toLowerCase() === "input") {
next.focus();
break;
}
}
}
// Move to previous field if empty (user pressed backspace)
else if (myLength === 0) {
var previous = target;
// Move to previous field if backspace is pressed
if (code == 8) {
previous = previous.previousElementSibling;
if (previous != null) {
if (previous.tagName.toLowerCase() === "input") {
previous.focus();
}
}
} else {
while (previous = previous.previousElementSibling) {
if (previous == null)
break;
if (previous.tagName.toLowerCase() === "input") {
var mLength = parseInt(previous.attributes["maxlength"].value, 10);
var pMyLength = previous.value.length;
// Move to previous field if it does not have required length
if (mLength == pMyLength) {
break;
} else {
previous.focus();
break;
}
}
}
}
}
}
I think this is a shorter way. As long as you use a specific structure in HTML
const inputHandler = (event) => {
if (event.target.nodeName == "INPUT" && event.target.nextElementSibling != null) {
event.target.nextElementSibling.focus();
}
}
<div class="enter-code">
<input type="text" maxlength="1" #keyup="inputHandler">
<input type="text" maxlength="1" #keyup="inputHandler">
<input type="text" maxlength="1" #keyup="inputHandler">
<input type="text" maxlength="1" #keyup="inputHandler">
</div>