I am writing a simple web-app with a register-login-logout functionality as a practice task. The backend logic is in PHP, the client side part is in pure HTML/CSS/Javascript. I was asked to make the register/login form dynamically generated according to current scope (register or login), so I decided to generate the form with Javascript and send the form data (and receive validation error messages) to the server with an AJAX request (in JSON format).
The problem is when the form is generated, the AJAX request automatically fires, without clicking the Login/Register button. Moreover, it seems that the function which sends the AJAX request, cannot get the input values from the input fields, so it only sends the scope.
Every time, when the form is regenerated (because the scope has changed) it resends the AJAX request.
I have already tried calling the function when the Login/Register buttons are clicked, and binding the function to the form only when it is generated, but nothing helped.
Here are the functions that send the AJAX:
function sendAjax(data) {
let xhr = new XMLHttpRequest();
xhr.open("POST", "process.php");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
let resData = JSON.parse(this.responseText);
Object.keys(resData).forEach(function (key) {
let e = resData[key];
if(document.getElementById(key+"span")!==null){
let div = document.getElementById(key+"G");
div.classList.add("has-error");
let span = document.getElementById(key+"span");
span.innerHTML = e;
}
});
}
};
xhr.send(data);
}
function process(scope) {
let inputs = document.getElementsByTagName("input");
console.log(inputs);
let filteredInputs = [];
for(let i = 0; i < inputs.length; i++){
if(inputs[i].type !== "submit"){
filteredInputs.push(inputs[i]);
}
}
console.log(filteredInputs);
let data = {scope: scope};
for(let i = 0; i < inputs.length; i++){
data[filteredInputs[i].name] = filteredInputs[i].value;
}
let dataJSON = JSON.stringify(data);
sendAjax(dataJSON);
return false;
}
This is how the form is generated:
let session = {
scope: "login",
setScope: function (scope) {
this.scope = scope;
document.getElementById("form-wrapper").appendChild(createForm(scope));
}
};
function createForm(scope){
if(document.getElementById("form")!==null){
let form = document.getElementById("form");
document.getElementById("form-wrapper").removeChild(form);
}
let form = document.createElement("form");
form.id = "form";
let buttons = document.createElement("div");
buttons.className = "form-group";
let title = document.createElement("h2");
if(scope === "login"){
title.innerHTML = "Bejelentkezés";
form.appendChild(title);
form.appendChild(createFormGroup("email"));
form.appendChild(createFormGroup("password"));
let login = document.createElement("input");
login.type = "submit";
login.className = "btn btn-primary";
login.value = "Bejelentkezés";
login.id = "btn-submit";
buttons.appendChild(login);
}
if(scope === "register"){
title.innerHTML = "Regisztráció";
form.appendChild(title);
formElementProps.forEach(((value, key) => form.appendChild(createFormGroup(key))));
let register = document.createElement("input");
register.type = "submit";
register.className = "btn btn-primary";
register.value = "Regisztráció";
register.id = "btn-submit";
let reset = document.createElement("input");
reset.type = "reset";
reset.className = "btn btn-deafult";
reset.value = "Törlés";
buttons.appendChild(register);
buttons.appendChild(reset);
}
let link = createLink(scope);
form.appendChild(buttons);
form.appendChild(link);
form.onsubmit = process(scope);
return form;
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById("form-wrapper").appendChild(createForm(session.scope));
});
Backend logic is seemingly fine, because when it fires automatically, it responds with the correct error message.
Your problem is with this line:
form.onsubmit = process(scope);
This passes the result of the function call process(scope)
as the onsubmit
attribute of form
. Which of course doesn't really make any sense, since the result (return value) is false
. But it does mean the function is called, thus triggering your Ajax - right when your createForm
function is called. Which is exactly the behaviour you're complaining about.
To fix it, you can just recall that form.onsubmit
like all HTML attributes, should be a "string", in this case a string of JS code which will be avaluated when the submit event happens. So this seemingly trivial change should just work as intended (EDIT: actually it won't, see my comment below):
form.onsubmit = "process(scope)";
However, it's not considered very good practice to use HTML on***
attributes to pass around strings which are evaluated as JS code. Much better to use the addEventListener
method, and pass it the actual function which should be called. Here than would be:
form.addEventListener("submit", function() {
process(scope);
});
Note that the second argument passed to addEventListener
here is a function - you might be tempted to do
form.addEventListener("submit", process(scope));
but that would fall foul of the exact problem you had originally - process(scope)
will be executed as soon as that line is encountered, and its return value would be expected to be a function (which of course it isn't).