Symfony 1.2, la guía definitiva

11.7. Interacciones complejas con Ajax

Entre los helpers de Ajax de Symfony, también existen utilidades que permiten construir interacciones complejas con una sola llamada a una función. Estas utilidades permiten mejorar la experiencia de usuario añadiendo características propias de las aplicaciones de escritorio (arrastrar y soltar, autocompletar, edición directa de contenidos, etc.) sin necesidad de escribir código JavaScript. En las siguientes secciones se describen los helpers de las interacciones complejas mediante ejemplos sencillos. Los parámetros adicionales y otras configuraciones se pueden consultar en la documentación de script.aculo.us.

Advertencia Aunque es sencillo incluir interacciones complejas, lo más complicado es configurarlas de forma que el usuario las perciba como algo natural en la página. Por tanto, solo se deben utilizar cuando se está seguro de que va a mejorar la experiencia de usuario. No deberían incluirse cuando su efecto es el de confundir al usuario.

11.7.1. Autocompletar

La interacción denominada "autocompletar" consiste en un cuadro de texto que muestra una lista de valores relacionados con los caracteres que teclea el usuario. Este efecto se puede conseguir con una única llamada al helper input_auto_complete_tag(), siempre que la acción remota devuelva una respuesta formateada como una lista de elementos HTML (<ul> y <li>) similar a la mostrada en el ejemplo 11-30.

Listado 11-30 - Ejemplo de respuesta compatible con el helper de autocompletar

<ul>
  <li>sugerencia 1</li>
  <li>sugerencia 2</li>
  ...
</ul>

El helper se puede incluir en cualquier plantilla de la misma forma que se incluiría cualquier cuadro de texto, como se muestra en el ejemplo 11-31.

Listado 11-31 - Uso del helper de autocompletar en una plantilla

<?php echo form_tag('mimodulo/miaccion') ?>
  Buscar un autor en función de su nombre:
  <?php echo input_auto_complete_tag('autor', 'nombre por defecto',
    'autor/autocompletar',
    array('autocomplete' => 'off'),
    array('use_style'    => true)
  ) ?>
  <?php echo submit_tag('Buscar') ?>
</form>

Cada vez que el usuario teclee un carácter en el cuadro de texto autor, se realiza una llamada a la acción remota autor/autocompletar. El código de esa acción depende de cada caso y aplicación, pero debe obtener una lista de valores sugeridos en forma de lista de elementos HTML como la mostrada en el listado 11-30. El helper muestra la lista devuelta debajo del cuadro de texto autor y si el usuario pincha sobre un valor o lo selecciona mediante el teclado, el cuadro de texto se completa con ese valor, tal y como muestra la figura 11-3.

Ejemplo de autocompletar

Figura 11.3 Ejemplo de autocompletar

El tercer argumento del helper input_auto_complete_tag() puede tomar uno de los siguientes parámetros:

  • use_style: aplica estilos CSS de forma automática a la lista que se muestra.
  • frequency: frecuencia con la que se realizan peticiones remotas (por defecto son 0.4 segundos).
  • indicator: el valor del atributo id de un elemento que se muestra cuando comienza la carga de las sugerencias y se oculta cuando se ha completado.
  • tokens: permite autocompletar por partes. Si el valor de este parámetro es , y el usuario introduce pedro, juan a la acción solo se le pasa el valor juan (siempre se le pasa el último valor después de trocear el cuadro de texto según el carácter definido por tokens).

11.7.2. Arrastrar y soltar

En las aplicaciones de escritorio suele ser normal coger un elemento con el ratón, moverlo y soltarlo en otro lugar. Sin embargo, en las aplicaciones web es mucho más raro de ver esta técnica, ya que es bastante difícil de programarla a mano con JavaScript. Afortunadamente, en Symfony se puede incluir esta técnica solo con una línea de código.

El framework incluye 2 helpers, draggable_element() y drop_receiving_element(), que se encargan de modificar el comportamiento de los elementos; estos helpers "observan" a los elementos y les añaden nuevas habilidades. Se utilizan para declarar a los elementos como "arrastrable" o como "elemento en el que se pueden soltar los elementos arrastrables". Un elemento arrastrable se activa cuando se pulsa con el ratón sobre el. Mientras no se suelte el ratón, el elemento se mueve siguiendo la posición del ratón. Los elementos en los que se pueden soltar los elementos arrastrables llaman a una función remota cuando el elemento arrastrable se suelta sobre esa zona. El listado 11-32 muestra un ejemplo de esta interacción mediante un elemento que hace de carrito de la compra.

Listado 11-32 - Elementos de arrastrar y soltar en un carrito de la compra

<ul id="elementos">
  <li id="elemento1" class="comida">Zanahoria</li>
  <?php echo draggable_element('elemento1', array('revert' => true)) ?>
  <li id="elemento2" class="comida">Manzana</li>
  <?php echo draggable_element('elemento2', array('revert' => true)) ?>
  <li id="elemento3" class="comida">Naranja</li>
  <?php echo draggable_element('elemento3', array('revert' => true)) ?>
</ul>
<div id="carrito">
  <p>El carrito está vacío</p>
  <p>Arrastra y suelta elementos aquí para añadirlos al carrito</p>
</div>
<?php echo drop_receiving_element('carrito', array(
  'url'        => 'carrito/anadir',
  'accept'     => 'comida',
  'update'     => 'carrito',
)) ?>

Cada uno de los elementos de la lista se pueden coger con el ratón y moverlos por la ventana del navegador. Cuando se suelta el ratón, el elemento vuelve a su posición original. Si el elemento se suelta sobre el elemento cuyo atributo id es carrito, se realiza una llamada a la acción remota carrito/anadir. La acción puede determinar el elemento que se ha añadido mediante el parámetro de petición id. De esta forma, el listado 11-32 es una aproximación muy realista al proceso físico de compra de productos: se cogen los productos, se sueltan en el carrito y después se realiza el pago.

Truco En el lsitado 11-32, los helpers aparecen justo después del elemento que modifican, aunque no es obligatorio. Si se quiere, se pueden agrupar todos los helpers draggable_element() y drop_receiving_element() al final de la plantilla. Lo único importante es el primer argumento que se pasa al helper y que indica el elemento al que se aplica.

El helper draggable_element() acepta los siguientes parámetros:

  • revert: si vale true, el elemento vuelve a su posición original cuando se suelta el ratón. También se puede indicar el nombre de una función que se ejecuta cuando finaliza el arrastre del elemento.
  • ghosting: realiza una copia del elemento original y el usuario mueve la copia, quedando inmóvil el elemento original.
  • snap: si vale false, el movimiento del elemento es libre. En otro caso, el elemento solo se puede desplazar de forma escalonada como si estuviera una gran rejilla a la que se ajusta el elemento. El valor del desplazamiento horizontal (x) y vertical (y) del elemento se puede definir como xy, [x,y] o function(x,y){ return [x,y] }.

El helper drop_receiving_element() acepta los siguientes parámetros:

  • accept: una cadena de texto o un array de cadenas de texto que representan a valores de clases CSS. Este elemento solo permitirá que se suelten sobre el los elementos cuyas clases CSS contengan al menos uno de los valores indicado.
  • hoverclass: clase CSS que se añade al elemento cuando el usuario arrastra (sin soltarlo) un elemento sobre esta zona.

11.7.3. Listas ordenables

Otra posibilidad que brindan los elementos arrastrables es la de ordenar una lista moviendo sus elementos con el ratón. El helper sortable_element() añade este comportamiento a los elementos de la lista, como se muestra en el ejemplo del listado 11-33.

Listado 11-33 - Ejemplo de lista ordenable

<p>What do you like most?</p>
<ul id="ordenar">
  <li id="elemento_1" class="ordenable">Zanahorias</li>
  <li id="elemento_2" class="ordenable">Manzanas</li>
  <li id="elemento_3" class="ordenable">Naranjas</li>
  // A nadie le gustan las coles de Bruselas
  <li id="elemento_4">Coles de Bruselas</li>
</ul>
<div id="respuesta"></div>
<?php echo sortable_element('ordenar', array(
  'url'    => 'elemento/ordenar',
  'update' => 'respuesta',
  'only'   => 'ordenable',
)) ?>

Gracias a la magia del helper sortable_element(), la lista <ul> se transforma en una lista ordenable dinámicamente, de forma que sus elementos se pueden reordenar mediante la técnica de arrastras y soltar. Cada vez que el usuario mueve un elemento y lo suelta para reordenar la lista, se realiza una petición Ajax con los siguientes parámetros:

POST /sf_sandbox/web/frontend_dev.php/elemento/ordenar HTTP/1.1
  ordenar[]=1&ordenar[]=3&ordenar[]=2&_=

La lista completa se pasa como un array con el formato ordenar[$rank]=$id, el $rank empieza en 0 y el $id es el valor que se indica después del guión bajo (_) en el valor del atributo id de cada elemento de la lista. El atributo id de la lista completa (ordenar en este caso) se utiliza para el nombre del array de parámetros que se pasan al servidor.

El helper sortable_element() acepta los siguientes parámetros:

  • only: una cadena de texto o un array de cadenas de texto que representan a valores de clases CSS. Solamente se podrán mover los elementos de la lista que tengan este valor en su atributo class.
  • hoverclass: clase CSS que se añade a la lista cuando el usuario posiciona el puntero del ratón encima de ella.
  • overlap: su valor debería ser horizontal si los elementos de la lista se muestran de forma horizontal y su valor debería ser vertical (que es el valor por defecto) cuando los elementos se muestran cada uno en una línea (como se muestran por defecto las listas en HTML).
  • tag: si la lista reordenable no contiene elemento <li>, se debe indicar la etiqueta que define los elementos que se van a hacer reordenables (por ejemplo div o dl).

Truco A partir de Symfony 1.1 también se puede utilizar el helper sortable_element() sin la opción url. De esta forma, no se realiza ninguna petición AJAX después de cada reordenación. El uso más común es el de realizar todas las peticiones AJAX cuando el usuario pulsa sobre el botón de Guardar o similar.

11.7.4. Edición directa de contenidos

Cada vez más aplicaciones web permiten editar los contenidos de sus páginas sin necesidad de utilizar formularios que incluyen el contenido de la página. El funcionamiento de esta interacción es muy sencillo. Cuando el usuario pasa el ratón por encima de un bloque de texto, este se resalta. Si el usuario pincha sobre el bloque, el texto se convierte en un control de formulario llamado área de texto (textarea) que muestra el texto original. Además, se muestra un botón para guardar los cambios. El usuario realiza los cambios en el texto original y pulsa sobre el botón de guardar los cambios. Una vez guardado, el área de texto desaparece y el texto modificado se vuelve a mostrar de forma normal. Con Symfony, toda esta interacción se puede realizar aplicando el helper input_in_place_editor_tag() al elemento. El listado 11-34 muestra el uso de este helper.

Listado 11-34 - Ejemplo de texto editable

<div id="modificame">Puedes modificar este texto</div>
<?php echo input_in_place_editor_tag('modificame', 'mimodulo/miaccion', array(
  'cols'        => 40,
  'rows'        => 10,
)) ?>

Cuando el usuario pincha sobre el texto editable, se reemplaza por un cuadro de texto que contiene el texto original y que se puede modificar. Al guardar los cambios, se llama mediante Ajax a la acción mimodulo/miaccion con el contenido modificado como valor del parámetro value. El resultado de la acción actualiza el elemento editable. Se trata de una interacción muy rápida de incluir y muy poderosa.

El helper input_in_place_editor_tag() acepta los siguientes parámetros:

  • cols y rows: el tamaño (en filas y columnas) del área de texto que se muestra para editar el contenido original (si el valor de rows es mayor que 1, se muestra un <textarea>; en otro caso, se muestra un <input type="text">).
  • loadTextURL: la URI de la acción que se llama para obtener el texto que se debe editar. Se trata de una opción útil cuando el contenido del elemento tiene un formato especial y se quiere que el usuario edite el texto sin ese formato aplicado.
  • save_text y cancel_text: el texto del enlace para guardar los cambios (el valor por defecto es "ok") y el del enlace para cancelar los cambios (el valor por defecto es "cancel").

Existen muchas otras opciones que se pueden consultar en la documentación de script.aculo.us sobre la edición directa de contenidos.