I am trying to develop a multiple-input form in which I paste a long number that automatically splits into each input by 2 numbers each.
Everything works fine until the user pastes a number of only 5 digits such as 45896
and instead of reporting an error, another number is created in the first input. I can't understand how the form leaves white space, which it then fills by inventing a number, in the first input instead of leaving a blank space on the last input.
In addition I developed else if (input.value.length !== 2 ) input.value = '0' + input.value;
to add a 0
in front of the number if the input has only one number but only if the user types in numbers.
I would like the paste function to show an error if one of the inputs contains less than 2 digits and that when pasting a number less than 6 digits long the white space remains on the last input and not on the first.
Is there something wrong in my code or should I improve it to reach my goal?
<style>
.circle input {
border-radius: 999px;
float: left;
max-width: 100px;
font-size: 2em;
text-align: center;
height: 100px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="tkt_1">
<div class="circle">
<input type="tel" min="1" max="99" minlength="2" maxlength="2" oninput="if((this.value.length) > 2) {this.value = this.value.substring(0, 2);}" onchange="handleChange(this);" placeholder="00" />
</div>
<div class="circle">
<input type="tel" min="1" max="99" minlength="2" maxlength="2" oninput="if((this.value.length) > 2) {this.value = this.value.substring(0, 2);}" onchange="handleChange(this);" placeholder="00" />
</div>
<div class="circle">
<input type="tel" min="1" max="99" minlength="2" maxlength="2" oninput="if((this.value.length) > 2) {this.value = this.value.substring(0, 2);}" onchange="handleChange(this);" placeholder="00" />
</div>
</form>
<script>
// Paste numbers from clipboard in multiple input
document.addEventListener("paste", function (e) {
// if the target is a text input
if (e.target.type === "tel") {
var data = e.clipboardData.getData('Text');
// split clipboard text into single characters
data = data.replace(/[^0-9]/g, "").split(/(?=(?:..)*$)/g);
// find all other text inputs
[].forEach.call(document.querySelectorAll("input[type=tel]"), (node, index) => {
// And set input value to the relative character
node.value = data[ index ] ?? "";
});
}
});
// Validation
function handleChange(input) {
if (input.value < 0) input.value = "";
else if (input.value.length !== 2 ) input.value = '0' + input.value;
else if (input.value < 0.9 || input.value > 99 || input.value.length === 1 || input.value.length === 0) {
input.style.backgroundColor = 'red';
return false;
} else {
input.style.backgroundColor = '';
return true;
}
}
// Input only Numbers
$('.circle input').keypress(function (event) {
event = event || window.event;
var charCode = event.which || event.keyCode;
var charStr = String.fromCharCode(charCode);
// FireFox key Del - Supr - Up - Down - Left - Right
if (event.key !== undefined && event.charCode === 0) {
return;
}
//Only Num
if (!/^([0-9])*$/.test(charStr)) {
event.preventDefault();
}
//Num and letters
if (!/^[a-zA-Z0-9]+$/.test(charStr)) {
event.preventDefault();
}
});
</script>
The main issue described with the splitting was happening due to the regex split which was leaving the first input single rather than the last input.
Also, another issue is that onchange
is not triggered by pasting values so the handleChange
function is never called on pasting the values.
<style>
.circle input {
border-radius: 999px;
float: left;
max-width: 100px;
font-size: 2em;
text-align: center;
height: 100px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="tkt_1">
<div class="circle">
<input type="tel" value="00" min="0" minlength="2" maxlength="2" oninput="if((this.value.length) > 2) {this.value = this.value.substring(0, 2);}" onchange="handleChange(this);" />
</div>
<div class="circle">
<input type="tel" value="00" min="0" minlength="2" maxlength="2" oninput="if((this.value.length) > 2) {this.value = this.value.substring(0, 2);}" onchange="handleChange(this);" />
</div>
<div class="circle">
<input type="tel" value="00" min="0" minlength="2" maxlength="2" oninput="if((this.value.length) > 2) {this.value = this.value.substring(0, 2);}" onchange="handleChange(this);" />
</div>
</form>
<script>
// Paste numbers from clipboard in multiple input
document.addEventListener("paste", function (e) {
// if the target is a text input
if (e.target.type === "tel") {
var data = e.clipboardData.getData('Text');
// split clipboard text into single characters
data = data.replace(/[^0-9]/g, "");
// Modified approach to split data into pairs
var pairs = [];
for (var i = 0; i < data.length; i += 2) {
pairs.push(data.slice(i, i + 2));
}
data = pairs;
// find all other text inputs
[].forEach.call(document.querySelectorAll("input[type=tel]"), (node, index) => {
// And set input value to the relative character
node.value = data[ index ] ?? "";
handleChange(node); //Added to trigger validation rules for pasted data
});
}
});
// Validation
function handleChange(input) {
if (input.value < 0) input.value = "";
else if (input.value.length !== 2 ) input.value = '0' + input.value;
else if (input.value < 0.9 || input.value > 99 || input.value.length === 1 || input.value.length === 0) {
input.style.backgroundColor = 'red';
return false;
} else {
input.style.backgroundColor = '';
return true;
}
}
var pasteModifier = false; // Store if control or the meta key is pressed state
// Need to handle the paste functionality
$('.circle input').keydown(function (event) {
if (event.metaKey || event.ctrlKey){
pasteModifier = true;
}
});
$('.circle input').keyup(function (event) {
if (event.metaKey || event.ctrlKey){
pasteModifier = false;
}
});
// Input only Numbers
$('.circle input').keypress(function (event) {
event = event || window.event;
var charCode = event.which || event.keyCode;
var charStr = String.fromCharCode(charCode);
// FireFox key Del - Supr - Up - Down - Left - Right
if (event.key !== undefined && event.charCode === 0) {
return;
}
// Handle paste command
if (charCode == 118 && pasteModifier){
return;
}
//Only Num
else if (!/^([0-9])*$/.test(charStr)) {
event.preventDefault();
}
//Num and letters
else if (!/^[a-zA-Z0-9]+$/.test(charStr)) {
event.preventDefault();
}
});
</script>
I have modified the code to use a regular slicing method to split the data string into 2 digits each and leave a single in the last input if applicable. The handleChange
function is also triggered within the loop setting the value for each input so it can act on the pasted data.
Currently if you paste 5 digits, it does not give an error but appends a 0 at the start of the last input due to the rule set up in the handleChange
function.
If you remove this line
else if (input.value.length !== 2 ) input.value = '0' + input.value;
you will encounter the red error instead.
In some browsers including Safari keypress events take higher precedence than the paste commands leading to breaking the paste functionality in this case.
Also, keypress
only sees the visible key triggers, unlike keydown
and keyup
. So I have included the additional keydown
and keyup
event handlers to check if the pressed key is the Meta
key for Mac or Ctrl
key for Windows and keep that stored as a state in the pasteModifier
variable.
This can then be a condition in the keypress
handler, where if the Meta or Ctrl
key is still pressed (keyup
did not fire) and then a V
is pressed, it returns as paste handler need not do anything.