I have looked through the many answers to the "closure" issue but cannot get any to work on my specific issue.
The following js code takes a json file and stores it and then does some form validation based on the data.
The problem is on submission of the form and execution of the validate function I should see two errors but I only get the error for the last field (logged in the console).
This is an obvious closure problem but having spent all day on it I still can't fix it. Below is the code, the click event is at the bottom...
I am only checking the min length rule at this moment.
// Get the json file and store
function loadJSON(callback) {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', 'js/rules.json');
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
// Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
callback(xobj.responseText);
}
};
xobj.send(null);
}
// Load json...
loadJSON(response);
// Create global vars...
var lookup = [], errors = [], i, e, id, lookupId, minLength;
function response(responseData) {
// Create objects from json data
var rulesSet = JSON.parse(responseData);
// Loop through objects
for (i = 0; i < rulesSet.length; i++) {
// Create a lookup for each object that can be used later
lookup[rulesSet[i].id] = rulesSet[i];
}
// Loop through form elements and store id's
// Validate the form
function validate(e) {
var elements = document.getElementsByTagName('input');
for (e = 0; e < elements.length; e++) {
id = elements[e].getAttribute('id');
lookupId = lookup[id].rules; var rules;
// Loop through rules of the matched ID's
for (rules of lookupId){
// Check if there is a min length rule
if(rules.name === 'min_length') {
minLength = rules.value.toString();
// Check if the rule is valid (is a number)
if(isNaN(minLength) || minLength.length === 0){
// Log an error somewhere here
// Run the minLength function and report an error if it fails validation
} else if(!checkMinLength(minLength, id)) {
errors[errors.length] = id + " - You must enter more than " + minLength + " characters";
}
}
}
// If there are errors, report them
if (errors.length > 0) {
reportErrors(errors);
//e.preventDefault();
}
}
}
validate();
// Check the length of the field
function checkMinLength(minLength, id){
var val = document.getElementById(id).value;
if(val < minLength){
return false;
}
return true;
}
// Error reporting
function reportErrors(errors){
for (var i=0; i<errors.length; i++) {
var msg = errors[i];
}
console.log(msg);
}
$('#email-submit').on('click',function(e){
validate(e);
});
}
Possibly not relevant but below is the json that is loaded...
[
{
"id": "search",
"rules": [
{
"name": "min_length",
"value": "5"
},
{
"name": "email"
}
]
},
{
"id": "phone-number",
"rules": [
{
"name": "min_length",
"value": 8
}
]
},
{
"id": "surname",
"rules": [
{
"name": "min_length",
"value": 10
}
]
}
]
And the basic form to validate...
<form action="index.html" name="searchForm" id="search-form">
<label for="search">Email</label>
<input type="text" id="search" name="email" placeholder="Enter email">
<input type="text" id="phone-number" name="name" placeholder="Enter name">
<button type="submit" id="email-submit">Submit</button>
</form>
The code does exactly what you tell it to do
// Error reporting
function reportErrors(errors){
for (var i=0; i<errors.length; i++) {
var msg = errors[i]; <-- setting the variable on each iteration
}
console.log(msg); <-- reads the last value from the last iteration
}
You need to move the console inside of the for loop
// Error reporting
function reportErrors(errors){
for (var i=0; i<errors.length; i++) {
var msg = errors[i];
console.log(msg);
}
}
or do not even loop
// Error reporting
function reportErrors(errors){
console.log(errors.join("\n"));
}
And now a logic issue, you are declaring a function inside a for loop
function response(responseData) {
// ignored code //
var elements = document.getElementsByTagName('input');
for (e = 0; e < elements.length; e++) {
function validate(e) { <-- THIS SHOULD NOT BE IN THE FOR LOOP
Again just like the error message thing, only the last one is going to be there...
Create more readable scheme without closure.
var submitButton = document.querySelector('#email-submit')
function validate (evt) {
async function loadRules (url) {
var rawData = await fetch(url)
return await rawData.json()
}
function reportErrors (error) {
evt.preventDefault()
// Report logic here
}
function evaluate (rules) {
// Your validate rules here
// loaded rules in rules object
// eg. rules[0]['rules'].name
}
loadRules('js/rules.json').then(evaluate)
}
submitButton.addEventLister('click', validate)
Related
(React web app development)
In order to check if the current stock status of products, I use ID of products to loop through json data.
I am trying to retrieve value of "DATAPAYLOAD" by key (id) from json (below). idsent is string passed from another component. But "if (Data.response[i].id === idsent)" this condition always appears to be false because I got "failed" in console.
That would be really helpful if someone could take a look at the following code and give me some sujections, thanks in advance!
onButtonClicked = () => {
const idsent="D56F36C6038DFC8244F"
for (var i = 0; i < Data.response.length; i++) {
if (Data.response[i].id === idsent) {
name = Data.response[i].DATAPAYLOAD;
const word = '<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>';
if (name.includes(word)) {
return true;
}
else {
return false;
}
}
console.log("failed");
}
The following is part of the json data that is requested through fetch get-method.
Data= {
"code": 200,
"response": [
{
"id": "CED62C6F96BD0E21655142F",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>OUTOFSTOCK</INSTOCKVALUE>\n</AVAILABILITY>"
},
{
"id": "D56F36C6038DFC8244F",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>LESSTHAN10</INSTOCKVALUE>\n</AVAILABILITY>"
},
{
"id": "4536C9E608B563A749",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>\n</AVAILABILITY>"
},
{
"id": "3A576872130625CABFADEE68",
"DATAPAYLOAD": "<AVAILABILITY>\n <CODE>200</CODE>\n
<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>\n</AVAILABILITY>"
}
]
}
Thank you again.
You probably wanted console.log("failed"); outside of the for loop like the following (so that it only executes once all the data is processed):
onButtonClicked = () => {
const idsent="D56F36C6038DFC8244F"
for (var i = 0; i < Data.response.length; i++) {
if (Data.response[i].id === idsent) {
name = Data.response[i].DATAPAYLOAD;
const word = '<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>';
if (name.includes(word)) {
return true;
}
else {
return false;
}
}
}
console.log("failed");
When the fetch is successful, You need to read and parse the data using json(). Pleas read this
onButtonClicked = async () => {
const idsent="D56F36C6038DFC8244F"
Data = await Data.json(); // json() will create a promise
for (var i = 0; i < Data.response.length; i++) {
if (Data.response[i].id === idsent) {
name = Data.response[i].DATAPAYLOAD;
const word = '<INSTOCKVALUE>INSTOCK</INSTOCKVALUE>';
if (name.includes(word)) {
return true;
}
else {
return false;
}
}
console.log("failed");
}
The reason you get failed, is because the first time through, the ID does not match the one sent, so it console logs the "failed" message. Then the second time through the for loop it matches the data, and then hits the next if, which checks for the value. Since the value you are searching for is included in the data, it returns true and the for loop is exited. The reason you see the fail log is because you are logging when the id doesn't match and there are 3 records in that array where the id don't match, the first one being one of them.
There is an exception occurring and I am not sure why because I am new to JS. As soon as I reach the line testData["beacons"].append(beacon); My code jumps to catch(e). I am assuming I cant append objects to other arrays?
JS:
$(document).ready(function() {
// Data to describe what kind of test
var testData = {
"timestamp": "",
"hive": 0,
"hdfs": 0,
// Contains a list of testData objects
"beacons":[]
};
var testRun = document.getElementById("test-form");
testRun.addEventListener('submit', function(event) {
event.preventDefault();
var selectedTest = document.querySelector('input[name=test-select]:checked');
alert(selectedTest);
var testType = selectedTest.id;
if (testType == "hdfs-test") {
testData["hdfs"] = 1;
testData["hive"] = 0;
} else if (testType == "hive-test") {
testData["hdfs"] = 0;
testData["hive"] = 1;
} else if (testType == "hdfs-hive-test") {
testData["hdfs"] = 1;
testData["hive"] = 1;
} else {
// null
}
var events = document.getElementById("event-textarea").value;
// check in valid input
var eventSource = events.replace("],[","],,,,[");
// beaconLists allows users to submit --> [{beacon1}, {beacon2}, ...], [{beacon3}, {beacon4}, ...]
var beaconLists = eventSource.split(",,,,");
for (var i = 0; i < beaconLists.length; i++) {
// inspect one list in beaconLists [{beacon1}, {beacon2}, ...]
var beaconList = beaconLists[i];
try {
// list of JSON objects
var beaconObjList = JSON.parse(beaconList);
for (var j = 0; j < beaconObjList.length; j++) {
var beaconObj = beaconObjList[j];
if (beaconObj["data"] && beaconObj["application"]) {
// successful parse to find events
// describe beacon being tested
alert("yes");
var beacon = {
"app_name": beaconObj["application"]["app_name"],
"device": beaconObj["application"]["device"],
"device_id": beaconObj["application"]["device_id"],
"os": beaconObj["application"]["os"],
"os_version": beaconObj["application"]["os_version"],
"browser": beaconObj["application"]["browser"],
"beacon": beaconObj
};
// append to testData
testData["beacons"].append(beacon);
// reset beacon so we can append new beacon later
beacon = {};
} else {
// notify event isn't in the correct format?
alert("no");
}
}
} catch (e) {
// notify bad JSON
alert("failed");
}
}
console.log(testData);
//$.ajax({
// type: "POST",
// url: "/test/",
// data: testData,
// success: function () {
// alert("yay");
// },
// failure: function () {
// alert("boo");
// }
//});
});
});
There is nothing wrong with having an array of objects. JavaScript handles that just fine. The main issue is that append is a jQuery API method for adding elements (psuedo native appendChild). push is how you add to an array.
testData["beacons"].push(beacon);
Further, this part of your code is problematic.
// reset beacon so we can append new beacon later
beacon = {};
Both the variable beacon and the one added here testData["beacons"] are the same. In JavaScript, the value of testData["beacons"]'s recent beacon is the same as the variable beacon. When the value in the variable beacon is set to {}, so is the array's value. This line of code simply needs to be removed. Inside of the variable environment set up, the use of var will set up a new variable for beacon each iteration.
You should use the push method, like:
testData["beacons"].push(beacon);
I am new to Javascript.
I need to make an username input, then validate it.
The username entered must not be one of the elements in this array: ["admin", "administrator", "demo", "user"] etc. The array can be longer.
I made this so far, but it works only for the 1st element in the array.
function arrayValidation () {
nonoUser = ["username", "admin", "administrator", "demo"];
valueOfInput = document.getElementById("user").value; // "user" here is id of input
for (var i = 0; i < nonoUser.length; i++) {
if (valueOfInput == nonoUser[i]) {
alert("you cant use this username");
}else {
return;
}
}
}
Try this:
function arrayValidation () {
nonoUser = ["username", "admin", "administrator", "demo"];
valueOfInput = document.getElementById("user").value; // "user" here is id of input
var isValid = true;
for (var i = 0; i < nonoUser.length && isValid; i++) {
if (valueOfInput == nonoUser[i]) {
isValid = false;
}
}
if (isValid) {
// Username is valid
}else{
// Username is invalid
}
}
However, you should never trust data that's sent to the server (Validate server-side as well)
It's trivial to change the js as a user.
This should point you in the right direction:
document.getElementById("user").onkeyup = function(){
var nonoUser = ["username", "admin", "administrator", "demo"];
nonoUser.indexOf(this.value) === -1 ? false : this.value = '';
}
demo: http://jsfiddle.net/Le2xC/
I also hope you're validating this server-side as well, otherwise users will still be able to use your "nono" usernames.
you need to add return false; to the code section with the alert, not the else statement.
The comments here should help you to see why your code isn't working as you expect:
// Your original code:
function arrayValidation () {
nonoUser = ["username", "admin", "administrator", "demo"];
valueOfInput = document.getElementById("user").value; // "user" here is id of input
for (var i = 0; i < nonoUser.length; i++) {
if (valueOfInput == nonoUser[i]) {
alert("you cant use this username");
} else {
// Using return here is the problem.
// This else block will run if a valid username was entered.
// The return statement stops execution of the function, thus ending the loop.
// So if the loop passes the test the first time, this return
// statement will stop the loop and function from completing
// the checks against the other keywords.
return;
}
}
}
This is your code modified to work as you expect:
function arrayValidation () {
nonoUser = ["username", "admin", "administrator", "demo"];
valueOfInput = document.getElementById("user").value; // "user" here is id of input
for (var i = 0; i < nonoUser.length; i++) {
if (valueOfInput == nonoUser[i]) {
alert("you cant use this username");
// We can end the function check return here.
// There's no point checking the others, as we've found an invalid one.
return false; // Return a meaningful result
}
// No need for an else statement either.
}
}
As many have pointed out, client side validation should be considered an optional extra, and server-side validation as essential.
My knowledge of jQuery and more specifically javascript has always been very limited and I am working hard to get better.
With that in mind I am trying to create some sort of "extensible validation framework" in that I can create an object with a validation function and its error message, push it into an array and then call all those methods and report the messages from the ones that fail.
So first I did something very simple just to see that the basic validation worked which was this:
var selectedTourDate = $("#tourDate").val();
var list = $("#bookedTourDate *");
var isDateBooked = list.filter(function () {
return (this.innerHTML == selectedTourDate) ;
}).length !== 0;
if (isDateBooked) {
alert("Date invalid");
return;
}
This works fine.
Then I created my "framework" which is this:
function Validator(fn, errorMessage) {
this.validationFunction = fn;
this.errorMessage = errorMessage;
};
var validationRunner = {
validators: [],
errorMessages: [],
validationResult: true,
addValidation: function (validator) {
validationRunner.validators.push(validator);
},
validate: function () {
for (var validator = 0; validator < validationRunner.validators.length; validator++) {
if (validationRunner.validators[validator] instanceof Validator) {
var result = validationRunner.validators[validator].validationFunction();
validationResult = validationRunner.validationResult && result;
if (!result)
validationRunner.errorMessages.push(validationRunner.validators[validator].errorMessage);
}
}
},
getErrorMessage: function () {
var message = "<ul>";
for (var errorMessage = 0; errorMessage > validationRunner.errorMessages.length; errorMessage++) {
message += "<li>" + errorMessage + "</li>";
}
message += "</ul>";
return message;
}
}
And then modified the first block of code as follows:
var selectedTourDate = $("#tourDate").val();
var list = $("#bookedTourDate *");
var tmp = new Validator(function () {
return list.filter(function () {
return (this.innerHTML == selectedTourDate);
}).length !== 0;
}, "Date already booked");
validationRunner.addValidation(tmp);
validationRunner.validate();
var msg = validationRunner.getErrorMessage();
This does not work at all.
It seems that the problem is that in this statement:
return (this.innerHTML == selectedTourDate);
The "this" is the Validator object instead of the html element from the list which confuses me since as its inside the function of the "filter" method it should be.
Is there a way to correct this so I can accomplish what I am trying to do here?
this does have the correct value but there are some mistakes in the getErrorMessage function.
To check the value of this don't rely on a debugger's "watch variable" function but do a console.log which will give you the value at a specific point of the code execution.
My changes:
for (var errorMessage = 0; errorMessage < validationRunner.errorMessages.length; errorMessage++) {
message += "<li>" + validationRunner.errorMessages[errorMessage] + "</li>";
}
The errorMessage variable is only a index and not the actual error message. Also you used a "bigger than" instead of a "smaller than" sign.
Here's a fiddle
That is very odd, as this should be the current DOM element per jquery api.
The function should take an index as a parameter, though. So what about trying
return list.filter(function (i) {
return (list.get(i).innerHTML == selectedTourDate);
}).length !== 0;
I can’t figure out what is missing so that when e-mail is valid it will skip the last invalid message and move to next item on form for validation:
enter code here
if (document.form1.email.value.length > 0) {
var tst = document.form1.email.value;
var okd = ['bankofamerica.com','baml.com','magner.com','ml.com','ust.com','ustrust.com']
for (var i = 0; i < okd.length; i++) { okd[i] == okd[i].toLowerCase() }
var emailRE = /^[a-zA-Z0-9._+-]+#([a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})$/
var aLst = emailRE.exec(tst)
if (!aLst) {
alert(tst + ' is not a valid e-mail')
} else {
var sLst = aLst[1].toLowerCase()
for (var i = 0; i < okd.length; i++) {
if (sLst == okd[i]) {
// alert(aLst[1] + ' is allowed');-->
}
}
if (i == okd.length) alert(aLst[1] + ' is not allowed. Please enter an email address with an authorized domain.')
document.form1.email.select();
return false;
}
}
I'd recommend placing this code into a function, maybe named ValidateEmail().
In your loop: if you've determined the email is valid, return true;. This will prevent further execution. If that domain doesn't match, have it continue looping to check the others.
If the loop completes without returning true, you'll know it didn't match anything so return false; at the very end.
EDIT: Use try/catch instead:
if (document.form1.email.value.length > 0) {
var tst = document.form1.email.value;
var okd = ['bankofamerica.com','baml.com','magner.com','ml.com','ust.com','ustrust.com']
for (var i = 0; i < okd.length; i++) { okd[i] == okd[i].toLowerCase() }
try {
var emailRE = /^[a-zA-Z0-9._+-]+#([a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})$/
var aLst = emailRE.exec(tst)
if (!aLst)
throw (tst + ' is not a valid e-mail');
// isValidDomain will be changed to 'true' only if it matches an item in the array
var isValidDomain = false;
var sLst = aLst[1].toLowerCase()
for (var i = 0; i < okd.length; i++) {
if (sLst == okd[i]) {
isValidDomain = true;
// We break here because a match has been found - no need to compare against the other domain names.
break;
}
}
if(!isValidDomain)
throw (aLst[1] + ' is not allowed. Please enter an email address with an authorized domain.');
// If execution reaches here, you know it passed both tests!
return true;
}
catch(err) {
// This code block runs whenever an error occurs
alert(err);
document.form1.email.select();
return false;
}
}
throw basically acts like a goto command. It will jump directly to the catch(err) portion of the code.
More info about try, catch, and throw:
http://www.w3schools.com/js/js_try_catch.asp
http://www.w3schools.com/js/js_throw.asp
Thank you very much Colin!
I had to remove the following 2 lines to avoid halting the code from running on to next validation field:
isValidDomain = true;
// We break here because a match has been found - no need to compare against the other domain names.
// break - exits code from running on down to next item on page
}
}
if (!isValidDomain)
throw (aLst[1] + ' is not allowed. Please enter an email address with an authorized domain.');
// If execution reaches here, you know it passed both tests!
// return true; - was not needed, stops code from running on page
}
catch (err) {