Why is setCustomValidity('') ignored on input (Chrome 65) - javascript

Note: to the best of my knowledge this question is not a duplicate question of the following:
HTML5: Why does my “oninvalid” attribute let the pattern fail?
HTML5 form required attribute. Set custom validation message?
How can I change or remove HTML5 form validation default error messages?
Overview
Given a field that:
Has pattern attribute set for validation, for example "[a-f,0-9]{4}" for a 4 character hex string input.
Has oninvalid set with setCustomValidity('...some message...') to define a custom validation message
Has oninput set with setCustomValidity('') to reset on input
Here is an example showing this:
/* jshint esnext: true */
const form = document.querySelector("#form");
const field = document.querySelector("#field");
const output = document.querySelector("#output");
form.addEventListener('submit', (e) => {
console.log("SUBMIT");
output.textContent = field.value;
e.preventDefault(); // Prevent default POST request
});
field.oninvalid = (event) => {
console.log("INVALID");
event.target.setCustomValidity('must be valid 4 hex characters');
}
field.oninput = (event) => {
console.log("INPUT");
event.target.setCustomValidity('');
}
Output: <span id="output">No output</span>
<form id="form">
<label for="field">Enter 4 character hex code: </label>
<input id="field" type="text" pattern="[a-f,0-9]{4}" autocomplete=off>
</form>
Validation works almost as desired, except when the user enters an invalid entry and then proceeds to try and edit it, where their following input states are still invalid:
At this point, neither the custom setCustomValidity message defined in oninvalid is used, nor the empty one defined in onInput.
Instead, as long as the field is in an invalid state and not blurred, the default Please match the requested format. message appears.
Question
What is going on here? Looking at the console, the oninput event is called each time, and therefore event.target.setCustomValidity(''); is called each time.
So why is it that we are still seeing the generic default validation message? Shouldn't setCustomValidity('') disable that?
An acceptable answer here should exhibit the following:
The parameter field is respected for validation.
Any validation message appears if and only if the user attempts to submit an invalid field and not when they modify the input immediately afterward.
The default Please match the requested format. message never appears at all.

It appears that this is a bug with Chrome 65 in windows.
using setCustomValidity('') in oninput should disable the default validation messages appearing on input.
The following workaround works for me:
/* jshint esnext: true */
const form = document.querySelector("#form");
const field = document.querySelector("#field");
const output = document.querySelector("#output");
const pattern = field.getAttribute("pattern");
form.addEventListener('submit', (e) => {
console.log("SUBMIT");
output.textContent = `User submitted: ${field.value}`;
e.preventDefault(); // Prevent default POST request
});
field.oninvalid = (event) => {
console.log("INVALID");
event.target.setCustomValidity('must be valid 4 hex characters');
}
field.oninput = (event) => {
console.log("INPUT");
event.target.setCustomValidity('');
event.target.removeAttribute("pattern");
}
field.onchange = (event) => {
console.log("CHANGE");
event.target.setAttribute("pattern", pattern);
}
Output: <span id="output">No output</span>
<form id="form">
<label for="field">Enter 4 character hex code: </label>
<input id="field" type="text" pattern="[a-f,0-9]{4}" autocomplete=off>
</form>

setCustomValidity is meant to be used when multiple inputs, in combination are invalid. That is why it has to be reset to the empty string manually after. Other wise the title attribute should be used.
Trying to hide the validation error after editing the input is understandable but it is against the HTML5 form philosophy. It is meant to be shown as long as the input is invalid.
Adding maxlength can help the user to not cross the upper limit.
If you really want your bullet points to be satisfied feel free to not use HTML5 form validation but something custom instead.
So the reason a tooltip is shown even when setCustomValidity is set to empty string is because the input element is still invalid as per pattern attribute.
<form id="form">
<label for="field">Enter 4 character hex code: </label>
<input id="field" type="text" pattern="[a-f,0-9]{4}" maxlength="4" minlength="4" autocomplete="off" title="must be valid 4 hex characters">
</form>
JS
const form = document.querySelector("#form");
const field = document.querySelector("#field");
const output = document.querySelector("#output");
form.addEventListener('submit', (e) => {
console.log("SUBMIT");
output.textContent = field.value;
e.preventDefault(); // Prevent default POST request
});
field.oninvalid = (event) => {
console.log("INVALID");
}
field.oninput = (event) => {
console.log("INPUT");
}

Related

checkValidity / reportVality not working for values set via code

The HTML5 checkValidity() / reportValidity() methods don't seem to work if values are set via JavaScript.
Consider this example (JSFiddle):
<input id="text-field" maxlength="3" placeholder="Max len: 3 chars" />
<button id="set-field-value">Set</button>
<button id="check-valid">Is valid?</button>
<script>
window.onload = function() {
var textField = document.getElementById('text-field');
document.getElementById('set-field-value').onclick = function() {
textField.value = 'This is a very looooooooooooooooooooooooooooooong text';
};
document.getElementById('check-valid').onclick = function() {
window.alert(textField.checkValidity());
};
};
</script>
If you click the Set button, the value of the input field is set to an invalid value (it's length is greater that 3 characters), but the checkValidity() method still says that the input is valid (checked on Chrome, Edge and Firefox).
Why? Is there a way to determine is the field is valid even if its value is set via code?
I investigated this a while. It seems that you're at least missing <form> wrapping the input + button content.
But still, even if I tried to set input field required, still the ValidityObject won't notice that length is exceeded.
As a semi-workaround, I came with an idea of using pattern property:
<input id='textfield' pattern='\S{0,3}'>
(= \S stands for 'all characters except spaces')
This at least will prevent for content beyond three characters. In addition, you can have a setCustomValidity message for invalid cases.
See working example at: https://codepen.io/zvona/pen/rNavqxP?editors=1010
I've tried a few things that led me to understand that checkValidity does not check the value itself.
When I click on #check-validity I get :
textField.validity.tooLong // false
textField.validity.valid // true
textField.value // This is a very looooooooooooooooooooooooooooooong text
When I type myself in the input, my browser does not let me type more than 3 characters.
Why?
Precisely I don't know, but there is this awesome article on constraint validation that is worth having a look.
Is there a way to determine is the field is valid even if its value is
set via code?
textField.value returns your string, you can then access it's length. In this context, I would have anyway prefered this way.
const tooLong = textField.value.length > 3;
if (tooLong) window.alert('Something');
I hope it helps.
You should check if the form valid not the input. BUT it seems that the maxLength attribute is not something that trigger validation at all...
If you want to check length of input text you can do that with this:
window.onload = function() {
var textField = document.getElementById('text-field');
document.getElementById('set-field-value').onclick = function() { textField.value = 'ab'; };
document.getElementById('check-valid').onclick = function() {
if (textField.value && // if exist AND
textField.value.length > 2 && // if value have 3 charecter at least
textField.value.trim().length > 2 // if value is not just spaces
) {alert ('input OK');} // alert that input ok
else { alert('please insert at least 3 charecters');} // else alert error
};
};
<form id="formCheck">
<input type="text" id="text-field" min="3" />
<button type="button" id="set-field-value">Set</button>
<button type="button" id="check-valid">Is valid?</button>
</form>
The checkValidity() method works as expected on this example (using input number and min attribute) though:
window.onload = function() {
var theForm = document.getElementById('formCheck');
var numberField = document.getElementById('number-field');
document.getElementById('set-field-value').onclick = function() { numberField.value = '2'; };
document.getElementById('check-valid').onclick = function() {
window.alert(theForm.checkValidity());
};
};
<form id="formCheck">
<input type="number" id="number-field" min="3" />
<button type="button" id="set-field-value">Set</button>
<button type="button" id="check-valid">Is valid?</button>
</form>

Redux Form Field Level Validation does not work in Edge

I am using the example code in this path https://redux-form.com/6.6.3/examples/fieldlevelvalidation/ to validate number input
const number = value => value && isNaN(Number(value)) ? 'Must be a number' : undefined
It won't stop me from typing non-numeric characters in the Microsoft Edge. How can I work around this?
The type="number" converts the input to a number input field. On mobile you would get the keyboard with only numbers on it. So it' more a browser thing. The validation will just prevent submitting and showing error messages when entering wrong values.
If you want to do this on your on use the onKeyPress event:
const NumberInput = ()=>{
const onKeyPress = (e) => {
if(e.which<48 || e.which>57) {
e.preventDefault()
}
}
return (
<input onKeyPress={onKeyPress} />
)
}
small example: https://codesandbox.io/s/loving-lichterman-7eel4

Stopping Form from submitting if not validated

I have a form with weights, and I am making sure that each weight is:
A number
Every weight > 0
Sum of all weights = 100.
The below example is just testing 1 and 3:
function assertWeightValidity() {
let weightSum = 0;
$.each($('[name^=weight_]'), function()
{
chai.assert(isNaN(this.value) === false, 'Weights should be numbers!');
let currWeight = Number(this.value);
console.log(currWeight);
weightSum += currWeight;
<!-- assert nothing is zero -->
});
console.log(weightSum);
}
onEnterCalculate("#calculate", assertWeightValidity);
and then I have onEnterCalculate function defined as:
function onEnterCalculate(selector, assertion) {
middleware = assertion || null;
document.onkeydown = function (evt) {
var keyCode = evt ? (evt.which ? evt.which : evt.keyCode) : event.keyCode;
if (keyCode == 13) {
if(middleware) { middleware(); }
$(selector).click();
}
}
}
I am a bit of a newbie in JavaScript. I have googled around but I cannot find the solution. What I am trying to achieve is, if chai at any point throws an Error, I do not want to submit the form, I want to alert the user and let them amend what they already inputted in the form. I found that something like that can be achieved with the preventDefault() call. Not sure how to grab the event inside of assertWeightValidity() (because presumable I would need to generate the event when chai throws an error). What currently happens is that chai throws an Uncaught Exception if the weight is bad, for example 'sadasda' but it conitnues and posts the form anyway.
Thanks
In a nutshell, you give your form a submit event where you run your validations. If you call e.preventDefault() and/or return false in that function, it won't submit. Otherwise, it will.
document.querySelector('form').addEventListener('submit', e => {
console.log('preventing submit');
e.preventDefault();
});
<form action="#">
<button type="submit">Submit</button>
</form>
From there, it is just a matter of validating your code however you want and then calling (or not calling) preventDefault() as necessary:
document.querySelector('form').addEventListener('submit', e => {
if (!document.querySelector('input').value) {
console.log('Must add a value');
e.preventDefault();
}
});
<form action="#">
<label>Value: <input /></label>
<button type="submit">Submit</button>
</form>
If you have multiple validation functions that all return a boolean (or better yet, an error message), an easy way to check would be to put them in an array and then use every() or filter() to see if they are all good.
const checkField = id => !!document.querySelector(`#${id}`).value;
document.querySelector('form').addEventListener('submit', e => {
if (![checkField('a'), checkField('b'), checkField('c')].every(Boolean)) {
console.log('All fields must have a value');
e.preventDefault();
}
});
<form action="#">
<label>Value: <input id="a"/></label>
<label>Value: <input id="b"/></label>
<label>Value: <input id="c"/></label>
<button type="submit">Submit</button>
</form>
Even better, it can return an error message if it doesn't then you can gather up the error messages with filter():
const checkField = id => document.querySelector(`#${id}`).value ? undefined : `Field "${id}" must have a value`;
document.querySelector('form').addEventListener('submit', e => {
const errors = [checkField('a'), checkField('b'), checkField('c')].filter(Boolean);
if (errors.length) {
console.log(errors);
e.preventDefault();
}
});
<form action="#">
<label>Value: <input id="a"/></label>
<label>Value: <input id="b"/></label>
<label>Value: <input id="c"/></label>
<button type="submit">Submit</button>
</form>
Finally, since you mentioned throwing errors, if you want it to actually throw errors, you can try catch them and then output those.
const checkField = id => {
if (!document.querySelector(`#${id}`).value) {
throw new Error(`Field ${id} must have a value`);
}
};
document.querySelector('form').addEventListener('submit', e => {
try {
checkField('a');
checkField('b');
checkField('c');
} catch (ex) {
console.log(ex.message);
e.preventDefault();
}
});
<form action="#">
<label>Value: <input id="a"/></label>
<label>Value: <input id="b"/></label>
<label>Value: <input id="c"/></label>
<button type="submit">Submit</button>
</form>
The downside of this though is you can't check multiple things at the same time, since it'll abort on the first error.
What you want is instead of listening to keyboard events, you should simply check the submit event fired by the form. It's a very convenient catch all, because any user interaction that triggers form submission (could be the enter key, clicking a submit button, or whatsoever) will be caught by this handler.
Since you did not tag your question with "jQuery" and you seem to be familiar with ES6 syntax, I have formulated my answer using the following assumptions. To get the result you want, it is quite simple:
Listen to the form's submit event
Refactor your assertWeightValidity() method so that it throws an error, which we can catch in the submit event handler
Call assertWeightValidity() in a try block
If no errors are thrown, we can then submit the form :)
More details for the assertWeightValidity() method: you need to (1) first check if the weight input elements have non-empty values that can be parsed into numbers, and (2) you also want to check the sum of these values if they match 100.
Use Array.map() to iterate through all your input elements and retrieve their value. Before returning, you can already implement the logic to check if they are numbers. When returning, make sure to use the + operator to coerce the values to numbers (HTML values are always returned as string!)
Use Array.reduce() to sum up the array of weights you have
Check if the sum matches 100.
A proof-of-concept example is as follow. Test case:
Leave any of the field blank. Should throw an error because one or more fields cannot be converted into a number
Use 1, 2, 3, 4 in all 4 input fields. Should throw an error because they don't sum to 100
Use 25 for each field, that will sum to 100 and you should see a console log informing you that the form is valid and will be submitted.
Note that since I don't know what chai is about, I have simply commented the line out:
const customForm = document.getElementById('customForm');
customForm.addEventListener('submit', function(e) {
// Intercept default form submission
e.preventDefault();
// Assert weight validity
try {
assertWeightValidity.call(this)
} catch (error) {
console.warn(error);
return;
}
// Programmatically trigger form submission if no errors are thrown
console.log('Submitting form now');
// Uncomment the next line to actually submit the form
// this.submit();
});
// Returns if the weights are valid or not
function assertWeightValidity() {
// Get array of all weights
const weightElements = Array.prototype.slice.call(this.querySelectorAll('input[name^=weight_]'));
// Use `Array.map` to check if numbers can be parsed and return an array of weights
const weightsArray = weightElements.map((weightElement) => {
// If weight is empty or not a number, we throw an error and exit
// chai.assert(!isNaN(weight.value), 'Weights should be numbers!');
if (weightElement.value === '' || isNaN(weightElement.value))
throw 'Weights should be numbers';
// Otherwise, we return the value
return +weightElement.value;
});
// Use `Array.reduce` to get the sum of weights
const totalWeight = weightsArray.reduce((weight, accumulatedWeight) => weight + accumulatedWeight);
if (totalWeight !== 100)
throw 'Weights do not add up to 100';
}
input {
display: block;
}
<form id="customForm">
<input type="number" name="weight_1" placeholder="Enter a value for weight 1" />
<input type="number" name="weight_2" placeholder="Enter a value for weight 2" />
<input type="number" name="weight_3" placeholder="Enter a value for weight 3" />
<input type="number" name="weight_4" placeholder="Enter a value for weight 4" />
<button id="calculate">Calculate</button>
</form>

JQuery and HTML5 custom validation not working as intended

I just started learning JS, Jquery and HTML online. I have a question, and have tried doing things which were told in the answers of similar questions on SO, but it won't help.
I have a password form which only accepts input which have atleast 6 characters, one uppercase letter and one number. I wish to show a custom validation message which could just state these conditions again.
Here's my HTML code -
<div class="password">
<label for="password"> Password </label>
<input type="password" class="passwrdforsignup" name="password" required pattern="(?=.*\d)(?=.*[A-Z]).{6,}"> <!--pw must contain atleast 6 characters, one uppercase and one number-->
</div>
I'm using JS to set the custom validation message.
JS code
$(document).ready(function () {
$('.password').on('keyup', '.passwrdforsignup', function () {
var getPW = $(this).value();
if (getPW.checkValidity() === false) {
getPW.setCustomValidity("This password doesn't match the format specified");
}
});
});
However, the custom validation message doesn't show. Please help. Thank you so much in advance! :)
UPDATE 1
I changed the password pattern to (?=.*\d)(?=.*[A-Z])(.{6,}). Based on 4castle's advise, I realized there were a few errors in my javascript, and changed them accordingly. However, the custom validation message still doesn't show.
JavaScript:
$(document).ready(function () {
$('.password').on('keyup', '.passwrdforsignup', function () {
var getPW = $(this).find('.passwrdforsignup').get();
if (getPW.checkValidity() === false) {
getPW.setCustomValidity("This password doesn't match the format specified");
}
});
});
Again, than you all in advance!
First, update this:
var getPW = $(this).find('.passwrdforsignup').get();
to this:
var getPW = $(this).get(0);
...because $(this) is already the textbox .passwrdforsignup, you can't find it in itself!
The problem with setCustomValidity is, that it does only work once you submit the form. So there is the option to do exactly that:
$(function () {
$('.password').on('keyup', '.passwrdforsignup', function () {
var getPW = $(this).get(0);
getPW.setCustomValidity("");
if (getPW.checkValidity() === false) {
getPW.setCustomValidity("This password doesn't match the format specified");
$('#do_submit').click();
}
});
});
Please note the getPW.setCustomValidity(""); which resets the message which is important because if you do not do this, getPW.checkValidity() will always be false!
For this to work the textbox (and the submit-button) must be in a form.
Working JSFiddle
There are several issues going on here.
The pattern doesn't have a capture group, so technically nothing can ever match it. Change the pattern to (?=.*\d)(?=.*[A-Z])(.{6,})
$(this).value() doesn't refer to the value of the input tag, it's referring to the value of .password which is the container div.
getPW.checkValidity() and getPW.setCustomValidity("blah") are getting run on a string, which doesn't have definitions for those functions, only DOM objects do.
Here is what you should do instead (JS code from this SO answer)
$(document).ready(function() {
$('.passwrdforsignup').on('invalid', function(e) {
var getPW = e.target;
getPW.setCustomValidity("");
if (!getPW.checkValidity())
getPW.setCustomValidity("This password doesn't match the format specified");
}).on('input', function(e) {
$(this).get().setCustomValidity("");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<div class="password">
<label for="password">Password</label>
<input type="password" class="passwrdforsignup" name="password"
required pattern="(?=.*\d)(?=.*[A-Z])(.{6,})" />
</div>
<input type="submit" />
</form>

Custom Validation on HTML Number Input Misbehaving

In putting together a small webapp, I'm trying to ensure that end users are unable to place invalid characters in a number field that can hold signed floats. I'm using Dojo to search on an applied CSS class (in this case, ogInputNumber) and set events on input, keyup, and blur.
Ideally, I would like the input to be type="number" and to only allow digits, a hyphen (for signed floats), and a period character to act as a decimal place. If a user includes more than one hyphen or period character, the JS should truncate that second invalid character and everything thereafter in the input. Unfortunately, the JS behaves differently depending on whether the input is type="number" or type="text".
For type="text", if I attempt to enter the text 2.6a, 2.6 is fine, but the a is caught on the input event and prevented from appearing in the input. This is the desired behavior, but I would like to have the input as type="number" so the number spinners appear and for ease of use with mobile devices (so the number keyboard is brought up by default).
For type="number", if I attempt to enter the text 2.6a, the 2.6 is allowed to remain, but as soon as a is typed, the entire field is cleared out. That will prevent any invalid characters, but it's annoyingly overzealous. I've replicated this behavior on Chrome, Firefox, IE11, and Opera.
Can anyone offer any suggestions as to why the JS operates differently between inputs with type="text" and those with type="number"?
HTML:
<p>
<label for="numberInput1">Text Input</label>
<input id="numberInput1" class="ogInputNumber" type="text" />
</p>
<p>
<label for="numberInput2">Number Input</label>
<input id="numberInput2" class="ogInputNumber" type="number" />
</p>
JS:
// Checks number input fields for proper formatting
require(["dojo/domReady!", "dojo/on", "dojo/query"],
function (ready, on, query) {
query(".ogInputNumber").forEach(function (node) {
// Replace all the non-numeric, non-period, and non-hyphen characters with nothing while the user is typing
on(node, "input, keyup", function () {
this.value = this.value.replace(/[^\d\.-]/g, '');
});
// When the user leaves the input, format it properly as a signed float (or zero if it's something weird)
on(node, "blur", function () {
try {
if (this.value) {
this.value = parseFloat(this.value).toString();
} else {}
} catch (error) {
this.value = 0;
}
});
});
});
Working JSFiddle: http://jsfiddle.net/etehy6o6/1/
I think that's the default behavior of number input type, but I'm not sure. It's logical to think the input should not let the user put anything that is not a number, so it clears all the value before you can fire your keyup event.
So to keep the last valid value declare a variable outside the scope of your event and set it to the replaced value that was not cleared because invalid key input.
Using the code in your Fiddle:
Edited because addressed bug in comments
HTML
<!-- I asigned default values to test other scenarios -->
<p>
<label for="numberInput1">Text Input</label>
<input id="numberInput2" class="ogInputNumber" type="text" value="3.1416" />
</p>
<p>
<label for="numberInput">Number Input</label>
<input id="numberInput" class="ogInputNumber" type="number" value="3.1416" />
</p>
Javascript
// Checks number input fields for proper formatting
require(["dojo/domReady!", "dojo/on", "dojo/query"],
function (ready, on, query) {
query(".ogInputNumber").forEach(function (node) {
var validValue = this.value;
// Replace all the non-numeric, non-period, and non-hyphen characters with nothing while the user is typing
on(node, "input, keyup", function () {
if (this.value == '' && validValue.length > 1) {
this.value = validValue;
}
this.value = this.value.replace(/[^\d\.-]/g, '');
validValue = this.value;
});
// When the user leaves the input, format it properly as a signed float (or zero if it's something weird)
on(node, "blur", function () {
try {
if (this.value) {
this.value = parseFloat(this.value).toString();
} else {}
} catch (error) {
this.value = 0;
}
});
});
});

Categories

Resources