There are several reasons for allowing a user to edit in the frontend. For one thing, users feel that working directly on the website is more user-friendly than logging into the backend. Or, it is important for an administrator not to release access to the administration area. Therefore, in the next step, we equip our component with the possibility to edit items in the frontend.
For impatient people: Look at the changed programme code in the Diff View[^codeberg.org/astrid/j4examplecode/compare/t24b...t25] and copy these changes into your development version.
Step by step
New files
administrator/components/com_foos/src/Service/HTML/Icon.php
The following file contains all the information needed to display an icon used to open the edit in the frontend - provided the viewer is allowed to edit.
administrator/components/com_foos/src/Service/HTML/Icon.php
// https://codeberg.org/astrid/j4examplecode/raw/branch/t25/src/administrator/components/com_foos/src/Service/HTML/Icon.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\UserFactoryInterface;
use FooNamespace\Component\Foos\Site\Helper\RouteHelper;
use Joomla\Registry\Registry;
\defined('_JEXEC') or die;
/**
* Content Component HTML Helper
*
* @since __BUMP_VERSION__
*/
class Icon
{
/**
* The user factory
*
* @var UserFactoryInterface
*
* @since __BUMP_VERSION__
*/
private $userFactory;
/**
* Service constructor
*
* @param UserFactoryInterface $userFactory The userFactory
*
* @since __BUMP_VERSION__
*/
public function __construct(UserFactoryInterface $userFactory)
{
$this->userFactory = $userFactory;
}
/**
* Method to generate a link to the create item page for the given category
*
* @param object $category The category information
* @param Registry $params The item parameters
* @param array $attribs Optional attributes for the link
*
* @return string The HTML markup for the create item link
*
* @since __BUMP_VERSION__
*/
public function create($category, $params, $attribs = [])
{
$uri = Uri::getInstance();
$url = 'index.php?option=com_$foo&task=$foo.add&return=' . base64_encode($uri) . '&id=0&catid=' . $category->id;
$text = '';
if ($params->get('show_icons')) {
$text .= '<span class="icon-plus icon-fw" aria-hidden="true"></span>';
}
$text .= Text::_('COM_FOOS_NEW_FOOS');
// Add the button classes to the attribs array
if (isset($attribs['class'])) {
$attribs['class'] .= ' btn btn-primary';
} else {
$attribs['class'] = 'btn btn-primary';
}
$button = HTMLHelper::_('link', Route::_($url), $text, $attribs);
return $button;
}
/**
* Display an edit icon for the $foo.
*
* This icon will not display in a popup window, nor if the $foo is trashed.
* Edit access checks must be performed in the calling code.
*
* @param object $foo The $foo information
* @param Registry $params The item parameters
* @param array $attribs Optional attributes for the link
* @param boolean $legacy True to use legacy images, false to use icomoon based graphic
*
* @return string The HTML for the $foo edit icon.
*
* @since __BUMP_VERSION__
*/
public function edit($foo, $params, $attribs = [], $legacy = false)
{
$user = Factory::getUser();
$uri = Uri::getInstance();
// Ignore if in a popup window.
if ($params && $params->get('popup')) {
return '';
}
// Ignore if the state is negative (trashed).
if ($foo->published < 0) {
return '';
}
// Show checked_out icon if the $foo is checked out by a different user
if (
property_exists($foo, 'checked_out')
&& property_exists($foo, 'checked_out_time')
&& !is_null($foo->checked_out)
&& $foo->checked_out !== $user->get('id')
) {
$checkoutUser = $this->userFactory->loadUserById($foo->checked_out);
$date = HTMLHelper::_('date', $foo->checked_out_time);
$tooltip = Text::sprintf('COM_FOOS_CHECKED_OUT_BY', $checkoutUser->name)
. ' <br> ' . $date;
$text = LayoutHelper::render('joomla.content.icons.edit_lock', ['$foo' => $foo, 'tooltip' => $tooltip, 'legacy' => $legacy]);
$attribs['aria-describedby'] = 'edit$foo-' . (int) $foo->id;
$output = HTMLHelper::_('link', '#', $text, $attribs);
return $output;
}
if (!isset($foo->slug)) {
$foo->slug = "";
}
$fooUrl = RouteHelper::getFooRoute($foo->slug, $foo->catid, $foo->language);
$url = $fooUrl . '&task=$foo.edit&id=' . $foo->id . '&return=' . base64_encode($uri);
if ((int) $foo->published === 0) {
$tooltip = Text::_('COM_FOOS_EDIT_UNPUBLISHED_FOOS');
} else {
$tooltip = Text::_('COM_FOOS_EDIT_PUBLISHED_FOOS');
}
$nowDate = strtotime(Factory::getDate());
$icon = $foo->published ? 'edit' : 'eye-slash';
if (
($foo->publish_up !== null && strtotime($foo->publish_up) > $nowDate)
|| ($foo->publish_down !== null && strtotime($foo->publish_down) < $nowDate)
) {
$icon = 'eye-slash';
}
$aria_described = 'edit$foo-' . (int) $foo->id;
$text = '<span class="icon-' . $icon . '" aria-hidden="true"></span>';
$text .= Text::_('JGLOBAL_EDIT');
$text .= '<div role="tooltip" id="' . $aria_described . '">' . $tooltip . '</div>';
$attribs['aria-describedby'] = $aria_described;
$output = HTMLHelper::_('link', Route::_($url), $text, $attribs);
return $output;
}
}
components/com_foos/forms/foo.xml
We adapt the XML file that Joomla uses to build the form.
components/com_foos/forms/foo.xml
<!-- https://codeberg.org/astrid/j4examplecode/raw/branch/t25/src/components/com_foos/forms/foo.xml -->
<?xml version="1.0" encoding="utf-8"?>
<form>
<fieldset
addruleprefix="FooNamespace\Component\Foos\Administrator\Rule"
addfieldprefix="FooNamespace\Component\Foos\Administrator\Field"
>
<field
name="id"
type="number"
label="JGLOBAL_FIELD_ID_LABEL"
default="0"
class="readonly"
readonly="true"
/>
<field
name="name"
type="text"
validate="Letter"
class="validate-letter"
label="COM_FOOS_FIELD_NAME_LABEL"
size="40"
required="true"
/>
<field
name="alias"
type="text"
label="JFIELD_ALIAS_LABEL"
size="45"
hint="JFIELD_ALIAS_PLACEHOLDER"
/>
</fieldset>
<fieldset name="language" label="JFIELD_LANGUAGE_LABEL">
<field
name="language"
type="contentlanguage"
label="JFIELD_LANGUAGE_LABEL"
>
<option value="*">JALL</option>
</field>
</fieldset>
<fieldset name="publishing" label="JGLOBAL_FIELDSET_PUBLISHING">
<field
name="featured"
type="list"
label="JFEATURED"
default="0"
validate="options"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="published"
type="list"
label="JSTATUS"
default="1"
id="published"
class="custom-select-color-state"
size="1"
>
<option value="1">JPUBLISHED</option>
<option value="0">JUNPUBLISHED</option>
<option value="2">JARCHIVED</option>
<option value="-2">JTRASHED</option>
</field>
<field
name="publish_up"
type="calendar"
label="COM_FOOS_FIELD_PUBLISH_UP_LABEL"
translateformat="true"
showtime="true"
size="22"
filter="user_utc"
/>
<field
name="publish_down"
type="calendar"
label="COM_FOOS_FIELD_PUBLISH_DOWN_LABEL"
translateformat="true"
showtime="true"
size="22"
filter="user_utc"
/>
<field
name="catid"
type="categoryedit"
label="JCATEGORY"
extension="com_foos"
addfieldprefix="Joomla\Component\Categories\Administrator\Field"
required="true"
default=""
/>
<field
name="access"
type="accesslevel"
label="JFIELD_ACCESS_LABEL"
size="1"
/>
<field
name="checked_out"
type="hidden"
filter="unset"
/>
<field
name="checked_out_time"
type="hidden"
filter="unset"
/>
</fieldset>
<fields name="params" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
<fieldset name="display" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
<field
name="show_name"
type="list"
label="COM_FOOS_FIELD_PARAMS_NAME_LABEL"
useglobal="true"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field
name="foos_layout"
type="componentlayout"
label="JFIELD_ALT_LAYOUT_LABEL"
class="custom-select"
extension="com_foos"
view="foo"
useglobal="true"
/>
</fieldset>
</fields>
</form>
components/com_foos/src/Controller/FooController.php
The file components/com_foos/src/Controller/FooController.php
contains the logic for processing in the form.
Note the function
save
. This is not usual in theFormController
, because Joomla takes care of everything for you. Since the ID is first created when an element is created and is therefore not known, Joomla forwards to the overview page after creation. We have not yet created this in the frontend. That is why I have changed this function here.
components/com_foos/src/Controller/FooController.php
// https://codeberg.org/astrid/j4examplecode/raw/branch/t25/src/components/com_foos/src/Controller/FooController.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Site\Controller;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
use Joomla\Utilities\ArrayHelper;
/**
* Controller for single foo view
*
* @since __DEPLOY_VERSION__
*/
class FooController extends FormController
{
/**
* The URL view item variable.
*
* @var string
* @since __DEPLOY_VERSION__
*/
protected $view_item = 'form';
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
*
* @since __DEPLOY_VERSION__
*/
public function getModel($name = 'form', $prefix = '', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, ['ignore_request' => false]);
}
/**
* Method override to check if you can add a new record.
*
* @param array $data An array of input data.
*
* @return boolean
*
* @since __DEPLOY_VERSION__
*/
protected function allowAdd($data = [])
{
if ($categoryId = ArrayHelper::getValue($data, 'catid', $this->input->getInt('catid'), 'int')) {
$user = Factory::getUser();
// If the category has been passed in the data or URL check it.
return $user->authorise('core.create', 'com_foos.category.' . $categoryId);
}
// In the absence of better information, revert to the component permissions.
return parent::allowAdd();
}
/**
* Method override to check if you can edit an existing record.
*
* @param array $data An array of input data.
* @param string $key The name of the key for the primary key; default is id.
*
* @return boolean
*
* @since __DEPLOY_VERSION__
*/
protected function allowEdit($data = [], $key = 'id')
{
$recordId = (int) isset($data[$key]) ? $data[$key] : 0;
if (!$recordId) {
return false;
}
// Need to do a lookup from the model.
$record = $this->getModel()->getItem($recordId);
$categoryId = (int) $record->catid;
if ($categoryId) {
$user = Factory::getUser();
// The category has been set. Check the category permissions.
if ($user->authorise('core.edit', $this->option . '.category.' . $categoryId)) {
return true;
}
// Fallback on edit.own.
if ($user->authorise('core.edit.own', $this->option . '.category.' . $categoryId)) {
return ($record->created_by == $user->id);
}
return false;
}
// Since there is no asset tracking, revert to the component permissions.
return parent::allowEdit($data, $key);
}
/**
* Method to save a record.
*
* @param string $key The name of the primary key of the URL variable.
* @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
*
* @return boolean True if successful, false otherwise.
*
* @since __DEPLOY_VERSION__
*/
public function save($key = null, $urlVar = null)
{
$result = parent::save($key, $urlVar = null);
$this->setRedirect(Route::_($this->getReturnPage(), false));
return $result;
}
/**
* Method to cancel an edit.
*
* @param string $key The name of the primary key of the URL variable.
*
* @return boolean True if access level checks pass, false otherwise.
*
* @since __DEPLOY_VERSION__
*/
public function cancel($key = null)
{
$result = parent::cancel($key);
$this->setRedirect(Route::_($this->getReturnPage(), false));
return $result;
}
/**
* Gets the URL arguments to append to an item redirect.
*
* @param integer $recordId The primary key id for the item.
* @param string $urlVar The name of the URL variable for the id.
*
* @return string The arguments to append to the redirect URL.
*
* @since __DEPLOY_VERSION__
*/
protected function getRedirectToItemAppend($recordId = 0, $urlVar = 'id')
{
// Need to override the parent method completely.
$tmpl = $this->input->get('tmpl');
$append = '';
// Setup redirect info.
if ($tmpl) {
$append .= '&tmpl=' . $tmpl;
}
$append .= '&layout=edit';
$append .= '&' . $urlVar . '=' . (int) $recordId;
$itemId = $this->input->getInt('Itemid');
$return = $this->getReturnPage();
$catId = $this->input->getInt('catid');
if ($itemId) {
$append .= '&Itemid=' . $itemId;
}
if ($catId) {
$append .= '&catid=' . $catId;
}
if ($return) {
$append .= '&return=' . base64_encode($return);
}
return $append;
}
/**
* Get the return URL.
*
* If a "return" variable has been passed in the request
*
* @return string The return URL.
*
* @since __DEPLOY_VERSION__
*/
protected function getReturnPage()
{
$return = $this->input->get('return', null, 'base64');
if (empty($return) || !Uri::isInternal(base64_decode($return))) {
return Uri::base();
}
return base64_decode($return);
}
}
components/com_foos/src/Model/FormModel.php
The file components/com_foos/src/Model/FormModel.php
organises all the necessary data for processing in the form.
components/com_foos/src/Model/FormModel.php
// https://codeberg.org/astrid/j4examplecode/raw/branch/t25/src/components/com_foos/src/Model/FormModel.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Site\Model;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
/**
* Foo Component Foo Model
*
* @since __DEPLOY_VERSION__
*/
class FormModel extends \FooNamespace\Component\Foos\Administrator\Model\FooModel
{
/**
* Model typeAlias string. Used for version history.
*
* @var string
* @since __DEPLOY_VERSION__
*/
public $typeAlias = 'com_foos.foo';
/**
* Name of the form
*
* @var string
* @since __DEPLOY_VERSION__
*/
protected $formName = 'form';
/**
* Method to get the row form.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return \JForm|boolean A \JForm object on success, false on failure
*
* @since __DEPLOY_VERSION__
*/
public function getForm($data = [], $loadData = true)
{
$form = parent::getForm($data, $loadData);
// Prevent messing with article language and category when editing existing foo with associations
if ($id = $this->getState('foo.id') && Associations::isEnabled()) {
$associations = Associations::getAssociations('com_foos', '#__foos_details', 'com_foos.item', $id);
// Make fields read only
if (!empty($associations)) {
$form->setFieldAttribute('language', 'readonly', 'true');
$form->setFieldAttribute('language', 'filter', 'unset');
}
}
return $form;
}
/**
* Method to get foo data.
*
* @param integer $itemId The id of the foo.
*
* @return mixed Foo item data object on success, false on failure.
*
* @throws Exception
*
* @since __DEPLOY_VERSION__
*/
public function getItem($itemId = null)
{
$itemId = (int) (!empty($itemId)) ? $itemId : $this->getState('foo.id');
// Get a row instance.
$table = $this->getTable();
// Attempt to load the row.
try {
if (!$table->load($itemId)) {
return false;
}
} catch (Exception $e) {
Factory::getApplication()->enqueueMessage($e->getMessage());
return false;
}
$properties = $table->getProperties();
$value = ArrayHelper::toObject($properties, 'JObject');
// Convert field to Registry.
$value->params = new Registry($value->params);
return $value;
}
/**
* Get the return URL.
*
* @return string The return URL.
*
* @since __DEPLOY_VERSION__
*/
public function getReturnPage()
{
return base64_encode($this->getState('return_page'));
}
/**
* Method to save the form data.
*
* @param array $data The form data.
*
* @return boolean True on success.
*
* @throws Exception
* @since __DEPLOY_VERSION__
*/
public function save($data)
{
// Associations are not edited in frontend ATM so we have to inherit them
if (Associations::isEnabled() && !empty($data['id'])
&& $associations = Associations::getAssociations('com_foos', '#__foos_details', 'com_foos.item', $data['id'])) {
foreach ($associations as $tag => $associated) {
$associations[$tag] = (int) $associated->id;
}
$data['associations'] = $associations;
}
return parent::save($data);
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @return void
*
* @throws Exception
*
* @since __DEPLOY_VERSION__
*/
protected function populateState()
{
$app = Factory::getApplication();
$input = $app->getInput();
$pk = $input->getInt('id');
$this->setState('foo.id', $pk);
$this->setState('foo.catid', $input->getInt('catid'));
$return = $input->get('return', '', 'base64');
$this->setState('return_page', base64_decode($return));
// Load the parameters.
$params = $app->getParams();
$this->setState('params', $params);
$this->setState('layout', $input->getString('layout'));
}
/**
* Allows preprocessing of the JForm object.
*
* @param Form $form The form object
* @param array $data The data to be merged into the form object
* @param string $group The plugin group to be executed
*
* @return Form
*
* @since __DEPLOY_VERSION__
*/
protected function preprocessForm(Form $form, $data, $group = 'foo')
{
if (!Multilanguage::isEnabled()) {
$form->setFieldAttribute('language', 'type', 'hidden');
$form->setFieldAttribute('language', 'default', '*');
}
return parent::preprocessForm($form, $data, $group);
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $name The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $options Configuration array for model. Optional.
*
* @return Table A Table object
*
* @since __DEPLOY_VERSION__
* @throws \Exception
*/
public function getTable($name = 'Foo', $prefix = 'Administrator', $options = [])
{
return parent::getTable($name, $prefix, $options);
}
}
components/com_foos/src/View/Form/HtmlView.php
The file components/com_foos/src/View/Form/HtmlView.php
fetches all the necessary data and passes it on to the template file edit.php
.
components/com_foos/src/View/Form/HtmlView.php
// https://codeberg.org/astrid/j4examplecode/raw/branch/t25/src/components/com_foos/src/View/Form/HtmlView.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Site\View\Form;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use FooNamespace\Component\Foos\Administrator\Helper\FooHelper;
/**
* HTML Foo View class for the Foo component
*
* @since __DEPLOY_VERSION__
*/
class HtmlView extends BaseHtmlView
{
/**
* @var \Joomla\CMS\Form\Form
* @since __DEPLOY_VERSION__
*/
protected $form;
/**
* @var object
* @since __DEPLOY_VERSION__
*/
protected $item;
/**
* @var string
* @since __DEPLOY_VERSION__
*/
protected $return_page;
/**
* @var string
* @since __DEPLOY_VERSION__
*/
protected $pageclass_sfx;
/**
* @var \Joomla\Registry\Registry
* @since __DEPLOY_VERSION__
*/
protected $state;
/**
* @var \Joomla\Registry\Registry
* @since __DEPLOY_VERSION__
*/
protected $params;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise an Error object.
*
* @throws Exception
* @since __DEPLOY_VERSION__
*/
public function display($tpl = null)
{
$user = Factory::getUser();
$app = Factory::getApplication();
// Get model data.
$this->state = $this->get('State');
$this->item = $this->get('Item');
$this->form = $this->get('Form');
$this->return_page = $this->get('ReturnPage');
if (empty($this->item->id)) {
$authorised = $user->authorise('core.create', 'com_foos') || count($user->getAuthorisedCategories('com_foos', 'core.create'));
} else {
// Since we don't track these assets at the item level, use the category id.
$canDo = FooHelper::getActions('com_foos', 'category', $this->item->catid);
$authorised = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $user->id);
}
if ($authorised !== true) {
$app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
$app->setHeader('status', 403, true);
return false;
}
// Check for errors.
if (count($errors = $this->get('Errors'))) {
$app->enqueueMessage(implode("\n", $errors), 'error');
return false;
}
// Create a shortcut to the parameters.
$this->params = $this->state->params;
// Escape strings for HTML output
$this->pageclass_sfx = htmlspecialchars($this->params->get('pageclass_sfx', ''));
// Override global params with foo specific params
$this->params->merge($this->item->params);
// Propose current language as default when creating new foo
if (empty($this->item->id) && Multilanguage::isEnabled()) {
$lang = Factory::getLanguage()->getTag();
$this->form->setFieldAttribute('language', 'default', $lang);
}
$this->_prepareDocument();
parent::display($tpl);
}
/**
* Prepares the document
*
* @return void
*
* @throws Exception
*
* @since __DEPLOY_VERSION__
*/
protected function _prepareDocument()
{
$app = Factory::getApplication();
$menus = $app->getMenu();
$title = null;
// Because the application sets a default page title,
// we need to get it from the menu item itself
$menu = $menus->getActive();
if ($menu) {
$this->params->def('page_heading', $this->params->get('page_title', $menu->title));
} else {
$this->params->def('page_heading', Text::_('COM_FOOS_FORM_EDIT_FOO'));
}
$title = $this->params->def('page_title', Text::_('COM_FOOS_FORM_EDIT_FOO'));
if ($app->get('sitename_pagetitles', 0) == 1) {
$title = Text::sprintf('JPAGETITLE', $app->get('sitename'), $title);
} else if ($app->get('sitename_pagetitles', 0) == 2) {
$title = Text::sprintf('JPAGETITLE', $title, $app->get('sitename'));
}
$this->document->setTitle($title);
$pathway = $app->getPathWay();
$pathway->addItem($title, '');
}
}
In the code example above, I have used the code in Joomla as a guide when checking the permissions. If someone is not authorised, a message is displayed. Depending on the environment in which the extension is programmed, it is more user-friendly to offer a login option immediately. In this case: Place in the file components/com_foos/src/View/Form/HtmlView.php
the following code excerpt
if ($authorised !== true) {
$app->redirect('index.php?option=com_users&view=login');
}
instead of this
if ($authorised !== true)
{
$app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
$app->setHeader('status', 403, true);
return false;
}
If the authorisation check fails, you are immediately redirected to the registration form.
components/com_foos/tmpl/form/edit.php
As a template, components/com_foos/tmpl/form/edit.php
ensures that the form is already displayed in the frontend.
components/com_foos/tmpl/form/edit.php
// https://codeberg.org/astrid/j4examplecode/raw/branch/t25/src/components/com_foos/tmpl/form/edit.php
<?php
/**
* @package Joomla.Administrator
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Associations;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
HTMLHelper::_('behavior.keepalive');
HTMLHelper::_('behavior.formvalidator');
HTMLHelper::_('script', 'com_foos/admin-foos-letter.js', ['version' => 'auto', 'relative' => true]);
$this->tab_name = 'com-foos-form';
$this->ignore_fieldsets = ['details', 'item_associations', 'language'];
$this->useCoreUI = true;
?>
<form action="<?php echo Route::_('index.php?option=com_foos&id=' . (int) $this->item->id); ?>" method="post" name="adminForm" id="adminForm" class="form-validate form-vertical">
<fieldset>
<?php echo HTMLHelper::_('uitab.startTabSet', $this->tab_name, ['active' => 'details']); ?>
<?php echo HTMLHelper::_('uitab.addTab', $this->tab_name, 'details', empty($this->item->id) ? Text::_('COM_FOOS_NEW_FOO') : Text::_('COM_FOOS_EDIT_FOO')); ?>
<?php echo $this->form->renderField('name'); ?>
<?php if (is_null($this->item->id)) : ?>
<?php echo $this->form->renderField('alias'); ?>
<?php endif; ?>
<?php echo $this->form->renderFieldset('details'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php if (Multilanguage::isEnabled()) : ?>
<?php echo HTMLHelper::_('uitab.addTab', $this->tab_name, 'language', Text::_('JFIELD_LANGUAGE_LABEL')); ?>
<?php echo $this->form->renderField('language'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php else : ?>
<?php echo $this->form->renderField('language'); ?>
<?php endif; ?>
<?php echo LayoutHelper::render('joomla.edit.params', $this); ?>
<?php echo HTMLHelper::_('uitab.endTabSet'); ?>
<input type="hidden" name="task" value=""/>
<input type="hidden" name="return" value="<?php echo $this->return_page; ?>"/>
<?php echo HTMLHelper::_('form.token'); ?>
</fieldset>
<div class="mb-2">
<button type="button" class="btn btn-primary" onclick="Joomla.submitbutton('foo.save')">
<span class="fas fa-check" aria-hidden="true"></span>
<?php echo Text::_('JSAVE'); ?>
</button>
<button type="button" class="btn btn-danger" onclick="Joomla.submitbutton('foo.cancel')">
<span class="fas fa-times-cancel" aria-hidden="true"></span>
<?php echo Text::_('JCANCEL'); ?>
</button>
</div>
</form>
components/com_foos/tmpl/form/edit.xml
Last but not least we need the file components/com_foos/tmpl/form/edit.xml
to create the menu item.
components/com_foos/tmpl/form/edit.xml
<!-- https://codeberg.org/astrid/j4examplecode/raw/branch/t25/src/components/com_foos/tmpl/form/edit.xml -->
<?xml version="1.0" encoding="utf-8"?>
<metadata>
<layout title="COM_FOOS_FORM_VIEW_DEFAULT_TITLE">
<help
key="JHELP_MENUS_MENU_ITEM_FOO_CREATE"
/>
<message>
<![CDATA[COM_FOOS_FORM_VIEW_DEFAULT_DESC]]>
</message>
</layout>
<fields name="params">
</fields>
</metadata>
Modified files
administrator/components/com_foos/src/Extension/FoosComponent.php
In the file administrator/components/com_foos/src/Extension/FoosComponent.php
we register the icon. In other words, we make the icon known to Joomla.
administrator/components/com_foos/src/Extension/FoosComponent.php
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\UserFactoryInterface;
use Joomla\CMS\Association\AssociationServiceInterface;
use Joomla\CMS\Association\AssociationServiceTrait;
use Joomla\CMS\Categories\CategoryServiceInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use FooNamespace\Component\Foos\Administrator\Service\HTML\AdministratorService;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Icon;
use Psr\Container\ContainerInterface;
use Joomla\CMS\Helper\ContentHelper;
public function boot(ContainerInterface $container)
{
$this->getRegistry()->register('foosadministrator', new AdministratorService);
$this->getRegistry()->register('fooicon', new Icon($container->get(UserFactoryInterface::class)));
}
/**
components/com_foos/tmpl/foo/default.php
We extend the template for the view: If you are allowed to edit the element if ($canEdit)
, then you see the icon to open the form.
components/com_foos/tmpl/foo/default.php
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Text;
if ($this->item->params->get('show_name')) {
$canDo = ContentHelper::getActions('com_foos', 'category', $this->item->catid);
$canEdit = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == Factory::getUser()->id);
$tparams = $this->item->params;
if ($tparams->get('show_name')) {
if ($this->params->get('show_foo_name_label')) {
echo Text::_('COM_FOOS_NAME');
}
echo $this->item->name;
}
?>
<?php if ($canEdit) : ?>
<div class="icons">
<div class="btn-group float-right">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton-<?php echo $this->item->id; ?>"
aria-label="<?php echo JText::_('JUSER_TOOLS'); ?>"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="fa fa-cog" aria-hidden="true"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton-<?php echo $this->item->id; ?>">
<li class="edit-icon"> <?php echo JHtml::_('fooicon.edit', $this->item, $tparams); ?> </li>
</ul>
</div>
</div>
<?php endif; ?>
<?php
echo $this->item->event->afterDisplayTitle;
echo $this->item->event->beforeDisplayContent;
echo $this->item->event->afterDisplayContent;
Tip: Do you want a user to be redirected to the finished view of an item after it has been created? This is only possible in a indirect way. Because you don't know the ID when you create it, you have to ask for it. Since we extend the model classes of Joomla-Core, we can access the ID via the model in the
postSaveHook()
method of the controller. Concretely, in the filesrc/components/com_foos/src/Controller/FooController.php
the following code could be used to set up the redirection:
...
protected function postSaveHook(\Joomla\CMS\MVC\Model\BaseDatabaseModel $model, $validData = [])
{
$id = $model->getState($model->getName() . '.id');
$this->setRedirect(Route::_('index.php?option=com_agosms&view=foo&id=' . $id, false));
return $id;
}
...
Test your Joomla component
- install your component in Joomla version 4 to test it:
Copy the files in the administrator
folder into the administrator
folder of your Joomla 4 installation.
Copy the files in the components
folder into the components
folder of your Joomla 4 installation.
Install your component as described in part one, after you have copied all the files. Joomla will update the namespaces for you during the installation. Since a new file has been added, this is necessary.
- Create a menu item to change a Foo element and one that displays a Foo element.
- Open the menu item to create a Foo element in the frontend. Make sure you have the necessary rights. If you have left the default rights, you must log in with a user who is at least an author. Make sure that you can create an element.
- Make sure that you see the edit icon in the detail view of an element and that an element is editable.
Links
Adding frontend edit for com_contact[^github.com/joomla/joomla-cms/pull/24311]
Webmentions