Creación de componentes para Joomla (II)

Si en el artículo anterior vimos cómo desarrollar la interfaz pública de un componente para Joomla, en esta ocasión veremos cómo crear la interfaz de administración, extendiendo nuestro pequeño componente de ejemplo, que mostraba citas aleatorias de entre las introducidas por los usuarios, y añadiendo opciones para editar y eliminar las citas.

Lo primero que tendremos que hacer es editar el archivo XML que contiene la información del componente. Recordaréis que la sección referente a la interfaz de administración (administration) estaba vacía en el ejemplo anterior.

Necesitamos añadir una sección files, en la que indicar los archivos que compondrán la parte de administración, y una etiqueta menu, especificando el nombre a mostrar en el menú de Componentes y la página que se cargará al hacer clic sobre él. Algo a tener en cuenta es que file tiene un argumento folder indicando que todos los archivos a los que hacemos referencia se encuentran en una nueva carpeta admin dentro del directorio de nuestro componente.

<?xml version="1.0" encoding="utf-8"?>
<install type="component" version="1.5">
  <name>Ejemplo</name>
  <author>Raul Gonzalez</author>
  <creationDate>Febrero 2010</creationDate>
  <copyright>(C) Raul Gonzalez</copyright>
  <license>GPL</license>
  <authorUrl>http://mundogeek.net/</authorUrl>
  <authorEmail>zootropo en gmail</authorEmail>
  <version>0.1</version>
  <description>Componente de ejemplo que muestra mensajes aleatorios escritos por los lectores</description>
  <install>
    <queries>
      <query>CREATE TABLE IF NOT EXISTS jos_mensajes(
          id int NOT NULL auto_increment,
          mensaje varchar(300) NOT NULL, PRIMARY KEY(id));</query>
      <query>INSERT INTO jos_mensajes(mensaje) VALUES
          ('At your command'),
          ('Hay 10 tipos de personas en el mundo: los que entienden binario y los que no'),
          ('Es mio, solo mio. Miii.... tessssssooooroo'),
          ('Hasta luego, y gracias por el pescado');</query>
    </queries>
  </install>
  <files>
    <file>ejemplo.php</file>
    <file>ejemplo.xml</file>
    <folder>controllers</folder>
    <folder>models</folder>
    <folder>views</folder>
  </files>
  <administration>
    <menu link="option=com_ejemplo">Ejemplo</menu>
    <files folder="admin">
      <file>ejemplo.php</file>
      <file>controller.php</file>
      <folder>views</folder>
    </files>
  </administration>
</install>

En el tutorial anterior ya comentamos que el punto de entrada al componente, tanto en el caso de la interfaz pública como el de la administración, es un archivo PHP con el nombre del componente, y que este, normalmente, no hará más que cargar el controlador adecuado. En este caso sólo vamos a utilizar un controlador, por lo que el código es mucho más sencillo. Creemos un nuevo archivo ejemplo.php dentro de nuestra nueva carpeta admin, con el siguiente contenido:

<?php
defined('_JEXEC') or die('Restricted access');

require_once('controller.php');

$controller = new EjemploController();

$controller->execute($task);
$controller->redirect();

Como vemos lo único que hace es cargar el código de nuestro único controlador, instanciar la clase, y ejecutar el método adecuado en el objeto dependiendo del parámetro task de la petición.

Creemos ahora el archivo controller.php en la carpeta admin:

<?php
defined('_JEXEC') or die('Restricted access');

jimport('joomla.application.component.controller');

class EjemploController extends JController {
  function __construct() {
    parent::__construct();
    $this->addModelPath(JPATH_COMPONENT_SITE.DS.'models');
  }

  function display() {
    $vista = $this->getView('ejemplo', 'html');
    $modelo = $this->getModel('mensaje', 'EjemploModel');
    $vista->setModel($modelo, true);

    parent::display();
  }

  function edit() {
    $vista = $this->getView('ejemplo', 'html');
    $modelo = $this->getModel('mensaje', 'EjemploModel');
    $vista->setModel($modelo, true);

    $vista->edit();
  }

  function save() {
    $modelo = $this->getModel('mensaje', 'EjemploModel');
    $id = JRequest::getVar('id');
    $mensaje = JRequest::getVar('mensaje');

    $correcto = $modelo->actualizarMensaje($id, $mensaje);

    if($correcto)
      $this->setRedirect('index.php?option=com_ejemplo', 'Mensaje actualizado con éxito');
    else
      $this->setRedirect('index.php?option=com_ejemplo', 'Ocurrió un error al actualizar el mensaje', 'error');
  }

  function remove() {
    $modelo = $this->getModel('mensaje', 'EjemploModel');
    $cid = JRequest::getVar('cid', 0, '', 'array');
    $correcto = true;
    foreach($cid as $id) {
      $resultado = $modelo->eliminarMensaje($id);
      if(!$resultado)
        $correcto = false;
    }

    if($correcto)
      $this->setRedirect('index.php?option=com_ejemplo', 'Mensajes eliminados con éxito');
    else
      $this->setRedirect('index.php?option=com_ejemplo', 'Ocurrió un error al eliminar los mensajes', 'error');
  }
}

En esta ocasión sobreescribimos el constructor del controlador para añadir una llamada al método addModelPath. Hacemos uso de este método para que el controlador tenga en cuenta también la carpeta en la que almacenamos el modelo en la interfaz pública. Editaremos ese archivo para añadir los nuevos métodos que necesitemos, en lugar de crear otro archivo de modelo. También podríamos haber almacenado el archivo en la carpeta de la parte de administración y hacer uso del método addModelPath en el controlador de la parte pública.

Si quisiéramos hacer uso de una vista de la parte pública, aunque es algo menos común, podríamos haber hecho uso del método addViewPath.

El resto del código no tiene mayor misterio.

Sólo nos queda crear la vista y las plantillas que vamos a utilizar. Creamos una carpeta views, dentro una carpeta ejemplo, y en ella un archivo view.html.php.

<?php
defined('_JEXEC') or die('Restricted access');

jimport('joomla.application.component.view');

class EjemploViewEjemplo extends JView {
  function display($tpl=null) {
    JToolBarHelper::title('Mensajes');
    JToolBarHelper::deleteList();
    JToolBarHelper::editList();

    $mensajes =& $this->get('Mensajes');
    $this->assignRef('mensajes', $mensajes);

    parent::display($tpl);
  }

  function edit() {
    JToolBarHelper::title('Editar mensaje');
    JToolBarHelper::save();
    JToolBarHelper::cancel();

    $modelo = $this->getModel();
    $cid = JRequest::getVar('cid', 0, '', 'array');

    $mensaje = $modelo->getMensajePorId($cid[0]);
    $this->assignRef('mensaje', $mensaje);

    parent::display('editar');
  }
}

En esta ocasión para generar nuestra vista hacemos uso de la clase JToolBarHelper, que contiene métodos para facilitarnos la creación de la barra de herramientas de nuestro componente. Tiene métodos para añadir títulos (title), divisores (divider), espaciadores (spacer), … botones de ayuda (help), para guardar elementos (save), borrar (trash), publicar (publish), … Este es el motivo de que haya utilizado palabras inglesas para nombrar los métodos del controlador: al pulsar el botón generado por editList, por ejemplo, se intentará ejecutar por defecto una tarea edit.

Este es el aspecto que tendrá ahora nuestro modelo, una vez editado el archivo correspondiente en la carpeta de la interfaz pública:

<?php
defined('_JEXEC') or die('Restricted access');
jimport('joomla.application.component.model');

class EjemploModelMensaje extends JModel {
  function getMensaje() {
    $db =& JFactory::getDBO();
    $query = 'SELECT mensaje FROM #__mensajes ORDER BY RAND() LIMIT 1';
    $db->setQuery($query);
    return $db->loadResult();
  }

  function getMensajePorId($id) {
    $db =& JFactory::getDBO();
    $query = "SELECT id, mensaje FROM #__mensajes WHERE id='{$id}'";
    $db->setQuery($query);
    return $db->loadObject();
  }

  function eliminarMensaje($id) {
    $db =& JFactory::getDBO();
    $query = "DELETE FROM #__mensajes WHERE id='{$id}'";
    $db->setQuery($query);
    $db->query();

    if ($db->getErrorNum())
      return false;

    return true;
  }

  function actualizarMensaje($id, $mensaje) {
    $db =& JFactory::getDBO();
    $query = "UPDATE #__mensajes SET mensaje='{$mensaje}' WHERE id='{$id}'";
    $db->setQuery($query);
    $db->query();

    if ($db->getErrorNum())
      return false;

    return true;
  }

  function getMensajes() {
    $query = 'SELECT id, mensaje FROM #__mensajes ORDER BY id ASC';
    return $this->_getList($query);
  }

  function anyadirMensaje($mensaje) {
    $db =& JFactory::getDBO();
    $query = "INSERT INTO #__mensajes(mensaje) VALUES('{$mensaje}')";
    $db->setQuery($query);
    $db->query();

    if ($db->getErrorNum())
      return false;

    return true;
  }
}

Lo único destacable es el uso del método _getList, que toma una consulta SQL y devuelve una lista con los resultados.

Vamos por último con las plantillas. Creamos la carpeta tmpl en views/ejemplo y en esta un archivo default.php, que será la plantilla que mostrará la lista de los mensajes almacenados en la base de datos.

<?php defined('_JEXEC') or die('Restricted access'); ?>

<form action="index.php" method="post" name="adminForm">
  <table class="adminlist">
  <thead>
    <tr>
      <th width="5">ID</th>
      <th width="20"><input type="checkbox" name="toggle" value="" onclick="checkAll(<?php echo count($this->mensajes); ?>);"/></th>
      <th>Mensaje</th>
    </tr>
  </thead>
  <?php
  $i = 0;
  foreach($this->mensajes as $mensaje) {
  ?>
    <tr>
      <td><?php echo $mensaje->id; ?></td>
      <td><?php echo JHTML::_('grid.id', $i, $mensaje->id); ?></td>
      <td><?php echo "<a href='index.php?option=com_ejemplo&task=edit&cid[]={$mensaje->id}'>{$mensaje->mensaje}</a>"; ?></td>
    </tr>
    <?php
    $i++;
  }
  ?>
  </table>

  <input type="hidden" name="option" value="com_ejemplo" />
  <input type="hidden" name="task" value="" />
  <input type="hidden" name="boxchecked" value="0" />
</form>

Aquí hay que tener en cuenta varias convenciones. Por ejemplo, el formulario tiene que tener como valor para el atributo nameadminForm“, para que funcione el script que marca todos los checkboxes al marcar el primero, y la tabla tiene que tener como valor para el atributo classadminlist“, si queremos que esta tenga el aspecto definido en la hoja de estilo.

A parte de eso lo único interesante es el uso del método JHTML::_(), que se utiliza para generar código HTML de uso común. Toma como primer parámetro una cadena con formato “clase.metodo”, lo que indica que queremos ejecutar el método indicando de la clase JHTMLClase. Si no se especifica ninguna clase, se llamará al metodo con ese nombre de la clase JHTML, si existe. Cualquier otro parámetro extra que se le pase a JHTML::_() se pasará como parámetro al método que queremos ejecutar.

Estas clases que agrupan código HTML de uso común son JHTMLBehavior, JHTMLContent, JHTMLEmail, JHTMLForm, JHTMLGrid, JHTMLImage, JHTMLList, JHTMLMenu y JHTMLSelect.

El método id de JHtmlGrid, concretamente, genera un widget de tipo checkbox que sigue las convenciones necesarias para que se utilice para seleccionar un elemento por su identificador.

Por último veamos la plantilla que se encargará de crear el formulario con el que editar el mensaje, default_editar.php. Este formulario es incluso más sencillo, y no requiere de mayores explicaciones.

<?php defined('_JEXEC') or die('Restricted access'); ?>

<form action="index.php" method="post" name="adminForm" id="adminForm">
  <table class="admintable">
    <tr>
      <td width="100" align="right" class="key"><label for="mensaje">Mensaje:</label></td>
      <td><input type="text" name="mensaje" id="mensaje" maxlength="300" value="<?php echo $this->mensaje->mensaje;?>" /></td>
    </tr>
  </table>

  <input type="hidden" name="option" value="com_ejemplo" />
  <input type="hidden" name="id" value="<?php echo $this->mensaje->id; ?>" />
  <input type="hidden" name="task" value="" />
</form>

Comentarios
  1. Tío, me vuelvo a quita el sombrero, te lo has currado mucho.

    Muy bien explicado todo.

    Por cierto, que opinas de Joomla como plataforma de desarrollo?? y como CMS?? Qué te ha parecido su API?

    Un saludo y gracias por la documentación.

    Responder

    • Como “framework” todavía está verde. Habrá que ver la 1.6. Como CMS, muy muy bueno, y muy sencillo de aprender para los novatos.

      Responder

  2. Javier

    Hola, he seguido los pasos, y me da el siguiente error en la parte de administración al seleccionar Componentes/Ejemplo:

    500 – Se ha producido un error

    View not found [name, type, prefix]: ejemplo,html,ejemploView

    Saludos y muchas gracias

    Responder

    • Pues, como dice el texto del error, eso es que no encuentra la vista. Comprueba que esté en la carpeta views/ejemplo (views/name), que el archivo se llame view.html.php (view.type.php) y que la clase se llame EjemploViewEjemplo (prefixname).

      Responder

      • Javier

        Mucha gracias, era ese el problema, me había equivocado en la ubicación del fichero de la vista.

        Responder

  3. Javier

    Otra pregunta: ¿Cómo puede Joomla comprobar qué módulos debe mostrar cuando se ejecuta nuestro componente en la parte pública?.

    Ahora mismo me muestra menús que no tengo ni publicados.

    Gracias de nuevo

    Responder

    • En principio sólo tendrías que ir al gestor de módulos, seleccionar el módulo que quieras modificar y en la sección de menús elegir las páginas para las que quieres que se muestre el módulo.

      Que te muestre un módulo que no tienes ni publicado es raro, raro, raro. Puede que sea alguna cosa extraña de la caché de Joomla.

      Responder

  4. Indalecio

    Muy buen trabajo!!, Todo muy bien explicado, gracias por todos tus tutoriales, todo muy machacado. GRACIAS!!

    Responder

  5. Francisco

    Hola Zootropo,

    Espero no incomodar a nadie con la pregunta.

    Conoces drupal?

    que te parece drupal frente a Joomla como CMS y como plataforma de desarrollo?

    Muy buen blog

    Responder

    • Sí, lo conozco, Francisco. Y como framework me parece mejor que Joomla. Como CMS, creo que deja mucho que desear en usabilidad, aunque tengo entendido que es uno de los puntos en los que más están trabajando para la próxima versión :)

      Responder

  6. Hola Zootropo, muy bueno el tutorial!
    Estuve creando uno, cambiando los nombres del componente y de la entidad, y en el momento de actualizar y borrar, lo hace de hecho. Pero me desloguea cuando pasa por el $this->setRedirect de los metodos en el controller de la parte de admin.
    Tendras idea de porque pasa esto?
    Gracias!

    Responder

Deja un comentario