How to submit form via AJAX and validate CSRF?
- bernhard
- Member | 52
Hi,
I want to submit a nette form via AJAX and did not find any information on the internet how that can be done… This is what I have so far: Using isValid() works as long as I do not add a CSRF token to the form! This is how I'm checking the form:
$form = ...
$form->addText(...);
...
if($config->ajax) {
$values = json_decode(file_get_contents('php://input'));
$form->setValues($values);
if($form->isValid()) {
bd('success');
bd($form->getValues());
return;
}
}
echo $form->render();
This is how I send the ajax request (using the uikit js microframework):
let formData = new FormData(form);
let data = Object.fromEntries(formData.entries());
util.ajax("./", {
responseType: 'json',
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-_token_': data['_token_'],
},
data: JSON.stringify(data),
}).then(function(xhr) {
let r = xhr.response;
console.log(r);
}).catch(err => {
const errStatus = err.response ? err.response.status : 500;
console.log(err.response);
});
Can anybody point me into the right direction please?
Thx in advance!
- Rick Strafy
- Nette Blogger | 81
Are you using forms only and not whole nette/application? o.O
Try to look at https://naja.js.org, if you use that, you can add ajax class
to your form and it will work.
Btw, use onSuccess callback if you can, for example https://github.com/…resenter.php#L62
(form is created in SnippetFormFactory)
Last edited by Rick Strafy (2021-06-11 16:43)
- bernhard
- Member | 52
Hi Rick,
thx for your answer! Yes, I forgot that to mention… I'm using Nette Forms standalone combined with the ProcessWire CMS. Thx also for pointing me to naja.js but that seems like overkill if all I want to do is submitting a form via ajax? Also I'm not a JS guru, so all those modern tools are a bit hard to understand – especially without having examples :(
What do you mean with “use onSuccess callback”? What would I use it for? How would that work? I don't understand the syntax $form->onSuccess[] = … :(
Is there a way to send form data via AJAX and validate on the backend? Why would I need an additional framework/library for that??
Sorry, I'm a little lost and would really appreciate some help!
- jiri.pudil
- Nette Blogger | 1032
Hi, the CsrfProtection
control intentionally
ignores the setValue()
method.
You should be able to:
- Submit the form using the standard
application/x-www-form-urlencoded
content type. The easiest way to achieve this is to pass theFormData
instance directly as the request's data. I don't know if UIkit supports this, but both XMLHttpRequest and Fetch API do, so I would be surprised if UIkit did not. - Then let nette/forms handle the data processing: just remove the
$form->setValues()
call and replace$form->isValid()
with$form->isSuccess()
– it checks if the form has been submitted and loads the data from the HTTP request automatically.
Last edited by jiri.pudil (2021-06-15 16:46)
- bernhard
- Member | 52
thx jiri.pudil!
The first part of your message worked :)
But unfortunately part 2 does not… checking $form->isSuccess() returns false all the time.
Checking the content of php://input shows this: https://i.imgur.com/L4INvbJ.png
So the data is there, but how can I bring that data into the form and check for successful submission?
This is what I'm using on JS – but I think the problem is on the server side…
// intercept submission of ajax forms
util.on(document, 'submit', 'form[rf-ajax]', function (e) {
e.preventDefault();
let form = e.target;
// send ajax request
let formData = new FormData(form);
console.log(formData);
let method = util.attr(form, 'method');
util.ajax("./", {
responseType: 'json',
method: method,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded',
},
data: formData,
}).then(function(xhr) {
let r = xhr.response;
console.log(r);
}).catch(err => {
const errStatus = err.response ? err.response.status : 500;
console.log(err.response);
// UIkit.modal.alert('Fehler beim Laden der Daten - bitte wenden Sie sich an den Support (Fehlercode ajax#'+errStatus+')');
});;
return false;
});
Last edited by bernhard (2021-06-15 18:07)
- jiri.pudil
- Nette Blogger | 1032
Well, apparently the client-side solution sends the request as
multipart/form-data
. Try omitting the explicit
Content-Type
header, I believe the client should be able to
populate the header automatically based on what kind of data you pass
to it.
Server-side, nette/forms loads the values from $_POST
automatically. You don't need to (and really shouldn't) setValues()
from stdin. As long as PHP can correctly populate the $_POST
superglobal, it should all be working with just the isSuccess()
condition I've mentioned before.
- bernhard
- Member | 52
Hey Jiri :)
Thank you!! That was the problem!! Great. Now I have a super simple setup without any external dependencies and super simple form code on the PHP side :)))
For everybody else interested in this topic this is my working solution:
CLIENT
// intercept submission of ajax forms having rf-ajax attribute
util.on(document, 'submit', 'form[rf-ajax]', function (e) {
e.preventDefault();
let form = e.target;
let formData = new FormData(form); // use FormData (not a json string or the like)!
let method = util.attr(form, 'method');
// send ajax request to current page
util.ajax("./", {
responseType: 'json',
method: method,
headers: {
// set header to make ProcessWire + TracyDebugger ajax bar work
'X-Requested-With': 'XMLHttpRequest',
},
// set formData as request data
data: formData,
}).then(function(xhr) {
let r = xhr.response;
console.log(r);
}).catch(err => {
const errStatus = err.response ? err.response.status : 500;
console.log(err.response);
});
return false;
});
SERVER
/** @var RockForms $rockforms */
$rockforms = $this->wire->modules->get('RockForms');
$form = $rockforms->form('rueckruf');
$form->setHtmlAttribute("rf-ajax");
$form->addText('phone')
->setHtmlAttribute('placeholder', 'Telefon')
->setRequired("Bitte geben Sie Ihre Telefonnummer an!");
$form->addText('name')
->setHtmlAttribute('placeholder', 'Name (optional)');
$form->addTextArea('comment')
->setHtmlAttribute('placeholder', 'Kommentar (optional)');
$form->addSubmit('submit', "Rückruf anfordern");
if($form->isSubmitted()) {
// echo json response
if($form->isSuccess()) [...]
else [...]
}
echo $form->render();
As easy as that :)