Curso Symfony 2: Integrando jQuery
En esta ocasión y motivado a sus peticiones con respecto al capítulo "Integrando AJAX" les traemos 2 ejemplos, elaborados con la participación de Juan Ardissone y mi persona, con el objetivo de aclarar sus dudas con respecto al manejo de AJAX en Symfony2 y darles ejemplos prácticos de su uso.
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.
Muy bien!!! ejemplos con JQuery 😀
Hola
En primer lugar, agradeceros el esfuerzo por un curso tan completo, ameno y funcional. Llevo un tiempo siguiendolo y me parece de lo mejor que he visto.
Sobre este tema en general, me preguntaba si podias explicar un poco mas la siguiente sentencia:
{{parent()}} {# incluimos las declaraciones de script del layout, como jQuery #}
Gracias y un saludo
Ello se explico en el capítulo de las Vistas con TWIG (http://www.maestrosdelweb.com/editorial/curso-symfony2-la-vista-twig/), {{parent()}} lo que hace es llamar a el contenido del bloque original en la plantilla heredada, es exactamente lo mismo que llamar al parent::method() en una función sobre-escrita en POO; en este caso lo que hace es incluir la declaración de jQuery en el layout.html.twig, de lo contrario tendrías que declararlo en la plantilla.
Gracias por todo este material viejo, excelente trabajo. “Disculpa” que en capítulo pasado te hice una crítica “sin fundamento” y todo por no leer bien y con calma. Gracias por el material. Saludos desde el Edo. Aragua. Venezuela. 😀
Mis felicitaciones a ambos, Juan y Maycol!, este manual se pone cada vez mejor, y a los demás compañeros: por favor hagamos críticas constructivas!!! Nadie es perfecto, por ese tipo de comentarios alejamos cada vez más a los que se atreven en hacer éste tipo de guías, en vez de protestar y solo quejarse, recomienden, sean amables, todos queremos aprender. Reflexionen.
Muchas gracias por la aclaracion.
Gracias de nuevo!
Maycol, Juan: muchas gracias. Muy bien explicado y concreto. Ya mismo voy a experimentar!
muy bueno el curso. Para cuando lo podremos tener en formato pdf
Una vez finalizado, faltan 2 capítulos, lo más probable que para dentro de 3 semanas
[…] anterior sobre Ajax hemos preparado unos ejemplos de implementación usando jQuery para el capítulo publicado el día de […]
Excelente Articulo, Muchas Gracias Maycol Alvarez!!!!
[…] Integrando jQuery […]
Gracias, amigos es un gran aporte. Esta bien explicado, ya intentare realizar algunas pruebas.
Tengo una pregunda.
Podría utilizarse una extensión de jquery para validar los campos, directamente con jquery, y no con symfony?
Claro que puedes, Pero NO te recomiendo que dejes de lado las validaciones del lado del Servidor, recuerda que las validaciones del lado del cliente (javascript) son adicionales y las que realmente aseguran tu aplicación son las hechas por el servidor, claro que puedes usar ajax para enviar los datos y validar en Symfony/PHP, pero de una vez aplicas lo concerniente a base de datos, NO caigas en validar con ajax, para luego enviar un POST desprotegido, ejemplo claro el de validar captchas vía ajax, una técnica ineficiente y complemente inútil, el captcha debe validarse junto a la data, no antes.
Hola Maycol, me encanta el curso, lo esplicais muy bien y la verdad que se me ha quitado el susto que le tenia a meterle mano a este FW.
Solo queria decirte que en el ejemplo 2 en el archvio de plantilla ‘index.html.twig’ se te ha olvidado añadir a la etiqueta a:
{{notice.title}}
la class=’notice_link’, así que cuando la clickeas no hace la llamada Ajax sino que se va directamente al enlace.
Un saludo, y muchas gracias por el curso.
Muy clarito todo. Gran trabajo.
Una consulta:
Si en la vista base.html.twig pongo este script en el header:
$(document).ready(function(){
$(‘a.BAjax’).click(function(event){
event.preventDefault();
$(‘#contenedorAjax’).load($(this).attr(‘href’));
});
});
funciona correctamente en la primera página, peri si en el html que cargo en el pongo ulgunos con la intención de modificar el contenido de este e ir cangando otros contenidos, ya no funciona el script.
Es este el comportamento normal? debo poner el script en cada html cargado?.
Un saludo y gracias.
Hola,
tengo una cuestión sobre cómo utilizar la función path con variables javascript.
En este capítulo hemos visto cómo cargar el contenido de una noticia en una capa utilizando el atributo href del enlace de la siguiente forma:
$(‘#notice_viewer’).load($(this).attr(‘href’));
Pero, y si necesitara utilizar la función path con alguna variable javascript creada anteriormente, u obtenida de la página. Por ejemplo, supongamos que quiero enlazar los eventos click de los enlaces a la siguiente función (los asteriscos los remarco para que veáis dónde uso la variable javascript):
$(document).ready(function(){
$(“a”).click(function(){
$(“#articulo”).load(” {{ path(“articulo”, { ‘id’: this.id }) }}”);
*****
});
});
…
{% for articulo in articulos %}
{{articulo.titulo}}
{% endfor %}
Esto daría un error ya que this no lo interpreta como una variable javascript. ¿Habría alguna forma de pasarle variables javascript a la función path?
Muchas gracias
Oops!, cierto, bueno ya el PDF está listo, gracias por la info
Recuerda que ése contenido cargado por AJAX aunque tenga la misma estructura no estuvo presente cuando se asignaron los eventos, por lo cual es un contenido añadido al DOM en el futuro, donde debes asignarle los eventos de nuevo, o usar jQuery.live()
Recuerda que PHP es un lenguaje del lado del servidor, estás tratando de mezclar PHP con javascript, debe ser de ésta forma:
$(“#articulo”).load(”{{ path(“articulo”, { ‘id’: ”}) }}” + $(this).id);
¿por qué?: porque path devolverá una ruta, ejemplo “articulo/id/” y si permites que id sea vacío no le especificas ningún parametro, luego cuando llegue a JS se concatena con el ID actual, resultando: $(“#articulo”).load(”articulo/id/” + $(this).id);
NOTA: $(this).id si no te funciona intenta con this.id o $(this).attr(‘id’).
Muchas gracias, me lo has aclarado.
Creía que si pasaba simplemente id sin atributos daría un error, o ni siquiera lo enrutaría bien. Además, como bien dices, estaba mezclando php con javascript, ya que creía que si cerraba la sentencia entre comillas recogería la variable creada anteriormente.
No me cansaré de agradeceros este espléndido manual.
Un saludo
Puedes Colocar un ejemplo de selects de tres niveles con ajax y sf2 ?por favor
Hola a todos el capitulo es muy bueno pero tengo una pregunta al final de todo no me saca nada a la pantalla (blanca) esto debe ser asi ¿?
veo que de todo esta puesta la definición {% block %}
ya o tengo resuelto el problema era que no me carga el dir de JS que tengo el comando de esta manera
y no me mostraba nada de la pagina pero me quito el problema cuando he cambio el codigo con este
de toda manera no se porque el primer codigo no funciono
Hola, muy bueno el manual.
Tengo un problema en el ejemplo 1. Cuando entro a http://localhost/Symfony/web/app_dev.php/ver-articulos para testear el ejemplo no me carga el contenido de la página al hacer click en el link, y cuando voy a ver el código fuente veo que no esta la linea que hace referencia al archivo de jquery. Revisé todo y puedo encontrar porque no aparece.
Pero si esto es genial, esto me ha ilustrado bastante de como se complementa con un FW de para AJAX y parece ser muy sencillo, de verdad que es un maravilloso tutorial, Gracias.
Estoy muy agradecido por su manual que ayuda bastante a entender como trabaja este framework. Tengo una consulta, estoy tratando de usar el autocomplete de jquery ui pero no logro codificar lo que obtengo en el servidor para enviarlo mediante json y manipularlo con javascript. este es el codigo de mi controlador:
public function afiliadosAction()
{
$consulta = new OldRegistros();
$em = $this->getDoctrine()->getEntityManager();
$consulta = $em->getRepository(‘AtencionBundle:OldRegistros’)->findAll();
$response = new Response(json_encode(array(‘datos’ => $consulta)));
$response->headers->set(‘Content-type’, ‘application/json; charset=utf-8’);
return $response;
}
pero si lo ejecuto lo que me devuelve es :
{“datos”:[{},{},{},{},{} ]}
Soy nuevo en synfony, y esto que hacen aca, si lo programan en php yii framework, es extremadamente facil, solamente se crea un “widget” que a su vez encapsula a un componente de interfaz jquery dentro de la vista, al crear el widget se pasa como parametro en el constructor, el nombre del evento o accion “ajax” que se ejecutará en el controlador, luego en el controlador se implementa la accion llamando al modelo para obtener los datos los cuales se envian por “json” para que los consuma el widget que está en la vista y de esa manera se ejecuta una renderizacion parcial. Ya veo que tengo un camino duro para emprender proyectos con synfony. Gracias.
Hola Maycol, este es un excelente articulo para aprender Symfony… actualmente me encuentro desarrollando un poyecto bajo Symfony 2.1, y pues he intentado hacer una petición ajax no con load sino con $.post de JQuery.. bien te comento mi problema…. en mi routing.yml he añadido las siguientes lineas
SASTIIHelpDeskBundle_setDepts:
pattern: /ajax/setDpt
defaults: { _controller: SASTIIHelpDeskBundle:Ciudades:setDpt }
como ya es sabido la petición Ajax es posible realizarla de la siguiente forma
$.post(“{{ path(‘SASTIIHelpDeskBundle_setDepts’) }}”, { idPais: pais },function(data) {
alert(“Data Loaded: ” + data);
});
y pues mi controlador esta hecho de la siguiente manera, pues por lo que se es necesario que el controlador siempre de una respuesta de tipo Response, por ahora no recojo el parametro idPais que envio desde el cliente, pero tengo una variable PHP con una cadena
/**
* Obtenemos la lista de depatamentos de un pais
*/
private function setDptAction()
{
$name=”Sergio Diaz”;
if($name!=””)
{//if the user has written his name
$greeting=’Hello ‘.$name.’. How are you today?’;
$return=array(“responseCode”=>200,
“greeting”=>$greeting);
}
else
{
$return=array(“responseCode”=>400,
“greeting”=>”You have to write your name!”);
}
$return=json_encode($return);//jscon encode the array
return new Response($return,200,
array(‘Content-Type’=>’application/json’));
}
Bien ya teniendo esto, en las anteriores versiones de Symfony me ha funcionado a la perfección, en esta nueva versión 2.1 obtengo el siguiente error bajo el numero de 500
The controller must be a callable (Array(0 => Object(SASTII\HelpDeskBundle\Controller\CiudadesController), 1 => setdptAction) given).
Bien he estado buscando la mejor solución para este problema, pero la documentación de este problema es escasa y la vedad no se a que se refiera con que el controlador deber se un “Callable”, te agradezco si me puedes dar una respuesta.
Te agradecería si puedes crear una articulo explicando como funcionarían estas peticiones de tipo POST en symfony
Muchas gracias
Muchas gracias por el curso sobre symfony, muy didactico y práctico, pude leer y aplicar los ejemplos en menos de dos días y eso es por lo practico del curso, ahora no sólo aplicaré Zend y CodeIgniter para PHP.
Actualmente integró Zend con Doctrine2, pero con symfony es mucho más facil y potente.
Simpático tutorial, muchas gracias por compartirlo con la comunidad.
Mi problema es que no se ajusta a una aplicación con rutas auto-generadas por Sonata Admin e interfaces de Sonata Block. Bueno, lo segundo no es tan problemático pues al fin y al cabo no son mas que elementos DIV modificados por atributos CSS, sin embargo no consigo que una página cargue dentro de un DIV específico (Firebug no muestra ningún error al respecto).
Pero lo que me confunde mas es que se supone debo colocar como parámetro del método load(). Según la bitácora de Symfony2 la ruta usada es _sonata_admin, está ruta existe en el archivo routing.yml pero parece sólo disponible dentro del bundle SonataAdmin pues al hacer ejecutar app/config route:debug no aparece listada dicha ruta. Colocar las rutas a cada acción correspondiente a cada modulo tampoco funciona (lo cual no es la idea pues desperdicia la rutas auto-generadas por SonataAdmin.
Te agradecería que me apuntaras en la dirección correcta, ya que no consigo casos ni iguales ni parecidos en Internet.