J3.x

J3.x:Developing an MVC Component/Adding Custom Fields

From Joomla! Documentation

< J3.x:Developing an MVC Component

Other languages:
Joomla! 
3.x
Tutorial
Developing an MVC Component


This is a multiple-article series of tutorials on how to develop a Model-View-Controller Component for Joomla! VersionJoomla 3.x.

Begin with the Introduction, and navigate the articles in this series by using the navigation button at the bottom or the box to the right (the Articles in this series).



This tutorial is part of the Developing an MVC Component for Joomla! 3.2 tutorial. You are encouraged to read the previous parts of the tutorial before reading this.

In this part, we will cover the various aspects of enabling custom fields to be added to our helloworld component.

Two videos accompanying this step are available, covering Custom Field general considerations and an Overview of Joomla Plugin handling.

Introduction

Since Joomla 3.7 is the capability included to define custom fields associated with some of the core Joomla! components. And this feature was designed in a way that could be easily included in custom components. If you're not familiar with custom fields it's worth going through the Adding custom fields tutorial and experimenting using the custom fields available with com_content and com_contact for example.

Much of the development to include custom fields mirrors what we had to do to incorporate categories in Adding Categories. The work is outlined in Implementing Custom Fields in your component but there are some differences between that page and what we have to do.

Functionality

Here is a list of the functional changes for this step.

Admin Field Definition

We provide administrators with a way of defining the custom fields which can be added to our helloworld records. We do this by adding "Fields" and "Field Groups" to the sidebar submenu which was built in the Adding Categories step. We'll also allow admins to set custom fields against our helloworld categories – so that when they click on Fields in the sidebar submenu there's a context dropdown to the left of the filter fields at the top.

Admin Setting Field Values

Once the fields are defined they are available to be used for our helloworld component. But we have to extend our "edit" form (which enables adding and editing helloworld records) to allow admins to enter values of the custom fields associated with each helloworld record.

We also want to allow admins to set values in custom fields of helloworld categories.

Admin Field Permissions

We define the permissions relating to managing Fields and Field Groups for our component. This then enables suitably authorised admins to define which user groups can edit fields and field groups, or enter data for custom fields.

Front-end Display

We extend the Hello World front end page to display the custom field values associated with that helloworld record.

You can also extend the front end category view to include custom field data associated with that category, but this is left as an exercise for the reader.

Front-end Form

We extend the front-end form (which allows a user to create a new helloworld record) so that data for the associated custom fields can be captured as well.

Admin Field Definition

In the Adding Categories step we added support to allow administrators to set up helloworld categories by creating the sidebar submenu and inserting a Categories link there which pointed to the com_categories functionality. We passed a URL parameter "extension=com_helloworld" to indicate that these were to be categories for our component.

When an administrator clicks on Categories then the helloworld categories are shown and functionality is made available to add / edit / delete etc them. The categories are stored in the #__categories table, which is "partitioned" by component, via the extension column which denotes the component to which the category belongs.

In the same way we add into the sidebar submenu a link to the functionality for managing custom Fields, and one for custom Field Groups. We add a URL parameter "context=com_helloworld.helloworld" to indicate that these records belong to our component, and that they're associated with a "section" called "helloworld". (What we set for this "section" part is important, as will be seen below).

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. However, we have an additional feature available here because we can set custom fields against helloworld categories as well as helloworld records. If in the Joomla admin back-end you look at Content / Fields then to the left of the filter fields at the top there's a dropdown which can be set to Articles or Category, and this defines what the custom fields are being defined for. This com_fields functionality (which is running here) makes a call back into the same helper file as our submenu is defined in, and looks for a static method getContexts() which it calls to find the text and associated context values for this dropdown.

So we will defined 2 contexts "com_helloworld.helloworld" for custom fields for the helloworld records and "com_helloworld.category" for custom fields for the helloworld categories.

Also note that when an administrator defines a custom field (no matter whether it's for an item or a category) he/she can restrict the categories to which it should apply, and these custom field id / category id combinations are stored in the #__fields_categories table.

In summary, we have only 1 file to change, but the explanation for why we need the validateSections() method is in the Front-end Form section below. (We use a com_helloworld language constant for one of the context values, but as it's com_fields which is the component which is running, we need to tell Joomla to load the com_helloworld language constants file).

admin/helpers/helloworld.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');

/**
 * HelloWorld component helper.
 *
 * @param   string  $submenu  The name of the active view.
 *
 * @return  void
 *
 * @since   1.6
 */
abstract class HelloWorldHelper extends JHelperContent
{
	/**
	 * Configure the Linkbar.
	 *
	 * @return Bool
	 */

	public static function addSubmenu($submenu) 
	{
		JHtmlSidebar::addEntry(
			JText::_('COM_HELLOWORLD_SUBMENU_MESSAGES'),
			'index.php?option=com_helloworld',
			$submenu == 'helloworlds'
		);

		JHtmlSidebar::addEntry(
			JText::_('COM_HELLOWORLD_SUBMENU_CATEGORIES'),
			'index.php?option=com_categories&view=categories&extension=com_helloworld',
			$submenu == 'categories'
		);

		// Set some global property
		$document = JFactory::getDocument();
		$document->addStyleDeclaration('.icon-48-helloworld ' .
										'{background-image: url(../media/com_helloworld/images/tux-48x48.png);}');
		if ($submenu == 'categories') 
		{
			$document->setTitle(JText::_('COM_HELLOWORLD_ADMINISTRATION_CATEGORIES'));
		}
		if (JComponentHelper::isEnabled('com_fields'))
		{
			JHtmlSidebar::addEntry(
				JText::_('JGLOBAL_FIELDS'),
				'index.php?option=com_fields&context=com_helloworld.helloworld',
				$submenu == 'fields.fields'
			);

			JHtmlSidebar::addEntry(
				JText::_('JGLOBAL_FIELD_GROUPS'),
				'index.php?option=com_fields&view=groups&context=com_helloworld.helloworld',
				$submenu == 'fields.groups'
			);
		}
	}
    
    /**
	 * Get the actions
	 */
	public static function getActions($component = '', $section = '', $messageId = 0)
	{	
		$result	= new JObject;

		if (empty($messageId)) {
			$assetName = 'com_helloworld';
		}
		else {
			$assetName = 'com_helloworld.message.'.(int) $messageId;
		}

		$actions = JAccess::getActions('com_helloworld', 'component');

		foreach ($actions as $action) {
            $value = JFactory::getUser()->authorise($action->name, $assetName);
			$result->set($action->name, $value);
		}

		return $result;
	}

	public static function getContexts()
	{
		JFactory::getLanguage()->load('com_helloworld', JPATH_ADMINISTRATOR);

		$contexts = array(
			'com_helloworld.helloworld' => JText::_('COM_HELLOWORLD_ITEMS'),
			'com_helloworld.categories' => JText::_('JCATEGORY')
		);

		return $contexts;
	}
	
	public static function validateSection($section, $item)
	{
		if (JFactory::getApplication()->isClient('site') && $section == 'form')
		{
			return 'helloworld';
		}
		if ($section != 'helloworld' && $section != 'form')
		{
			return null;
		}

		return $section;
	}
}

Admin Setting Field Values

Once the custom fields are defined, we need to change our helloworld edit screen so that it allows an admin to specify values for the custom fields for that helloworld record. As we have seen with Joomla forms, there are 3 stages

  1. Setting up the form structure in XML
  2. Rendering the form fields, ie creating html from the fields in the XML form structure.
  3. Handling the POST data – performing field validation and saving the data after the user has submitted the form

These same 3 stages apply with our custom fields.

Setting the XML form structure

Our admin edit form is built in our admin helloworld model (in admin/models/helloworld.php) and our model class HelloWorldModelHelloWorld inherits (via AdminModel/JModelAdmin) from FormModel (aka JModelForm). Once the form is built in XML in memory the FormModel::preprocessForm() method triggers a Joomla event onContentPrepareForm, which is picked up by the System Fields Plugin. It is this plugin which reads the relevant custom fields for our component and builds them dynamically into the form structure (in a similar way to our changes to preprocessForm() code in Adding Associations#Admin_Helloworld_.28edit.29_MVC).

The key it uses to find the custom fields in the #__fields table is the name of the form, and as our form is called "com_helloworld.helloworld" we must have this as the context for our custom fields. This is why we need to have the section part of the context above set to "helloworld" - to match the name of our admin form. (The form name is set in our HelloWorldModelHelloWorld::getForm() method, where we call $this->loadForm(), passing the form name as the first parameter).

Also (as mentioned above) custom fields can be restricted to certain categories, and the System Fields Plugin code handles that too. If it finds a field in our form XML which is called "catid" then it assumes that this is the id of the category record and it includes custom fields which are available to this category and excludes custom fields which are restricted to other categories.

Note that a custom field is made available to subcategories. So for example if you defined a custom field called "number of legs" which was associated with items which had a category "animal", then that custom field would be available to items which had a subcategory of "animal", such as "insect" or "fish". So the System Fields Plugin needs to look up the category tree to find custom fields which are associated with categories which are ancestors of the "catid" category.

It also adds an HTML onChange trigger on the category field, so that if the administrator changes the category, then this triggers an HTML POST to the server with the task set to section concatenated with ".reload" (hence in our case "helloworld.reload") together with the current values of the form input elements. The HTML page is reloaded, but this time the System Fields Plugin uses the category id saved in the form fields, and adds the custom fields associated with the updated category.

So provided we match our form name and custom fields context, and name our category id field as "catid", our custom fields are built into our form XML structure for no additional effort on our part.

Rendering the Custom Fields

The second aspect is to render the fields, and to do this Joomla provides a standard layout file in layouts/joomla/edit/params.php. This will output the custom fields in separate tabs of the edit form, one tab for each Field Group, plus one tab for Fields which are not in a Field Group. The layout will actually render more fields than this, including the parameters for the component, so we have to pass to the layout the tabs which we don't want it to handle. So the only change necessary is to include 2 extra lines in our layout file:

<?php $this->ignore_fieldsets = array('details', 'image-info', 'params', 'item_associations', 'accesscontrol'); ?>
<?php echo JLayoutHelper::render('joomla.edit.params', $this); ?>

We also need to consider editing our helloworld categories, and including the custom fields associated with categories in the category edit form. However, all this is automatically handled for us by Joomla, and there's no additional work to do.

Hence our only code change here is to add the additional lines into our layout file:

admin/views/helloworld/tmpl/edit.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access
defined('_JEXEC') or die('Restricted access');
JHtml::_('behavior.formvalidator');

// The following is to enable setting the permission's Calculated Setting 
// when you change the permission's Setting. 
// The core javascript code for initiating the Ajax request looks for a field
// with id="jform_title" and sets its value as the 'title' parameter to send in the Ajax request
JFactory::getDocument()->addScriptDeclaration('
	jQuery(document).ready(function() {
        greeting = jQuery("#jform_greeting").val();
		jQuery("#jform_title").val(greeting);
	});
');

// Required for proper display of fields generated by com_associations
JHtml::_('formbehavior.chosen', 'select');

// if &tmpl=component used on first invocation, ensure it's on subsequent ones too
$input = JFactory::getApplication()->input;
$tmpl = $input->getCmd('tmpl', '') === 'component' ? '&tmpl=component' : '';
?>
<form action="<?php echo JRoute::_('index.php?option=com_helloworld&layout=edit' . $tmpl . '&id=' . (int) $this->item->id); ?>"
    method="post" name="adminForm" id="adminForm" class="form-validate">
    
    <input id="jform_title" type="hidden" name="helloworld-message-title"/>
    
    <div class="form-horizontal">

    <?php echo JHtml::_('bootstrap.startTabSet', 'myTab', array('active' => 'details')); ?>
    <?php echo JHtml::_('bootstrap.addTab', 'myTab', 'details', 
        empty($this->item->id) ? JText::_('COM_HELLOWORLD_TAB_NEW_MESSAGE') : JText::_('COM_HELLOWORLD_TAB_EDIT_MESSAGE')); ?>
        <fieldset class="adminform">
            <legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_DETAILS') ?></legend>
            <div class="row-fluid">
                <div class="span3">
                    <?php echo $this->form->renderFieldset('details');  ?>
                </div>
                <div class="span9">
                    <?php echo $this->form->getInput('description');  ?>
                </div>
            </div>
        </fieldset>
    <?php echo JHtml::_('bootstrap.endTab'); ?>

    <?php echo JHtml::_('bootstrap.addTab', 'myTab', 'image', JText::_('COM_HELLOWORLD_TAB_IMAGE')); ?>
        <fieldset class="adminform">
            <legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_IMAGE') ?></legend>
            <div class="row-fluid">
                <div class="span6">
                    <?php echo $this->form->renderFieldset('image-info');  ?>
                </div>
            </div>
        </fieldset>
    <?php echo JHtml::_('bootstrap.endTab'); ?>
    
    <?php echo JHtml::_('bootstrap.addTab', 'myTab', 'params', JText::_('COM_HELLOWORLD_TAB_PARAMS')); ?>
        <fieldset class="adminform">
            <legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_PARAMS') ?></legend>
            <div class="row-fluid">
                <div class="span6">
                    <?php echo $this->form->renderFieldset('params');  ?>
                </div>
            </div>
        </fieldset>
    <?php echo JHtml::_('bootstrap.endTab'); ?>

    <?php if (JLanguageAssociations::isEnabled()) : ?>
        <?php echo JHtml::_('bootstrap.addTab', 'myTab', 'associations', JText::_('COM_HELLOWORLD_TAB_ASSOCIATIONS')); ?>
            <fieldset class="adminform">
                <legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_ASSOCIATIONS') ?></legend>
                <div class="row-fluid">
                    <div class="span12">
                        <?php echo JLayoutHelper::render('joomla.edit.associations', $this);  ?>
                    </div>
                </div>
            </fieldset>
        <?php echo JHtml::_('bootstrap.endTab'); ?>
    <?php endif; ?>
    
    <?php echo JHtml::_('bootstrap.addTab', 'myTab', 'permissions', JText::_('COM_HELLOWORLD_TAB_PERMISSIONS')); ?>
        <fieldset class="adminform">
            <legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_PERMISSIONS') ?></legend>
            <div class="row-fluid">
                <div class="span12">
                    <?php echo $this->form->renderFieldset('accesscontrol');  ?>
                </div>
            </div>
        </fieldset>
    <?php echo JHtml::_('bootstrap.endTab'); ?>

	<?php $this->ignore_fieldsets = array('details', 'image-info', 'params', 'item_associations', 'accesscontrol'); ?>
	<?php echo JLayoutHelper::render('joomla.edit.params', $this); ?>

    <?php echo JHtml::_('bootstrap.endTabSet'); ?>

    </div>
    <input type="hidden" name="task" value="helloworld.edit" />
    <?php echo JHtml::_('form.token'); ?>
</form>

Handling the POST data

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 transferred as elements of a jform[com_fields] array. Our server code must validate the data and then save it, verifying that the current user is authorised to perform the associated operation.

Standard practice within Joomla is to add any validation aspects to the fields when the form structure is created. So when the POST is received this form is once again built, and then the validation routines run using the $form->validate(), and there is no additional work for us.

The saving of data is handled in the AdminController save() method. After the data is stored in the component's table this method emits the event onContentAfterSave, passing the context, the item being edited and the validated data. This event is picked up by the System Fields Plugin, which handles the verification of the user's authorisation and the saving of the custom fields, and once again there is nothing for us to do.

Admin Field Permissions

We have to define the types of permissions which relate to custom fields. In general the ability to create a custom field or fieldgroup associated with a component is defined by whether the user can create an item of that component. So if a user can create an article, then the same user can create a com_content custom field and a com_content custom fieldgroup. We can't change this for our component.

In terms of the Joomla assets (where permissions are stored), the hierarchy is Component > Custom Fieldgroup > Custom Field, so here we must define permissions at the Fieldgroup level (which relate to a fieldgroup and fields within that fieldgroup) and permissions at the Field level (which relate to that field). So for example, the setting of "core.create" for a specific fieldgroup controls the creation of custom fields within that fieldgroup. Because we have now introduced a "core.edit.value" at the fieldgroup and field level (associated with being able to set a value in a custom field or fieldgroup), we must now also have this permission at the higher "component" level.

The Joomla System Fields Plugin functionality uses the settings of the permissions below against the system usergroups to determine if the current user can edit, set values in, etc each custom field. The custom field is presented only if the current user is authorised to perform the relevant operation.

For our component we use the standard permissions used within Joomla core components, and our access.xml file is now:

admin/access.xml

<?xml version="1.0" encoding="utf-8" ?>
<access component="com_helloworld">
	<section name="component">
		<action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
		<action name="core.manage" title="JACTION_MANAGE" description="JACTION_MANAGE_COMPONENT_DESC" />
		<action name="core.create" title="JACTION_CREATE" description="JACTION_CREATE_COMPONENT_DESC" />
		<action name="core.delete" title="JACTION_DELETE" description="JACTION_DELETE_COMPONENT_DESC" />
		<action name="core.edit" title="JACTION_EDIT" description="JACTION_EDIT_COMPONENT_DESC" />
		<action name="core.edit.state" title="JACTION_EDITSTATE" description="JACTION_EDITSTATE_COMPONENT_DESC" />
		<action name="core.edit.own" title="JACTION_EDITOWN" description="JACTION_EDITOWN_COMPONENT_DESC" />
		<action name="core.edit.value" title="JACTION_EDITVALUE" description="JACTION_EDITVALUE_COMPONENT_DESC" />
	</section>
	<section name="category">
		<action name="core.create" title="JACTION_CREATE" description="COM_CATEGORIES_ACCESS_CREATE_DESC" />
		<action name="core.delete" title="JACTION_DELETE" description="COM_CATEGORIES_ACCESS_DELETE_DESC" />
		<action name="core.edit" title="JACTION_EDIT" description="COM_CATEGORIES_ACCESS_EDIT_DESC" />
		<action name="core.edit.state" title="JACTION_EDITSTATE" description="COM_CATEGORIES_ACCESS_EDITSTATE_DESC" />
		<action name="core.edit.own" title="JACTION_EDITOWN" description="COM_CATEGORIES_ACCESS_EDITOWN_DESC" />
	</section>
	<section name="message">
		<action name="core.delete" title="JACTION_DELETE" description="COM_HELLOWORLD_ACCESS_DELETE_DESC" />
		<action name="core.edit" title="JACTION_EDIT" description="COM_HELLOWORLD_ACCESS_EDIT_DESC" />
	</section>
	<section name="fieldgroup">
		<action name="core.create" title="JACTION_CREATE" description="COM_FIELDS_GROUP_PERMISSION_CREATE_DESC" />
		<action name="core.delete" title="JACTION_DELETE" description="COM_FIELDS_GROUP_PERMISSION_DELETE_DESC" />
		<action name="core.edit" title="JACTION_EDIT" description="COM_FIELDS_GROUP_PERMISSION_EDIT_DESC" />
		<action name="core.edit.state" title="JACTION_EDITSTATE" description="COM_FIELDS_GROUP_PERMISSION_EDITSTATE_DESC" />
		<action name="core.edit.own" title="JACTION_EDITOWN" description="COM_FIELDS_GROUP_PERMISSION_EDITOWN_DESC" />
		<action name="core.edit.value" title="JACTION_EDITVALUE" description="COM_FIELDS_GROUP_PERMISSION_EDITVALUE_DESC" />
	</section>
	<section name="field">
		<action name="core.delete" title="JACTION_DELETE" description="COM_FIELDS_FIELD_PERMISSION_DELETE_DESC" />
		<action name="core.edit" title="JACTION_EDIT" description="COM_FIELDS_FIELD_PERMISSION_EDIT_DESC" />
		<action name="core.edit.state" title="JACTION_EDITSTATE" description="COM_FIELDS_FIELD_PERMISSION_EDITSTATE_DESC" />
		<action name="core.edit.value" title="JACTION_EDITVALUE" description="COM_FIELDS_FIELD_PERMISSION_EDITVALUE_DESC" />
	</section>
</access>

Front-end Display

We now consider displaying the custom fields on our front-end Hello World page. This page displays the fields of the record with "Hello World!" as the greeting, and we need to extend this to output the values of associated custom fields. There are a few different ways of achieving this and we illustrate a couple of these in this step.

Overall there are 2 stages

  1. Extracting the custom field data from the database and storing them in a data structure
  2. Rendering the data, ie generating the HTML from the data structure, for outputting on the web page.

Extracting the Custom Field Data

One way of getting the field data is by using the System Fields Plugin, by triggering the event "onContentPrepare". We pass the context parameter and also pass by reference the helloworld item loaded from the database (so that it can be modified).

$dispatcher = JEventDispatcher::getInstance();
$dispatcher->trigger('onContentPrepare', array ('com_helloworld.helloworld', &$item, &$item->params));

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 etc. 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, as illustrated in Adding_custom_fields/Overrides.

Note that here we're doing something similar to what we did in the admin edit form, but there are significant differences:

  • in the admin form the event onContentPrepareForm is raised: here onContentPrepare is raised
  • in the admin form we pass the $form XML structure: here we pass an $item structure.
  • in the admin form the plugin adds the fields as additional XML under a "com_fields" fieldgroup into the form structure: here the plugin adds the fields as an array referenced by the $item->jcfields property.

Another way of getting the custom field data is by doing:

JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');
$fields = FieldsHelper::getFields('com_helloworld.helloworld', $this->item, true);

Once again we're passing the context and item and what is returned here is an array of the fields the same as the jcfields structure, except just an array of the field objects, not an associative array keyed by the field id.

Rendering the Custom Fields data

For each custom field Joomla provides an Automatic Display option which can be set to

  • After Title
  • Before Display
  • After Display
  • Do not automatically display

Joomla then provides an easy way of getting the HTML for each of the first 3 display options, eg:

$results = $dispatcher->trigger('onContentAfterTitle', array('com_helloworld.helloworld', &$item, &$item->params));
echo trim(implode("\n", $results));

This can be used to output in an appropriate position within the layout file the fields with the Automatic Display option set to After Title.

Alternatively we can use a layout (in com_fields/layouts/field/render.php) to render a $field which is one of the jcfields array elements:

echo FieldsHelper::render($field->context, 'field.render', array('field' => $field));

Finally we can use the fact that each jcfields object $field has got properties such as $field->label, $field->value, $field->rawvalue etc which we can echo directly to the output.

Updated Front-end Display Code

In our updated code we illustrate the different methods of displaying the custom fields.

In the view file we use the event triggering method to obtain the HTML for those fields which have an Automatic Display option set. (Because the code was integrated to support com_content primarily, we have to set a property $item->text for it to work).

site/views/helloworld/view.html.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
 
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
 
/**
 * HTML View class for the HelloWorld Component
 *
 * @since  0.0.1
 */
class HelloWorldViewHelloWorld extends JViewLegacy
{
	/**
	 * Display the Hello World view
	 *
	 * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
	 *
	 * @return  void
	 */
	function display($tpl = null)
	{
		// Assign data to the view
        $this->item = $this->get('Item');
		$user = JFactory::getUser();
		$app = JFactory::getApplication();
		
		// for custom fields
		$dispatcher = JEventDispatcher::getInstance();
		JPluginHelper::importPlugin('content');
		$item = $this->item;
		$item->text = null;

		$dispatcher->trigger('onContentPrepare', array ('com_helloworld.helloworld', &$item, &$item->params, null));

		$results = $dispatcher->trigger('onContentAfterTitle', array('com_helloworld.helloworld', &$item, &$item->params, null));
		$item->afterDisplayTitle = trim(implode("\n", $results));

		$results = $dispatcher->trigger('onContentBeforeDisplay', array('com_helloworld.helloworld', &$item, &$item->params, null));
		$item->beforeDisplayContent = trim(implode("\n", $results));

		$results = $dispatcher->trigger('onContentAfterDisplay', array('com_helloworld.helloworld', &$item, &$item->params, null));
		$item->afterDisplayContent = trim(implode("\n", $results));

        // Check for errors.
		if (count($errors = $this->get('Errors')))
		{
			JLog::add(implode('<br />', $errors), JLog::WARNING, 'jerror');

			return false;
		}
		
		// Take action based on whether the user has access to see the record or not
		$loggedIn = $user->get('guest') != 1;
		if (!$this->item->canAccess)
		{
			if ($loggedIn)
			{
				$app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error');
				$app->setHeader('status', 403, true);
				return;
			}
			else
			{
				$return = base64_encode(JUri::getInstance());
				$login_url_with_return = JRoute::_('index.php?option=com_users&return=' . $return, false);
				$app->enqueueMessage(JText::_('COM_HELLOWORLD_MUST_LOGIN'), 'notice');
				$app->redirect($login_url_with_return, 403);
			}
		}
		
        $this->addMap();
		
		$tagsHelper = new JHelperTags;
		$this->item->tags = $tagsHelper->getItemTags('com_helloworld.helloworld' , $this->item->id);
                
		$model = $this->getModel();
		$this->parentItem = $model->getItem($this->item->parent_id);
		$this->children = $model->getChildren($this->item->id);
		// getChildren includes the record itself (as well as the children) so remove this record
		unset($this->children[0]);
		
		// Display the view
		parent::display($tpl);
	}
    
	function addMap() 
	{
		$document = JFactory::getDocument();

		// everything's dependent upon JQuery
		JHtml::_('jquery.framework');

		// we need the Openlayers JS and CSS libraries
		$document->addScript("https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.6.4/ol.js");
		$document->addStyleSheet("https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.6.4/ol.css");

		// ... and our own JS and CSS
		$document->addScript(JURI::root() . "media/com_helloworld/js/openstreetmap.js");
		$document->addStyleSheet(JURI::root() . "media/com_helloworld/css/openstreetmap.css");

		// get the data to pass to our JS code
		$params = $this->get("mapParams");
		$document->addScriptOptions('params', $params);
	}
}

In the layout file we echo the HTML for the custom fields with the Automatic Display. For those custom fields set to not automatically display we use the com_fields render.php layout instead.

site/views/helloworld/tmpl/default.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
 
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
$lang = JFactory::getLanguage()->getTag();
if (JLanguageMultilang::isEnabled() && $lang)
{
    $query_lang = "&lang={$lang}";
}
else
{
    $query_lang = "";
}
?>
<h1><?php echo $this->item->greeting.(($this->item->category and $this->item->params->get('show_category'))
                                      ? (' ('.$this->item->category.')') : ''); ?>
</h1>
<?php 
	echo $this->item->description; 
    $tagLayout = new JLayoutFile('joomla.content.tags');
    echo $tagLayout->render($this->item->tags);
    $src = $this->item->imageDetails['image'];
    if ($src)
    {
        $html = '<figure>
                    <img src="%s" alt="%s" >
                    <figcaption>%s</figcaption>
                </figure>';
        $alt = $this->item->imageDetails['alt'];
        $caption = $this->item->imageDetails['caption'];
        echo sprintf($html, $src, $alt, $caption);
    } ?>
	
<?php if ($this->parentItem->id > 1) : ?>
	<h1><?php echo JText::_('COM_HELLOWORLD_PARENT') ?>
	</h1>
	<h3>
		<?php $url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $this->parentItem->id . ':' . $this->parentItem->alias . '&catid=' . $this->parentItem->catid . $query_lang); ?>
		<a href="<?php echo $url; ?>"><?php echo $this->parentItem->greeting; ?></a>
	</h3>
<?php endif; ?>

<?php if ($this->children) : 
		$baseLevel = $this->item->level; ?>
		<h1><?php echo JText::_('COM_HELLOWORLD_CHILDREN') ?>
		</h1>
		<?php foreach ($this->children as $i => $child) : ?>
			<h3>
				<?php $prefix = JLayoutHelper::render('joomla.html.treeprefix', array('level' => $child->level - $baseLevel)); ?>
				<?php echo $prefix; ?>
				<?php $url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $child->id . ':' . $child->alias . '&catid=' . $child->catid . $query_lang); ?>
				<a href="<?php echo $url; ?>"><?php echo $child->greeting; ?></a>
			</h3>
	<?php endforeach; ?>
<?php endif; ?>

<?php
	echo "<h3>After display title:</h3>";
	echo $this->item->afterDisplayTitle;
	echo "<h3>Before display content:</h3>";
	echo $this->item->beforeDisplayContent;
	echo "<h3>After display content:</h3>";
	echo $this->item->afterDisplayContent;
	
	JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');
    $fields = FieldsHelper::getFields('com_helloworld.helloworld', $this->item, true);
	echo "<h3>Fields set to not display automatically:</h3>";
	foreach ($fields as $field)
	{
		if ($field->params->get("display") == "0")
		{
			echo FieldsHelper::render($field->context, 'field.render', array('field' => $field)); 
			echo "<br>";
		}
	}
?>
<div id="map" class="map"></div>
<div class="map-callout map-callout-bottom" id="greeting-container"></div>
<div id="searchmap">
    <?php echo '<input id="token" type="hidden" name="' . JSession::getFormToken() . '" value="1" />'; ?>
    <button type="button" class="btn btn-primary" onclick="searchHere();">
        <?php echo JText::_('COM_HELLOWORLD_SEARCH_HERE_BUTTON') ?>
    </button>
    <div id="searchresults">
    </div>
</div>

Front-end Form

With the Front-end form we have the same aspects to support as with the Admin Setting Field Values above, namely:

  1. Setting up the form structure in XML
  2. Rendering the form fields, ie creating html from the fields in the XML form structure.
  3. Handling the POST data – performing field validation and saving the data

Setting up the form structure in XML

This works in the same way as for the admin case. The FormController::preprocessForm method triggers the onContentPrepareForm event, and the System Fields Plugin adds in the custom forms associated with the helloworld record, taking account of the predefined default category and also the ACL privileges of the current user.

However, we now face 3 issues which we must address.

The first is that (as described above) the System Fields Plugin uses the form name to determine what the context of the Custom Fields should be. The context we're using is "com_helloworld.helloworld", but the form name is "com_helloworld.form" (set in our models/form.php file) and so the plugin will look for custom fields with a context of "com_helloworld.form" instead.

However, Joomla provides a solution to this by looking for a function validateSection() in our admin helper file helloworld.php. The System Fields Plugin calls validateSection() passing in what it currently holds as the section part of the context, and takes the string returned from the function as the revised section value. So in our validateSection() we look for a section parameter set to "form" and in this case return "helloworld", so that the code uses the correct context to retrieve the fields from the database.

The second concerns the task="helloworld.reload" which is sent in the HTTP POST request whenever the user changes the category. The issue is that the FormController::reload() method (which is then run) executes the line $model = $this->getModel(); to get the model. However, the default model here is our "helloworld" model, and the model we've been using for the front-end form is the "form" model. To handle this we override getModel() in our controller code, and we'll use a scheme where (in our layout file) we set the name of the model to use in a hidden field in the form, and then (in our getModel() override) pick this off our POST jform[] parameter array.

Unfortunately also for us, the FormController::reload() method doesn't use our existing view parameter in the URL when it generates the HTTP Redirect, but rather bases the view parameter within the Redirect on a protected variable $view_item. So we have to set this variable, and we do this inside the controller constructor method. (Our layout parameter is copied ok from the existing URL to the redirect URL).

Rendering the form fields

To render the fields it is possible to use the same approach as in the admin functionality and use the 'joomla.edit.params' layout to present the fields in the same tabbed structure. However, we'll use a different approach here. The custom fields are added into the form XML structure within a form fields group named "com_fields". (A form fields group is created when we use <fields> … </fields> elements in the form XML). We can find the fieldsets within the field group using

$fieldsets = $this->form->getFieldsets('com_fields');

then for each $fieldset with this array we can render the fields within it using:

echo $this->form->renderFieldset($fieldset->name);

Handling the POST data

Just as in the admin case, this is all taken care of for us, and we don't need to do anything.

Updated Front-end Form Code

The changes to include validateSection() in the helper file are shown above. The remaining changes are to our helloworld controller and form layout.

site/controllers/helloworld.php

<?php
/**
 * @package     Joomla.Site
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// No direct access to this file
defined('_JEXEC') or die('Restricted access');

/**
 * HelloWorld Controller
 *
 * @package     Joomla.Site
 * @subpackage  com_helloworld
 *
 * Used to handle the http POST from the front-end form which allows 
 * users to enter a new helloworld message
 *
 */
class HelloWorldControllerHelloWorld extends JControllerForm
{   
	protected $view_item;  // default view within JControllerForm for reload function
	
	public function __construct($config = array())
	{
		$input = JFactory::getApplication()->input;
		$this->view_item = $input->get("view", "helloworld", "string");
		parent::__construct($config); 
	}
	
	public function cancel($key = null)
	{
		parent::cancel($key);
        
		// set up the redirect back to the same form
		$this->setRedirect(
			(string)JUri::getInstance(), 
			JText::_(COM_HELLOWORLD_ADD_CANCELLED)
		);
	}
    
	/*
	* Function handing the save for adding a new helloworld record
	* Based on the save() function in the JControllerForm class
	*/
	public function save($key = null, $urlVar = null)
	{
		// Check for request forgeries.
		JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
        
		$app = JFactory::getApplication(); 
		$input = $app->input; 
		$model = $this->getModel('form');
               
		// Get the current URI to set in redirects. As we're handling a POST, 
		// this URI comes from the <form action="..."> attribute in the layout file above
		$currentUri = (string)JUri::getInstance();

		// Check that this user is allowed to add a new record
		if (!JFactory::getUser()->authorise( "core.create", "com_helloworld"))
		{
			$app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error');
			$app->setHeader('status', 403, true);

			return;
		}
        
		// get the data from the HTTP POST request
		$data  = $input->get('jform', array(), 'array');
        
		// set up context for saving form data
		$context = "$this->option.edit.$this->context"; 
        
		// save the form data and set up the redirect back to the same form, 
		// to avoid repeating them under every error condition
		$app->setUserState($context . '.data', $data);
		$this->setRedirect($currentUri);
        
		// Validate the posted data.
		// First we need to set up an instance of the form ...
		$form = $model->getForm($data, false);

		if (!$form)
		{
			$app->enqueueMessage($model->getError(), 'error');
			return false;
		}

		// ... and then we validate the data against it
		// The validate function called below results in the running of the validate="..." routines
		// specified against the fields in the form xml file, and also filters the data 
		// according to the filter="..." specified in the same place (removing html tags by default in strings)
		$validData = $model->validate($form, $data);

		// Handle the case where there are validation errors
		if ($validData === false)
		{
			// Get the validation messages.
			$errors = $model->getErrors();

			// Display up to three validation messages to the user.
			for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
			{
				if ($errors[$i] instanceof Exception)
				{
					$app->enqueueMessage($errors[$i]->getMessage(), 'warning');
				}
				else
				{
					$app->enqueueMessage($errors[$i], 'warning');
				}
			}

			return false;
		}
        
		// Handle the uploaded file - get it from the PHP $_FILES structure
		$fileinfo = $this->input->files->get('jform', array(), 'array');
		$file = $fileinfo['imageinfo']['image'];
		/* The $file variable above should contain an array of 5 elements as follows:
		*   name: the name of the file (on the system from which it was uploaded), without directory info
		*   type: should be something like image/jpeg
		*   tmp_name: pathname of the file where PHP has stored the uploaded data 
		*   error: 0 if no error
		*   size: size of the file in bytes
		*/
        
		// Check if any files have been uploaded
		if ($file['error'] == 4)   // no file uploaded (see PHP file upload error conditions)
		{
			$validData['imageinfo'] = null;
		} 
		else 
		{
			if ($file['error'] > 0)
			{
				$app->enqueueMessage(JText::sprintf('COM_HELLOWORLD_ERROR_FILEUPLOAD', $file['error']), 'warning');
				return false;
			}
            
			// make sure filename is clean
			jimport('joomla.filesystem.file');
			$file['name'] = JFile::makeSafe($file['name']);
			if (!isset($file['name']))
			{
				// No filename (after the name was cleaned by JFile::makeSafe)
				$app->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_BADFILENAME'), 'warning');
				return false;
			}
            
			// files from Microsoft Windows can have spaces in the filenames
			$file['name'] = str_replace(' ', '-', $file['name']);

			// do checks against Media configuration parameters
			$mediaHelper = new JHelperMedia;
			if (!$mediaHelper->canUpload($file))
			{
				// The file can't be uploaded - the helper class will have enqueued the error message
				return false;
			}
            
			// prepare the uploaded file's destination pathnames
			$mediaparams = JComponentHelper::getParams('com_media');
			$relativePathname = JPath::clean($mediaparams->get($path, 'images') . '/' . $file['name']);
			$absolutePathname = JPATH_ROOT . '/' . $relativePathname;
			if (JFile::exists($absolutePathname))
			{
				// A file with this name already exists
				$app->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_FILE_EXISTS'), 'warning');
				return false;
			}
            
			// check file contents are clean, and copy it to destination pathname
			if (!JFile::upload($file['tmp_name'], $absolutePathname))
			{
				// Error in upload
				$app->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_UNABLE_TO_UPLOAD_FILE'));
				return false;
			}
            
			// Upload succeeded, so update the relative filename for storing in database
			$validData['imageinfo']['image'] = $relativePathname;
		}
        
		// add the 'created by' and 'created' date fields
		$validData['created_by'] = JFactory::getUser()->get('id', 0);
		$validData['created'] = date('Y-m-d h:i:s');
        
		// Attempt to save the data.
		if (!$model->save($validData))
		{
			// Handle the case where the save failed - redirect back to the edit form
			$this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
			$this->setMessage($this->getError(), 'error');

			return false;
		}
        
		// data has been saved ok, so clear the data in the form
		$app->setUserState($context . '.data', null);
        
		// notify the administrator that a new helloworld message has been added on the front end
        
		// get the id of the person to notify from global config
		$params   = $app->getParams();
		$userid_to_email = (int) $params->get('user_to_email');
		$user_to_email = JUser::getInstance($userid_to_email);
		$to_address = $user_to_email->get("email");
        
		// get the current user (if any)
		$current_user = JFactory::getUser();
		if ($current_user->get("id") > 0) 
		{
			$current_username = $current_user->get("username");
		}
		else 
		{
			$current_username = "a visitor to the site";
		}
        
		// get the Mailer object, set up the email to be sent, and send it
		$mailer = JFactory::getMailer();
		$mailer->addRecipient($to_address);
		$mailer->setSubject("New helloworld message added by " . $current_username);
		$mailer->setBody("New greeting is " . $validData['greeting']);
		try 
		{
			$mailer->send(); 
		}
		catch (Exception $e)
		{
			JLog::add('Caught exception: ' . $e->getMessage(), JLog::Error, 'jerror');
		}
        
		$this->setRedirect(
				$currentUri,
				JText::_('COM_HELLOWORLD_ADD_SUCCESSFUL')
				);
            
		return true;
        
	}

	public function getModel($name = '', $prefix = '', $config = array('ignore_request' => true))
	{
		if (empty($name))
		{
			$input = JFactory::getApplication()->input;
			$modelname = $input->get("modelname", "helloworld", "string");
			return parent::getModel($modelname, $prefix, $config);
		}

		return parent::getModel($name, $prefix, $config);
	}
}

site/views/form/tmpl/edit.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This layout file is for displaying the front end form for capturing a new helloworld message
 *
 */

// No direct access
defined('_JEXEC') or die('Restricted access');
JHtml::_('behavior.formvalidator');
$fieldsets = $this->form->getFieldsets('com_fields');

?>
<form action="<?php echo JRoute::_('index.php?option=com_helloworld&view=form&layout=edit'); ?>"
    method="post" name="adminForm" id="adminForm" class="form-validate" enctype="multipart/form-data">

	<div class="form-horizontal">
		<fieldset class="adminform">
			<legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_DETAILS') ?></legend>
			<div class="row-fluid">
				<div class="span6">
					<?php echo $this->form->renderFieldset('details');  ?>
				</div>
			</div>
		</fieldset>
	</div>
	<?php 
		foreach($fieldsets as $fieldset) 
		{
			echo $this->form->renderFieldset($fieldset->name);
		}
		?>
	<div class="btn-toolbar">
		<div class="btn-group">
			<button type="button" class="btn btn-primary" onclick="Joomla.submitbutton('helloworld.save')">
				<span class="icon-ok"></span><?php echo JText::_('JSAVE') ?>
			</button>
		</div>
		<div class="btn-group">
			<button type="button" class="btn" onclick="Joomla.submitbutton('helloworld.cancel')">
				<span class="icon-cancel"></span><?php echo JText::_('JCANCEL') ?>
			</button>
		</div>
	</div>

	<input type="hidden" name="task" />
	<input type="hidden" name="modelname" value="form"/>
	<?php echo JHtml::_('form.token'); ?>
</form>

Updated Language Strings

In addition to the language constant for the custom field context section which we introduced, we need to include a constant COM_HELLOWORLD because this is what the com_fields component uses to indicate it's the helloworld component when the admin clicks on Fields or Field Groups.

admin/language/en-GB/en-GB.com_helloworld.ini

; Joomla! Project
; Copyright (C) 2005 - 2018 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8

COM_HELLOWORLD_ADMINISTRATION="HelloWorld - Administration"
COM_HELLOWORLD_ADMINISTRATION_CATEGORIES="HelloWorld - Categories"
COM_HELLOWORLD_NUM="#"
COM_HELLOWORLD_HELLOWORLDS_FILTER="Filters"
COM_HELLOWORLD_AUTHOR="Author"
COM_HELLOWORLD_LANGUAGE="Language"
COM_HELLOWORLD_CREATED_DATE="Created"
COM_HELLOWORLD_PUBLISHED="Published"
COM_HELLOWORLD_HELLOWORLDS_NAME="Name"
COM_HELLOWORLD_HELLOWORLDS_POSITION="Position"
COM_HELLOWORLD_HELLOWORLDS_IMAGE="Image"
COM_HELLOWORLD_HELLOWORLDS_ASSOCIATIONS="Associations"
COM_HELLOWORLD_ID="Id"

COM_HELLOWORLD_HELLOWORLD_CREATING="HelloWorld - Creating"
COM_HELLOWORLD_HELLOWORLD_DETAILS="Details"
COM_HELLOWORLD_HELLOWORLD_EDITING="HelloWorld - Editing"
COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE="Some values are unacceptable"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC="The category the messages belongs to"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL="Category"
COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_DESC="This message will be displayed"
COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_LABEL="Message"
COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_DESC="Message description"
COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_LABEL="Description"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL="Show category"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC="If set to Show, the title of the message&rsquo;s category will show."
COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_LABEL="Latitude"
COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_DESC="Enter the position latitude, between -90 and +90 degrees"
COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_LABEL="Longitude"
COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_DESC="Enter the position longitude, between -180 and +180 degrees"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL="Parent"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC="Select the parent record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_FIRST="-- First record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_LAST="-- Last record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_TEXT="Ordering will be available after saving."
COM_HELLOWORLD_HELLOWORLD_FIELD_LANGUAGE_DESC="Select the appropriate language"
COM_HELLOWORLD_IMAGE_FIELDS="Image details"
COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_LABEL="Select image"
COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_DESC="Select an image from the library, or upload a new one"
COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_LABEL="Alt text"
COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_DESC="Alternative text (if image cannot be displayed)"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_LABEL="Caption"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_DESC="Provide a caption for the image"
COM_HELLOWORLD_HELLOWORLD_HEADING_GREETING="Greeting"
COM_HELLOWORLD_HELLOWORLD_HEADING_ID="Id"
COM_HELLOWORLD_MANAGER_HELLOWORLD_EDIT="HelloWorld manager: Edit Message"
COM_HELLOWORLD_MANAGER_HELLOWORLD_NEW="HelloWorld manager: New Message"
COM_HELLOWORLD_MANAGER_HELLOWORLDS="HelloWorld manager"
COM_HELLOWORLD_EDIT_HELLOWORLD="Edit message"
COM_HELLOWORLD_N_ITEMS_DELETED_1="One message deleted"
COM_HELLOWORLD_N_ITEMS_DELETED_MORE="%d messages deleted"
COM_HELLOWORLD_N_ITEMS_PUBLISHED="%d message(s) published"
COM_HELLOWORLD_N_ITEMS_UNPUBLISHED="%d message(s) unpublished"
COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL="Greeting"
COM_HELLOWORLD_HELLOWORLD_GREETING_DESC="Add Hello World Greeting"
COM_HELLOWORLD_SUBMENU_MESSAGES="Messages"
COM_HELLOWORLD_SUBMENU_CATEGORIES="Categories"
COM_HELLOWORLD_CONFIGURATION="HelloWorld Configuration"
COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_LABEL="Messages settings"
COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_DESC="Settings that will be applied to all messages by default"
COM_HELLOWORLD_CONFIG_INTEGRATION_SETTINGS_DESC="Settings relating to integration with the Syndicated Feed module"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL="Captcha"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC="Select Captcha to use on front end form"
COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_LABEL="User to email"
COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_DESC="Select user to email when a new message is entered on front end"
COM_HELLOWORLD_FIELDSET_RULES="Message Permissions"
COM_HELLOWORLD_FIELD_RULES_LABEL="Permissions"
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to edit this message?"
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to delete this message?"
COM_HELLOWORLD_TAB_NEW_MESSAGE="New Message"
COM_HELLOWORLD_TAB_EDIT_MESSAGE="Message Details"
COM_HELLOWORLD_TAB_PARAMS="Parameters"
COM_HELLOWORLD_TAB_ASSOCIATIONS="Associations"
COM_HELLOWORLD_TAB_PERMISSIONS="Permissions"
COM_HELLOWORLD_TAB_IMAGE="Image"
COM_HELLOWORLD_LEGEND_DETAILS="Message Details"
COM_HELLOWORLD_LEGEND_PARAMS="Message Parameters"
COM_HELLOWORLD_LEGEND_ASSOCIATIONS="Message Associations"
COM_HELLOWORLD_LEGEND_PERMISSIONS="Message Permissions"
COM_HELLOWORLD_LEGEND_IMAGE="Image info"
; Column ordering in the Helloworlds view
COM_HELLOWORLD_ORDERING_ASC="Ordering ascending"
COM_HELLOWORLD_ORDERING_DESC="Ordering descending"
COM_HELLOWORLD_GREETING_ASC="Greeting ascending"
COM_HELLOWORLD_GREETING_DESC="Greeting descending"
COM_HELLOWORLD_AUTHOR_ASC="Author ascending"
COM_HELLOWORLD_AUTHOR_DESC="Author descending"
COM_HELLOWORLD_CREATED_ASC="Creation date ascending"
COM_HELLOWORLD_CREATED_DESC="Creation date descending"
COM_HELLOWORLD_PUBLISHED_ASC="Unpublished first"
COM_HELLOWORLD_PUBLISHED_DESC="Published first"
COM_HELLOWORLD_LANGUAGE_ASC="Language ascending"
COM_HELLOWORLD_LANGUAGE_DESC="Language descending"
COM_HELLOWORLD_ASSOCIATION_ASC="Association ascending"
COM_HELLOWORLD_ASSOCIATION_DESC="Association descending"
COM_HELLOWORLD_ACCESS_ASC="Access ascending"
COM_HELLOWORLD_ACCESS_DESC="Access descending"
; Helloworld menuitem - selecting a greeting via modal
COM_HELLOWORLD_MENUITEM_SELECT_MODAL_TITLE="Select greeting"
COM_HELLOWORLD_MENUITEM_SELECT_HELLOWORLD="Select"
COM_HELLOWORLD_MENUITEM_SELECT_BUTTON_TOOLTIP="Select a helloworld greeting"
; Checking in records
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_0="No records checked in."
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_1="%d record successfully checked in."
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_MORE="%d records successfully checked in."
; Batch functionality
COM_HELLOWORLD_BATCH_OPTIONS="Helloworld component batch options"
COM_HELLOWORLD_BATCH_SETPOSITION_LABEL="Set location"
COM_HELLOWORLD_BATCH_KEEP_POSITION="Keep existing location"
COM_HELLOWORLD_BATCH_CHANGE_POSITION="Change location"
; Custom Fields functionality
COM_HELLOWORLD="Helloworld component"
COM_HELLOWORLD_ITEMS="Helloworld items"

Packaging the Component

Contents of your code directory. Each file link below takes you to the step in the tutorial which has the latest version of that source code file.

helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.0" method="upgrade">

	<name>COM_HELLOWORLD</name>
	<!-- The following elements are optional and free of formatting constraints -->
	<creationDate>January 2018</creationDate>
	<author>John Doe</author>
	<authorEmail>john.doe@example.org</authorEmail>
	<authorUrl>http://www.example.org</authorUrl>
	<copyright>Copyright Info</copyright>
	<license>License Info</license>
	<!--  The version string is recorded in the components table -->
	<version>1.1.0</version>
	<!-- The description is optional and defaults to the name -->
	<description>COM_HELLOWORLD_DESCRIPTION</description>

	<!-- Runs on install/uninstall/update; New in 2.5 -->
	<scriptfile>script.php</scriptfile>

	<install> <!-- Runs on install -->
		<sql>
			<file driver="mysql" charset="utf8">sql/install.mysql.utf8.sql</file>
		</sql>
	</install>
	<uninstall> <!-- Runs on uninstall -->
		<sql>
			<file driver="mysql" charset="utf8">sql/uninstall.mysql.utf8.sql</file>
		</sql>
	</uninstall>
	<update> <!-- Runs on update; New since J2.5 -->
		<schemas>
			<schemapath type="mysql">sql/updates/mysql</schemapath>
		</schemas>
	</update>

	<!-- Site Main File Copy Section -->
	<!-- Note the folder attribute: This attribute describes the folder
		to copy FROM in the package to install therefore files copied
		in this section are copied from /site/ in the package -->
	<files folder="site">
		<filename>index.html</filename>
		<filename>helloworld.php</filename>
		<filename>controller.php</filename>
		<filename>router.php</filename>
		<folder>controllers</folder>
		<folder>views</folder>
		<folder>models</folder>
		<folder>helpers</folder>
	</files>

		<languages folder="site/language">
			<language tag="en-GB">en-GB/en-GB.com_helloworld.ini</language>
			<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.ini</language>
		</languages>

	<media destination="com_helloworld" folder="media">
		<filename>index.html</filename>
		<folder>images</folder>
		<folder>js</folder>
		<folder>css</folder>
	</media>

	<administration>
		<!-- Administration Menu Section -->
		<menu link='index.php?option=com_helloworld' img="../media/com_helloworld/images/tux-16x16.png">COM_HELLOWORLD_MENU</menu>
		<!-- Administration Main File Copy Section -->
		<!-- Note the folder attribute: This attribute describes the folder
			to copy FROM in the package to install therefore files copied
			in this section are copied from /admin/ in the package -->
		<files folder="admin">
			<!-- Admin Main File Copy Section -->
			<filename>index.html</filename>
			<filename>config.xml</filename>
			<filename>helloworld.php</filename>
			<filename>controller.php</filename>
			<filename>access.xml</filename>
			<!-- SQL files section -->
			<folder>sql</folder>
			<!-- tables files section -->
			<folder>tables</folder>
			<!-- models files section -->
			<folder>models</folder>
			<!-- views files section -->
			<folder>views</folder>
			<!-- controllers files section -->
			<folder>controllers</folder>
			<!-- helpers files section -->
			<folder>helpers</folder>
			<!-- layout files section -->
			<folder>layouts</folder>
		</files>
		<languages folder="admin/language">
			<language tag="en-GB">en-GB/en-GB.com_helloworld.ini</language>
			<language tag="en-GB">en-GB/en-GB.com_helloworld.sys.ini</language>
			<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.ini</language>
			<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.sys.ini</language>
		</languages>
	</administration>

</extension>

Contributors

Contributors