Autocompletado de un campo en Symfony2

He visto que hay mucha gente que pregunta acerca de este tema. Hay bundles como GenemuFormBundle que nos proporcionan esta funcionalidad, pero en este artículo vamos a ver qué pasos hay que seguir para crearnos campos con autocompletado y haremos un ejemplo para poder verlo en acción. Hay que tener en cuenta que hay muchas formas de hacer esto, aquí veremos una de ellas.

Los campos con autocompletado son muy útiles, por ejemplo, cuando tenemos muchos registros de una entidad y el usuario debe seleccionar uno de estos registros. De esta forma no se cargan todos los registros disponibles y se van cargando bajo demanda.

Antes de empezar con el código, vamos a ver qué necesitamos, en el backend necesitaremos un formulario con el campo que queramos autocompletar y una acción en un controlador (aparte de la de mostrar el formulario) que será la que se llame cuando escribamos algo en el campo y nos mande las sugerencias. En el frontend necesitaremos el código JavaScript para que cada vez que escribamos en el campo llamemos a la acción que hemos creado, recuperar las sugerencias que nos envíe, mostrarlas y cuando el usuario pinche en un resultado, seleccionar ese resultado. Para la parte del frontend de JavaScript usaremos jQuery UI Autocomplete.

Vamos a empezar con el ejemplo que va a consistir en un formulario donde el usuario indica en qué ciudad vive y para elegir la ciudad será con autocompletado.

Backend

Lo primero será crear el formulario:

Este sería el formulario que usaríamos normalmente (no tiene asociada una clase en data_class para que el código sea más corto), pero hay que modificarlo porque no queremos que cargue todas las entities al mostrar el campo y el Type entity no nos permite esto, por lo que tendremos que crearnos nuestro propio Type.

Para hacerlo lo más sencillo posible, este nuevo Type que le llamaremos autocomplete_entity será un campo hidden, sólo guardará el id de la ciudad y en la vista imprimiremos el campo hidden y otro input de tipo text que será sobre el que funcionará el autocomplete y cuando seleccionemos una ciudad actualizaremos el campo hidden. Como lo que nosotros queremos es una Entity en nuestro modelo, tendremos que añadirle un DataTransformer para pasar del id a la Entity y al contrario. El nuevo Type quedará así:

El Type recibe el ObjectManager para pasarlo al DataTransformer que veremos a continuación. Hemos configurado como obligatorios los atributos class y update_route, la class la usaremos en el DataTransformer para cuando nos llegue el id saber a qué entidad tenemos que transformarlo y update_route será la ruta que se le llame conforme vamos escribiendo y nos devuelva las sugerencias. Hemos añadido esta ruta en el Type para que de esta forma el código JavaScript pueda ser genérico y poder incluir varios campos autocomplete en el mismo formulario, por lo tanto hay que pasar el parámetro a la vista. Como hemos comentado antes, este Type será un input hidden.

El DataTransformer EntityToIdTransformer creo que no hace falta explicarlo, es muy simple y tiene la siguiente pinta:

Teniendo estas piezas nos queda registrar el Type al que le pasamos el EntityManager (este xml estará cargado en la Extension del Bundle):

Y cambiar el SelectCityType para que use nuestro nuevo Type:

Finalmente lo que nos queda por hacer en el backend es agregar la acción que se le llamará por AJAX para obtener el listado de ciudades que cumple con un criterio (la que hemos indicado en el update_route):

El método findByTerm devolverá sólo el id y el name de las ciudades.

Frontend

Sólo vamos a usar un fichero, lo haremos muy sencillo como habíamos comentado, creo que es mejor ver el código y luego repasarlo:

La parte más importante es la que está marcada (L3-6) y es donde está definido en bloque que corresponde a cómo se renderizará el campo autocomplete_entity. Este bloque debería estar en otro fichero aparte para poder usarlo en cualquier formulario, pero así vemos todo el código junto. Aquí se imprimirá el campo hidden y un input de tipo text que será donde iremos escribiendo, este input tiene dentro unos atributos especiales:

  • data-id: Hace referencia al id (HTML) del campo hidden donde se guardará la Entity (se guardará el id de la Entity) que seleccionemos.
  • data-url:  Es la ruta que debe llamar el autocomplete cada vez que escribimos algo.

Luego añadimos los ficheros jQuery UI necesarios y el código JavaScript correspondiente que lo único que hace es recorrer los campos con la clase autocomplete y usar la función autocomplete de jQuery UI. Cuando seleccionamos un elemento cambia el valor del campo hidden. Finalmente está el bloque del formulario.

Observaciones

Como he comentado al principio esto es una forma de hacerlo, la idea del artículo es ver internamente cómo funciona o qué partes se deben tocar, sobre esto se pueden ir añadiendo funcionalidades según queramos. Igual no queremos que llame a una ruta y las sugerencias vengan de un array que le pasemos, en este caso update_route será opcional y debemos añadir otro atributo al Type para las sugerencias.

Otra cosa importante es que hay que tener en cuenta que este tipo de campos requieren de JavaScript (como los select dependientes), por lo que siempre tendremos que añadir código JavaScript en la vista, no nos va a valer sólo con el Type. Una posible solución es crear una función en Twig que imprima el JavaScript (y css) necesario, como hace GenemuFormBundle.

El resultado final se puede ver en el ejemplo.

Ver Demo