Tables and saving data
Creating or changing a record from the panel takes three ready-made Joomla classes. Table represents one table row and knows how to load and store it. AdminModel validates form data and passes it to Table. FormController handles the Save and Close buttons. Your part is a short subclass of each.
Files
administrator/components/com_hello/
├── forms/
│ └── post.xml the form definition
├── src/
│ ├── Controller/PostController.php
│ ├── Model/PostModel.php
│ ├── Table/PostTable.php
│ └── View/Post/HtmlView.php
└── tmpl/
└── post/
└── edit.phpCreate them by hand and add the line <folder>forms</folder> to the files section inside administration in the manifest.
The table class
// administrator/components/com_hello/src/Table/PostTable.php
namespace My\Component\Hello\Administrator\Table;
defined('_JEXEC') or die;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
class PostTable extends Table
{
public function __construct(DatabaseDriver $db)
{
parent::__construct('#__hello_posts', 'id', $db);
}
}The constructor names the table and its primary key. Joomla finds out the columns itself. The object gets the methods load($id), bind($data), check(), store() and delete($id). You can override check() to verify data before it is written.
The form definition
<?xml version="1.0" encoding="UTF-8"?>
<!-- administrator/components/com_hello/forms/post.xml -->
<form>
<fieldset name="details">
<field name="id" type="hidden" />
<field name="title" type="text" label="Title" required="true" />
<field name="body" type="textarea" label="Text" rows="8" />
</fieldset>
</form>Field names match the table columns. Forms are the subject of the next lesson.
The model
// administrator/components/com_hello/src/Model/PostModel.php
namespace My\Component\Hello\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\AdminModel;
class PostModel extends AdminModel
{
public function getForm($data = [], $loadData = true)
{
return $this->loadForm('com_hello.post', 'post', ['control' => 'jform', 'load_data' => $loadData]);
}
protected function loadFormData()
{
$data = Factory::getApplication()->getUserState('com_hello.edit.post.data', []);
return $data ?: $this->getItem();
}
}getForm() loads the file forms/post.xml. loadFormData() returns data for the fields: first whatever the user already typed but could not save because of an error, otherwise the record from the database. The methods getItem() and save() already exist in AdminModel; it finds the PostTable class by its own name.
The controller
// administrator/components/com_hello/src/Controller/PostController.php
namespace My\Component\Hello\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\FormController;
class PostController extends FormController
{
}The class is empty: the actions add, edit, save, apply and cancel are inherited.
The edit screen
// administrator/components/com_hello/src/View/Post/HtmlView.php
namespace My\Component\Hello\Administrator\View\Post;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
public $form;
public $item;
public function display($tpl = null): void
{
$model = $this->getModel();
$this->form = $model->getForm();
$this->item = $model->getItem();
ToolbarHelper::title($this->item->id ? 'Edit post' : 'New post');
ToolbarHelper::apply('post.apply');
ToolbarHelper::save('post.save');
ToolbarHelper::cancel('post.cancel');
parent::display($tpl);
}
}<?php
// administrator/components/com_hello/tmpl/post/edit.php
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
Factory::getApplication()->getDocument()->getWebAssetManager()
->useScript('keepalive')
->useScript('form.validate');
?>
<form action="<?php echo Route::_('index.php?option=com_hello&layout=edit&id=' . (int) $this->item->id); ?>"
method="post" name="adminForm" id="adminForm" class="form-validate">
<?php echo $this->form->renderFieldset('details'); ?>
<input type="hidden" name="task" value="">
<?php echo HTMLHelper::_('form.token'); ?>
</form>renderFieldset('details') prints all fields of the group with their labels.
The result
Open Components → Hello and press New. A form with the Title and Text fields appears. Enter data and press Save & Close — you return to the list with a success message and a new row. Clicking a title opens the same form filled with the post data.
What happens on save
- The button submits the form with
task=post.save. FormControllerchecks the token and the user's permissions.AdminModelvalidates the data against the form definition.PostTablerunsbind(),check()andstore().- The controller redirects to the list — the
postsview.
Common mistake
Custom actions that change data are written without a token check. Such an action can be triggered by a link from another site: the browser of a logged-in administrator attaches the cookie by itself. The ready-made controllers already check it; in your own methods make $this->checkToken(); the first line — and do not forget HTMLHelper::_('form.token') in the form.