Sometimes websites need to use forms to collect user input.
Security is a key issue when it comes to processing submitted data. Given the way form data is submitted in http requests, we cannot rely on front-end form validation.
In addition to proper validation, form handling includes aspects such as processing submitted data and pre-populating forms with data.
getting started
ff provides a one-stop-shop class for form handling with the ffForms class. It's part of ff core, so it can be used out of the box.
In its simplest form, `ffForms is used to validate and process data submitted by the user.
An example of such a scenario could be a contact form.
A setup could look like this:
File presenter/myContactForm.html:
<section>
<form method="post" data-ffMethod='{"name": "parseContactForm", "mode": "outer"}'>
<fieldset>
<legend>Contact</legend>
<label for="name">name</label>
<input id="name" name="name">
<label for="email">email</label>
<input id="email" name="email" required>
<input type="submit" value="submit">
</fieldset>
</form>
</section>
The form uses a method hook parseContactForm for further processing.
The method will return the parsed form back to the template and replace the DOMElement, hence the "mode":"outer" option.
File presenter/myContactForm.php:
class myContactForm extends ffPresenter
{
/**
* Turns <form> from template into an ffForms object,
*/
public function parseContactForm($el){
// create form object
$form = ffForms::fromDOMElement($el);
// submitted values will "stick"
$form->sticky = true;
// return the parsed form
return (string) $form;
}
}
This will create a "sticky" form, that keeps submitted values between submits.
To implement logic, we could write something like this:
class myContactForm extends ffPresenter
{
/**
* Turns <form> from template into an ffForms object,
*/
public function parseContactForm($el){
// create form object
$form = ffForms::createFromDOMElement($el);
// no further processing needed,
// return the parsed form
if(!$form->hasBeenSubmitted()){
return (string) $form;
}
// submitted values will "stick"
$form->sticky = true;
// valid form submission
if($form->validate()) {
$submitted = $form->getSubmittedValues();
// ... do something with the submitted data
}
// return the parsed form
return (string) $form;
}
}
Invalid submitted inputs should be marked with the "inputtextWarning" class
If you need to access the form object from within several method hooks in your presenter, you will likely need a central instance of the form object.
You can use this construction:
<section>
<div data-ffMethod="showOnSuccess">Thanks for contacting us!</div>
<form method="post" data-ffMethod='{"name": "parseContactForm", "mode": "outer"}'>
<fieldset>
<legend>Contact</legend>
<label for="name">name</label>
<input id="name" name="name">
<label for="email">email</label>
<input id="email" name="email" required>
<input type="submit" value="submit">
</fieldset>
</form>
</section>
class myContactForm extends ffPresenter
{
protected function getForm($el){
static $form;
if($form !== null){
return $form;
}
$form = ffForms::createFromString($el->ownerDocument->saveHTML());
return $form;
}
public function showOnSuccess($el){
// get form object
$form = $this->getForm($el);
if(!$form->hasBeenSubmitted() || !$form->validate()){
return false;
}
}
public function parseContactForm($el){
// get form object
$form = $this->getFrom($el);
//...
}
}
If you want to use caller api for form submission and -validation, you have various options. Just keep in mind that form validation happens within the presenter itself.
Option A: Back-end-only
Use back-end-only approach to reload the containing presenter and let it take care of form validation.
In this case you only need to make sure, that the api is instantiated.
File presenter/myContactForm.html:
<form data-contact-api="submitForm" method="post" data-ffMethod='{"name": "parseContactForm", "mode": "outer"}'>
<input name="name">
<input type="submit">
</form>
File presenter/contactAPI.js:
ffCaller.add({
hook:'data-contact-api',
});
you can decouple form validation from presenter output logic by re-instantiating the presenter in the listener:
File presenter/myContactForm.html:
<form data-contact-api="submitForm" method="post" data-ffMethod='{"name": "parseContactForm", "mode": "outer"}'>
<input name="name">
<input type="submit">
</form>
File presenter/contactAPI.js:
ffCaller.add({
hook:'data-contact-api',
actions: {
submitForm: function(options, el){
this.send(options).then(function(xhr){
// do something based on the response
});
}
}
});
File classes/contactListener.php:
class contactListener extends ffOpenListener{
public function submitForm(){
$form = ffForms::createFromString((string) ffPresenter::create('myContactForm'));
return $form->validate();
}
}
Often times you want to store form data persistently and pre-populate the form with that data.
In this example we use ff's ffData object to store data, but basically any data object that can hold key/value pairs will do it.
File presenter/myContactForm.php:
class myContactForm extends ffPresenter
{
/**
* Turns <form> from template into an ffForms object,
*/
public function parseContactForm($el){
// create form object
$form = ffForms::fromDOMElement($el);
// create an instance of a data store with some reasonable
// defaults (session-only-storage and namespaced to this form)
$store = new ffData(session_id(), [
'autosave' => false,
'sessionNS' => $form->getName()
]);
// valid form submission
if($form->validate()) {
// updated stored values
foreach($form->getSubmittedValues() as $key => $value){
$store->setDatum($key, $value);
};
}
// read data from store
$storedData = $store->getData() ?: [];
// filter out data that doesn't belong to this form
$storedData = array_intersect_key($storedData, $form->getInputs());
foreach($storedData as $key => $value){
// sets stored data "as if it was submitted"
$form->setAsSubmitted($key, $value);
}
// return the parsed form
return (string) $form;
}
}
You can create a form object programmatically like so:
$form = new ffForms();
$form->addInput('name', ['type' => 'text']);
$form->addInput('email', ['type' => 'email']);
echo (string) $form;
29 März 2024