Link Component with Joomla Custom Fields

You can enable custom fields feature to your component. This allows to create and display additional attributes to items or records.

Following changes are required to implement custom fields:

  1. Add custom fields and field groups in the backend
  2. Display custom fields in the editing form (both backend and frontend)
  3. Handle saving of custom fields
  4. Display value of custom field on the single item page (frontend)
  5. Display value of custom field on the list of items page (frontend)

Custom fields can be used by many components. Each component can have custom fields on multiple views as well. So, context (combination of component name and view) is used to identify the custom field.

1. Component XML File

stars.xml

In the XML file, create submenu items to allow administrators to add custom fields and field groups, adding the content as com_stars.planet. This indicates that these records belong to the Stars component, and that they are associated with the section called "planet".

<submenu>
    <menu link="option=com_fields&amp;view=fields&amp;context=com_stars.planet">JGLOBAL_FIELDS</menu>
    <menu link="option=com_fields&amp;view=groups&amp;context=com_stars.planet">JGLOBAL_FIELD_GROUPS</menu>
</submenu>

You will see these menu items after updating the component as Joomla includes these menu items when the component is installed or updated.

When an administrator clicks on one of these links, then the com_fields functionality is run, which displays the existing Fields or Field Groups and allows the administrator to manage them. The custom fields are stored in #__fields and the custom field groups in #__fields_groups, and each table is partitioned via the context field. 

2. ACL

admin/access.xml

As custom fields uses Joomla ACL, you have to add the following sections to your access.xml file.

<section name="fieldgroup">
    <action name="core.create" title="JACTION_CREATE" />
    <action name="core.delete" title="JACTION_DELETE" />
    <action name="core.edit" title="JACTION_EDIT" />
    <action name="core.edit.state" title="JACTION_EDITSTATE" />
    <action name="core.edit.own" title="JACTION_EDITOWN" />
    <action name="core.edit.value" title="JACTION_EDITVALUE" />
</section>
<section name="field">
    <action name="core.delete" title="JACTION_DELETE" />
    <action name="core.edit" title="JACTION_EDIT" />
    <action name="core.edit.state" title="JACTION_EDITSTATE" />
    <action name="core.edit.value" title="JACTION_EDITVALUE" />
</section>

The administrators can now create fields and assign them to an item.

3. Edit Form

admin/tmpl/planet/edit.php

site/tmpl/form/edit.php

As the form class inherits FormModel (or AdminModel), the preprocessForm() method triggers a Joomla event onContentPrepareForm, which is picked up by the System Fields Plugin. This plugin reads the relevant custom fields for the component and builds them dynamically into the form structure. This is similar to changes in form while adding Associations.

The custom fields are added to the form as different fieldsets based on filed groups. If the custom fields are not in any group, then they are added to the fieldset with name fields-0.

<?php echo $this->form->renderFieldset('fields-0'); ?>

The above line will render the field set.

4. Component Class

admin/src/Extension/StarsComponent.php

To use custom fields, first you need to implement the class with FieldsServiceInterface.

use Joomla\CMS\Fields\FieldsServiceInterface;

class StarsComponent extends MVCComponent implements
    FieldsServiceInterface
{

}

Then, you need to define two methods - validateSection() and getContexts() in the class.

As the custom fields can be setup for different views in the same component (for example, in Content Component, there is a dropdown which can be set to Articles or Category), you need to define a method getContexts() which returns context values for this dropdown.

public function getContexts(): array
{
    Factory::getLanguage()->load('com_stars', JPATH_ADMINISTRATOR);

    $contexts = [
        'com_stars.planet' => Text::_('COM_STARS_PLANET'),
    ];

    return $contexts;
}

The System Fields Plugin finds the relevant custom fields in the #__fields table using the name of the form as section. The section is set to "planet", but in the frontend the name of the form is "form". This is why you need to have the section part of the context set to "planet".

public function validateSection($section, $item = null)
{
    if (Factory::getApplication()->isClient('site') && $section == 'form')
    {
        return 'planet';
    }

    if ($section != 'planet')
    {
        return null;
    }

    return $section;
}

In the frontend form, the field may be disabled due to permissions. In the Permissions tab of the field, set the Edit Custom Field Value to Allowed for Public.

5. Saving of Custom Fields

When the form is submitted, the data is sent to the server in an HTTP POST request, with the data for custom fields included with the data for standard fields in the jform array. Custom fields are stored in jform[com_fields] array.

If you are using Jooma model's save() method, custom fields will be saved automatically.

6. Frontend Display

site/src/View/Planet/HtmlView.php

The custom field can be displayed at multiple locations - After Title, Before Display, After Display or Do not automatically display. This setting can be set while editing custom field under Automatic Display.

Joomla provides an easy way of getting the HTML for each of the display options. Add the following code in the display() method.

$app = Factory::getApplication();
$item = $this->item;

$this->dispatchEvent(new Event('onContentPrepare', ['com_stars.planet', &$item, &$item->params, $offset]));

$item->event = new \stdClass();

$results = $app->triggerEvent('onContentAfterTitle', ['com_stars.planet', &$item, &$item->params, $offset]);
$item->event->afterDisplayTitle = trim(implode("\n", $results));

$results = $app->triggerEvent('onContentBeforeDisplay', ['com_stars.planet', &$item, &$item->params, $offset]);
$item->event->beforeDisplayContent = trim(implode("\n", $results));

$results = $app->triggerEvent('onContentAfterDisplay', ['com_stars.planet', &$item, &$item->params, $offset]);
$item->event->afterDisplayContent = trim(implode("\n", $results));

Now, you can display the HTML output of fields at relevant position in the layout file.

After the onContentPrepare event is fired, you can also find all the attached fields in $item->jcfields property. This event is handled by the System Fields Plugin which reads the associated custom fields and their values from the database, taking account of the current user's Access privilege. It then updates the $item by adding a property $item->jcfields which is an associative array of the custom field data, keyed by the custom field id.

7. Layout File

site/tmpl/planet/default.php

<?php echo $this->item->event->afterDisplayTitle; ?>
<?php echo $this->item->event->beforeDisplayContent; ?>
<?php echo $this->item->event->afterDisplayContent; ?>

Alternatively, you can use the $item->jcfields property.

8. Get Fields by Groups

If you want to display fields by custom field groups, you can use $item->jcfields property to get output as custom field groups.

$groups = array();
foreach ($jcfields as $field)
{
    if (!isset($groups[$field->group_id]))
    {
        $groups[$field->group_id] = array();
    }
            
    $groups[$field->group_id][] = $field;
}

$output = array();

foreach ($groups as $group_id => $group)
{
    $output[$group_id] = FieldsHelper::render($context, 'fields.render', ['item' => $item, 'context' => $context, 'fields' => $group]);
}

First, we sort fields into groups. Then, we output each group.