J3.x

Desarrollo de un Componente MVC/Agregar un proceso por lotes

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 a batch process and the translation is 98% complete.

Outdated translations are marked like this.
Other languages:
English • ‎español • ‎français • ‎italiano • ‎中文(台灣)‎
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.2. Te invitamos a leer las partes anteriores del tutorial antes de leer esto.

En este paso, agregamos un proceso por lotes helloworld al backend. Un video adjunto está disponible en Adding a batch process.

Introducción

Joomla proveé un número de funciones para permitir a los administradores actualizar artículos y otros tipo de elementos en lote. Éstas incluyen:

  • mover o copiar registros dentro de registros con una categoría seleccionada
  • configurar el idioma y/ el acceso a los registros seleccionados
  • agregar una etiqueta a los registros seleccionados

En este paso, agregamos estas funciones a nuesto componente helloworld y tambien proporcionamos un proceso por lotes para permitir a los administradores setear la latitud y longitud.

Enfoque

Para implementar el proceso por lotes tenemos que considerar los siguientes aspectos.

Botón de Lote y Modal

  1. Mostramos un botón de lote en la barra de herramientas en nuestra vista helloworlds. El administrador puede hacer clic en las casillas de verificación de los registros de helloworld para procesar por lotes y luego hacer clic en Lote para continuar.
  2. Cuando se hace clic en el botón Lote queremos que aparezca una modal con las opciones disponibles para que agreguemos el html para la modal de Bootstrap. Lo haremos en el archivo layout helloworlds y utilizaremos un par de nuevos layouts subsidiarios para el cuerpo de la modal y el pie de página.

El cuerpo de la modal contendrá los elementos html para las diversas opciones - ej: establecer idioma, acceso, lat/long, etc. Los elementos para establecer el idioma, acceso, categoría y etiqueta son todos layouts estandares de la librería de Joomla dentro del arbol de directorio /layouts. Para establecer la posición lat/long escribiremos nuestro propio archivo layout en el cual pondremos en un nuevo directorio admin/layouts (y tenemos que recordar de incluir este nuevo directorio en el archivo de manifiesto principal helloworld.xml).

El pie de página de la modal contendrá los botones Cancelar (para cancelar la operación) y Procesar (para empezar el proceso por lotes).

Handling the HTTP POST in the Controller

Joomla Batch Processing

Cuando se presiona el botón Procesar el código javascript enviará el formulario e iniciará una solicitud HTTP POST al servidor con los parámetros que incluye dicho formulario.

  • los ids de los registros que se han seleccionados para el procesamiento por lotes
  • las opciones de lote que se han elegido
  • la variable task establecida en task=helloworld.batch

Joomla enrutará esto a un método batch() en admin/controllers/helloworld.php.

En nuestro método batch() del controlador helloworld hacemos lo siguiente:

  • configurar el modelo para que sea el modelo helloworld
  • configurar la redirección para presentar la vista de helloworlds nuevamente, una vez que se complete el procesamiento por lotes
  • llamar al método padre batch, pasando el modelo como parámetro

El método padre batch (in JControllerForm) obtendrá los párametros POST, realizará alguna verificación de ellos y llamará al método batch() del modelo pasado, pasando los parámetros POST.

Ejecutando los procesos por lotes en el Modelo

La forma en que funciona la funcionalidad del modelo por lotes de Joomla es que la clase JModelAdmin tiene una propiedad $batch_commands que es un array asociativo que mapea el índice de los parámetros del lote al método que realizará esa operación por lotes.

	protected $batch_commands = array(
		'assetgroup_id' => 'batchAccess',
		'language_id' => 'batchLanguage',
		'tag' => 'batchTag',
	);

Entonces si en la modal de lotes un administrador establece el selector de idioma a uno de los idiomas disponibles luego batch[language_id] es establecida dentro de los parámetros HTTP POST. El código del modelo busca "language_id" en esta propiedad $batch_commands, encuentra "batchLanguage" y luego procede a llamar al método batchLanguage().

En nuestro caso tendremos un método llamado batchPosition() que establecerá la latitud/longitud de los registros seleccionados, por lo que debemos aseguranos de que:

  • los elementos de entrada html se definen para que los parámetros de batch[position] se envíen en HTTP POST
  • agregamos un registro adicional al array $batch_commands, llamado 'position' => 'batchPosition'.

El primer aspecto está hecho donde definimos el html de los elementos de entrada en el cuerpo de nuestra modal.

El segundo aspecto está hecho en un método batch() que escribimos en nuestro modelo helloworld. Esto agrega en el elemento extra del array y luego llama a parent::batch() para inicializar todas las operaciones por lotes.

Finalmente debemos escribir nuestro método batchPosition() para actualizar el conjunto de registros con los valores de latitud y longitud pasados.

Botón de Lote

Agregamos el botón de lote en nuestra vista de helloworlds. Debido a que este es un botón personalizado, necesitamos obtener un identificador de la instancia de la barra de herramientas y luego usar appendButton(), ya que no podemos usar una función ToolbarHelper para esto.

admin/views/helloworlds/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');

/**
 * HelloWorlds View
 *
 * @since  0.0.1
 */
class HelloWorldViewHelloWorlds 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)
        {
                // Get application
                $app = JFactory::getApplication();

                // Get data from the model
                $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');
        
                // What Access Permissions does this user have? What can (s)he do?
                $this->canDo = JHelperContent::getActions('com_helloworld');

                // Check for errors.
                if (count($errors = $this->get('Errors')))
                {
                        JError::raiseError(500, implode('<br />', $errors));

                        return false;
                }
        
                // Set the sidebar submenu and toolbar, but not on the modal window
                if ($this->getLayout() !== 'modal')
                {
                        HelloWorldHelper::addSubmenu('helloworlds');
                        $this->addToolBar();
                }
                else
                {
                        // If it's being displayed to select a record as an association, then forcedLanguage is set
                        if ($forcedLanguage = $app->input->get('forcedLanguage', '', 'CMD'))
                        {
                                // Transform the language selector filter into an hidden field, so it can't be set
                                $languageXml = new SimpleXMLElement('<field name="language" type="hidden" default="' . $forcedLanguage . '" />');
                                $this->filterForm->setField($languageXml, 'filter', true);

                                // Also, unset the active language filter so the search tools is not open by default with this filter.
                                unset($this->activeFilters['language']);
                        }
                }

                // Prepare a mapping from parent id to the ids of its children
                $this->ordering = array();
                foreach ($this->items as $item)
                {
                        $this->ordering[$item->parent_id][] = $item->id;
                }

                // Display the template
                parent::display($tpl);

                // Set the document
                $this->setDocument();
        }

        /**
         * Add the page title and toolbar.
         *
         * @return  void
         *
         * @since   1.6
         */
        protected function addToolBar()
        {
                $title = JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLDS');

                $bar = JToolbar::getInstance('toolbar');

                if ($this->pagination->total)
                {
                        $title .= "<span style='font-size: 0.5em; vertical-align: middle;'>(" . $this->pagination->total . ")</span>";
                }

                JToolBarHelper::title($title, 'helloworld');
                if ($this->canDo->get('core.create')) 
                {
                        JToolBarHelper::addNew('helloworld.add', 'JTOOLBAR_NEW');
                }
                if ($this->canDo->get('core.edit')) 
                {
                        JToolBarHelper::editList('helloworld.edit', 'JTOOLBAR_EDIT');
                }
                if ($this->canDo->get('core.delete')) 
                {
                        JToolBarHelper::deleteList('', 'helloworlds.delete', 'JTOOLBAR_DELETE');
                }
                if ($this->canDo->get('core.edit') || JFactory::getUser()->authorise('core.manage', 'com_checkin'))
                {
                        JToolBarHelper::checkin('helloworlds.checkin');
                }
                // Add a batch button
                if ($this->canDo->get('core.create') && $this->canDo->get('core.edit')
                        && $this->canDo->get('core.edit.state'))
                {
                        // we use a standard Joomla layout to get the html for the batch button
                        $layout = new JLayoutFile('joomla.toolbar.batch');
                        $batchButtonHtml = $layout->render(array('title' => JText::_('JTOOLBAR_BATCH')));
                        $bar->appendButton('Custom', $batchButtonHtml, 'batch');
                }
                if ($this->canDo->get('core.admin')) 
                {
                        JToolBarHelper::divider();
                        JToolBarHelper::preferences('com_helloworld');
                }
        }
        /**
         * Method to set up the document properties
         *
         * @return void
         */
        protected function setDocument() 
        {
                $document = JFactory::getDocument();
                $document->setTitle(JText::_('COM_HELLOWORLD_ADMINISTRATION'));
        }
}

Modal de Lotes

Agregamos el html para la modal de lotes en el archivo layout de helloworlds.

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'));
$user = JFactory::getUser();
$userId = $user->get('id');
$saveOrder = ($listOrder == 'lft' && strtolower($listDirn) == 'asc');
if ($saveOrder)
{
        $saveOrderingUrl = 'index.php?option=com_helloworld&task=helloworlds.saveOrderAjax&tmpl=component';
        // pass true as parameter 7 to indicate that we have a nested set
        JHtml::_('sortablelist.sortable', 'helloworldList', 'adminForm', strtolower($listDirn), $saveOrderingUrl, false, true);
}
$assoc = JLanguageAssociations::isEnabled();
$authorFieldwidth = $assoc ? "10%" : "25%";
JLoader::register('JHtmlHelloworlds', JPATH_ADMINISTRATOR . '/components/com_helloworld/helpers/html/helloworlds.php');
?>
<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="span12">
                <?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" id="helloworldList">
            <thead>
            <tr>
                <th width="1%">
                    <?php echo JHtml::_('searchtools.sort', '', 'lft', $listDirn, $listOrder, null, 'asc', 'JGRID_HEADING_ORDERING', 'icon-menu-2'); ?>
                </th>
                <th width="1%"><?php echo JText::_('COM_HELLOWORLD_NUM'); ?></th>
                <th width="1%">
                    <?php echo JHtml::_('grid.checkall'); ?>
                </th>
                <th width="10%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLDS_NAME', 'greeting', $listDirn, $listOrder); ?>
                </th>
                <th width="10%">
                    <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_POSITION'); ?>
                </th>
                <th width="10%">
                    <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_IMAGE'); ?>
                </th>
                <th width="20%">
                    <?php echo JHtml::_('searchtools.sort',  'JGRID_HEADING_ACCESS', 'access', $listDirn, $listOrder); ?>
                </th>
                <?php if ($assoc) : ?>
                    <th width="10%">
                        <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLDS_ASSOCIATIONS', 'association', $listDirn, $listOrder); ?>
                    </th>
                <?php endif; ?>
                <th width="<?php echo $authorFieldwidth; ?>">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_AUTHOR', 'author', $listDirn, $listOrder); ?>
                </th>
                <th width="10%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_LANGUAGE', 'language', $listDirn, $listOrder); ?>
                </th>
                <th width="10%">
                    <?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);
                        // create a list of the parents up the hierarchy to the root 
                        if ($row->level > 1)
                        {
                            $parentsStr = '';
                            $_currentParentId = $row->parent_id;
                            $parentsStr = ' ' . $_currentParentId;
                            for ($j = 0; $j < $row->level; $j++)
                            {
                                foreach ($this->ordering as $k => $v)
                                {
                                    $v = implode('-', $v);
                                    $v = '-' . $v . '-';
                                    if (strpos($v, '-' . $_currentParentId . '-') !== false)
                                    {
                                        $parentsStr .= ' ' . $k;
                                        $_currentParentId = $k;
                                        break;
                                    }
                                }
                            }
                        }
                        else
                        {
                            $parentsStr = '';
                        }
                    ?>
                        <tr class="row<?php echo $i % 2; ?>" sortable-group-id="<?php echo $row->parent_id; ?>" item-id="<?php echo $row->id; ?>" parents="<?php echo $parentsStr; ?>" level="<?php echo $row->level; ?>">
                            <td><?php
                                $iconClass = '';
                                $canReorder  = $user->authorise('core.edit.state', 'com_helloworld.helloworld.' . $row->id);
                                if (!$canReorder)
                                {
                                    $iconClass = ' inactive';
                                }
                                elseif (!$saveOrder)
                                {
                                    $iconClass = ' inactive tip-top hasTooltip" title="' . JHtml::_('tooltipText', 'JORDERINGDISABLED');
                                }
                                ?>
                                <span class="sortable-handler<?php echo $iconClass ?>">
                                    <span class="icon-menu" aria-hidden="true"></span>
                                </span>
                                <?php if ($canReorder && $saveOrder) : ?>
                                    <input type="text" style="display:none" name="order[]" size="5" value="<?php echo $row->lft; ?>" class="width-20 text-area-order" />
                                <?php endif; ?>
                            </td>
                            <td><?php echo $this->pagination->getRowOffset($i); ?></td>
                            <td>
                                <?php echo JHtml::_('grid.id', $i, $row->id); ?>
                            </td>
                            <td>
                                <?php $prefix = JLayoutHelper::render('joomla.html.treeprefix', array('level' => $row->level)); ?>
                                <?php echo $prefix; ?>
                                <?php if ($row->checked_out) : ?>
                                    <?php $canCheckin = $user->authorise('core.manage', 'com_checkin') || $row->checked_out == $userId; ?>
                                    <?php echo JHtml::_('jgrid.checkedout', $i, $row->editor, $row->checked_out_time, 'helloworlds.', $canCheckin); ?>
                                <?php endif; ?>
                                <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>
                                <div class="small">
                                    <?php echo 'Path: ' . $this->escape($row->path); ?>
                                </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 $this->escape($row->access_level); ?>
                            </td>
                            <?php if ($assoc) : ?>
                                <td align="center">
                                    <?php if ($row->association) : ?>
                                        <?php echo JHtml::_('helloworlds.association', $row->id); ?>
                                    <?php endif; ?>
                                </td>
                            <?php endif; ?>
                            <td align="center">
                                <?php echo $row->author; ?>
                            </td>
                            <td align="center">
                                <?php echo JLayoutHelper::render('joomla.content.language', $row); ?>
                            </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>
        <?php // load the modal for displaying the batch options
            echo JHtml::_(
            'bootstrap.renderModal',
            'collapseModal',
            array(
                'title' => JText::_('COM_HELLOWORLD_BATCH_OPTIONS'),
                'footer' => $this->loadTemplate('batch_footer')
            ),
            $this->loadTemplate('batch_body')
        ); ?>
        <input type="hidden" name="task" value=""/>
        <input type="hidden" name="boxchecked" value="0"/>
        <?php echo JHtml::_('form.token'); ?>
    </div>
</form>

El método loadTemplate('batch_xxx') anterior buscará un archivo en el mismo directorio con un nombre de archivo que es una concatenación del nombre de archivo actual 'default' y 'batch_xxx'.

Ponemos el html para el cuerpo de la modal en un nuevo archivo default_batch_body.php, usando layouts estándar de Joomla para las opciones estándar. Para nuestra opción Position, representamos un layout que definimos nosotros mismos a continuación.

admin/views/helloworlds/tmpl/default_batch_body.php

<?php
/**
 * Layout file for the main body component of the modal showing the batch options
 * This layout displays the various html input elements relating to the batch processes
 */
defined('_JEXEC') or die;
$published = $this->state->get('filter.published');
?>

<div class="container-fluid">

	<div class="row-fluid">

		<div class="control-group span6">
			<div class="controls">
				<?php echo JLayoutHelper::render('joomla.html.batch.item', array('extension' => 'com_helloworld')); ?>
			</div>
			<div class="controls">
				<?php echo JLayoutHelper::render('position', array()); ?>
			</div>
		</div>

		<div class="control-group span6">
			<div class="controls">
				<?php echo JLayoutHelper::render('joomla.html.batch.language', array()); ?>
			</div>
			<div class="controls">
				<?php echo JLayoutHelper::render('joomla.html.batch.access', array()); ?>
			</div>
			<div class="controls">
				<?php echo JLayoutHelper::render('joomla.html.batch.tag', array()); ?>
			</div>
		</div>

	</div>
	
</div>

Y definimos nuestro layout para los elementos de Position en un nuevo archivo que colocamos en un nuevo directorio admin/layouts

admin/layouts/position.php

<?php

defined('_JEXEC') or die;

$html   = array();
$html[] = '<fieldset>';
$html[] = '<label id="batch_setposition-lbl" for="batch_setposition">' .
			JText::_('COM_HELLOWORLD_BATCH_SETPOSITION_LABEL') . '</label>';
$html[] = '<select name="batch[position][setposition]">' .
			'<option value="keepPosition" selected>' . JText::_('COM_HELLOWORLD_BATCH_KEEP_POSITION') . '</OPTION>' .
			'<option value="changePosition">' . JText::_('COM_HELLOWORLD_BATCH_CHANGE_POSITION') . '</OPTION>' .
		  '</select>';
$html[] = '<label id="batch_latitude-lbl" for="batch_latitude">' . 
				JText::_('COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_LABEL') . '</label>';
$html[] = '<input id="batch_latitude" name="batch[position][latitude]" class="inputbox" type="number" step=any min=-90.0 max=90.0 placeholder="0.0">';
$html[] = '<label id="batch_longitude-lbl" for="batch_longitude">' . 
				JText::_('COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_LABEL') . '</label>';
$html[] = '<input id="batch_longitude" name="batch[position][longitude]" class="inputbox" type="number" step=any min=-180.0 max=180.0 placeholder="0.0">';
$html[] = '</fieldset>';

echo implode('', $html);

El html para los botones de la modal Cancelar y Procesar los colocamos en el archivo de pie de página por lotes.

admin/views/helloworlds/tmpl/default_batch_footer.php

<?php
/**
 * Layout file for the footer component of the modal showing the batch options
 */
defined('_JEXEC') or die;
?>
<a class="btn" type="button" onclick="document.getElementById('batch-category-id').value='';document.getElementById('batch-access').value='';document.getElementById('batch-language-id').value='';document.getElementById('batch-user-id').value='';document.getElementById('batch-tag-id').value=''" data-dismiss="modal">
	<?php echo JText::_('JCANCEL'); ?>
</a>
<button class="btn btn-success" type="submit" onclick="Joomla.submitbutton('helloworld.batch');">
	<?php echo JText::_('JGLOBAL_BATCH_PROCESS'); ?>
</button>

Cambios en el controlador

En nuestro controlador helloworld sobreescribimos el método batch() de JControllerForm para configurar el modelo y la redirección a la misma URL, antes de continuar con el procesamiento por lotes normal.

admin/controllers/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');

/**
 * HelloWorld Controller
 *
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 * @since       0.0.9
 */
class HelloWorldControllerHelloWorld extends JControllerForm
{
	/**
	* Implement to allowAdd or not
	*
	* Not used at this time (but you can look at how other components use it....)
	* Overwrites: JControllerForm::allowAdd
	*
	* @param array $data
	* @return bool
	*/
	protected function allowAdd($data = array())
	{
		return parent::allowAdd($data);
	}
	/**
	* Implement to allow edit or not
	* Overwrites: JControllerForm::allowEdit
	*
	* @param array $data
	* @param string $key
	* @return bool
	*/
	protected function allowEdit($data = array(), $key = 'id')
	{
		$id = isset( $data[ $key ] ) ? $data[ $key ] : 0;
		if( !empty( $id ) )
		{
			return JFactory::getUser()->authorise( "core.edit", "com_helloworld.helloworld." . $id );
		}
	}

	public function batch($model = null)
	{
		$model = $this->getModel('helloworld');
		$this->setRedirect((string)JUri::getInstance());
		return parent::batch($model);
	}
}

Cambios en el Modelo

Sobreescribimos el método batch() de JModelAdmin para incluir nuestro propio método de posición por lotes en el array de procesos por lotes. Luego escribimos nuestro método batchPosition(), que verificará que el usuario tenga permiso para cambiar cada uno de los registros de helloworld, y luego aplicaremos los cambios de latitud/longitud a los registros.

También sobreescribimos el método generateTitle() que se usa para generar un nuevo título en el caso en el que estemos copiando un registro en una nueva categoría y hay un problema de unicidad del campo alias (similar al problema de Guardar como Copia que se abordó en el paso del tutorial [S:MyLanguage/J3.2:Developing an MVC Component/Adding an alias | Agregar un alias]]. Necesitamos sobreescribir el método porque asume que 'title' es el nombre del campo a ajustar, mientras que en nuestro caso el nombre del campo es 'gretting'.

Además, como se describe en el video adjunto, la funcionalidad de JModelAdmin para copiar registros en una categoría diferente no funciona del todo para componentes con Niveles. Para corregir esto, podemos sobreescribir el método batchCopy() de JModelAdmin e incluir código para colocar cada nuevo registro antes de que se guarde.

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';
	
	// batch processes supported by helloworld (over and above the standard batch processes)
	protected $helloworld_batch_commands = array(
		'position' => 'batchPosition',
		);

	/**
	 * Method overriding batch in JModelAdmin so that we can include the additional batch processes
	 * which the helloworld component supports.
	 */
	public function batch($commands, $pks, $contexts)
	{
		$this->batch_commands = array_merge($this->batch_commands, $this->helloworld_batch_commands);
		return parent::batch($commands, $pks, $contexts);
	}
	
	/**
	 * Method implementing the batch setting of lat/long values
	 */
	protected function batchPosition($value, $pks, $contexts)
	{
		$app = JFactory::getApplication();

		if (isset($value['setposition']) && ($value['setposition'] === 'changePosition'))
		{
			if (empty($this->batchSet))
			{
				// Set some needed variables.
				$this->user = JFactory::getUser();
				$this->table = $this->getTable();
				$this->tableClassName = get_class($this->table);
				$this->contentType = new JUcmType;
				$this->type = $this->contentType->getTypeByTable($this->tableClassName);
			}

			foreach ($pks as $pk)
			{
				if ($this->user->authorise('core.edit', $contexts[$pk]))
				{
					$this->table->reset();
					$this->table->load($pk);
					if (isset($value['latitude']))
					{
						$latitude = floatval($value['latitude']);
						if ($latitude <= 90 && $latitude >= -90)
						{
							$this->table->latitude = $latitude;
						}
					}
					if (isset($value['longitude']))
					{
						$longitude = floatval($value['longitude']);
						if ($longitude <= 180 && $longitude >= -180)
						{
							$this->table->longitude = $longitude;
						}
					}
					if (!$this->table->store())
					{
						$this->setError($this->table->getError());

						return false;
					}
				}
				else
				{
					$this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));

					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * Method to override generateTitle() because the helloworld component uses 'greeting' as the title field
	 */
	public function generateTitle($categoryId, $table)
	{
		// Alter the title & alias
		$data = $this->generateNewTitle($categoryId, $table->alias, $table->greeting);
		$table->greeting = $data['0'];
		$table->alias = $data['1'];
	}

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

Actualización cadenas de idioma

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

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

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

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

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.30</version>
	<!-- The description is optional and defaults to the name -->
	<description>COM_HELLOWORLD_DESCRIPTION</description>

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

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

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

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

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

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

</extension>

Colaboradores