Symfony 1.1, la guía definitiva

11.3. Helpers de Ajax

¿Qué sucede si se quiere actualizar un elemento de la página no con JavaScript como en el listado 11-5, sino mediante un script de PHP que se encuentra en el servidor? De esta forma, sería posible modificar parte de la página en función de una respuesta del servidor. El helper remote_function() realiza exactamente esa tarea, como se demuestra en el listado 11-8.

Listado 11-8 - Uso del helper remote_function()

<div id="mizona"></div>
<?php echo javascript_tag(
  remote_function(array(
    'update'  => 'mizona',
    'url'     => 'mimodulo/miaccion',
  ))
) ?>

Nota El parámetro url puede contener una URI interna (modulo/accion?clave1=valor1&...) o el nombre de una regla del sistema de enrutamiento, al igual que sucede con el helper url_for().

Cuando se ejecuta, el script anterior actualiza el contenido del elemento cuyo id es igual a mizona con la respuesta de la acción mimodulo/miaccion. Este tipo de interacción se llama Ajax, y es el núcleo de las aplicaciones web más interactivas. La versión en inglés de la Wikipedia (http://en.wikipedia.org/wiki/AJAX) lo describe de la siguiente manera:

Ajax permite que las páginas web respondan de forma más rápida mediante el intercambio en segundo plano de pequeñas cantidades de datos con el servidor, por lo que no es necesario recargar la página entera cada vez que el usuario realiza un cambio. El objetivo es aumentar la interactividad, la rapidez y la usabilidad de la página.

Ajax depende de XMLHttpRequest, un objeto JavaScript cuyo comportamiento es similar a un frame oculto, cuyo contenido se puede actualizar realizando una petición al servidor y se puede utilizar para manipular el resto de la página web. Se trata de un objeto a muy bajo nivel, por lo que los navegadores lo tratan de forma diferente y el resultado es que se necesitan muchas líneas de código para realizar peticiones Ajax a mano. Afortunadamente, Prototype encapsula todo el código necesario para trabajar con Ajax y proporciona un objeto Ajax mucho más simple y que también utiliza Symfony. Este es el motivo por el que la librería Prototype se carga automáticamente cuando se utiliza un helper de Ajax en la plantilla.

Truco Los helpers de Ajax no funcionan si la URL de la acción remota no pertenece al mismo dominio que la página web que la llama. Se trata de una restricción por motivos de seguridad que imponen los navegadores y que no puede saltarse.

Las interacciones de Ajax están formadas por tres partes: el elemento que la invoca (un enlace, un formulario, un botón, un contador de tiempo o cualquier otro elemento que el usuario manipula e invoca la acción), la acción del servidor y una zona de la página en la que mostrar la respuesta de la acción. Se pueden crear interacciones más complejas si por ejemplo la acción remota devuelve datos que se procesan en una función JavaScript en el navegador del cliente. Symfony incluye numerosos helpers para insertar interacciones Ajax en las plantillas y todos contienen la palabra remote en su nombre. Además, todos comparten la misma sintaxis, un array asociativo con todos los parámetros de Ajax. Debe tenerse en cuenta que los helpers de Ajax generan código HTML, no código JavaScript.

11.3.1. Enlaces Ajax

Los enlaces Ajax constituyen una de las partes más importantes de las interacciones Ajax realizadas en las aplicaciones de la Web 2.0. El helper link_to_remote() muestra un enlace que llama a una función remota. La sintaxis es muy similar a link_to(), excepto que el segundo parámetro es el array asociativo con las opciones Ajax, como muestra el listado 11-9.

Listado 11-9 - Enlace Ajax realizado con el helper link_to_remote()

<div id="respuesta"></div>
<?php echo link_to_remote('Borrar este post', array(
    'update' => 'respuesta',
    'url'    => 'post/borrar?id='.$post->getId(),
)) ?>

En el ejemplo anterior, al pulsar sobre el enlace "Borrar este post" se realiza una llamada en segundo plano a la acción post/borrar. La respuesta devuelta por el servidor se muestra automáticamente en el elemento de la página cuyo atributo id sea igual a respuesta. La figura 11-1 ilustra el proceso completo.

Ejecutando una actualización remota mediante un enlace

Figura 11.1 Ejecutando una actualización remota mediante un enlace

También es posible utilizar una imagen en vez de texto para mostrar el enlace, utilizar el nombre de una regla de enrutamiento en vez de modulo/accion y añadir opciones a la etiqueta <a> como tercer argumento, tal y como muestra el listado 11-10.

Listado 11-10 - Opciones del helper link_to_remote()

<div id="emails"></div>
<?php echo link_to_remote(image_tag('refresh'), array(
    'update' => 'emails',
    'url'    => '@listado_emails',
), array(
    'class'  => 'enlace_ajax',
)) ?>

11.3.2. Formularios Ajax

Los formularios web normalmente realizan una llamada a una acción que provoca que se deba recargar la página completa. El helper equivalente a link_to_function() para un formulario sería un helper que enviara los datos del formulario al servidor y que actualizara un elemento de la página con la respuesta del servidor. Eso es precisamente lo que hace el helper form_remote_tag(), y su sintaxis se muestra en el listado 11-11.

Listado 11-11 - Formulario Ajax con el helper form_remote_tag()

<div id="lista_elementos"></div>
<?php echo form_remote_tag(array(
    'update'   => 'lista_elementos',
    'url'      => 'elemento/anadir',
)) ?>
  <label for="elemento">Elemento:</label>
  <?php echo input_tag('elemento') ?>
  <?php echo submit_tag('Añadir') ?>
</form>

El helper form_remote_tag() crea una etiqueta <form> de apertura, como sucede con el helper form_tag(). El envío del formulario consiste en el envío en segundo plano de una petición de tipo POST a la acción elemento/anadir y con la variable elemento como parámetro de la petición. La respuesta del servidor reemplaza los contenidos del elemento cuyo atributo id sea igual a lista_elementos, como se muestra en la figura 11-2. Los formularios Ajax se cierran con una etiqueta </form> de cierre de formularios.

Ejecutando una actualización remota mediante un formulario

Figura 11.2 Ejecutando una actualización remota mediante un formulario

Advertencia Los formularios Ajax no pueden ser multipart, debido a una limitación del objeto XMLHttpRequest. En otras palabras, no es posible enviar archivos mediante formularios Ajax. Existen algunas técnicas para saltarse esta limitación, como por ejemplo utilizar un iframe oculto en vez del objeto XMLHttpRequest.

Si es necesario incluir un formulario que sea normal y Ajax a la vez, lo mejor es definirlo como formulario normal y añadir, además del botón de envío tradicional, un segundo botón (<input type="button" />) para enviar el formulario mediante Ajax. Symfony define este botón mediante el helper submit_to_remote(). De esta forma, es posible definir interacciones Ajax que se degradan correctamente en los navegadores que no las soportan. El listado 11-12 muestra un ejemplo.

Listado 11-12 - Formulario con envío de datos tradicional y Ajax

<div id="lista_elementos"></div>
<?php echo form_tag('@elemento_anadir_normal') ?>
  <label for="elemento">Elemento:</label>
  <?php echo input_tag('elemento') ?>
  <?php if_javascript(); ?>
    <?php echo submit_to_remote('envio_ajax', 'Anadir con Ajax', array(
        'update'   => 'lista_elementos',
        'url'      => '@elemento_anadir',
    )) ?>
  <?php end_if_javascript(); ?>
  <noscript>
    <?php echo submit_tag('Anadir') ?>
  </noscript>
</form>

Otro ejemplo en el que se podría utilizar la combinación de botones normales y botones Ajax es el de un formulario que edita un artículo o noticia. Podría incluir un botón realizado con Ajax para previsualizar los contenidos y un botón normal para publicar los contenidos directamente.

Nota Si el usuario envía el formulario pulsando la tecla Enter, el formulario se envía utilizando la acción definida en la etiqueta <form> principal, es decir, la acción normal y no la acción Ajax.

Los formularios más modernos no solo se encargan de enviar sus datos cuando el usuario pulsa sobre el botón de envío, sino que también pueden reaccionar a los cambios producidos por el usuario sobre alguno de sus campos. Symfony proporciona el helper observe_field() para realizar esa tarea. El listado 11-13 muestra un ejemplo de uso de este helper para crear un sistema que sugiere valores a medida que el usuario escribe sobre un campo: cada carácter escrito en el campo elemento lanza una petición Ajax que actualiza el valor del elemento sugerencias_elemento de la página.

Listado 11-13 - Ejecutando una función remota cada vez que cambia el valor de un campo de formulario mediante observe_field()

<?php echo form_tag('@elemento_anadir_normal') ?>
  <label for="elemento">Elemento:</label>
  <?php echo input_tag('elemento') ?>
  <div id="sugerencias_elemento"></div>
  <?php echo observe_field('elemento', array(
      'update'   => 'sugerencias_elemento',
      'url'      => '@elemento_escrito',
  )) ?>
  <?php echo submit_tag('Anadir') ?>
</form>

El par modulo/accion correspondiente a la regla @elemento_escrito se ejecuta cada vez que el usuario modifica el valor del campo de formulario que se está observando (en este caso, elemento) sin necesidad de enviar el formulario. La acción puede acceder a los caracteres escritos en cada momento por el usuario mediante el parámetro elemento de la petición. Si se necesita enviar otro valor en vez del contenido del campo de formulario que se está observando, se puede especificar en forma de expresión JavaScript en el parámetro with. Si por ejemplo es necesario que la acción disponga de un parámetro llamado param, se puede utilizar el helper observe_field() como muestra el listado 11-14.

Listado 11-14 - Pasando parámetros personalizados a la acción remota con la opción with

<?php echo observe_field('elemento', array(
    'update'   => 'sugerencias_elemento',
    'url'      => '@elemento_escrito',
    'with'     => "'param=' + value",
)) ?>

Este helper no genera un elemento HTML, sino que añade un comportamiento (del inglés, "behavior") al elemento que se pasa como parámetro. Más adelante en este capítulo se describen más ejemplos de helpers de JavaScript que añaden comportamientos.

Si se quieren observar todos los campos de un formulario, se puede utilizar el helper observe_form(), que llama a una función remota cada vez que se modifica uno de los campos del formulario.

11.3.3. Ejecución periódica de funciones remotas

Por último, el helper periodically_call_remote() permite crear una interacción de Ajax que se repite cada pocos segundos. No está asociado con ningún elemento HTML de la página, sino que se ejecuta de forma transparente en segundo plano como una especie de comportamiento de la página entera. Se puede utilizar para seguir la posición del puntero del ratón, autoguardar el contenido de un área de texto grande, etc. El listado 11-15 muestra un ejemplo de uso de este helper.

Listado 11-15 - Ejecutando periódicamente una función remota mediante periodically_call_remote()

<div id="notificacion"></div>
<?php echo periodically_call_remote(array(
    'frequency' => 60,
    'update'    => 'notificacion',
    'url'       => '@observa',
    'with'      => "'param=' + \$F('micontenido')",
)) ?>

Si no se especifica el número de segundos (mediante el parámetro frequency) que se esperan después de cada repetición, se tiene en cuenta el valor por defecto que son 10 segundos. El parámetro with se evalúa con JavaScript, así que se puede utilizar cualquier función de Prototype, como por ejemplo la función $F().