Como mencioné anteriormente Symfony2 es un Framework PHP orientado al desarrollo en el servidor, por su parte AJAX es una técnica que se implementa desde el JavaScript (JS) cliente, cuyo único objetivo es realizar las peticiones HTTP desde JavaScript y obtener la respuesta para manipularla directamente, sea añadiéndola al DOM o lo que quieras con JavaScript, por lo cual en dicho caso tanto Symfony2 como PHP sólo pueden detectar si la petición fue realizada por dicha técnica, razón por la cual su implementación es realmente simple; también aclaramos que desde su versión 1.3 el proyecto Symfony ha optado por no apoyar ni integrar ningún Framework JS, debido a que ello queda a elección del programador.

En esta continuación del capítulo anterior elegimos a jQuery como el Framework JS a utilizar para los ejemplos, por ser uno de los más populares y fáciles de implementar, reiteramos que puedes usar el FW JS que desees y queda bajo tu absoluta elección, además aclaro que los ejemplos se concentran en el uso de AJAX con jQuery y que para conservar la facilidad con la que implementen los ejemplos no se usaron modelos reales de doctrine, en su caso arrays multidimensionales, si quieren complementarlos con Doctrine, pueden consultar su capítulo: Configurando nuestra Base de Datos.

Ejemplo 1: Ejecutando una llamada ajax con jQuery

Descargamos el script de la página de jQuery y la guardamos en el directorio web\js\. Para este caso la versión actual es jquery-1.7.2.min.js.

Importamos el archivo dentro del template base que se encuentra en app\Resources\views\base.html.twig:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
        <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
                <script src="{{ asset('js/jquery-1.7.2.min.js') }}"></script>
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

Con esto ya tenemos el soporte para jQuery en todas nuestras páginas que hereden mediante Twig a este template.

Ya teníamos la página http://localhost/Symfony/web/app_dev.php/articulos/listar que nos mostraba una lista de artículos de la base de datos por lo que podemos hacer otra que simplemente llame por ajax a esta página y muestre los artículos. Para esto creamos la página ver-articulos que simplemente tendrá una link para cargar por ajax el contenido de la página que contiene la lista de artículos. Como cada vez que necesitamos crear una página hacemos los 3 pasos:

CREAMOS LA RUTA

Agregamos a nuestro archivo routing.yml las siguientes líneas para crear la nueva ruta:

ver_articulos:
    pattern:  /ver-articulos
    defaults: { _controller: MDWDemoBundle:Articulos:verArticulos }

CREAMOS EL ACTION

//Dentro del controlador src\MDW\DemoBundle\Controller\ArticulosController.php agregamos el siguiente action
public function verArticulosAction()
{
        return $this->render('MDWDemoBundle:Articulos:ver_articulos.html.twig', array());
}

Como podemos ver lo único que hace es llamar a la vista que creamos a continuación.

CREAMOS LA VISTA

Creamos el archivo ver_articulos.html.twig dentro de la carpeta src\MDW\DemoBundle\Resources\views\Articulos

{% extends '::base.html.twig' %}

{% block title %}Symfony - AJAX{% endblock %}

{% block body %}

<div id="articulos"></div>
<a id="link_articulos" href="#">Cargar articulos</a>

{% endblock %}

{% block javascripts %}
<script>

$(document).ready(function(){
        $('#link_articulos').click(function(){
                $('#articulos').load('{{ path('articulo_listar') }}');
        });
});

</script>
{% endblock %}

En esta página hemos heredado el contenido de template base con el extend y hemos incluído los bloques. Vemos que en el body lo único que tenemos es un div vacío con id “articulos” y link con id “link_articulos” para obtenerlo utilizamos jQuery. La idea es que al ingresar a la página solo nos muestre el link y no así los artículos. Al dar click sobre el link “Cargar Artículos” se ejecutará una llamada ajax mediante jQuery y cargaremos asincrónicamente la ruta {{ path(‘articulo_listar’) }} que sería la página que ya tenemos lista y que vimos en el capítulo 9 – Manipulando datos con Doctrine.

Entendamos el código jQuery:

<script>

$(document).ready(function(){
        $('#link_articulos').click(function(){
                $('#articulos').load('{{ path('articulo_listar') }}');
        });
});

</script>

Primeramente registramos el evento ready para que ejecute la función anónima una vez que todo el DOM sea cargado.

Una vez ejecutado el evento ready, agregamos una acción al evento click de nuestro link que ejecutará la función que carga la segunda página:

$('#link_articulos').click(function(){
        $('#articulos').load('{{ path('articulo_listar') }}');
});

La función load() de jQuery se ejectua sobre el div vacío para que el resultado de la respuesta Ajax se cargue dentro de este div y como argumento del método pasamos la dirección en donde, para no escribir la URL a mano, usamos la función Twig {{ path(‘articulo_listar’) }} para que los genere la ruta relativa de la ruta articulo_listar.

Con esto ya podemos acceder a la página http://localhost/Symfony/web/app_dev.php/ver-articulos donde veremos un link “Cargar artículos”. Presionando el link veremos como se obtiene, sin recargar la página, el listado de artículos de la base de datos.

Ejemplo 2: Gestionando llamadas AJAX con jQuery, Twig y herencia en 3 niveles

En el ejemplo anterior apreciamos lo sencillo que es implementar una llamada AJAX por medio del FW jQuery y comprendimos que en realidad Symfony2 interviene prácticamente en nada porque su alcance se limita al desarrollo del lado del servidor, en este ejemplo práctico haremos uso de las facilidades que brinda Symfony para detectar peticiones AJAX y modificar la respuesta en función de las necesidades.

Para este ejemplo crearemos una sección o módulo de noticias, en donde nos aparece un listado principal de noticias de las cuales al hacer click nos redirigirá al detalle de la noticia como tal. La idea es conservar un enlace directo a cada noticia, con el cual un motor de búsqueda pueda indexar y a su vez cargar el detalle de la noticia en una capa por medio de AJAX (con load jQuery) con el objetivo de que el usuario pueda cargar las noticias sin recargar la página y si en caso llega desde un buscador pueda apreciar la noticia y el resto de contenidos que ofrezca nuestro layout principal.

Para ello creamos primero nuestro layout:

{# /src/MDW/DemoBundle/views/layout.html.twig #}
{% extends '::base.html.twig'  %} {# extendemos del layout por defecto #}

{% block javascripts %}
{# añadimos la CDN de jQuery o en su defecto lo descargamos e incluimos con:
<script src="{{ asset('js/jquery-1.7.2.min.js') }}"></script>
 #}
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
{% endblock javascripts %}

{# creamos una estructura para el layout general #}
{% block body %}
<div>
    <h1>*** Web de Noticias ***</h1>
    {# según el patrón de 3 niveles, creamos el bloque de contenido #}
    {% block content %}{% endblock content %}
</div>
{% endblock body%}

Procedemos ahora a crear las rutas hacia nuestro controlador, abre el src/MDW/DemoBundle/Resources/config/routing.yml y agrega:

# /src/MDW/DemoBundle/Resources/config/routing.yml

MDWDemoBundle_noticias:
    pattern:  /noticias
    defaults: { _controller: MDWDemoBundle:Notice:index }

MDWDemoBundle_noticeView:
    pattern:  /leerNoticia/{notice_id}
    defaults: { _controller: MDWDemoBundle:Notice:noticeView }

Procedemos ahora a crear el controlador, en este ejemplo utilizaremos como Modelo un array de noticias, para enfocarnos en el uso de AJAX:

<?php
// src/MDW/DemoBundle/Controller/NoticeController.php
namespace MDW\DemoBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class NoticeController extends Controller
{
    // tenemos un array con los datos básicos
    private $array_notice = array(
        array(
            'title' => 'Titulo de noticia 0',
            'content' => 'Contenido de noticia 0'
        ),
        array(
            'title' => 'Titulo de noticia 1',
            'content' => 'Contenido de noticia 1'
        ),
    );    

    public function indexAction()
    {
        // suponiendo que obtenemos del modelo el listado de noticias
        return $this->render('MDWDemoBundle:Notice:index.html.twig', array(
            'notices' => $this->array_notice
        ));
    }

    public function noticeViewAction($notice_id)
    {
        //obtenemos la noticia del modelo, en este ejemplo proviene de un array
        $notice = $this->array_notice[$notice_id];
        return $this->render('MDWDemoBundle:Notice:noticeView.html.twig', array(
            'notice' => $notice
        ));
    }
}

Procedemos ahora a crear las vistas principales:

{# src/MDW/DemoBundle/Resources/views/Notice/index.html.twig #}

{% extends 'MDWDemoBundle::layout.html.twig'  %}
{% block content %}
<div>
    <p>Noticias recientes</p>
    <ol>
        {% for index,notice in notices %}
        <li><a href="{{ path('MDWDemoBundle_noticeView', {'notice_id': index}) }}">{{notice.title}}</a></li>
        {% endfor %}
    </ol>
    <div id="notice_viewer">
    {# en esta capa serán cargadas las noticias por ajax #}
    </div>
</div>
{% endblock content %}

{# extendemos el bloque javascript #}
{% block javascripts %}
{{parent()}} {# incluimos las declaraciones de script del layout, como jQuery #}

<script type="text/javascript">
{# añadirmos una función al evento click de todos los enlaces a.notice_link, para
usar AJAX en vez de su comportamiento por defecto #}
$(document).ready(function(){
    $('a.notice_link').click(function(event){
        event.preventDefault(); //cancela el comportamiento por defecto
        $('#notice_viewer').load($(this).attr('href')); //carga por ajax a la capa "notice_viewer"
    });
});
</script>
{% endblock javascripts %}

Como puede notar en index.html.twig se ha extendido el bloque JavaScript para añadir la carga por medio de jQuery.load (AJAX) hacia la capa DIV “notice_viewer”, esto con el objetivo de que si un buscador indexa nuestra página pueda hallar los links íntegros de las noticias sin afectar al SEO, además de que nos permite cargar por AJAX el contenido de nuestras noticias directamente en la capa asignada.

{# src/MDW/DemoBundle/Resources/views/Notice/noticeView.html.twig #}

{# note que en este caso utilizamos el layout de ajax para así no cargar todo el contenido del layout general #}
{% extends app.request.isXmlHttpRequest ? "MDWDemoBundle::layout_ajax.html.twig" : "MDWDemoBundle::layout.html.twig" %}
{% block content %}
<div>
    <h2>{{notice.title}}</h2>
    <p>{{notice.content}}</p>
</div>
{% endblock content %}

Como puede apreciar ahora en noticeView.html.twig se hace una bifurcación de los layouts para cuando se trata de una petición AJAX, en la cual se utiliza el layout principal cuando el enlace es accedido directamente (origen de un buscador, o de un usuario con Javascript desactivado) y al contrario si proviene de AJAX se utiliza un layout especial:

{# /src/MDW/DemoBundle/views/layout_ajax.html.twig #}

{# como puede apreciar, el layout para ajax sólo debe incluir el bloque contenido,
adicionalmente podemos añadir extras #}
<div>
    <strong>Visor de Noticias</strong>
    {% block content %}{% endblock content %}
</div>

De esta forma con Symfony podemos adaptar las respuestas en función de si la petición es AJAX o no, y en este caso devolver sólo el contenido necesario, debido a que en una petición AJAX no es necesario devolver la estructura completa HTML como en una petición normal, sino el fragmento de código que nos interesa.

CONCLUSIÓN

Como bien se explica más arriba, las interacciones Ajax no son parte del framework Symfony ya que para esto usamos JavaScript mientras que Symfony es un framework PHP. Existiendo tantas librerías bien robustas para manejo de Ajax como por ejemplo jQuery, incluímos la que más nos guste y ejecutamos la llamada al ajax. La manera de trabajar con librerías JavaScript en Symfony es simplemente incluírlo como un asset de la misma forma que trabajaríamos con las librerías http://www.tinymce.com o http://lightbox.com. Incluímos el archivo y la programación que hacemos para usarla ya es JavaScript y no PHP.