<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Cabeza de Ratón &#187; autocompleter</title>
	<atom:link href="http://cabezaderaton.com.ar/tag/autocompleter/feed/" rel="self" type="application/rss+xml" />
	<link>http://cabezaderaton.com.ar</link>
	<description>versus el Amor Letal</description>
	<lastBuildDate>Fri, 02 Jul 2010 17:03:04 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=abc</generator>
		<item>
		<title>AutoCompletado con Symfony</title>
		<link>http://cabezaderaton.com.ar/autocompletado-con-symfony/</link>
		<comments>http://cabezaderaton.com.ar/autocompletado-con-symfony/#comments</comments>
		<pubDate>Fri, 17 Jul 2009 22:28:40 +0000</pubDate>
		<dc:creator>retrofox</dc:creator>
				<category><![CDATA[Symfony]]></category>
		<category><![CDATA[autocompleter]]></category>
		<category><![CDATA[sfFormExtraPlugin]]></category>
		<category><![CDATA[sfWidgetFormPropelJQueryAutocompleter]]></category>

		<guid isPermaLink="false">http://cabezaderaton.com.ar/?p=431</guid>
		<description><![CDATA[Denominamos &#8216;Autocompletado&#8217;  al comportamiento que se produce en campos de texto especiales donde a medida que tipeamos este nos devuelve, generalmente en forma de lista, los resultados de una búsqueda en función del texto introducido. Es muy útil cuando consideramos que la cantidad de opciones de un campo tipo select es muy grande; por ejemplo, [...]]]></description>
			<content:encoded><![CDATA[<p>Denominamos &#8216;Autocompletado&#8217;  al comportamiento que se produce en campos de texto especiales donde a medida que tipeamos este nos devuelve, generalmente en forma de lista, los resultados de una búsqueda en función del texto introducido.</p>
<p>Es muy útil cuando consideramos que la cantidad de opciones de un campo tipo select es muy grande; por ejemplo, un listado de países.<span id="more-431"></span></p>
<h2>Consideraciones Iniciales</h2>
<p>En este ejemplo se muestra como implementar los scripts necesarios. Utilizaremos el widget sfWidgetPropelChoice con algunas funcionalidades adicionales incluidas en el plugin <a title="sfFormExtraPlugin" href="http://www.symfony-project.org/plugins/sfFormExtraPlugin" target="_blank">sfFormExtraPlugin</a> [1].</p>
<h3>Instalando el plugin</h3>
<p>No vamos a perder mucho tiempo en esto; simplemente:</p>
<pre class="brush: jscript;">
$ symfony plugin:install sfFormExtraPlugin
$ symfony clear:cache
</pre>
<p>Si surgen dudas puedes consultar el <a href="http://www.symfony-project.org/plugins/sfFormExtraPlugin" target="_blank">readme</a> de la doc.</p>
<h3>Agregando jQuery</h3>
<p>Por si no lo comenté anteriormente, necesitamos el framework de javascript <em>jQuery</em> para nuestro input de autocompletado. El plugin <em>sfFormExtraPlugin</em> no lo incluye por lo que tendremos que hacerlo a manopla. Puedes descargar el framework desde este <a title="Descargar jQuery" href="http://docs.jquery.com/Downloading_jQuery" target="_blank">link</a> [2]. En estos momentos yo he descargado jquery-1.3.2.min.js.</p>
<p>Luego tienes que incluirlo en tu aplicación, para eso edita el archivo view.yml que se encuentra en la carpeta /config de la misma. Algo así:</p>
<pre class="brush: jscript;">
  javascripts:    [ jquery-1.3.2.min.js ]
</pre>
<p>Puedes verificar si sQuery es cargado por la aplicación ojeando el código html generado; hasta puedes copiar la ruta del script y cargarla en el del browser para la comprobación final.</p>
<h2>Schema de nuestro ejemplo.</h2>
<p>Usaremos como ejemplo una tabla denominada <em>producto</em> y otra <em>categoria</em> para la relación 1-n:</p>
<pre style="font-size: 10px">propel:
  categoria:
    _attributes: { phpName: Categoria, idMethod: native }
    id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
    nombre: { type: varchar(50), required: true }
    descripcion: { type: longvarchar }

  producto:
    _attributes: { phpName: Producto, idMethod: native }
    id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
    codigo: { type: varchar(20), required: true }
    nombre: { type: varchar(100), required: true }
    marca: { type: varchar(80) }
    descripcion: { type: longvarchar }
    categoria_id: { type: integer, foreignTable: categoria, foreignReference: id }
</pre>
<p>Creamos una aplicación y para simplificar la existencia generamos un módulo administrativo con el generador de propel. En mi caso yo generé la aplicación <strong>pruebas</strong> y el módulo a partir de la clase <strong>Producto</strong>.</p>
<pre>$ symfony generate:app pruebas
$ propel:generate-admin pruebas Producto
</pre>
<h3>Configurando el widget</h3>
<p>Como todo hijo de vecino sabe Propel genera clases del modelo (ORM) y también las clases de formulario del modelo entre otras cositasa más. Tendremos que redefinir el widget para nuestro campo <em>categoria_id</em>.</p>
<p>Entonces, dentro de la clase <em>ProductoForm.class.php</em> definiremos:</p>
<pre class="brush: php;">
  public function configure() {
   // ..
    $this-&gt;widgetSchema['categoria_id'] = new sfWidgetFormChoice(array(
      'choices'          =&gt; array(),
      'renderer_class'   =&gt; 'sfWidgetFormPropelJQueryAutocompleter',
      'renderer_options' =&gt; array(
        'model' =&gt; 'Categoria',
        'url'   =&gt; $this-&gt;getOption('url')
      )
    ));
  // ..
  }
</pre>
<p>Acá es donde invocamos a la clase <em>sfWidgetFormPropelJQueryAutocompleter</em> definida en el archivo <em>sfWidgetFormPropelJQueryAutocompleter.class.php</em> que es agregada en el plugin <em>sfFormExtraPlugin</em>.</p>
<p>La usamos como clase de renderizado en el parámetro <strong>renderer_class</strong>. También definimos el modelo &#8216;<em>Categoria</em>&#8216; que usamos para la relacion 1-n y finalmente definimos &#8216;url&#8217; mediante el método <em>getObject(&#8216;url&#8217;)</em>;</p>
<p>El parámetro &#8216;url&#8217; define la dirección url que utilizará javascript cuando realice las búsquedas, a través de AJAX, de las distintas categorías para nuestro ejemplo. Con &#8216;<em>$this-&gt;getObject(&#8216;url&#8217;)&#8217;</em> esperamos que este valor &#8216;venga&#8217; cuando creamos el objeto de formulario; por lo que tendremos que definirlo.</p>
<h3>En el controlador &#8230;</h3>
<p>En el controlador, en nuestra clase de acciones, vamos a redefinir el método <em>executeNew</em>.</p>
<pre class="brush: php;">
  public function executeNew(sfWebRequest $request)
  {
    $this-&gt;producto = new Producto ();
    $this-&gt;form = new ProductoForm ($this-&gt;producto, array('url' =&gt; $this-&gt;getController()-&gt;genUrl('producto/request2Autoomplete')));
  }
</pre>
<p>Hemos creado un nuevo objeto de formulario donde se ha pasado dentro del array de opciones el parámetro &#8216;<em>url</em>&#8216; con el valor del link (renderizado) de la acción &#8216;<em>producto/autocomplete</em>&#8216;.</p>
<p>Esto significa que el browser, gracias a los scripts de javascript, hará que cada vez que escribamos en el input se produzca un request a esta acción y es esta misma quien deberá &#8216;contestar&#8217; ese pedido con los datos (en formato JSON) de la búsqueda en la tabla <em>&#8216;categoria&#8217;</em> dentro de la base de datos.</p>
<p>Entonces nosotros hemos definido el nombre de la acción<em> &#8216;autoComplete</em>&#8216; de nuestro módulo <em>&#8216;producto&#8217;</em> por lo que tendremos que programarla:</p>
<pre class="brush: php;">
  public function executeAutoComplete($request)
  {
    $this-&gt;getResponse()-&gt;setContentType('application/json');
    $categorias = CategoriaPeer::retrieveForAutoSelect($request-&gt;getParameter('q'), $request-&gt;getParameter('limit'));
    return $this-&gt;renderText(json_encode($categorias));
  }
</pre>
<p>En esta acción hemos definido el content-type de la página de respuesta como application/json, creado una variable $categorias que son todas las categorías que nos devuelve el método de la clase <em>CategoriaPeer</em> denominado <em>&#8216;retrieveForAutoSelect&#8217;</em> y finalmente renderizamos la respuesta de la acción transformando el array $categorias a notación <a href="http://es.wikipedia.org/wiki/JSON" target="_blank">JSon</a> [3].</p>
<p>Por si no te has dado cuenta el método peer <em>&#8216;retrieveForAutoSelect&#8217;</em> no existe. Amigo .. a arremangarse.</p>
<h3>El método Peer</h3>
<p>Finalmente debemos programar el método Peer que nos devuelve un array con todas las categorías encontradas en función de los datos que vamos introduciendo en el campo de texto:</p>
<pre class="brush: php;">
  static public function retrieveForAutoSelect($q, $limit)
  {
    $criteria = new Criteria();
    $criteria-&gt;add(CategoriaPeer::NOMBRE, '%'.$q.'%', Criteria::LIKE);
    $criteria-&gt;addAscendingOrderByColumn(CategoriaPeer::NOMBRE);
    $criteria-&gt;setLimit($limit);

    $categorias = array();
    foreach (CategoriaPeer::doSelect($criteria) as $categoria)
    {
      $categorias[$categoria-&gt;getId()] = (string) $categoria;
    }

    return $categorias;
  }
</pre>
<h2>Final.</h2>
<p>Con todo lo realizado deberíamos tener funcionando nuestro autocompletado en el campo de las categorías. He subido un ejemplo onLine con el comportamiento correcto para que se pueda apreciar el funcionamiento.</p>
<p>Al agregar un <a title="Producto Nuevo" href="http://cabezaderaton.com.ar/symfony/web/index.php/producto/new" target="_blank">producto nuevo</a> [6] se aprecia el funcionamiento en el campo <em>&#8216;categoria_id&#8217;</em>. Puedes agregar <a title="Más Categrías" href="http://cabezaderaton.com.ar/symfony/web/index.php/categoria/new" target="_blank">más categorías</a> [7] para que el ejemplo funcione mejor.</p>
<p>Gran parte de mi comprensión sobre este tema se debe a la traducción de formularios de librosWeb.es [4], hoy por hoy material imprescindible para el buen aprendizaje de symfony en idioma castellano; y al artículo <em><a title="Haz tu elección" href="http://www.puentesdiaz.com.ar/symfony/haz-tu-eleccion.php" target="_blank">Haz tu Elección</a> </em>de Roberto Puentes Díaz [5].</p>
<p>Saludos &#8230;</p>
<h4>Enlaces</h4>
<p>[1] <a href="http://www.symfony-project.org/plugins/sfFormExtraPlugin" target="_self">http://www.symfony-project.org/plugins/sfFormExtraPlugin</a><br />
[2] <a href="http://www.symfony-project.org/plugins/sfFormExtraPlugin" target="_blank">http://docs.jquery.com/Downloading_jQuery</a><br />
[3] <a href="http://es.wikipedia.org/wiki/JSON" target="_blank">http://es.wikipedia.org/wiki/JSON</a><br />
[4] <a href="http://www.librosweb.es/symfony_formularios/capitulo12/widgets_para_elecciones.html" target="_blank">http://www.librosweb.es/symfony_formularios/capitulo12/widgets_para_elecciones.html</a><br />
[5] <a title="Haz tu elección" href="http://www.puentesdiaz.com.ar/symfony/haz-tu-eleccion.php">http://www.puentesdiaz.com.ar/symfony/haz-tu-eleccion.php</a><br />
[6] <a title="Agregar Producto Nuevo" href="http://cabezaderaton.com.ar/symfony/web/index.php/producto/new" target="_blank">http://cabezaderaton.com.ar/symfony/web/index.php/producto/new</a><br />
[7] <a title="Agregar Categoría nueva" href="http://cabezaderaton.com.ar/symfony/web/index.php/categoria/new" target="_blank">http://cabezaderaton.com.ar/symfony/web/index.php/producto/new</a></p>
]]></content:encoded>
			<wfw:commentRss>http://cabezaderaton.com.ar/autocompletado-con-symfony/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
