Form for Creating and Editing a Record
How to properly implement adding and editing a record in Nette, using the same form for both?
In many cases, the forms for adding and editing a record are the same, differing only by the label on the button. We will show examples of simple presenters where we use the form first to add a record, then to edit it, and finally combine the two solutions.
Adding a Record
An example of a presenter used to add a record. We will leave the actual database work to the Facade
class, whose
code is not relevant for the example.
use Nette\Application\UI\Form;
class RecordPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Facade $facade,
) {
}
protected function createComponentRecordForm(): Form
{
$form = new Form;
// ... add form fields ...
$form->onSuccess[] = [$this, 'recordFormSucceeded'];
return $form;
}
public function recordFormSucceeded(Form $form, array $data): void
{
$this->facade->add($data); // add record to database
$this->flashMessage('Successfully added');
$this->redirect('...');
}
public function renderAdd(): void
{
// ...
}
}
Editing a Record
Now let's see what a presenter used to edit a records would look like:
use Nette\Application\UI\Form;
class RecordPresenter extends Nette\Application\UI\Presenter
{
private $record;
public function __construct(
private Facade $facade,
) {
}
public function actionEdit(int $id): void
{
$record = $this->facade->get($id);
if (
!$record // verify the existence of the record
|| !$this->facade->isEditAllowed(/*...*/) // check permissions
) {
$this->error(); // 404 error
}
$this->record = $record;
}
protected function createComponentRecordForm(): Form
{
// verify that the action is 'edit'
if ($this->getAction() !== 'edit') {
$this->error();
}
$form = new Form;
// ... add form fields ...
$form->setDefaults($this->record); // set default values
$form->onSuccess[] = [$this, 'recordFormSucceeded'];
return $form;
}
public function recordFormSucceeded(Form $form, array $data): void
{
$this->facade->update($this->record->id, $data); // update record
$this->flashMessage('Successfully updated');
$this->redirect('...');
}
}
In the action method, which is invoked right at the beginning of the presenter lifecycle, we verify the existence of the record and the user's permission to edit it.
We store the record in the $record
property so that it is available in the
createComponentRecordForm()
method for setting defaults, and recordFormSucceeded()
for the ID. An
alternative solution would be to set the default values directly in actionEdit()
and the ID value, which is part of
the URL, is retrieved using getParameter('id')
:
public function actionEdit(int $id): void
{
$record = $this->facade->get($id);
if (
// verify existence and check permissions
) {
$this->error();
}
// set default form values
$this->getComponent('recordForm')
->setDefaults($record);
}
public function recordFormSucceeded(Form $form, array $data): void
{
$id = (int) $this->getParameter('id');
$this->facade->update($id, $data);
// ...
}
}
However, and this should be the most important takeaway from all the code, we need to make sure that the action is
indeed edit
when we create the form. Because otherwise the validation in the actionEdit()
method
wouldn't happen at all!
Same Form for Adding and Editing
And now we will combine both presenters into one. Either we could distinguish which action is involved in the
createComponentRecordForm()
method and configure the form accordingly, or we can leave it directly to the
action-methods and get rid of the condition:
class RecordPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Facade $facade,
) {
}
public function actionAdd(): void
{
$form = $this->getComponent('recordForm');
$form->onSuccess[] = [$this, 'addingFormSucceeded'];
}
public function actionEdit(int $id): void
{
$record = $this->facade->get($id);
if (
!$record // verify the existence of the record
|| !$this->facade->isEditAllowed(/*...*/) // check permissions
) {
$this->error(); // 404 error
}
$form = $this->getComponent('recordForm');
$form->setDefaults($record); // set defaults
$form->onSuccess[] = [$this, 'editingFormSucceeded'];
}
protected function createComponentRecordForm(): Form
{
// verify that the action is 'add' or 'edit'
if (!in_array($this->getAction(), ['add', 'edit'])) {
$this->error();
}
$form = new Form;
// ... add form fields ...
return $form;
}
public function addingFormSucceeded(Form $form, array $data): void
{
$this->facade->add($data); // add record to database
$this->flashMessage('Successfully added');
$this->redirect('...');
}
public function editingFormSucceeded(Form $form, array $data): void
{
$id = (int) $this->getParameter('id');
$this->facade->update($id, $data); // update record
$this->flashMessage('Successfully updated');
$this->redirect('...');
}
}