J3.x

Desarrollo de un Componente MVC/Agregar un alias

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 an alias and the translation is 100% complete.

Other languages:
Deutsch • ‎English • ‎español • ‎français
Joomla! 
3.x
Tutorial
Desarrollo de un Componente MVC


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.x. Te invitamos a leer las partes anteriores del tutorial antes de leer esto.

El propósito principal de este paso es proporcionar una introducción a la funcionalidad del enrutador de Joomla, que es responsable de tomar la URL en la solicitud HTTP entrante, averiguar qué componente debe ser responsable de su manejo y configurar los parámetros de la solicitud (view=abc, id=123, etc.) correctamente para que el componente pueda acceder a ellos a través de las llamadas API estándar.

Configuraremos un campo de alias en cada uno de nuestros registros de helloworld, y usaremos ese alias dentro de las URL de SEF (Search Engine Friendly) para acceder a nuestros mensajes de helloworld. Para lograr esto definiremos nuestro propio enrutador de componentes personalizados. En este paso tutorial, el enrutador será muy básico, pero lo haremos más sofisticado en el próximo paso cuando nuestro componente sea multilingüe. And we'll develop it further whenever we add associations.

En el camino, veremos algunas de las API asociadas con Menús y Menuitems.

Se introdujo un nuevo enrutador en Joomla 3.8, pero este paso del tutorial solo considera el enrutador tradicional.

Hay disponibles dos vídeos (en inglés) asociados con este paso, que abarcan Principios generales de enrutamiento de Joomla y descripción del código del tutorial.

Antecedentes sobre URLs SEF

Las URLs SEF son URL que tienen sentido tanto para los humanos como para los motores de búsqueda. Entonces, por ejemplo, si en lugar de una base de datos de registros de hellowworld tuviéramos una base de datos de montañas, entonces una URL SEF sería algo así como:

www.nombredominio.net/montañas/himalayas/everest

en lugar de algo como

www.nombredominio.net/montañas?catid=37&id=52

Puedes activar/desactivar las URLs SEF a través de la Configuración global de Joomla, pestaña Sitio, configuración de SEO.

Anteriormente en "Agregar una solicitud de variable en el tipo del menú" agregamos la capacidad de un administrador para crear un elemento de menú de Helloworld y seleccionar el saludo particular que se mostrará en la página web. Así que un administrador podría agregar un elemento de menú que tenga una URL similar a

localhost/misitiojoomla/hello-world y al hacer clic en él, se mostraba "Hola mundo", porque él/ella había elegido mostrar ese saludo en particular en esa página web.

Sin embargo, en nuestro código anterior, nunca asumimos ningún registro específico de hellowworld; solo tomamos una identificación y encontramos el saludo asociado con esa identificación. Y de hecho podemos introducir una URL.

localhost/misitiojoomla/hello-world?id=2

y terminar mostrando un saludo diferente.

Por lo tanto, la identificación que el administrador eligió en el menú se anula por la identificación que ingresa en el parámetro de URL. Cuando el enrutador Joomla analiza una URL, primero encuentra el elemento de menú correspondiente y cualquier parámetro (por ejemplo, id, id de categoría, vista) asociado con él. Sin embargo, anulará esos parámetros predeterminados con los que encuentre en la cadena de consulta de URL. ¡Este es un punto clave a entender!

Sin embargo, nuestra URL para obtener un mensaje alternativo no es muy parecida a SEF. Lo que preferiríamos tener es un elemento de menú con una URL como

localhost/misitiojoomla/mensajes

a lo que podríamos agregar el saludo particular que queríamos mostrar por ejemplo localhost/misitiojoomla/mensajes/hello-world

localhost/misitiojoomla/mensajes/goodbye-world

Esto es lo que este paso en el tutorial nos permite hacer.

Functionalidad

Agregaremos un campo de alias a nuestro registro de helloworld y una funcionalidad dentro de nuestros formularios de administración para configurar y mantener los datos de alias. Esta funcionalidad será similar a como se usa el alias dentro de otros componentes de Joomla como com_content, excepto que forzaremos que nuestro alias sea único (pero lo revisaremos cuando consideremos la posibilidad de incluir varios idiomas). También actualizaremos nuestro formulario de usuario para permitir que un usuario ingrese un alias.

Forzar que el alias sea único realmente causará un problema con la función Guardar como Copia del administrador, y tendremos que solucionarlo.

Proporcionaremos un equivalente de la Lista de categorías del artículo, que mostrará una lista de los registros de helloworld que tienen una Categoría determinada. Esto nos permitirá ver el formato de URL de los mensajes de hellowworld que no tienen un artículo específico dedicado a ellos, y al hacer clic en la URL podremos acceder a ellos.

En el paso anterior, proporcionamos una lista de mensajes de helloworld a través de ajax cuando alguien hice clic en el botón Buscar aquí asociado con el mapa. En este paso, proporcionaremos un enlace para cada uno de esos mensajes, de modo que un usuario pueda hacer clic en ellos para acceder a los mismos.

Finalmente, desarrollaremos nuestra funcionalidad de enrutamiento personalizada para permitir que se generen las URLs SEF.

Enfoque

Base de datos

Definiremos nuestro campo de alias de la misma manera que los componentes principales de Joomla. Como queremos que nuestro campo de alias se complete (no sea nulo) y sea único, rellenaremos el campo de alias para los registros existentes en nuestro script de actualización de SQL basado en el campo de identificación único.

Gestión de administración

La administración de los datos del alias se integrará naturalmente en nuestro marco de administración existente para administrar los registros de helloworld con cambios mínimos. Solo tenemos que asegurarnos de que el alias sea único y no contenga caracteres que no estén permitidos en las URL, y lo manejaremos al reemplazar el método JTable::check().

Guardar como Copia

Al editar un registro de helloworld, el administrador puede hacer clic en el botón Guardar como copia y esto hará que los valores de campo actuales en el formulario se guarden como un nuevo registro. Sin embargo, si el campo de alias no se ha modificado desde el registro original, Guardar como copia generará un error debido al índice único en el alias. Si bien esto es estrictamente correcto, no es muy fácil de usar, por lo que adoptaremos el mismo enfoque que com_content, que agrega números crecientes al título y alias del artículo hasta que encuentre un número que no cause problemas.

Vista Lista de Categorías

Esto será bastante sencillo, y será más como revisar los pasos anteriores en el tutorial.

Una vez que hayas desarrollado e instalado esta nueva funcionalidad, deberás ingresar al administrador de Joomla y crear un menuitem con alias "mensajes" que apunten a una vista lista de categorías de helloworld. Al crear el artículo, no importa la categoría que selecciones. Este menuitem es donde colgaremos los saludos de helloworld, de modo que si la URL del menuitem es

localhost/missitiojoomla/mensajes

entonces las URLs de nuestros saludos de helloworld serán, por ejemplo,

localhost/misitiojoomla/mensajes/hello-world

localhost/misitiojoomla/mensajes/goodbye-world

Formulario Lado Cliente

Como el alias es obligatorio y debe ser único, debemos actualizar nuestro formulario para permitir que el usuario también ingrese el campo alias. Esto requerirá cambios en nuestra definición de formulario xml, pero nuestro diseño y código de modelo existentes estarán bien.

Cambios en Ajax

En nuestro código del lado servidor de Joomla, específicamente en nuestro modelo de helloworld, con cada mensaje devuelto, agregaremos la URL de ese mensaje, que se obtiene llamando a JRoute::_ y pasando el ID del mensaje de helloworld.

Sin embargo, aquí nos encontramos con un pequeño problema. El problema es que podemos llegar a nuestro mapa y, por lo tanto, a nuestra llamada ajax a través de nuestro menú que muestra un mensaje específico de hellowworld, por ejemplo

localhost/misitiojoomla/hello-world

Si acabamos de agregar nuestro ID de mensaje a la llamada de JRoute, terminaríamos con URL como

localhost/misitiojoomla/hello-world/goodbye-world

que no es realmente lo que queremos.

Esto se debe a que cada vez que crea una URL SEF JRoute::_ toma como predeterminado el artículo en el que se encuentra actualmente (el menú activo), que en este caso es el elemento con el alias hello-world. Si queremos que genere una URL diferente, asociada con un artículo diferente, entonces debemos pasar el Itemid de ese otro artículo. Por lo tanto, en nuestro código tendremos que buscar en los menuitems del sistema para encontrar uno con un alias de "mensajes" y pasar a JRoute::_ el Itemid del menuitem que encontramos. (El Itemid es lo que está en la columna de ID más a la derecha cuando miras los artículos en el administrador de Joomla).

Al pasar de nuestro menuitem "hola mundo" a nuestro menuitem "mensajes" tenemos 2 posibilidades:

  1. En nuestro código de manejo de ajax del servidor, después de que hayamos encontrado los registros de helloworld en nuestro modelo de helloworld, podríamos agregar el "message" menuitem Itemid a la llamada JRoute::_ agregando "&Itemid=xxx" para generar la URL para cada uno de estos registros.
  2. Podríamos cambiar la URL de nuestra llamada ajax, de modo que se ejecutará dentro del contexto del menuitem "mensajes". Entonces, cuando se llama a JRoute::_, ya está en el contexto correcto, y no tendremos que pasarle ningún artículo a su artículo.

En el siguiente código hemos optado por el segundo enfoque.

Enrutador

Desarrollaremos nuestro código de enrutador personalizado. Más sobre esto más adelante.

Configuración de datos

Una vez que hayas instalado esta nueva versión, debes crear el menuitem "mensajes" como se describe anteriormente.

Actualización de la base de datos

Agregar el campo de alias al registro de la base de datos.

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',
	`greeting` VARCHAR(25) NOT NULL,
	`alias`  VARCHAR(40)  NOT NULL DEFAULT '',
	`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`);

INSERT INTO `#__helloworld` (`greeting`,`alias`) VALUES
('Hello World!','hello-world'),
('Goodbye World!','goodbye-world');

Nuevo archivo de actualización de SQL: /admin/sql/updates/mysql/0.0.20.sql

ALTER TABLE`#__helloworld` ADD COLUMN `alias` VARCHAR(40) NOT NULL DEFAULT '' AFTER `greeting`;
UPDATE `#__helloworld` AS h1
SET alias = (SELECT CONCAT('id-', ID) FROM (SELECT * FROM `#__helloworld`) AS h2 WHERE h1.id = h2.id);
CREATE UNIQUE INDEX `aliasindex` ON `#__helloworld` (`alias`);

Cada registro de helloworld actualizado termina con un alias de "id-" + el id del registro. Después de instalar este paso del tutorial, es posible que desees cambiar algunos de estos para tener un alias más significativo.

Gestión de administración

Nuestra definición XML del formulario para editar registros 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"
				/>
	</fieldset>
	<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>

Nuestro modelo de helloworlds, para proporcionar los datos para nuestra visualización de registros de helloworld: admin/models/helloworlds.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');

/**
 * HelloWorldList Model
 *
 * @since  0.0.1
 */
class HelloWorldModelHelloWorlds extends JModelList
{
	/**
	 * Constructor.
	 *
	 * @param   array  $config  An optional associative array of configuration settings.
	 *
	 * @see     JController
	 * @since   1.6
	 */
	public function __construct($config = array())
	{
		if (empty($config['filter_fields']))
		{
			$config['filter_fields'] = array(
				'id',
				'greeting',
				'author',
				'created',
				'published'
			);
		}

		parent::__construct($config);
	}

	/**
	 * Method to build an SQL query to load the list data.
	 *
	 * @return      string  An SQL query
	 */
	protected function getListQuery()
	{
		// Initialize variables.
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true);

		// Create the base select statement.
		$query->select('a.id as id, a.greeting as greeting, a.published as published, a.created as created, 
			  a.image as imageInfo, a.latitude as latitude, a.longitude as longitude, a.alias as alias')
			  ->from($db->quoteName('#__helloworld', 'a'));

		// Join over the categories.
		$query->select($db->quoteName('c.title', 'category_title'))
			->join('LEFT', $db->quoteName('#__categories', 'c') . ' ON c.id = a.catid');
        
		// Join with users table to get the username of the author
		$query->select($db->quoteName('u.username', 'author'))
			->join('LEFT', $db->quoteName('#__users', 'u') . ' ON u.id = a.created_by');
            
		// Filter: like / search
		$search = $this->getState('filter.search');

		if (!empty($search))
		{
			$like = $db->quote('%' . $search . '%');
			$query->where('greeting LIKE ' . $like);
		}

		// Filter by published state
		$published = $this->getState('filter.published');

		if (is_numeric($published))
		{
			$query->where('a.published = ' . (int) $published);
		}
		elseif ($published === '')
		{
			$query->where('(a.published IN (0, 1))');
		}

		// Add the list ordering clause.
		$orderCol	= $this->state->get('list.ordering', 'greeting');
		$orderDirn 	= $this->state->get('list.direction', 'asc');

		$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));

		return $query;
	}
}

Para la visualización de los registros de helloworld, mostraremos el alias de la misma manera que se muestra en com_content, es decir, en letras pequeñas debajo del saludo. admin/views/helloworlds/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');

use Joomla\Registry\Registry;

JHtml::_('formbehavior.chosen', 'select');

$listOrder     = $this->escape($this->state->get('list.ordering'));
$listDirn      = $this->escape($this->state->get('list.direction'));
?>
<form action="index.php?option=com_helloworld&view=helloworlds" method="post" id="adminForm" name="adminForm">
	<div id="j-sidebar-container" class="span2">
		<?php echo JHtmlSidebar::render(); ?>
	</div>
	<div id="j-main-container" class="span10">
        <div class="row-fluid">
            <div class="span6">
                <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_FILTER'); ?>
                <?php
                    echo JLayoutHelper::render(
                        'joomla.searchtools.default',
                        array('view' => $this)
                    );
                ?>
            </div>
        </div>
        <table class="table table-striped table-hover">
            <thead>
            <tr>
                <th width="1%"><?php echo JText::_('COM_HELLOWORLD_NUM'); ?></th>
                <th width="2%">
                    <?php echo JHtml::_('grid.checkall'); ?>
                </th>
                <th width="15%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLDS_NAME', 'greeting', $listDirn, $listOrder); ?>
                </th>
                <th width="15%">
                    <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_POSITION'); ?>
                </th>
                <th width="30%">
                    <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_IMAGE'); ?>
                </th>
                <th width="15%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_AUTHOR', 'author', $listDirn, $listOrder); ?>
                </th>
                <th width="15%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_CREATED_DATE', 'created', $listDirn, $listOrder); ?>
                </th>
                <th width="5%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_PUBLISHED', 'published', $listDirn, $listOrder); ?>
                </th>
                <th width="2%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_ID', 'id', $listDirn, $listOrder); ?>
                </th>
            </tr>
            </thead>
            <tfoot>
                <tr>
                    <td colspan="5">
                        <?php echo $this->pagination->getListFooter(); ?>
                    </td>
                </tr>
            </tfoot>
            <tbody>
                <?php if (!empty($this->items)) : ?>
                    <?php foreach ($this->items as $i => $row) :
                        $link = JRoute::_('index.php?option=com_helloworld&task=helloworld.edit&id=' . $row->id);
                        $row->image = new Registry;
                        $row->image->loadString($row->imageInfo);
                    ?>
                        <tr>
                            <td><?php echo $this->pagination->getRowOffset($i); ?></td>
                            <td>
                                <?php echo JHtml::_('grid.id', $i, $row->id); ?>
                            </td>
                            <td>
                                <a href="<?php echo $link; ?>" title="<?php echo JText::_('COM_HELLOWORLD_EDIT_HELLOWORLD'); ?>">
                                    <?php echo $row->greeting; ?>
                                </a>
                                <span class="small break-word">
                                	<?php echo JText::sprintf('JGLOBAL_LIST_ALIAS', $this->escape($row->alias)); ?>
                                </span>
                                <div class="small">
                                    <?php echo JText::_('JCATEGORY') . ': ' . $this->escape($row->category_title); ?>
                                </div>
                            </td>
                            <td align="center">
                                <?php echo "[" . $row->latitude . ", " . $row->longitude . "]"; ?>
                            </td>
                            <td align="center">
                                <?php
                                    $caption = $row->image->get('caption') ? : '' ;
                                    $src = JURI::root() . ($row->image->get('image') ? : '' );
                                    $html = '<p class="hasTooltip" style="display: inline-block" data-html="true" data-toggle="tooltip" data-placement="right" title="<img width=\'100px\' height=\'100px\' src=\'%s\'>">%s</p>';
                                    echo sprintf($html, $src, $caption);  ?>
                            </td>
                            <td align="center">
                                <?php echo $row->author; ?>
                            </td>
                            <td align="center">
                                <?php echo substr($row->created, 0, 10); ?>
                            </td>
                            <td align="center">
                                <?php echo JHtml::_('jgrid.published', $row->published, $i, 'helloworlds.', true, 'cb'); ?>
                            </td>
                            <td align="center">
                                <?php echo $row->id; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                <?php endif; ?>
            </tbody>
        </table>
        <input type="hidden" name="task" value=""/>
        <input type="hidden" name="boxchecked" value="0"/>
        <?php echo JHtml::_('form.token'); ?>
    </div>
</form>

Cuando escribimos algo en la base de datos utilizando JTable, se llama a la función check() antes de guardar el registro. Por lo tanto, podemos anular esto para asegurarnos de que nuestro campo de alias no esté en blanco y tenga caracteres incorrectos eliminados usando el método JFilterOutput::stringURLSafe (). Esto comprobará tanto los registros ingresados a través del formulario de administración como los ingresados a través del formulario del lado cliente. Nuestro índice único en la tabla helloworld garantizará que los valores de alias no se dupliquen.

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

/**
 * Hello Table class
 *
 * @since  0.0.1
 */
class HelloWorldTableHelloWorld extends JTable
{
	/**
	 * Constructor
	 *
	 * @param   JDatabaseDriver  &$db  A database connector object
	 */
	function __construct(&$db)
	{
		parent::__construct('#__helloworld', 'id', $db);
	}
	/**
	 * 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);
		}

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

Guardar como Copia

Para comprender lo que debemos hacer, intenta hacer clic en Guardar como copia al editar un artículo. Joomla crea un nuevo artículo, agregando (1) al título y "-1" al alias.

Cierra el nuevo artículo, regresa y edita el artículo original, luego haz clic en Guardar como copia nuevamente. Esta vez, Joomla agrega (2) al título y "-2" al alias, omitiendo el registro (1) que ya existe. Para soportar esta adición de números al título y alias, Joomla tiene una función de utilidad llamada generateNewTitle() que continúa aumentando el número hasta que encuentra una combinación donde la combinación de alias más categoría no existe en la base de datos . Esta es la función que utilizaremos en nuestro modelo de helloworld de administrador actualizado y, al mismo tiempo, presentaremos una cierta validación del catid cargado desde el formulario. (Esto no solucionará el problema al 100%, pero lo hará cuando, en el siguiente paso, cambiemos nuestro índice único para que sea alias + categoría, en lugar de solo alias).

We'll do this by overriding save() in our model. Why save() and not save2copy(), seeing as the task in this case is set to save2copy? If you look at the code of JControllerForm you'll see

$this->registerTask('save2copy', 'save');

lo que significa que el envío del formulario de edición con task=save2copy hace que el método save() se ejecute en el controlador, que a su vez ejecuta save() método en el modelo. Todos los diferentes tipos de funciones de guardar ejecutan el método save() en el controlador y el modelo, por lo que si queremos insertar cualquier código específico para save2copy, tenemos que verificar en qué se configuró la variable de tarea original.

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
{
	/**
	 * 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
	 */
	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();
		}
		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 get the script that have 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;
		}

		return parent::save($data);
	}
	/**
	 * 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 );
		}
	}
}

La adición de números (1), (2), etc. a nuestro saludo de helloworld nos causa un problema, porque hemos definido que el saludo solo contiene letras. Así que relajaremos esa condición y permitiremos que contenga cualquier cosa excepto un asterisco. Tenemos que cambiar tanto la validación de javascript del cliente como la validación de php del servidor de lo que especificamos en Agregar verificaciones.

admin/models/rules/greeting.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');
 
/**
 * Form Rule class for the Joomla Framework.
 */
class JFormRuleGreeting extends JFormRule
{
	/**
	 * The regular expression.
	 *
	 * @access	protected
	 * @var		string
	 * @since	2.5
	 */
	protected $regex = '^[^\*]+$';
}

admin/models/forms/helloworld.js

jQuery(function() {
    document.formvalidator.setHandler('greeting',
        function (value) {
            regex=/^[^\*]+$/;
            return regex.test(value);
        });
});

Vista Lista de Categorías

La vista Lista de categorías es similar a una versión del lado cliente de nuestra vista de Helloworlds de administración que muestra la lista de mensajes de helloworld. Así que nuestro código es similar a lo que tenemos allí. Solo se requiere el controlador para mostrar la vista, por lo que no es necesario realizar ningún cambio, pero necesitaremos:

  • una nueva vista - que pondremos en una nueva carpeta de categoría de sitio
  • un nuevo archivo de diseño asociado
  • un nuevo modelo asociado
  • un archivo XML en el directorio de diseño - para la opción de menú, para que el administrador pueda crear un elemento que apunte a nuestra lista de categorías
  • un archivo XML asociado con el formulario de filtro

Cuando estemos creando nuevas carpetas, debemos recordar colocar archivos ficticios index.html en cada una.

En general, no hay nada realmente nuevo aquí que no haya sido cubierto en pasos anteriores del tutorial .

Nuestro nuevo archivo vista lista de categorías: site/views/category/view.html.php

<?php
/*
 * View file for the view which displays a list of helloworld messages in a given category
 */
 
defined('_JEXEC') or die;

class HelloworldViewCategory extends JViewLegacy
{
	public function display($tpl = null)
	{
		$this->items = $this->get('Items');
		$this->pagination = $this->get('Pagination');
		$this->state = $this->get('State');
		$this->filterForm    	= $this->get('FilterForm');
		$this->activeFilters 	= $this->get('ActiveFilters');

		parent::display($tpl);
	}
}

Nuestro nuevo archivo de diseño, en el que mostramos el saludo, el alias y lo que obtenemos como URL de JRoute::_ cuando pasamos los parámetros como se muestra a continuación.

site/views/category/tmpl/default.php

<?php
/**
 * Layout file for displaying helloworld messages belonging to a given category
 */

defined('_JEXEC') or die;

JHtml::_('formbehavior.chosen', 'select');

$listOrder     = $this->escape($this->state->get('list.ordering'));
$listDirn      = $this->escape($this->state->get('list.direction'));
?>
<form action="#" method="post" id="adminForm" name="adminForm">

<div id="j-main-container" class="span10">
    <div class="row-fluid">
        <div class="span10">
            <?php
                echo JLayoutHelper::render(
                    'joomla.searchtools.default',
                    array('view' => $this, 'searchButton' => false)
                );
            ?>
        </div>
    </div>
<table class="table table-striped table-hover">
    <thead>
    <tr>
        <th width="5%"><?php echo JText::_('JGLOBAL_NUM'); ?></th>
        <th width="20%">
            <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL', 'greeting', $listDirn, $listOrder); ?>
        </th>
        <th width="20%">
            <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLD_ALIAS_LABEL', 'alias', $listDirn, $listOrder); ?>
        </th>
        <th width="20%">
            <?php echo JText::_('COM_HELLOWORLD_HELLOWORLD_FIELD_URL_LABEL'); ?>
        </th>
        <th width="5%">
            <?php echo JHtml::_('searchtools.sort', 'JGLOBAL_FIELD_ID_LABEL', 'id', $listDirn, $listOrder); ?>
        </th>
    </tr>
    </thead>
    <tfoot>
        <tr>
            <td colspan="5">
                <?php echo $this->pagination->getListFooter(); ?>
            </td>
        </tr>
    </tfoot>
    <tbody>
        <?php if (!empty($this->items)) : ?>
            <?php foreach ($this->items as $i => $row) : 
                $url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $row->id);
                ?>
                <tr>
                    <td align="center"><?php echo $this->pagination->getRowOffset($i); ?></td>
                    <td align="center"><?php echo $row->greeting; ?></td>
                    <td align="center"><?php echo $row->alias; ?></td>
                    <td align="center"><a href="<?php echo $url; ?>"><?php echo $url; ?></a></td>
                    <td align="center"><?php echo $row->id; ?></td>
                </tr>
            <?php endforeach; ?>
        <?php endif; ?>
    </tbody>
</table>
</div>
</form>

Nuestro nuevo modelo

site/models/category.php

<?php
/**
 * Model for displaying the helloworld messages in a given category
 */

defined('_JEXEC') or die;

class HelloworldModelCategory extends JModelList
{
	public function __construct($config = array())
	{
		if (empty($config['filter_fields']))
		{
			$config['filter_fields'] = array(
				'id',
				'greeting',
				'alias',
			);
		}

		parent::__construct($config);
	}
    
	protected function populateState($ordering = null, $direction = null)
	{
		parent::populateState($ordering, $direction);
        
		$app = JFactory::getApplication('site');
		$catid = $app->input->getInt('id');

		$this->setState('category.id', $catid);
	}
    
	protected function getListQuery()
	{
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true);

		$catid = $this->getState('category.id'); 
		$query->select('id, greeting, alias')
			->from($db->quoteName('#__helloworld'))
			->where('catid = ' . $catid);

		$orderCol	= $this->state->get('list.ordering', 'greeting');
		$orderDirn 	= $this->state->get('list.direction', 'asc');

		$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));

		return $query;	
	}
}

Nuestro nuevo archivo XML para la lista de categorías menuitem:

site/views/category/tmpl/default.xml

<?xml version="1.0" encoding="utf-8"?>
<metadata>
	<layout title="COM_HELLOWORLD_CATEGORY_VIEW_TITLE">
		<message>
			<![CDATA[COM_HELLOWORLD_CATEGORY_VIEW_DESC]]>
		</message>
	</layout>

	<!-- Add fields to the request variables for the layout. -->
	<fields name="request">
		<fieldset name="request"
			addfieldpath="/administrator/components/com_helloworld/models/fields"
		>
			<field 
				name="id"
				type="category"
				label="JGLOBAL_CHOOSE_CATEGORY_LABEL"
				description="JGLOBAL_CHOOSE_CATEGORY_DESC"
				extension="com_helloworld"
			/>
		</fieldset>
	</fields>
</metadata>

Nuestra definición de los campos de filtro que queremos tener en esta vista:

site/models/forms/filter_category.xml

<?xml version="1.0" encoding="utf-8"?>
<form>
	<fields name="list">
        <field
			name="fullordering"
			type="list"
			onchange="this.form.submit();"
			default="greeting ASC"
			>
			<option value="">COM_HELLOWORLD_SORT_BY</option>
			<option value="greeting ASC">COM_HELLOWORLD_GREETING_ASC</option>
			<option value="greeting DESC">COM_HELLOWORLD_GREETING_DESC</option>
			<option value="id ASC">COM_HELLOWORLD_ID_ASC</option>
			<option value="id DESC">COM_HELLOWORLD_ID_DESC</option>
			<option value="alias ASC">COM_HELLOWORLD_ALIAS_ASC</option>
			<option value="alias DESC">COM_HELLOWORLD_ALIAS_DESC</option>
		</field>
		<field
			name="limit"
			type="limitbox"
			class="input-mini"
			default="10"
			onchange="this.form.submit();"
		/>
	</fields>
</form>

Formulario Lado Cliente

Nuestro archivo de definición de formulario XML actualizado: site/models/forms/add-form.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"
				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="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>
    </fieldset>
</form>

Cambios en Ajax

En nuestro archivo javascript ahora esperaremos que la URL de Ajax sea proporcionada desde el servidor: media/js/openstreetmap.js

var map;
var ajaxurl;

jQuery(document).ready(function() {
    
    // get the data passed from Joomla PHP
    // params is a Javascript object with properties for the map display: 
    // centre latitude, centre longitude and zoom, and the helloworld greeting
    const params = Joomla.getOptions('params');
    ajaxurl = params.ajaxurl; 
    
    // We'll use OpenLayers to draw the map (http://openlayers.org/)
    
    // Openlayers uses an x,y coordinate system for positions
    // We need to convert our lat/long into an x,y pair which is relative
    // to the map projection we're using, viz Spherical Mercator WGS 84
    const x = parseFloat(params.longitude);
    const y = parseFloat(params.latitude);
    const mapCentre = ol.proj.fromLonLat([x, y]); // Spherical Mercator is assumed by default
    
    // To draw a map, Openlayers needs:
    // 1. a target HTML element into which the map is put
    // 2. a map layer, which can be eg a Vector layer with details of polygons for
    //    country boundaries, lines for roads, etc, or a Tile layer, with individual
    //    .png files for each map tile (256 by 256 pixel square).
    // 3. a view, specifying the 2D projection of the map (default Spherical Mercator),
    //    map centre coordinates and zoom level
    map = new ol.Map({
        target: 'map',
        layers: [
            new ol.layer.Tile({  // we'll get the tiles from the OSM server
                source: new ol.source.OSM()
            })
        ],
        view: new ol.View({  // default is Spherical Mercator projection
            center: mapCentre,
            zoom: params.zoom
        })
    });
    
    // Now we add a marker for our Helloworld position
    // To do that, we specify it as a Point Feature, and we add styling 
    // to define how that Feature is presented on the map
    var helloworldPoint = new ol.Feature({geometry: new ol.geom.Point(mapCentre)});
    // we'll define the style as a red 5 point star with blue edging
    const redFill = new ol.style.Fill({
        color: 'red'
    });
    const blueStroke = new ol.style.Stroke({
        color: 'blue',
        width: 3
    });
    const star = new ol.style.RegularShape({
        fill: redFill,
        stroke: blueStroke,
        points: 5,
        radius1: 20,   // outer radius of star
        radius2: 10,   // inner radius of star
    })
    helloworldPoint.setStyle(new ol.style.Style({
        image: star
    }));
    // now we add the feature to the map via a Vector source and Vector layer
    const vectorSource = new ol.source.Vector({});
    vectorSource.addFeature(helloworldPoint);
    const vector = new ol.layer.Vector({
        source: vectorSource
    });
    map.addLayer(vector);
    
    // If a user clicks on the star, then we'll show the helloworld greeting
    // The greeting will go into another HTML element, with id="greeting-container"
    // and this will be shown as an Overlay on the map
    var overlay = new ol.Overlay({
        element: document.getElementById('greeting-container'),
    });
    map.addOverlay(overlay);
        
    // Finally we add the onclick listener to display the greeting when the star is clicked
    // The way this works is that the onclick listener is attached to the map,
    // and then it works out which Feature or Features have been hit
    map.on('click', function(e) {
        let markup = '';
        let position;
        map.forEachFeatureAtPixel(e.pixel, function(feature) {  // for each Feature hit
            markup = params.greeting;
            position = feature.getGeometry().getCoordinates();
        }, {hitTolerance: 5});  // tolerance of 5 pixels
        if (markup) {
            document.getElementById('greeting-container').innerHTML = markup;
            overlay.setPosition(position);
        } else {
            overlay.setPosition();  // this hides it, if we click elsewhere
        }
    });    
});

function getMapBounds(){
    var mercatorMapbounds = map.getView().calculateExtent(map.getSize());
    var latlngMapbounds = ol.proj.transformExtent(mercatorMapbounds,'EPSG:3857','EPSG:4326');
    return { minlat: latlngMapbounds[1],
             maxlat: latlngMapbounds[3],
             minlng: latlngMapbounds[0],
             maxlng: latlngMapbounds[2] }
}
    
function searchHere() {
    var mapBounds = getMapBounds();
    var token = jQuery("#token").attr("name");
    jQuery.ajax({
        url: ajaxurl,
        data: { [token]: "1", task: "mapsearch", view: "helloworld", format: "json", mapBounds: mapBounds },
        success: function(result, status, xhr) { displaySearchResults(result); },
        error: function() { console.log('ajax call failed'); },
    });
}

function displaySearchResults(result) {
    if (result.success) {
        var html = "";
        for (var i=0; i<result.data.length; i++) {
            html += '<p><a href="' + result.data[i].url + '">' +
                result.data[i].greeting + '</a>' +
                " @ " + result.data[i].latitude + 
                ", " + result.data[i].longitude + "</p>";
        }
        jQuery("#searchresults").html(html);
    } else {
        var msg = result.message;
        if ((result.messages) && (result.messages.error)) {
            for (var j=0; j<result.messages.error.length; j++) {
                msg += "<br/>" + result.messages.error[j];
            }
        }
        jQuery("#searchresults").html(msg);
    }
}

Tendremos que proporcionar esta URL ajax desde el modelo: site/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');

JLoader::register('HelloworldHelperRoute', JPATH_ROOT . '/components/com_helloworld/helpers/route.php');

/**
 * HelloWorld Model
 *
 * @since  0.0.1
 */
class HelloWorldModelHelloWorld extends JModelItem
{
	/**
	 * @var object item
	 */
	protected $item;

	/**
	 * Method to auto-populate the model state.
	 *
	 * This method should only be called once per instantiation and is designed
	 * to be called on the first call to the getState() method unless the model
	 * configuration flag to ignore the request is set.
	 *
	 * Note. Calling getState in this method will result in recursion.
	 *
	 * @return	void
	 * @since	2.5
	 */
	protected function populateState()
	{
		// Get the message id
		$jinput = JFactory::getApplication()->input;
		$id     = $jinput->get('id', 1, 'INT');
		$this->setState('message.id', $id);

		// Load the parameters.
		$this->setState('params', JFactory::getApplication()->getParams());
		parent::populateState();
	}

	/**
	 * 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);
	}

	/**
	 * Get the message
	 * @return object The message to be displayed to the user
	 */
	public function getItem()
	{
		if (!isset($this->item)) 
		{
			$id    = $this->getState('message.id');
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true);
			$query->select('h.greeting, h.params, h.image as image, c.title as category, h.latitude as latitude, h.longitude as longitude')
				  ->from('#__helloworld as h')
				  ->leftJoin('#__categories as c ON h.catid=c.id')
				  ->where('h.id=' . (int)$id);
			$db->setQuery((string)$query);
		
			if ($this->item = $db->loadObject()) 
			{
				// Load the JSON string
				$params = new JRegistry;
				$params->loadString($this->item->params, 'JSON');
				$this->item->params = $params;

				// Merge global params with item params
				$params = clone $this->getState('params');
				$params->merge($this->item->params);
				$this->item->params = $params;

				// Convert the JSON-encoded image info into an array
				$image = new JRegistry;
				$image->loadString($this->item->image, 'JSON');
				$this->item->imageDetails = $image;
			}
		}
		return $this->item;
	}

	public function getMapParams()
	{
		if ($this->item) 
		{
			$url = HelloworldHelperRoute::getAjaxURL();
			$this->mapParams = array(
				'latitude' => $this->item->latitude,
				'longitude' => $this->item->longitude,
				'zoom' => 10,
				'greeting' => $this->item->greeting,
				'ajaxurl' => $url
			);
			return $this->mapParams; 
		}
		else
		{
			throw new Exception('No helloworld details available for map', 500);
		}
	}

	public function getMapSearchResults($mapbounds)
	{
		try 
		{
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true);
			$query->select('h.id, h.greeting, h.latitude, h.longitude')
			   ->from('#__helloworld as h')
			   ->where('h.latitude > ' . $mapbounds['minlat'] . 
				' AND h.latitude < ' . $mapbounds['maxlat'] .
				' AND h.longitude > ' . $mapbounds['minlng'] .
				' AND h.longitude < ' . $mapbounds['maxlng']);
			$db->setQuery($query);
			$results = $db->loadObjectList(); 
		}
		catch (Exception $e)
		{
			$msg = $e->getMessage();
			JFactory::getApplication()->enqueueMessage($msg, 'error'); 
			$results = null;
		}

		for ($i = 0; $i < count($results); $i++) 
		{
			$results[$i]->url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $results[$i]->id);
		}

		return $results; 
	}
}

Pero ponemos la compleja funcionalidad para encontrar la URL de Ajax en un archivo de ayuda separado, en una nueva carpeta de helpers (en la que debemos colocar el archivo ficticio habitual index.html). En el siguiente código:

$sitemenu= $app->getMenu(); devuelve la estructura del menú para todo el sitio.

$thismenuitem = $sitemenu->getActive(); devuelve la estructura de menuitem para el menuitem en el que estamos actualmente, es decir, el menuitem que seleccionó el enrutador de Joomla que nos llevó a nuestro código aquí.

$mainmenuitems = $sitemenu->getItems('menutype', 'mainmenu'); devuelve el subconjunto de menuitems adjuntos al Menú principal, es decir, donde menutype property = "mainmenu".

Si ya estamos en el menuitem de "mensajes", entonces podemos pasar una URL nula a nuestro Javascript. Cuando en nuestro Javascript pasamos una URL nula a la llamada jQuery Ajax, jQuery simplemente enviará la solicitud HTTP a la URL actual.

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()
	{
		$app  = JFactory::getApplication();
		$sitemenu= $app->getMenu();
		$thismenuitem = $sitemenu->getActive();

		// if we haven't got an active menuitem, or we're currently on the messages menuitem then just stay there
		if (!$thismenuitem || $thismenuitem->alias == "messages")
		{
			return null;
		}

		$mainmenuitems = $sitemenu->getItems('menutype', 'mainmenu');
		foreach ($mainmenuitems as $menuitem)
		{
			if ($menuitem->alias == "messages")
			{
				$itemid = $menuitem->id; 
				$url = JRoute::_("index.php?Itemid=$itemid&view=helloworld&format=json");
				return $url;
			}
		}
		return null; 
	}
}

Enrutador

Finalmente, ahora podemos considerar nuestro código de enrutador. Primero, entendamos un poco sobre cómo funciona el enrutamiento de Joomla, observando cómo analizaría una URL SEF del formulario: http://www.nombredominio.net/es/menu1/submenu-a/segmento1/segmento2

El objetivo del enrutador es dirigir esta solicitud al componente correcto y haber definido:

  • el idioma
  • Las opciones de formato, etc. para aplicar a esta página web.
  • los parámetros como id, formato (por ejemplo, html/json), vista, etc.

En primer lugar, el enrutador eliminará la parte del nombre de dominio, ya que se refiere a todo el sitio y no figura en el enrutamiento. Así que nos queda un camino:

es/menu1/submenu-a/segmento1/segmento2

Ahora el enrutador aplica varias reglas para tratar de analizar los segmentos de la URL, analizando desde el lado izquierdo. A medida que los segmentos coinciden, el enrutador construye los parámetros de configuración y elimina aquellos segmentos coincidentes de la URL, antes de aplicar la siguiente regla.

Si el sitio es multilingüe, se ejecutará la regla para analizar el idioma. En este caso, encontrará 'es' y establecerá el idioma en es-ES, y lo eliminará de la ruta, dejando:

menu1/submenu-a/segmento1/segmento2

Ahora el enrutador ejecutará una regla para tratar de encontrar un elemento del menú coincidente. Obtendrá todos los artículos en el sitio y comparará la ruta de cada elemento del menú con los segmentos más a la izquierda de la ruta. En este ejemplo, puede encontrar 2 que coincidan, una que coincida con "menu1" y otra que coincida con "menu1 / submenu-a", en cuyo caso tomará la que tenga la coincidencia más larga.

Ahora que ha encontrado el elemento del menú, conoce el componente, los parámetros del componente y también el formato y otras opciones asociadas con este elemento del menú. Ahora nos quedamos con

segmento1/segmento2

Es en este punto que el enrutador gira hacia el componente para analizar los segmentos restantes.

Previamente, obtuvo una lista de todos los componentes del sitio, y verificó cada directorio de componentes buscando un archivo llamado router.php con funciones de clase que implementara la interfaz del enrutador, a saber, funciones build(), parse() y preprocess(). Si encuentra uno, configura esa clase como el enrutador de componente, de lo contrario, simplemente establece un enrutador de componente heredado predeterminado.

Para analizar los segmentos restantes, llama a la función parse() del enrutador del componente y pasa un array de los segmentos restantes. Lo que quiere a cambio es una lista de los parámetros de consulta (p. Ej., Id=xxx, view=yyy), y los utilizará para sobrescribir cualquier parámetro equivalente que se haya establecido en el menuitem.

Finalmente, establece los parámetros de solicitud HTTP a los que ha encontrado, de modo que cuando se ejecuta el componente puede usar $input->get() para acceder a ellos.

Mirando el lado opuesto, cuando llamamos a JRoute::_ le pasamos una cadena de consulta como "id=52&view=helloworld". Si las URL de SEF están habilitadas en el sitio, su trabajo es generar los segmentos de la URL de SEF, y lo hace de la siguiente manera:

  • verifica si el idioma es multilingüe, y si es así agrega el segmento de idioma apropiado
  • agregando la ruta del elemento que se pasó en la parte "Itemid=xxx" de la cadena de consulta, o si no se pasa un Itemid, tomará el elemento del menú actual ("activo") que está en
  • Si todavía hay parámetros de consulta que no coinciden con los del elemento del menú, llamará a la función build() del enrutador del componente, pasando una matriz de esos parámetros de consulta y esperando que regresen los segmentos de la URL que debe agregar.

La tercera función preprocess() en nuestro enrutador personalizado es una que se llama desde el principio para permitir que el código configure cualquier dato inicial, pero no la usaremos.

Nuestras funciones build() y parse() son casi tan simples como pueden ser: nuestro build() toma un id y devuelve el alias asociado, y nuestro parse() toma un alias y devuelve el id asociado.

site/router.php

<?php

defined('_JEXEC') or die;

class HelloworldRouter implements JComponentRouterInterface
{

	public function build(&$query)
	{
		$segments = array();
		if (isset($query['id']))
		{
			$db = JFactory::getDbo();
			$qry = $db->getQuery(true);
			$qry->select('alias');
			$qry->from('#__helloworld');
			$qry->where('id = ' . $db->quote($query['id']));
			$db->setQuery($qry);
			$alias = $db->loadResult();
			$segments[] = $alias;
			unset($query['id']);
		}
		unset($query['view']);
		return $segments;
	}
  
	public function parse(&$segments)
	{
		$vars = array();
    
		$db = JFactory::getDbo();
		$qry = $db->getQuery(true);
		$qry->select('id');
		$qry->from('#__helloworld');
		$qry->where('alias = ' . $db->quote($segments[0]));
		$db->setQuery($qry);
		$id = $db->loadResult();
        
		if(!empty($id))
		{
			$vars['id'] = $id;
			$vars['view'] = 'helloworld';
		}

		return $vars;
	}
  
	public function preprocess($query)
	{
		return $query;
	}
}

Actualización cadenas de idioma

admin/language/en-GB/en-GB.com_helloworld.sys.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="Hello World!"
COM_HELLOWORLD_DESCRIPTION="This is the Hello World description"
COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_TITLE="Hello World"
COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_DESC="This view displays a selected message"
COM_HELLOWORLD_ADD_VIEW_TITLE="Hello World form"
COM_HELLOWORLD_ADD_VIEW_DESC="This displays a form to allow the user to enter a new message"
COM_HELLOWORLD_CATEGORY_VIEW_TITLE="Category List"
COM_HELLOWORLD_CATEGORY_VIEW_DESC="This displays the messages in the selected category"
COM_HELLOWORLD_INSTALL_TEXT="HelloWorld Install script"
COM_HELLOWORLD_MENU="Hello World!"
COM_HELLOWORLD_POSTFLIGHT_DISCOVER_INSTALL_TEXT="HelloWorld postflight discover install script"
COM_HELLOWORLD_POSTFLIGHT_INSTALL_TEXT="HelloWorld postflight install script"
COM_HELLOWORLD_POSTFLIGHT_UNINSTALL_TEXT="HelloWorld postflight uninstall script"
COM_HELLOWORLD_POSTFLIGHT_UPDATE_TEXT="HelloWorld postflight update script"
COM_HELLOWORLD_PREFLIGHT_DISCOVER_INSTALL_TEXT="HelloWorld preflight discover install script"
COM_HELLOWORLD_PREFLIGHT_INSTALL_TEXT="HelloWorld preflight install script"
COM_HELLOWORLD_PREFLIGHT_UNINSTALL_TEXT="HelloWorld preflight uninstall script"
COM_HELLOWORLD_PREFLIGHT_UPDATE_TEXT="HelloWorld preflight update script"
COM_HELLOWORLD_UNINSTALL_TEXT="HelloWorld Uninstall script"
COM_HELLOWORLD_UPDATE_TEXT="HelloWorld Update script. HelloWorld now updated to version %s."


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

; add new message form
COM_HELLOWORLD_LEGEND_DETAILS="New Helloworld Message Details"
COM_HELLOWORLD_HELLOWORLD_CREATING="Add message"
COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE="Sorry, you have an error"
COM_HELLOWORLD_HELLOWORLD_DETAILS="Message details"
COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL="Greeting"
COM_HELLOWORLD_HELLOWORLD_GREETING_DESC="Please specify the greeting to add"
COM_HELLOWORLD_HELLOWORLD_GREETING_HINT="Any characters allowed"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL="Category"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC="Please select the associated category"
COM_HELLOWORLD_HELLOWORLD_MESSAGE_LABEL="Reason"
COM_HELLOWORLD_HELLOWORLD_MESSAGE_DESC="Please say why you're adding this greeting"
COM_HELLOWORLD_HELLOWORLD_MESSAGE_HINT="No HTML tags!"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL="Spam protection"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC="Prove you're a real person!"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL="Display category or not?"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC="Select if you want the category displayed too"
COM_HELLOWORLD_HELLOWORLD_IMAGE_LABEL="Image information"
COM_HELLOWORLD_HELLOWORLD_PICTURE_LABEL="Image file to upload"
COM_HELLOWORLD_HELLOWORLD_PICTURE_DESC="Select the file with the image to upload"
COM_HELLOWORLD_HELLOWORLD_CAPTION_LABEL="Caption"
COM_HELLOWORLD_HELLOWORLD_CAPTION_DESC="Text to use as a caption for the image"
COM_HELLOWORLD_HELLOWORLD_ALTTEXT_LABEL="Alt text"
COM_HELLOWORLD_HELLOWORLD_ALTTEXT_DESC="Text to display if image cannot be shown"
; save and cancel confirmation messages
COM_HELLOWORLD_ADD_SUCCESSFUL="New greeting successfully saved"
COM_HELLOWORLD_ADD_CANCELLED="New greeting cancelled ok"
; file upload error conditions
COM_HELLOWORLD_ERROR_FILEUPLOAD="PHP Error %s encountered when uploading file"
COM_HELLOWORLD_ERROR_FILETOOLARGE="Upload file exceeds max size configured in Joomla"
COM_HELLOWORLD_ERROR_BADFILENAME="Upload file has an invalid filename"
COM_HELLOWORLD_ERROR_FILE_EXISTS="Upload file already exists"
COM_HELLOWORLD_ERROR_UNABLE_TO_UPLOAD_FILE="Error creating uploaded file"
; helloworld greeting page
COM_HELLOWORLD_SEARCH_HERE_BUTTON="Search here"
; Ajax handling errors
COM_HELLOWORLD_ERROR_NO_RECORDS="Didn't get any records"
COM_HELLOWORLD_ERROR_NO_MAP_BOUNDS="Error: no map bounds"
; category view, search and ordering fields and headings
COM_HELLOWORLD_SORT_BY="Sort by ..."
COM_HELLOWORLD_GREETING_ASC="Greeting asc"
COM_HELLOWORLD_GREETING_DESC="Greeting desc"
COM_HELLOWORLD_ID_ASC="id asc"
COM_HELLOWORLD_ID_DESC="id desc"
COM_HELLOWORLD_ALIAS_ASC="alias asc"
COM_HELLOWORLD_ALIAS_DESC="alias desc"
COM_HELLOWORLD_HELLOWORLD_ALIAS_LABEL="Alias"
COM_HELLOWORLD_HELLOWORLD_FIELD_URL_LABEL="URL"

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.20</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>
        </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>
		</languages>
	</administration>

</extension>


Colaboradores