J3.x

Desarrollo de un Componente MVC/Agregar Etiquetas

From Joomla! Documentation

< J3.x:Developing an MVC Component
This page is a translated version of the page J3.x:Developing an MVC Component/Adding Tags and the translation is 100% complete.
Other languages:
Deutsch • ‎English • ‎español • ‎français • ‎italiano • ‎中文(台灣)‎
Joomla! 
3.x
Tutorial
Desarrollo de un Componente MVC

Agregar una variable de petición en el tipo de menú

Utilizando la base de datos

Lado servidor básico

Agregar gestión de idioma

Agregar acciones del lado servidor

Agregar decoraciones del lado servidor

Agregar verificaciones

Agregar categorías

Agregar configuración

  1. Agregar ACL

Agregar un archivo de secuencia de comandos instalar-desinstalar-actualizar

Agregar un formulario del lado cliente

  1. Agregar una imagen
  2. Agregar un mapa
  3. Agregar AJAX
  4. Agregar un alias

Usar la facilidad filtro de idioma

  1. Agregar una Modal
  2. Agregar Asociaciones
  3. Agregar Comprobación
  4. Agregar Ordenamiento
  5. Agregar Niveles
  6. Agregar Control de Versiones
  7. Agregar Etiquetas
  8. Agregar Accesos
  9. Agregar procesos por lote
  10. Agregar Caché
  11. Agregar un Canal de Noticias

Agregar un servidor de actualización

  1. Agregar campos personalizados
  2. Upgrading to Joomla4



Esta es una serie multi-artículos de tutoriales sobre cómo desarrollar un Componente Modelo-Vista-Controlador para Joomla! VersiónJoomla 3.x.

Comenzar con la Introducción, y navegar por los artículos de esta serie usando el botón de navegación en la parte inferior o en el cuadro de la derecha (los "Artículos de esta serie").



Este artículo es parte del tutorial Desarrollo de un Componente MVC para Joomla! 3.2. Te invitamos a leer las partes anteriores del tutorial antes de leer esto.

En este paso agregamos etiquetas a nuestro componente helloworld. Un vídeo que acompaña este paso está disponible en Paso 18 Agregar etiquetas.

Introducción

Las etiquetas ofrecen una vista alternativa que permite a los visitantes de un sitio web explorar el contenido por materia, a través de las etiquetas que se han asignado a los elementos, en lugar de a través de la estructura del menú del sitio web. Joomla le permite agregar etiquetas a artículos, canales de noticias, categorías y contactos, entre otros elementos, y aunque un elemento puede pertenecer como máximo a una categoría, un elemento puede tener varias etiquetas. En este paso, extendemos esta funcionalidad a nuestros registros y categorías de helloworld.

Joomla ha diseñado la funcionalidad de etiquetas en la parte superior de las tablas UCM (Modelo de contenido unificado) en la base de datos. La UCM fue un concepto que solo se desarrolló parcialmente en Joomla y tiene un futuro incierto. Para los diversos tipos de contenido en Joomla - artículos, contactos, fuentes de noticias, etc. - apuntó a almacenar información común en una tabla de base de datos común (la tabla ucm_content, junto con la tabla asociada ucm_base), y solo los campos que eran especiales para eso tipo de contenido en una tabla separada.

Joomla Tags E-R diagram

La funcionalidad de las etiquetas de Joomla se construye sobre UCM, como se muestra en el diagrama de relaciones de entidades, con la tabla contentitem_tag_map implementando la gran cantidad de relaciones entre las etiquetas y los elementos de contenido. Para nosotros, esto significa que debemos tener campos clave de los registros de helloworld y las categorías copiadas en la tabla ucm_content, y habilitamos esto especificando los datos de configuración en la tabla content_types, similar a lo que hicimos en el último paso Agregar control de Versiones.

Las etiquetas de Joomla utilizan el mismo patrón Observador que el control de versiones de Joomla: siempre que un registro se almacena en la clase JTable, los eventos onBeforeStore, onAfterStore y onBeforeDelete son recogidos por el código de las etiquetas, y cuando una etiqueta se establece por primera vez en un registro, copia los campos desde el registro original a la tabla ucm_content, utilizando la asignación de campos definida en los datos de configuración content_types, y también escribe un registro resumido en la tabla ucm_base. A continuación, escribe un registro contentitem_tag_map que enlaza con los registros ucm_content y tags, para almacenar el hecho de que la etiqueta x se especifica contra el elemento de contenido y.

Enfoque

Algunos detalles de cómo incluir la funcionalidad de etiquetas en una extensión se dan en Usar etiquetas en una extensión (texto en inglés). En los archivos de nuestra base de datos, agregamos los datos de configuración a los registros de tipo de contenido de helloworld que permitirán que la funcionalidad de las etiquetas de Joomla copie los campos importantes de nuestros registros de helloworld y los registros de categoría de helloworld a los registros de ucm_content.

Agregamos el código a nuestra clase table de helloworld para que los observadores de etiquetas se suscriban a los eventos asociados con nuestra clase table de helloworld. Agregar esto al constructor de nuestra clase table de helloworld significa que esto se aplicará tanto al lado servidor como lado cliente, ya que ambos usan el mismo código de table de helloworld.

Agregamos un campo etiquetas de Joomla estándar a nuestro formulario de administración para editar un registro de helloworld, y a nuestro formulario del lado cliente para crear un registro de helloworld. El campo de etiquetas tiene 2 modos de operación, vea Campo de Formulario, Tipo tag:

  1. anidadas: las etiquetas se presentan como una lista fija desde la cual el usuario puede seleccionar una o más (asumiendo que la opción de campo multiple="true" esté establecida). Las etiquetas se implementan como un árbol en una estructura de tabla anidada, por lo que en la presentación dentro de las etiquetas, las subetiquetas del campo están sangradas, de la misma manera que los submenús y subcategorías se sangran en esos formularios de administración.
  2. ajax (predeterminado): las etiquetas se presentan como una lista, pero como una lista de rutas de tablas anidadas, en lugar de tener sangrías de subetiquetas. Además, el usuario puede ingresar texto libre: cuando se ingresan varios caracteres, Joomla ejecuta una solicitud de Ajax al servidor para encontrar etiquetas que coincidan con el texto ingresado. Si no se encuentran etiquetas coincidentes, el usuario puede crear una nueva etiqueta sobre la marcha de esta manera, excepto si la opción custom="deny" se agrega al campo de etiquetas.

Usamos el modo ajax en el formulario de administración y el modo anidado en el formulario del lado cliente.

Con respecto a la presentación de las etiquetas que tiene un registro, en el formulario de edición de administración del lado administrador queremos mostrar qué etiquetas ya están presentes al rellenar previamente el campo de etiquetas, por lo que dentro de nuestro código de modelo obtenemos estos datos de la base de datos. No mostraremos las etiquetas en el formulario de admin helloworlds.

Del lado cliente, mostraremos las etiquetas asociadas con un registro de hellowworld en el artículo Hello World, y para esto usaremos un diseño estándar de Joomla.

Base de datos

En nuestro archivo de actualización de la base de datos agregamos dentro de los registros de content_types, los registros de la tabla y la información de mapeo de campo que permitirán que esos valores de campo se copien en la tabla ucm_content. Para los registros de categoría solo seguimos el ejemplo de los componentes principales de Joomla. Para los registros de helloworld debemos considerar qué datos relacionados con las etiquetas se pueden mostrar en otras páginas web, por ejemplo, si el administrador creó un elemento que apunta a uno de los tipos de elementos del menú Etiquetas. Dichas páginas tendrán enlaces a los registros de helloworld, por lo que se deben incluir campos como el alias y el idioma que se utilizan en la construcción de las URL. Las asignaciones de campo "especiales" no se utilizan actualmente, por lo que es seguro omitirlas.

Los administradores pueden configurar Tipos de elementos del menú del lado cliente (de tipo Etiquetas) que muestran enlaces a elementos que tienen etiquetas específicas. En el caso de que un elemento sea un registro de helloworld o un registro de categoría de helloworld, Joomla buscará un método de ayuda tipo ruta que se especificará en el campo del enrutador ucm_content, por lo que actualizaremos los registros para que contengan valores para esto. Definimos estos métodos a continuación en la sección Helper Ruta del Sitio.

admin/sql/updates/mysql/0.0.28.sql

UPDATE `#__content_types` SET
`table` = '{"special":{"dbtable":"#__helloworld","key":"id","type":"Helloworld","prefix":"HelloworldTable","config":"array()"},
"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}',
`field_mappings` = 
'{"common": {
    "core_content_item_id": "id",
    "core_title": "greeting",
    "core_state": "published",
    "core_alias": "alias",
    "core_language":"language", 
    "core_created_time": "created",
    "core_body": "description",
    "core_catid": "catid"
  }}',
`router` = 'HelloworldHelperRoute::getHelloworldRoute'
WHERE `type_alias` = 'com_helloworld.helloworld';

UPDATE `#__content_types` SET
`table` = '{"special":{"dbtable":"#__categories","key":"id","type":"Category","prefix":"JTable","config":"array()"},
"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}',
`field_mappings` = 
'{"common": {
	"core_content_item_id":"id",
	"core_title":"title",
	"core_state":"published",
	"core_alias":"alias",
	"core_created_time":"created_time",
	"core_modified_time":"modified_time",
	"core_body":"description", 
	"core_hits":"hits",
	"core_publish_up":"null",
	"core_publish_down":"null",
	"core_access":"access", 
	"core_params":"params", 
	"core_featured":"null", 
	"core_metadata":"metadata", 
	"core_language":"language", 
	"core_images":"null", 
	"core_urls":"null", 
	"core_version":"version",
	"core_ordering":"null", 
	"core_metakey":"metakey", 
	"core_metadesc":"metadesc", 
	"core_catid":"parent_id", 
	"core_xreference":"null", 
	"asset_id":"asset_id"}, 
  "special":{
    "parent_id":"parent_id",
	"lft":"lft",
	"rgt":"rgt",
	"level":"level",
	"path":"path",
	"extension":"extension",
	"note":"note"}}',
`router` = 'HelloworldHelperRoute::getCategoryRoute'
WHERE `type_alias` = 'com_helloworld.category';

admin/sql/install.mysql.utf8.sql

DROP TABLE IF EXISTS `#__helloworld`;

CREATE TABLE `#__helloworld` (
	`id`       INT(11)     NOT NULL AUTO_INCREMENT,
	`asset_id` INT(10)     NOT NULL DEFAULT '0',
	`created`  DATETIME    NOT NULL DEFAULT '0000-00-00 00:00:00',
	`created_by`  INT(10) UNSIGNED NOT NULL DEFAULT '0',
	`checked_out` INT(10) NOT NULL DEFAULT '0',
	`checked_out_time` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
	`greeting` VARCHAR(25) NOT NULL,
	`description` VARCHAR(4000) NOT NULL DEFAULT '',
	`alias`  VARCHAR(40)  NOT NULL DEFAULT '',
    `language`  CHAR(7)  NOT NULL DEFAULT '*',
	`parent_id`	int(10)    NOT NULL DEFAULT '1',
	`level`	int(10)    NOT NULL DEFAULT '0',
	`path`	VARCHAR(400)    NOT NULL DEFAULT '',
	`lft`	int(11)    NOT NULL DEFAULT '0',
	`rgt`	int(11)    NOT NULL DEFAULT '0',
	`published` tinyint(4) NOT NULL DEFAULT '1',
	`catid`	    int(11)    NOT NULL DEFAULT '0',
	`params`   VARCHAR(1024) NOT NULL DEFAULT '',
	`image`   VARCHAR(1024) NOT NULL DEFAULT '',
	`latitude` DECIMAL(9,7) NOT NULL DEFAULT 0.0,
	`longitude` DECIMAL(10,7) NOT NULL DEFAULT 0.0,
	PRIMARY KEY (`id`)
)
	ENGINE =MyISAM
	AUTO_INCREMENT =0
	DEFAULT CHARSET =utf8;

CREATE UNIQUE INDEX `aliasindex` ON `#__helloworld` (`alias`, `catid`);

INSERT INTO `#__helloworld` (`greeting`,`alias`,`language`, `parent_id`, `level`, `path`, `lft`, `rgt`, `published`) VALUES
('helloworld root','helloworld-root-alias','en-GB', 0, 0, '', 0, 5, 1),
('Hello World!','hello-world','en-GB', 1, 1, 'hello-world', 1, 2, 0),
('Goodbye World!','goodbye-world','en-GB', 1, 1, 'goodbye-world', 3, 4, 0);

INSERT INTO `#__content_types` (`type_title`, `type_alias`, `content_history_options`, `table`, `field_mappings`, `router`) 
VALUES
('Helloworld', 'com_helloworld.helloworld', 
'{"formFile":"administrator\\/components\\/com_helloworld\\/models\\/forms\\/helloworld.xml", 
"hideFields":["asset_id","checked_out","checked_out_time","version","lft","rgt","level","path"], 
"ignoreChanges":["checked_out", "checked_out_time", "path"],
"convertToInt":[], 
"displayLookup":[
{"sourceColumn":"created_by","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},
{"sourceColumn":"parent_id","targetTable":"#__helloworld","targetColumn":"id","displayColumn":"greeting"},
{"sourceColumn":"catid","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"}]}',
'{"special":{"dbtable":"#__helloworld","key":"id","type":"Helloworld","prefix":"HelloworldTable","config":"array()"},
"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}',
'{"common": {
    "core_content_item_id": "id",
    "core_title": "greeting",
    "core_state": "published",
    "core_alias": "alias",
    "core_language":"language", 
    "core_created_time": "created",
    "core_body": "description",
    "core_catid": "catid"
  }}',
'HelloworldHelperRoute::getHelloworldRoute'),
('Helloworld Category', 'com_helloworld.category',
'{"formFile":"administrator\\/components\\/com_categories\\/models\\/forms\\/category.xml", 
"hideFields":["asset_id","checked_out","checked_out_time","version","lft","rgt","level","path","extension"], 
"ignoreChanges":["modified_user_id", "modified_time", "checked_out", "checked_out_time", "version", "hits", "path"],
"convertToInt":["publish_up", "publish_down"], 
"displayLookup":[
{"sourceColumn":"created_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},
{"sourceColumn":"access","targetTable":"#__viewlevels","targetColumn":"id","displayColumn":"title"},
{"sourceColumn":"modified_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},
{"sourceColumn":"parent_id","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"}]}',
'{"special":{"dbtable":"#__categories","key":"id","type":"Category","prefix":"JTable","config":"array()"},
"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}',
'{"common": {
	"core_content_item_id":"id",
	"core_title":"title",
	"core_state":"published",
	"core_alias":"alias",
	"core_created_time":"created_time",
	"core_modified_time":"modified_time",
	"core_body":"description", 
	"core_hits":"hits",
	"core_publish_up":"null",
	"core_publish_down":"null",
	"core_access":"access", 
	"core_params":"params", 
	"core_featured":"null", 
	"core_metadata":"metadata", 
	"core_language":"language", 
	"core_images":"null", 
	"core_urls":"null", 
	"core_version":"version",
	"core_ordering":"null", 
	"core_metakey":"metakey", 
	"core_metadesc":"metadesc", 
	"core_catid":"parent_id", 
	"core_xreference":"null", 
	"asset_id":"asset_id"}, 
  "special":{
    "parent_id":"parent_id",
	"lft":"lft",
	"rgt":"rgt",
	"level":"level",
	"path":"path",
	"extension":"extension",
	"note":"note"}}',
'HelloworldHelperRoute::getCategoryRoute');

En nuestro script sql de desinstalación, debemos eliminar todos los registros de helloworld que se hayan copiado en las tablas ucm_base y ucm_content, y también eliminar los enlaces a los registros de helloworld en la tabla de enlaces de etiquetas contentitem_tag_map.

admin/sql/uninstall.mysql.utf8.sql

DROP TABLE IF EXISTS `#__helloworld`;
DELETE FROM `#__ucm_history` WHERE ucm_type_id in 
	(select type_id from `#__content_types` where type_alias in ('com_helloworld.helloworld','com_helloworld.category'));
DELETE FROM `#__ucm_base` WHERE ucm_type_id in 
	(select type_id from `#__content_types` WHERE type_alias in ('com_helloworld.helloworld','com_helloworld.category'));
DELETE FROM `#__ucm_content` WHERE core_type_alias in ('com_helloworld.helloworld','com_helloworld.category');
DELETE FROM `#__contentitem_tag_map`WHERE type_alias in ('com_helloworld.helloworld','com_helloworld.category');
DELETE FROM `#__content_types` WHERE type_alias in ('com_helloworld.helloworld','com_helloworld.category');

Configurar etiquetas Observador

En el paso Agregar control de versiones usamos la función JObserverMapper::addObserverClassToClass() para configurar el observador ContentHistory. En este paso, usamos el mecanismo alternativo, utilizando JTableObserverTags::createObserver() para configurar el observador de etiquetas. Llamamos a esto en nuestro constructor de tablas helloworld, asegurándonos que se llame después del constructor principal.

admin/tables/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
defined('_JEXEC') or die('Restricted access');

/**
 * Hello Table class
 *
 * @since  0.0.1
 */
class HelloWorldTableHelloWorld extends JTableNested
{
	/**
	 * Constructor
	 *
	 * @param   JDatabaseDriver  &$db  A database connector object
	 */
	function __construct(&$db)
	{
		JObserverMapper::addObserverClassToClass('JTableObserverContenthistory', 'HelloWorldTableHelloWorld', array('typeAlias' => 'com_helloworld.helloworld'));
		parent::__construct('#__helloworld', 'id', $db);
		JTableObserverTags::createObserver($this, array('typeAlias' => 'com_helloworld.helloworld'));
	}
	/**
	 * Overloaded bind function
	 *
	 * @param       array           named array
	 * @return      null|string     null is operation was satisfactory, otherwise returns an error
	 * @see JTable:bind
	 * @since 1.5
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['params']) && is_array($array['params']))
		{
			// Convert the params field to a string.
			$parameter = new JRegistry;
			$parameter->loadArray($array['params']);
			$array['params'] = (string)$parameter;
		}
        
        if (isset($array['imageinfo']) && is_array($array['imageinfo']))
		{
			// Convert the imageinfo array to a string.
			$parameter = new JRegistry;
			$parameter->loadArray($array['imageinfo']);
			$array['image'] = (string)$parameter;
		}
        
        // Bind the rules.
		if (isset($array['rules']) && is_array($array['rules']))
		{
			$rules = new JAccessRules($array['rules']);
			$this->setRules($rules);
		}
        
		if (isset($array['parent_id']))
		{
			if (!isset($array['id']) || $array['id'] == 0)
			{   // new record
				$this->setLocation($array['parent_id'], 'last-child');
			}
			elseif (isset($array['helloworldordering']))
			{
				// when saving a record load() is called before bind() so the table instance will have properties which are the existing field values
				if ($this->parent_id == $array['parent_id'])
				{
					// If first is chosen make the item the first child of the selected parent.
					if ($array['helloworldordering'] == -1)
					{
						$this->setLocation($array['parent_id'], 'first-child');
					}
					// If last is chosen make it the last child of the selected parent.
					elseif ($array['helloworldordering'] == -2)
					{
						$this->setLocation($array['parent_id'], 'last-child');
					}
					// Don't try to put an item after itself. All other ones put after the selected item.
					elseif ($array['helloworldordering'] && $this->id != $array['helloworldordering'])
					{
						$this->setLocation($array['helloworldordering'], 'after');
					}
					// Just leave it where it is if no change is made.
					elseif ($array['helloworldordering'] && $this->id == $array['helloworldordering'])
					{
						unset($array['helloworldordering']);
					}
				}
				// Set the new parent id if parent id not matched and put in last position
				else
				{
					$this->setLocation($array['parent_id'], 'last-child');
				}
			}
		}

		return parent::bind($array, $ignore);
	}
    
    /**
	 * Method to compute the default name of the asset.
	 * The default name is in the form `table_name.id`
	 * where id is the value of the primary key of the table.
	 *
	 * @return	string
	 * @since	2.5
	 */
	protected function _getAssetName()
	{
		$k = $this->_tbl_key;
		return 'com_helloworld.helloworld.'.(int) $this->$k;
	}
	/**
	 * Method to return the title to use for the asset table.
	 *
	 * @return	string
	 * @since	2.5
	 */
	protected function _getAssetTitle()
	{
		return $this->greeting;
	}
	/**
	 * Method to get the asset-parent-id of the item
	 *
	 * @return	int
	 */
	protected function _getAssetParentId(JTable $table = NULL, $id = NULL)
	{
		// We will retrieve the parent-asset from the Asset-table
		$assetParent = JTable::getInstance('Asset');
		// Default: if no asset-parent can be found we take the global asset
		$assetParentId = $assetParent->getRootId();

		// Find the parent-asset
		if (($this->catid)&& !empty($this->catid))
		{
			// The item has a category as asset-parent
			$assetParent->loadByName('com_helloworld.category.' . (int) $this->catid);
		}
		else
		{
			// The item has the component as asset-parent
			$assetParent->loadByName('com_helloworld');
		}

		// Return the found asset-parent-id
		if ($assetParent->id)
		{
			$assetParentId=$assetParent->id;
		}
		return $assetParentId;
	}

	public function check()
	{
		$this->alias = trim($this->alias);
		if (empty($this->alias))
		{
			$this->alias = $this->greeting;
		}
		$this->alias = JFilterOutput::stringURLSafe($this->alias);
		return true;
	}

	public function delete($pk = null, $children = false)
	{
		return parent::delete($pk, $children);
	}
}

Formulario de Edición del Administrador

En el formulario de edición del administrador, agregamos el campo de etiquetas de Joomla estándar, lo que permite al administrador seleccionar varias etiquetas y también crear una nueva etiqueta sobre la marcha.

También agregamos un campo de estado, que establecemos en 1 (publicado) de forma predeterminada. El motivo de esto es que nuestro campo published en la base de datos tiene un valor predeterminado de 1, por lo que los nuevos registros de helloworld obtienen de forma predeterminada un estado de publicado. Sin embargo, el campo equivalente state en la tabla ucm_content se establece en cero de forma predeterminada, lo que significa que no se ha publicado. En ese caso, si utilizamos algunas de las funciones de Joomla para mostrar los elementos asociados con una determinada etiqueta, entonces no aparecerá nuestro registro de helloworld, ya que Joomla solo muestra los elementos publicados. Cuando incluimos nuestro campo de estado en el formulario, su valor se copia en el registro ucm_content (basado en nuestra asignación de campos anterior), y así coincide con su valor en el registro de helloworld.

admin/models/forms/helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<form
				addrulepath="/administrator/components/com_helloworld/models/rules"
>
	<fieldset
				name="details"
				label="COM_HELLOWORLD_HELLOWORLD_DETAILS"
	>
		<field
				name="id"
				type="hidden"
				/>
                <field
				name="greeting"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_GREETING_DESC"
				size="40"
				class="inputbox validate-greeting"
				validate="greeting"
				required="true"
				default=""
				/>
		<field 
				name="alias" 
				type="text" 
				label="JFIELD_ALIAS_LABEL"
				description="JFIELD_ALIAS_DESC"
				hint="JFIELD_ALIAS_PLACEHOLDER"
				size="40" 
				/>
        <field
				name="catid"
				type="category"
				extension="com_helloworld"
				class="inputbox"
				default=""
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC"
				required="true"
		>
			<option value="0">JOPTION_SELECT_CATEGORY</option>
		</field>
		<field
				name="latitude"
				type="number"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_DESC"
				min="-90.0"
				max="90.0"
				class="inputbox"
				required="true"
				default="0.0"
				/>
		<field
				name="longitude"
				type="number"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_DESC"
				min="-180.0"
				max="180.0"
				class="inputbox"
				required="true"
				default="0.0"
				/>
		<field  
                name="language" 
        		type="contentlanguage" 
                label="JFIELD_LANGUAGE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_LANGUAGE_DESC"
		>
			<option value="*">JALL</option>
		</field>
		<field 	name="published" 
				type="list" 
				label="JSTATUS"
				description="JFIELD_PUBLISHED_DESC" 
				class="chzn-color-state"
				filter="intval" 	
				size="1" 
				default="1"
		>
			<option value="1">
				JPUBLISHED</option>
			<option value="0">
				JUNPUBLISHED</option>
		</field>
		<field 
				name="tags" 
				type="tag"
				label="JTAG" 
				description="JTAG_DESC"
				multiple="true"
		>
		</field>
		<field
				name="parent_id"
				type="helloworldparent"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC"
				default="1"
				filter="int">
		</field>
		<field
				name="helloworldordering"
				type="helloworldordering"
				label="JFIELD_ORDERING_LABEL"
				description="JFIELD_ORDERING_DESC"
				filter="int"
				size="1">
		</field>
		<field 	name="version_note"
				type="text"
				label="JGLOBAL_FIELD_VERSION_NOTE_LABEL"
				description="JGLOBAL_FIELD_VERSION_NOTE_DESC"
				class="inputbox" 
				size="45"
				labelclass="control-label">
		</field>
    </fieldset>
		<field 	name="description" 
				type="editor"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_LABEL" 
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_DESC"
				filter="JComponentHelper::filterText" 
				buttons="true" 
		/>
	<fields name="imageinfo">
		<fieldset
				name="image-info"
				label="COM_HELLOWORLD_IMAGE_FIELDS"
		>
			<field
                name="image"
                type="media"
                preview="tooltip"
                label="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_LABEL"
                description="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_DESC" />
            <field name="alt"
                type="text"
                label="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_LABEL"
                description="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_DESC"
                size="30"/>
            <field name="caption"
                type="text"
                label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_LABEL"
                description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_DESC"
                size="30"/>
		</fieldset>
	</fields>
	<fields name="params">
		<fieldset
				name="params"
				label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS"
		>
			<field
					name="show_category"
					type="list"
					label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
					description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
					default=""
			>
				<option value="">JGLOBAL_USE_GLOBAL</option>
				<option value="0">JHIDE</option>
				<option value="1">JSHOW</option>
			</field>
		</fieldset>
	</fields>
	<fieldset
			name="accesscontrol"
			label="COM_HELLOWORLD_FIELDSET_RULES"
	>
    	<field
				name="asset_id"
				type="hidden"
				filter="unset"
				/>
    	<field
				name="rules"
				type="rules"
				label="COM_HELLOWORLD_FIELD_RULES_LABEL"
				filter="rules"
				validate="rules"
				class="inputbox"
				component="com_helloworld"
				section="message"
				/>
    </fieldset>
</form>

En el formulario debemos rellenar previamente el campo con cualquier etiqueta existente establecida en ese registro, y lo hacemos en el modelo getItem(). Solo necesitamos proporcionar los identificadores de las etiquetas ya establecidas. Los títulos de todas las etiquetas disponibles y sus identificadores correspondientes se obtendrán en la lista de opciones para la declaración html <select>, y las etiquetas ya presentes obtendrán la opción selectes del grupo de atributos basado en la coincidencia de sus identificadores con la lista de opciones. (Ten en cuenta que la declaración html <select> se convierte en la biblioteca de JavaScript Chosen, por lo que no se ve en el fuente de la página web ).

admin/models/helloworld.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 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');

use Joomla\Registry\Registry;

/**
 * HelloWorld Model
 *
 * @since  0.0.1
 */
class HelloWorldModelHelloWorld extends JModelAdmin
{
    // JModelAdmin needs to know this for storing the associations 
	protected $associationsContext = 'com_helloworld.item';
    
	// Contenthistory needs to know this for restoring previous versions
	public $typeAlias = 'com_helloworld.helloworld';
	
	/**
	 * Method to override getItem to allow us to convert the JSON-encoded image information
	 * in the database record into an array for subsequent prefilling of the edit form
	 * We also use this method to prefill the tags and associations
	 */
	public function getItem($pk = null)
	{
		$item = parent::getItem($pk);
		if ($item AND property_exists($item, 'image'))
		{
			$registry = new Registry($item->image);
			$item->imageinfo = $registry->toArray();
		}

		if (!empty($item->id))
		{
			$tagsHelper = new JHelperTags;
			$item->tags = $tagsHelper->getTagIds($item->id, 'com_helloworld.helloworld');
		}
        
		// Load associated items
		if (JLanguageAssociations::isEnabled())
		{
			$item->associations = array();

			if ($item->id != null)
			{
				$associations = JLanguageAssociations::getAssociations('com_helloworld', '#__helloworld', 'com_helloworld.item', (int)$item->id);

				foreach ($associations as $tag => $association)
				{
					$item->associations[$tag] = $association->id;
				}
			}
		}
		return $item; 
	}
	
	/**
	 * Method to get a table object, load it if necessary.
	 *
	 * @param   string  $type    The table name. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  JTable  A JTable object
	 *
	 * @since   1.6
	 */
	public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array())
	{
		return JTable::getInstance($type, $prefix, $config);
	}

	/**
	 * Method to get the record 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  mixed    A JForm object on success, false on failure
	 *
	 * @since   1.6
	 */
	public function getForm($data = array(), $loadData = true)
	{
		// Get the form.
		$form = $this->loadForm(
			'com_helloworld.helloworld',
			'helloworld',
			array(
				'control' => 'jform',
				'load_data' => $loadData
			)
		);

		if (empty($form))
		{
			return false;
		}

		return $form;
	}
    
    /**
	 * Method to preprocess the form to add the association fields dynamically
	 *
	 * @return     none
	 */
	protected function preprocessForm(JForm $form, $data, $group = 'helloworld')
	{
		// Association content items
		if (JLanguageAssociations::isEnabled())
		{
			$languages = JLanguageHelper::getContentLanguages(false, true, null, 'ordering', 'asc');

			if (count($languages) > 1)
			{
				$addform = new SimpleXMLElement('<form />');
				$fields = $addform->addChild('fields');
				$fields->addAttribute('name', 'associations');
				$fieldset = $fields->addChild('fieldset');
				$fieldset->addAttribute('name', 'item_associations');

				foreach ($languages as $language)
				{
					$field = $fieldset->addChild('field');
					$field->addAttribute('name', $language->lang_code);
					$field->addAttribute('type', 'modal_helloworld');
					$field->addAttribute('language', $language->lang_code);
					$field->addAttribute('label', $language->title);
					$field->addAttribute('translate_label', 'false');
				}

				$form->load($addform, false);
			}
		}
		parent::preprocessForm($form, $data, $group);
	}

	/**
	 * Method to get the script to be included on the form
	 *
	 * @return string	Script files
	 */
	public function getScript() 
	{
		return 'administrator/components/com_helloworld/models/forms/helloworld.js';
	}
	
	/**
	 * Method to get the data that should be injected in the form.
	 *
	 * @return  mixed  The data for the form.
	 *
	 * @since   1.6
	 */
	protected function loadFormData()
	{
		// Check the session for previously entered form data.
		$data = JFactory::getApplication()->getUserState(
			'com_helloworld.edit.helloworld.data',
			array()
		);

		if (empty($data))
		{
			$data = $this->getItem();
		}

		return $data;
	}
	/**
	 * Method to override the JModelAdmin save() function to handle Save as Copy correctly
	 *
	 * @param   The helloworld record data submitted from the form.
	 *
	 * @return  parent::save() return value
	 */
	public function save($data)
	{
		$input = JFactory::getApplication()->input;

		JLoader::register('CategoriesHelper', JPATH_ADMINISTRATOR . '/components/com_categories/helpers/categories.php');

		// Validate the category id
		// validateCategoryId() returns 0 if the catid can't be found
		if ((int) $data['catid'] > 0)
		{
			$data['catid'] = CategoriesHelper::validateCategoryId($data['catid'], 'com_helloworld');
		}

		// Alter the greeting and alias for save as copy
		if ($input->get('task') == 'save2copy')
		{
			$origTable = clone $this->getTable();
			$origTable->load($input->getInt('id'));

			if ($data['greeting'] == $origTable->greeting)
			{
				list($greeting, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['greeting']);
				$data['greeting'] = $greeting;
				$data['alias'] = $alias;
			}
			else
			{
				if ($data['alias'] == $origTable->alias)
				{
					$data['alias'] = '';
				}
			}
			// standard Joomla practice is to set the new record as unpublished
			$data['published'] = 0;
		}

		$result = parent::save($data);
		if ($result)
		{
			$this->getTable()->rebuild(1);
		}
		return $result;
	}
	/**
	 * Method to check if it's OK to delete a message. Overrides JModelAdmin::canDelete
	 */
	protected function canDelete($record)
	{
		if( !empty( $record->id ) )
		{
			return JFactory::getUser()->authorise( "core.delete", "com_helloworld.helloworld." . $record->id );
		}
	}
	/**
	 * Prepare a helloworld record for saving in the database
	 */
	protected function prepareTable($table)
	{
	}
	
	/**
	 * Save the record reordering after a record is dragged to a new position in the helloworlds view
	 */
	public function saveorder($idArray = null, $lft_array = null)
	{
		// Get an instance of the table object.
		$table = $this->getTable();

		if (!$table->saveorder($idArray, $lft_array))
		{
			$this->setError($table->getError());

			return false;
		}

		return true;
	}
}

Formulario Lado Cliente

Al igual que en el formulario del administrador, agregamos un campo de etiquetas al formulario del lado cliente, excepto que usamos la opción anidada y no permitimos que el usuario defina una nueva etiqueta. También incluimos un campo publicado con un valor predeterminado de 1, pero simplemente configuramos este valor como oculto.

site/models/forms/add-form.xml

<?xml version="1.0" encoding="utf-8"?>
<form
    addrulepath="/administrator/components/com_helloworld/models/rules"
    addfieldpath="/administrator/components/com_helloworld/models/fields"
    >
    <fieldset
				name="details"
				label="COM_HELLOWORLD_HELLOWORLD_DETAILS"
	>
		<field
				name="id"
				type="hidden"
				/>
		<field
				name="greeting"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_GREETING_DESC"
				size="40"
				class="inputbox"
				validate="greeting"
				required="true"
				hint="COM_HELLOWORLD_HELLOWORLD_GREETING_HINT"
				default=""
				/>
		<field 
				name="alias" 
				type="text" 
				label="JFIELD_ALIAS_LABEL"
				description="JFIELD_ALIAS_DESC"
				hint="JFIELD_ALIAS_PLACEHOLDER"
				size="40" 
				/>
		<field
				name="catid"
				type="category"
				extension="com_helloworld"
				class="inputbox"
				default=""
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC"
				required="true"
				>
				<option value="0">JOPTION_SELECT_CATEGORY</option>
		</field>
		<fields name="imageinfo" label="COM_HELLOWORLD_HELLOWORLD_IMAGE_LABEL">
			<field
				name="image"
				type="file"
				label="COM_HELLOWORLD_HELLOWORLD_PICTURE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_PICTURE_DESC" 
				accept="image/*"
				>
			</field>
			<field
 				name="caption"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_CAPTION_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_CAPTION_DESC"
				size="40"
				class="inputbox"
				>
			</field>
			<field
				name="alt"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_ALTTEXT_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_ALTTEXT_DESC"
				size="40"
				class="inputbox"
				>
			</field>
		</fields>
		<field
				name="language"
				type="contentlanguage"
				label="JFIELD_LANGUAGE_LABEL"
				description="JFIELD_LANGUAGE_DESC"
				class="inputbox">
		</field>
		<field 
				name="tags" 
				type="tag"
				label="JTAG" 
				description="JTAG_DESC"
				mode="nested"
				multiple="true">
		</field>
		<field
				name="message"
				type="textarea"
				rows="5"
				cols="80"
				label="COM_HELLOWORLD_HELLOWORLD_MESSAGE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_MESSAGE_DESC"
				hint="COM_HELLOWORLD_HELLOWORLD_MESSAGE_HINT"
				required="true"
				>
        </field>
        <field
				name="captcha"
				type="captcha"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC"
				validate="captcha"
                >
		</field>
        <fields name="params">
            <field
                    name="show_category"
                    type="list"
                    label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
                    description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
                    default=""
                    useglobal="true"
            >
                <option value="0">JHIDE</option>
                <option value="1">JSHOW</option>
            </field>
        </fields>
		<field
				name="parent_id"
				type="helloworldparent"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC"
				default="1"
				filter="int">
		</field>
		<field name="published" type="hidden" default="1" />
    </fieldset>
</form>

Mostrar registros de Hello World del lado cliente

En el código de la vista, obtenemos los datos de la etiqueta utilizando la función TagsHelper::getItemTags(). Esta vez no solo necesitamos los identificadores de etiquetas, sino también otra información, como el alias y el título de la etiqueta, para que el enlace de la etiqueta se pueda construir correctamente en el html.

En el código de diseño, usamos el diseño estándar de joomla en layouts/joomla/content/tags.php, y en la llamada render() pasamos los datos de etiqueta que se configuraron en la vista. Ten en cuenta que este diseño está pensado para usarse solo en el lado cliente.

site/views/helloworld/view.html.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 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');

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

			return false;
		}

		$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);
	}
}

site/views/helloworld/tmpl/default.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 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; ?>

<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>

Helper Ruta del Sitio

Como se mencionó en la sección Base de datos anterior, agregamos los métodos getHelloworldRoute() y getCategoryRoute() al archivo helper ruta del sitio. Esto no es estrictamente necesario porque si no escribimos estas funciones, Joomla usará un helper de ruta predeterminado, pero tendrá menos rendimiento que escribir el código nosotros mismos.

site/helpers/route.php

<?php

defined('_JEXEC') or die;

/**
 * Helloworld Component Helper file for generating the URL Routes
 *
 */
class HelloworldHelperRoute
{
	/**
	 * When the Helloworld message is displayed then there is also shown a map with a Search Here button.
	 * This function generates the URL which the Ajax call will use to perform the search. 
	 * 
	 */
	public static function getAjaxURL()
	{
		if (!JLanguageMultilang::isEnabled())
		{
			return null;
		}
        
		$lang = JFactory::getLanguage()->getTag();
		$app  = JFactory::getApplication();
		$sitemenu= $app->getMenu();
		$thismenuitem = $sitemenu->getActive();

		// if we haven't got an active menuitem, or we're currently on a menuitem 
		// with view=category or note = "Ajax", then just stay on it
		if (!$thismenuitem || strpos($thismenuitem->link, "view=category") !== false || $thismenuitem->note == "Ajax")
		{
			return null;
		}

		// look for a menuitem with the right language, and a note field of "Ajax"
		$menuitem = $sitemenu->getItems(array('language','note'), array($lang, "Ajax"));
		if ($menuitem)
		{
			$itemid = $menuitem[0]->id; 
			$url = JRoute::_("index.php?Itemid=$itemid&view=helloworld&format=json");
			return $url;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Helper function for generating the URL to a Helloworld page
	 * This is needed for the Tags functionality
	 */
	public static function getHelloworldRoute($id, $catid = 0, $language = 0)
	{
		// Create the link
		$link = 'index.php?option=com_helloworld&view=helloworld&id=' . $id;

		if ((int) $catid > 1)
		{
			$link .= '&catid=' . $catid;
		}

		if ($language && $language !== '*' && JLanguageMultilang::isEnabled())
		{
			$link .= '&lang=' . $language;
		}

		return $link;
	}

	/**
	 * Helper function for generating the URL to a Helloworld Category page
	 * This is needed for the Tags functionality
	 */
	public static function getCategoryRoute($catid, $language = 0)
	{
		if ($catid instanceof JCategoryNode)
		{
			$id = $catid->id;
		}
		else
		{
			$id = (int) $catid;
		}

		if ($id < 1)
		{
			$link = '';
		}
		else
		{
			$link = 'index.php?option=com_helloworld&view=category&id=' . $id;

			if ($language && $language !== '*' && JLanguageMultilang::isEnabled())
			{
				$link .= '&lang=' . $language;
			}
		}

		return $link;
	}
}

Empaquetado del componente

Contenido de su directorio de código. Cada enlace a un archivo a continuación te lleva al paso en el tutorial que tiene la última versión de ese archivo de código fuente.

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>0.0.28</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>
		</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>

Colaboradores