Work In Progress

This book is currently work in progress. Some sections are not yet written. Thank you for your understanding!

Custom fields

Implementing custom fields is relatively simple. For starters, your component extension must implement the Joomla\CMS\Fields\FieldsServiceInterface. The two methods defined in the interface are implemented a lot like this in most cases:

public function validateSection($section, $item = null)
{
  if (!in_array($section, ['categories', 'item'])) {
    return null;
  }

  return $section;
}

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

  return [
    'com_example.item'       => Text::_('COM_EXAMPLE_TITLE_ITEMS'),
    'com_example.categories' => Text::_('JCATEGORY'),
  ];
}

The former method makes sure that a context's section (the stuff after the dot) is a valid section where you expect custom fields to exist. In this example we expect two sections, categories and item.

The latter method returns a list of contexts for our component where custom fields can be defined. We defined the two contexts corresponding to the sections we accept in the validateSection method. The return values will be used by com_fields to render the drop-down which lets the user select the section for which they define custom fields for your component.

Your XML manifest must, of course, include two links to the Joomla Fields component, one for the Fields and one for the Fields group. Use one of the contexts you returned in the getContexts method above. Do NOT use the context for categories.

<menu link="option=com_fields&amp;view=fields&amp;context=com_example.item">
  JGLOBAL_FIELDS
  <params>
    <menu-quicktask><![CDATA[index.php?option=com_fields&view=field&layout=edit&context=com_example.item]]></menu-quicktask>
    <menu-quicktask-title>COM_EXAMPLE_SUBMENU_FIELDS_NEW</menu-quicktask-title>
    <menu-quicktask-permission>core.create;com_fields</menu-quicktask-permission>
  </params>
</menu>

<menu link="option=com_fields&amp;view=groups&amp;context=com_example.item">
  JGLOBAL_FIELD_GROUPS
  <params>
    <menu-quicktask><![CDATA[index.php?option=com_fields&view=group&layout=edit&context=com_example.item]]></menu-quicktask>
    <menu-quicktask-title>COM_EXAMPLE_SUBMENU_FIELD_GROUPS_NEW</menu-quicktask-title>
    <menu-quicktask-permission>core.create;com_fields</menu-quicktask-permission>
  </params>
</menu>

Your backend item edit pages should display the custom fields automatically — as long as you render all of the form's fieldset without looking for specific names. For example, something similar to this code:

<?php
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
?>
<form action="<?= Route::_('index.php?option=com_example&view=item&layout=edit&id=' . $this->item->id) ?>"
  aria-label="<?= Text::_('COM_ATS_TITLE_TICKETS_EDIT', true) ?>"
  class="form-validate"
  id="adminForm"
  method="post"
  name="adminForm"
>
  <input name="task" type="hidden" value="">
  <?= HTMLHelper::_('form.token') ?>

  <?php foreach ($this->form->getFieldsets() as $fieldSet): ?>

  <div class="card mb-2">
    <h3 class="card-header bg-info text-white">
      <?= Text::_($this->form->getFieldsets()[$fieldSet]->label) ?>
    </h3>
    <div class="card-body">
      <?php echo $this->form->renderFieldset($fieldSet); ?>
    </div>
  </div>

  <?php endforeach; ?>

</form>

Joomla handles loading and saving the values of custom fields automatically as long as the Content - Fields plugin is published.

In the frontend, fields' contents are returned through the onContentAfterTitle, onContentAfterDisplay and onContentBeforeDisplay events. Typically, the implementation in your HtmlView class looks similar to this:

<?php
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Plugin\PluginHelper;

class HtmlView extends BaseHtmlView
{
  /**
   * The item object details
   *
   * @var    \Joomla\CMS\Object\CMSObject
   *
   * @since  1.6
   */
  protected $item;

  // ... your code here ...

  /**
   * Execute and display a template script.
   *
   * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
   *
   * @return  void|boolean
   */
  public function display($tpl = null)
  {
    $app        = Factory::getApplication();
    $state      = $this->get('State');
    $item       = $this->get('Item');
    $this->form = $this->get('Form');

    // ... your code here ...

    // Process the content plugins.
    PluginHelper::importPlugin('content');
    $offset = $state->get('list.offset');

    // Some plugins require a text attribute without checking if it exists
    $item->text = '';

    $app->triggerEvent('onContentPrepare', [
      'com_example.item', &$item, &$item->params, $offset
    ]);

    // Store the events for later
    $item->event = new \stdClass();

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

    $results = $app->triggerEvent('onContentBeforeDisplay', [
      'com_example.item', &$item, &$item->params, $offset,
    ]);

    $item->event->beforeDisplayContent = trim(implode("\n", $results));

    $results = $app->triggerEvent('onContentAfterDisplay', [
      'com_example.item', &$item, &$item->params, $offset,
    ]);

    $item->event->afterDisplayContent = trim(implode("\n", $results));

    $this->item = $item;

    // ... your code here ...
  }
}

Your view template can then output $this->event->afterDisplayTitle etc in appropriate places.

[Tip]Tip

If you want to see how you can override Joomla's default custom field display to format it in a way that's more pleasant to the eye you can download Akeeba Ticket System Core (it's free of charge) and look at \Akeeba\Component\ATS\Site\View\Ticket\HtmlView.

I have a method called getCustomFieldsDisplay which renders fields using custom layout overrides for the field.render and fields.render Joomla layouts. Ignore the section with the comment “Filter fields by ATS Private Display”, this is some special handling I use to make some fields invisible to the general public and only visible to the owner of the helpdesk ticket and the helpdesk personnel — this uses information from a custom plugin I ship with my software and is very specific to my use case.

The two layout files (components/com_ats/layouts/field/render.php and components/com_ats/layouts/fields/render.php) are used to format the custom fields in a way which visually integrates more pleasantly than Joomla's default “word vomit” display style of nested unsorted lists.

It goes without saying, my clients can of course choose to override these layout files OR the entire view template to render custom fields the way they see fit. For example, a site integrator might choose to have a custom field which is never displayed automatically, get its value using the field name and display it in a visually pleasant display in the view template. For instance, someone could have a custom field plugin which saves geographical coordinates. These could be displayed in the frontend of the site as an OpenStreetMap map. With custom fields, sky's the limit!