<?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>Desarrollosweb.net &#187; desarrollo web</title>
	<atom:link href="http://www.desarrollosweb.net/category/desarrollo-web/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.desarrollosweb.net</link>
	<description>Desarrollo de webs y aplicaciones</description>
	<lastBuildDate>Fri, 26 Mar 2010 10:53:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Internet Explorer 8 &#8211; Suscripción a Contenidos con Web Slices</title>
		<link>http://www.desarrollosweb.net/2009/03/09/internet-explorer-8-suscripcion-a-contenidos-con-web-slices/</link>
		<comments>http://www.desarrollosweb.net/2009/03/09/internet-explorer-8-suscripcion-a-contenidos-con-web-slices/#comments</comments>
		<pubDate>Mon, 09 Mar 2009 13:31:45 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[Internet Explorer 8]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[suscripcion contenidos]]></category>
		<category><![CDATA[web slices]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=85</guid>
		<description><![CDATA[Hasta ahora, cuando una persona se quería subscribir al contenido de una página tenía como opción principal la sindicación a un feed RSS. Este tipo de subscripción requiere que ciertos contenidos (a los que el usuario se quiere subscribir) se tengan que duplicar en un fichero especial definido en XML. Los Web Slices, (introducidos por [...]]]></description>
			<content:encoded><![CDATA[<p>Hasta ahora, cuando una persona se quería subscribir al contenido de una página tenía como opción principal la sindicación a un feed RSS. Este tipo de subscripción requiere que ciertos contenidos (a los que el usuario se quiere subscribir) se tengan que duplicar en un fichero especial definido en XML.</p>
<p>Los <strong>Web Slices</strong>, (introducidos por Microsoft en su nuevo navegador<strong> Internet Explorer 8.0</strong>) son, sin embargo, “trozos” de una web a los que un usuario se puede subscribir directamente sin necesidad de especificar un fichero especial.<br />
Los puntos clave de los <strong>Web Slices</strong>:</p>
<ul>
<li>Se desarrollan en HTML, de hecho forman parte del contenido de nuestra web. Para indicar al navegador qué partes de nuestra web son Web Slices, tenemos que hacer uso de ciertas clases CSS.</li>
<li>El propio <strong>Web Slice</strong> puede indicar al navegador cada cuanto se actualiza, aunque el tiempo mínimo que se puede indicar es de 15 minutos.</li>
</ul>
<h1><strong>Mini-tutorial de desarrollo.</strong></h1>
<h2>Introducción.</h2>
<p>Los Web Slices están basados en el formato <a title="hAtom format documentation" href="http://microformats.org" target="_blank">hAtom [http://microformats.org]</a>, al que se le han añadido ciertas propiedades. Está desarrollado completamente en HTML, por lo que el contenido al que un usuario se puede subscribir, como se dijo anteriormente, forma parte del contenido de nuestra página.</p>
<p>Esto permite, por ejemplo, que podamos permitir que un usuario se subscriba directamente a un listado de noticias de nuestro portal únicamente haciendo click en cierto área (que definimos nosotros previamente).<br />
Básicamente lo que hace Internet Explorer 8.0 para descubrir los <strong>Web Slices</strong> de una web es recorrer todo el código HTML buscando aquellos elementos que pertenecen a la clase CSS <em>hslice</em>, están identificados una propiedad id única y tienen por lo menos un elemento cuya clase CSS es <em>entry-title</em>.</p>
<h2>Descubrimiento y subscripción de slices.</h2>
<p>Cuando un usuario pasa el puntero del ratón por encima de estos elementos, se remarcan con un recuadro, a la vez que se muestra un icono específico que se ha diseñado para este formato de subscripción.</p>
<p><img class="aligncenter size-medium wp-image-86" title="Web Slice discovery" src="http://www.desarrollosweb.net/wp-content/captura_1-300x128.png" alt="Web Slice discovery" width="300" height="128" /></p>
<p>Además el navegador, mostrará todos los Web Slices detectados para la página actual desplegando un menú que se encuentra en la barra de tareas del navegador.</p>
<p><img class="aligncenter size-full wp-image-89" title="captura_3" src="http://www.desarrollosweb.net/wp-content/captura_3.png" alt="captura_3" width="341" height="131" /></p>
<p>Si el usuario se subscribe a uno de estos contenidos, se añadirá a la barra superior.</p>
<p><img class="aligncenter size-medium wp-image-90" title="captura_2" src="http://www.desarrollosweb.net/wp-content/captura_2-300x88.png" alt="captura_2" width="300" height="88" /></p>
<p>Además, si el <strong>Web Slice</strong> tiene un elemento marcado como <em>entry-content</em>, este se mostrará cuando el usuario haga click en la barra superior sobre el título del contenido subscrito.  Además no hace falta que el usuario esté visitando la web del contenido, como se puede ver a continuación.</p>
<p><img class="aligncenter size-medium wp-image-91" title="captura_4" src="http://www.desarrollosweb.net/wp-content/captura_4-300x140.png" alt="captura_4" width="300" height="140" /></p>
<p>Hay que puntualizar una cosa, el título mostrado en la barra superior será la concatenación de todos los elementos entry-title que tenga el Web Slice, es decir, podemos formar el título de la subscripción con diferentes trozos del contenido.</p>
<p>El código de un Web Slice básico es muy sencillo, como se puede ver.</p>
<pre class="html" name="code">
<div id="mainSlice" class="hslice">
<h2 class="entry-title">Hello world web slice!</h2>

This is my first web slice, remember<span class="entry-title"> my first web slice</span> don't ask me too much questions 

This slice will be updated every <abbr class="ttl" title="30">thirty minutes</abbr>.
<div class="entry-content">

This is the content that IE8 will show in the taskbar</div>

<a style="display:none;" rel="feedurl" href="http://appsrv02.desarrollosweb.net/slidefeed.php?feed=main"></a><a>
</a></div>
</pre>
<h2>Actualización de contenido.</h2>
<p>Los usuarios que estén subscritos a un <strong>Web Slice</strong> serán avisados de las actualizaciones de los contenidos. Para ello el navegador visitará periódicamente la página web donde se encuentra el contenido. Por esta razón es muy importante que los web-slices mantengan siempre el mismo id.</p>
<p>Si la web no ha indicado un intervalo de actualización por defecto toma el de 24 horas. Para indicar un tiempo distinto de actualización se utiliza un elemento HTML cuya clase CSS es ttl (time to live) y cuyo contenido es numérico.</p>
<pre class="html" name="code"><a>This slice will be updated every <span class="ttl">60</span>.
</a></pre>
<p>Aunque también tenemos otra opción que es indicar el tiempo en el campo title de un elemento abbr.</p>
<pre class="html" name="code"><a>This slice will be updated every <abbr class="ttl" title="30">thirty minutes</abbr>.</a></pre>
<p>Otra forma de indicar cuando un contenido ha caducado es utilizando un elemento de la clase CSS endtime con un valor title indicando un timestamp en el formato:</p>
<ul>
<li>
<p>YYYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]</p>
</li>
</ul>
<pre class="html" name="code"><abbr class="”endtime”" title="”2009-03-07T20:00:00+01:00”"><a>Caducará a las 20:00 del día 7 de marzo de 2009</a></abbr></pre>
<p>Debido a que por defecto el navegador vuelve a visitar la página que contiene el Slice y la pide entera, esto puede sobrecargar nuestros sites. Para evitar esta situación se puede indicar una URL alternativa que sólo devuelva el trozo de HTML del Web Slice.</p>
<p>Para ello se utiliza un elemento enlace que tenga las siguientes propiedades:</p>
<pre class="html" name="code"><a style="display:none;" rel="feedurl" href="http://appsrv02.desarrollosweb.net/slidefeed.php?feed=main"></a></pre>
<p>De esta forma, el navegador en vez de descargarse todo el contenido de nuestra web sólo pedirá el trozo HTML necesario al feeder.</p>
<h2>Visualización.</h2>
<p>Como se dijo anteriormente, todo aquellos elementos que pertenezcan a la clase entry-content, se mostrarán concatenados cuando el usuario pulse sobre el título del contenido subscrito en la barra superior de IE8.</p>
<p>Lo que hace el navegador es mostrar el contenido del elemento de la clase hslice dentro de una etiqueta BODY. Además de poder indicar cuál es la URL de actualización de un contenido, podemos indicar también qué URL nos devuelve el contenido de visualización.</p>
<pre class="html" name="code"><a style="display:none;" rel="entry-content" href="http://appsrv02.desarrollosweb.net/viewfeed.php?feed=main"></a></pre>
<p>De esta forma cuando el usuario pinche sobre el contenido subscrito se mostrará el contenido indicada por este elemento, en lugar del original de la página.</p>
<p>Es posible mezclar la URL alternativa para actualización de contenido y la URL alternativa de visualización. Para que esto funcione en el Web Slice principal se debe indicar únicamente la URL de actualización:</p>
<pre class="html" name="code"><a style="display:none;" rel="feedurl" href="http://appsrv02.desarrollosweb.net/slidefeed.php?feed=main"></a></pre>
<p>Y el feed que devuelve la actualización (en este caso slidefeed.php) debe devolver la información necesaria para mostrar la información.</p>
<pre class="html" name="code">
<div id="mainSlice" class="hslice">
<h2 class="entry-title"><a>Hello world web slice!</a></h2>
<div class="entry-content">

<a>This is the content that IE8 will show in the taskbar
</a></div>

<a>		</a><a style="display:none;" rel="entry-content" href="http://appsrv02.desarrollosweb.net/slidefeed.php?feed=main"></a><a>
	</a></div>
</pre>
<h2>Navegación.</h2>
<p>Cuando el usuario pre-visualiza el contenido del Slice, tiene la posibilidad de ir a la web original de donde. Para ello IE8 provee un botón:</p>
<p>Es posible indicar que se vaya a una página distinta de la original, para ello hay que utilizar un elemento como el que mostramos a continuación:</p>
<pre class="html" name="code"><a style="display:none;" rel="bookmark" href="http://appsrv02.desarrollosweb.net/alternativecontent.php?feed=main"></a></pre>
<h2>Referencias.</h2>
<ul>
<li><a title="MSDN Web Slices" href="http://msdn.microsoft.com/en-us/library/cc956158(VS.85).aspx" target="_blank">MSDN. Web Slices [http://msdn.microsoft.com/en-us/library/cc956158(VS.85).aspx]</a></li>
<li><a title="MSDN Web Slices - Subscribing to content" href="http://msdn.microsoft.com/en-us/library/cc196992.aspx" target="_blank">MSDN. Subscribing to Content with Web Slices [http://msdn.microsoft.com/en-us/library/cc196992.aspx]</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2009/03/09/internet-explorer-8-suscripcion-a-contenidos-con-web-slices/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Ext JS &#8211; Primera conferencia Anual y presentación de Ext JS 3.0</title>
		<link>http://www.desarrollosweb.net/2009/03/06/ext-js-primera-conferencia-anual-y-presentacion-de-ext-js-30/</link>
		<comments>http://www.desarrollosweb.net/2009/03/06/ext-js-primera-conferencia-anual-y-presentacion-de-ext-js-30/#comments</comments>
		<pubDate>Fri, 06 Mar 2009 09:28:04 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[Ext JS]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[web 2.0]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=83</guid>
		<description><![CDATA[Jack Slocum y su equipo anuncian la celebración de la primera conferencia de Ext JS que se celebrará del 14 al 16 de Abril en Orlando, Florida, concretamente en el hotel Ritz-Carlton Grande Lakes. La conferencia pretende ser un punto de encuentro entre todos los usuarios de Ext JS, aunque también se aprovechará para hablar [...]]]></description>
			<content:encoded><![CDATA[<p>Jack Slocum y su equipo anuncian la celebración de la primera conferencia de <strong>Ext JS</strong> que se celebrará del 14 al 16 de Abril en Orlando, Florida, concretamente en el <em>hotel Ritz-Carlton Grande Lakes</em>.</p>
<p>La conferencia pretende ser un punto de encuentro entre todos los usuarios de <strong>Ext JS</strong>, aunque también se aprovechará para hablar de las novedades que el equipo de Ext JS nos tiene preparadas: <strong>Ext JS 3.0</strong>,<strong> Ext GWT 2.0</strong> y <strong>Ext Core 1.0</strong>.</p>
<p>El precio de la subscripción es de 250$ y si decides hacer noche en el hotel los organizadores han conseguido pactar un precio de 149$ la noche.</p>
<p>La conferencia se ha dividido en 24 sesiones en las que se tratarán diversos temas: novedades en los frameworks, optimización de rendimiento, buenas prácticas en el código, etc. pasando incluso por una sesión dedicada a javascript básico y temas Web 2.0 en general.</p>
<p>Parece ser que <strong>Ext JS 3.0</strong> viene cargado de novedades y no sólo estamos hablando de nuevos widgets, sino funcionalidades pensadas a mejorar la estructuración del código y facilitar la integración con servicios de datos.</p>
<p>Os mantendremos informados. De momento podéis consultar más información en los siguientes enlaces:</p>
<ul>
<li><a title="Ext JS Roadmap" href="# http://extjs.com/products/extjs/roadmap.php" target="_blank">http://extjs.com/products/extjs/roadmap.php</a></li>
<li><a title="Ext JS 2009 Conference" href="http://www.extjs.com/conference/" target="_blank">http://www.extjs.com/conference/</a></li>
<li><a title="Ext JS Blog, conference 2009" href="http://extjs.com/blog/2009/02/03/ext-conference-2009/" target="_blank">http://extjs.com/blog/2009/02/03/ext-conference-2009/</a></li>
<li><a title="Ext JS 2009 Conference Sessions" href="http://extjs.com/conference/sessions/" target="_blank">http://extjs.com/conference/sessions/</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2009/03/06/ext-js-primera-conferencia-anual-y-presentacion-de-ext-js-30/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Aptana Cloud, una nueva forma de hosting</title>
		<link>http://www.desarrollosweb.net/2009/03/05/aptana-cloud-una-nueva-forma-de-hosting/</link>
		<comments>http://www.desarrollosweb.net/2009/03/05/aptana-cloud-una-nueva-forma-de-hosting/#comments</comments>
		<pubDate>Thu, 05 Mar 2009 17:43:09 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[IT - Tecnologías de la información]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[aptana]]></category>
		<category><![CDATA[cloud computing]]></category>
		<category><![CDATA[hosting]]></category>
		<category><![CDATA[IDE]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=75</guid>
		<description><![CDATA[Como podemos ver en la web de Aptana, a través de la última versión de su IDE podemos acceder a una nueva forma de entender el hosting de aplicaciones web basada en Cloud Computing. Lo realmente nuevo de la solución aportada por Aptana, es que todos los parámetros y configuración de la instancia se pueden [...]]]></description>
			<content:encoded><![CDATA[<p>Como podemos ver en la web de <strong>Aptana</strong>, a través de la última versión de su IDE podemos acceder a una nueva forma de entender el hosting de aplicaciones web basada en <strong>Cloud Computing</strong>. Lo realmente nuevo de la solución aportada por Aptana, es que todos los parámetros y configuración de la instancia se pueden manejar desde el propio IDE, así como la subida a producción, la sincronización de ficheros y el repositorio de desarrollo.</p>
<p>Lo interesante de la solución propuesta por Aptana (como ya comenté al hablar de Cloud Computing) es que el precio es realmente bajo y asequible. Para aplicaciones que consuman pocos recursos o como máquina de desarrollo podemos utilizar una instancia en la &#8220;nube&#8221; de Aptana desde 0,04$ (menos de 1 euro diario).</p>
<div>Como una imagen (en este caso un video) vale más que 1000 palabras, aquí podéis ver dos videos (en inglés) que muestran de una forma muy rápida cómo publicar aplicaciones web con muy pocos clicks.</div>
<div><strong>Introducción a Aptana Cloud</strong></div>
<p><object width="500" height="313" data="http://tv.aptana.com/images_flash/player_500.swf" type="application/x-shockwave-flash"><param name="id" value="player_500" /><param name="align" value="middle" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /><param name="FlashVars" value="context=embed&amp;base=http://tv.aptana.com/&amp;movie=http%3A%2F%2Ftv.aptana.com%2Fvideos%2Fintroduction-to-cloud" /><param name="quality" value="high" /><param name="bgcolor" value="#000000" /><param name="src" value="http://tv.aptana.com/images_flash/player_500.swf" /><param name="name" value="player_500" /><param name="flashvars" value="context=embed&amp;base=http://tv.aptana.com/&amp;movie=http%3A%2F%2Ftv.aptana.com%2Fvideos%2Fintroduction-to-cloud" /><param name="allowfullscreen" value="true" /></object></p>
<p><strong>Desarrollo con PHP, Aptana Studio y Aptana Cloud</strong></p>
<div>
<div><object width="500" height="313" data="http://tv.aptana.com/images_flash/player_500.swf" type="application/x-shockwave-flash"><param name="id" value="player_500" /><param name="align" value="middle" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /><param name="FlashVars" value="context=embed&amp;base=http://tv.aptana.com/&amp;movie=http%3A%2F%2Ftv.aptana.com%2Fvideos%2Fworking-with-php-and-aptana-cloud" /><param name="quality" value="high" /><param name="bgcolor" value="#000000" /><param name="src" value="http://tv.aptana.com/images_flash/player_500.swf" /><param name="name" value="player_500" /><param name="flashvars" value="context=embed&amp;base=http://tv.aptana.com/&amp;movie=http%3A%2F%2Ftv.aptana.com%2Fvideos%2Fworking-with-php-and-aptana-cloud" /><param name="allowfullscreen" value="true" /></object>Más información en:</p>
<ul>
<li><a title="Sitio web de aptana" href="http://www.aptana.com/cloud" target="_blank">Aptana sitio web oficial</a></li>
<li><a title="JohnBs Personal Blog" href="http://john.beynon.org.uk/2008/11/11/digging-deeper-into-aptana-cloud/" target="_blank">John Bs personal Blog</a></li>
</ul>
</div>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2009/03/05/aptana-cloud-una-nueva-forma-de-hosting/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Factores relevantes de SEO (Search Engine Optimization) en una página Web, o como hacer páginas “Google friendly”</title>
		<link>http://www.desarrollosweb.net/2009/03/04/factores-relevantes-de-seo-en-una-web-o-como-hacer-paginas-google-friendly/</link>
		<comments>http://www.desarrollosweb.net/2009/03/04/factores-relevantes-de-seo-en-una-web-o-como-hacer-paginas-google-friendly/#comments</comments>
		<pubDate>Wed, 04 Mar 2009 13:12:31 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[SEO]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[optimizacion sites]]></category>
		<category><![CDATA[reglas diseño]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=60</guid>
		<description><![CDATA[Son muchos y muy extensos los factores que influyen en el indexado correcto de una página web, uno de ellos (y muy importante es el tiempo). Con esto queremos decir que es muy difícil que Google (o cualquier otro buscador) le dé importancia a tu web si hace apenas unos meses que ha echado a [...]]]></description>
			<content:encoded><![CDATA[<p>Son muchos y muy extensos los factores que influyen en el indexado correcto de una página web, uno de ellos (y muy importante es el tiempo). Con esto queremos decir que es muy difícil que Google (o cualquier otro buscador) le dé importancia a tu web si hace apenas unos meses que ha echado a andar.</p>
<p>Además, cuanto más tiempo haya pasado más contenidos habrás generado, lo cual es otro punto muy importante a tener en cuenta: más contenidos, más posibilidades de que te indexen. El paso del tiempo también influirá en cuanta gente habrá visitado tu página, y con ello el número de enlaces que estarán apuntando a ella. Pero en este documento vamos a ir más allá del factor tiempo, que por sí solo no va a hacer que tu página sea mejor indexada o más visitada.</p>
<p><strong>1. Cuida la cantidad de texto en una página</strong>.</p>
<p>Una página que tenga poco texto a los ojos de un buscador será un mero listado. Es por ello que hay que prestar atención a la cantidad de información que se publica. Los buscadores le dan más peso a las páginas que tienen una gran cantidad de contenido informativo. El número de palabras de cada página en tu web deberá rondar entre 500 y 3000.</p>
<p><strong>2. El número de palabras clave (keywords) también importa.</strong></p>
<p>¿Cuándo estás buscando cierto contenido en un libro por donde sueles empezar a buscar? Normalmente buscas en el índice en qué página se cita cierta palabra que para ti es clave. Pues en internet pasa exactamente lo mismo. En cada página, artículo, etc. Hay que seleccionar cuidadosamente las palabras que identifican la temática de tu texto. Una vez identificadas hay que intentar repetirlas y usarlas el mayor número de veces… eso sí, siempre intentando que el sentido del texto se pierda.</p>
<p>Obviamente la longitud del texto influye en cuantas veces podemos usarlas (esto se llama densidad), aunque se recomienda que por lo menos se usen tres o cuatro veces dentro del texto.</p>
<p>Los buscadores suelen penalizar que se construyan frases sólo con palabras clave y le dan más importancia a encontrarlas sueltas por todo el texto. Pero como siempre existe una excepción a esta regla: la repetición de frases construidas por palabras clave en el mismo orden a lo largo del texto, potenciando su significado utilizando las palabras clave por separado en otras áreas del texto.</p>
<p>Por ejemplo, si hemos hecho un artículo que habla de &#8221; crear grids con el framework ExtJS&#8221; y hemos decidido que las palabras clave son “extjs”, “grids” y “framework”, podemos usar la frase “Los grids mediante extjs framework” y repetirla varias veces en nuestro texto (siempre con cabeza y moderación), a la vez que se van “nombrando” las palabras claves de forma suelta por el texto.</p>
<p><strong>3. La densidad de palabras clave y su localización</strong></p>
<p>La densidad de palabras clave mide la frecuencia de aparición de éstas respecto al total de palabras del texto, expresándose normalmente en porcentaje. Es una herramienta que se puede usar para identificar palabras clave “a posteriori”, es decir una vez hayamos terminado de escribir nuestro texto.</p>
<p>Es importante, como siempre, encontrar un equilibrio en el uso de las palabras clave: si la densidad es baja, el robot del buscador no le dará importancia, mientras que si la densidad es demasiado alta, los sistemas “anti-engaños” de los buscadores pueden saltar y penalizar nuestra página.</p>
<p>Se suele decir que la densidad óptima de una palabra clave debe estar entre el 5%  y el 7%. Además de esto, cuanto más cerca del comienzo del documento esté una palabra clave o una frase de palabras clave, más significado o peso tendrá esta para el motor de búsqueda. Y ya no digamos si resaltamos esas palabras (por ejemplo con una negrita o una cursiva). Eso sí, no conviene resaltarlas cada vez que aparezcan, pues puede ser contraproducente. La lógica indica que se resalten la primera vez que se nombran, o bien cuando existan muchos párrafos por el medio sin la palabra resaltada.</p>
<p>Otro factor importante, es utilizar palabras clave en el texto de los enlaces que hagan referencia a otras páginas o secciones de nuestro site, pues potencian la importancia de la página globalmente.  Ya por finalizar con el tema de las palabras clave, aunque parece que últimamente los buscadores no prestan atención a la etiqueta META ‘keywords’, sí que conviene introducir en ella el valor de las palabras clave.</p>
<p><strong>4. Formatear el texto también ayuda</strong></p>
<p>Los motores de búsqueda, aunque parezca que sólo buscan palabras, prestan especial atención al texto cuando está remarcado. Aunque en sistemas como wordrpess los editores de noticias, no llegan a tocar el código HTML, los diseñadores de las plantillas sí que tienen en cuenta estas recomendaciones:</p>
<ul>
<li>Uso de las palabras clave en las cabeceras. Las cabeceras son texto remarcado con el tag de HTML “H”. Los tags “h1” y “h2” son los más efectivos, modificando su apariencia mediante los CSS.</li>
<li>Remarcar palabras clave con “negrita” (tag “strong” de HTML mejor que tag “b”). Se remarcará la palabra clave o la frase clave específicamente.</li>
</ul>
<p><strong>5. La etiqueta de título &lt;TITLE&gt;</strong></p>
<p>Sin lugar a duda el tag &lt;TITLE&gt;&lt;/TITLE&gt; es de los más importantes para los buscadores, siendo prácticamente fundamental su uso para optimizar una página. Esta etiqueta, por lo tanto, vale para algo más que para mostrar el nombre de nuestra web en la ventana del navegador.</p>
<p>Como se dijo antes (al hablar de las palabras clave), el título debe contener el mayor número de palabras clave porque además es utilizado habitualmente como parte del texto que presentan los buscadores al mostrar los resultados.<br />
Además de incluir el mayor número de palabras clave, debe ser atractivo para el usuario y hacer que se sientan atraídos por visitar nuestra web. Aunque no es una regla escrita, se recomienda que la longitud esté entre los 60 caracteres y los 85 (se dice que el valor óptimo son 65 caracteres para Google), aunque tampoco debemos obsesionarnos con ello.</p>
<p><strong>6. No dejes “desnudas” a las imágenes</strong></p>
<p>Toda imagen en HTML tiene un atributo opcional “ALT” o “alternative text” al que se le puede asociar un texto. Por ejemplo en Wordpres, cuando asociamos una imagen, se nos da la posibilidad de introducirlo.</p>
<p>Este tag es importante porque en caso de que la imagen no se cargue por alguna razón, el navegador mostrará ese texto en su lugar. Además los motores de búsqueda como Google, indexan las imágenes por este valor.</p>
<p><strong>7. Describe lo que estás publicando…</strong></p>
<p>Existe un campo META (de METATags o METAetiquetas) que es utilizado para describir el contenido de una página. Aunque su influencia en temas de SEO no es directa, sí que importa. La mayoría de motores de búsqueda muestran la información de este campo en los resultados de búsqueda si la etiqueta existe y contiene información relativa al texto de la página y de las palabras de búsqueda.</p>
<p>En wordrpess (si está instalado el plugin “All in one SEO Pack” se puede editar este campo independientemente para cada post (al igual que el título y las palabras clave).</p>
<p style="text-align: center;"><strong>La estructura del site no sólo es útil para nuestros ojos</strong></p>
<p style="text-align: left;">Aunque pueda parecer que la estructura del site sólo sea de importancia para el diseño o la facilidad de navegación, lo cierto es que los buscadores han avanzado tanto que son sensibles incluso a cómo esté distribuida la información en una página.</p>
<p style="text-align: left;"><strong>1. Número de páginas</strong></p>
<p style="text-align: left;">Cuantas más páginas con contenido interesante existan en una web más visibles serán a los “ojos” de un buscador. Esto, que aunque parezca una verdad de Perogrullo, es un tema que a veces se olvida. Además de la cantidad y la calidad, los buscadores tienen en cuenta que los contenidos se generen constantemente y se actualicen (por eso mismo los blogs bien actualizados están alcanzando puestos tan altos en los buscadores)</p>
<p style="text-align: left;"><strong>2 El menú no solo es para comer…</strong></p>
<p style="text-align: left;">Todo sitio web debe tener un menú de navegación bien estructurado desde el que se enlace con todas las páginas importantes del site. Además es una buena práctica utilizar palabras clave en este menú para darle relevancia a la página.</p>
<p style="text-align: left;"><strong>3. Seo y la portada principal del Site</strong></p>
<p style="text-align: left;">Es buena idea optimizar la página principal del Site para conseguir la mayor combinación de palabras clave relacionadas con el contenido del site, de esta forma la página será la mejor posicionada en los resultados que apunten a nuestra web.</p>
<p style="text-align: left;">En un blog (como wordpress) puede ser difícil ya siempre se están mostrando las entradas más recientes, y no sería muy útil tener una entrada fija diseñada para albergar las palabras clave.  Para ello tenemos plugins como la “nube de etiquetas” que muestran en la página principal una lista con un resumen de todas las palabras clave con las que se han etiquetado los contenidos, o la lista de posts más destacados.</p>
<p style="text-align: left;"><strong>4. Intercambiar enlaces</strong></p>
<p style="text-align: left;">Muchos buscadores miden la importancia de una página por la cantidad de enlaces que apuntan a ella. Esto lleva muchas veces a una mala utilización de esta característica de los buscadores y hay gente que se dedica a intentar colocar el link a su página en cientos o miles de webs sin importarle la temática o la posición, sin saber que Google (y Yahoo, y MSN) penalizan aquellos enlaces que son “dudosos” (ya sea por su calidad o características)</p>
<p style="text-align: left;">Así pues, es importante tener algo menos de enlaces, pero de calidad y páginas serias.</p>
<p style="text-align: center;"><strong>Hasta en SEO se pueden cometer errores</strong></p>
<p style="text-align: left;"><strong>1. Imágenes con texto</strong></p>
<p style="text-align: left;">Hasta ahora no hay buscadores que hagan la tarea de OCR (reconocimiento óptico de caracteres), de forma que todo aquel texto que pongamos dentro de una web, embebido dentro de una imagen, será invisible a “ojos” del buscador.<br />
Debemos evitar a toda costa utilizar imágenes para los elementos del menú de la web, así como para nuestros artículos (si necesitamos copiar texto de otra web, es mejor copiar el texto que plantar una captura de pantall).</p>
<p style="text-align: left;">Asimismo se debe evitar crear enlaces cuyo elemento descriptivo se una imagen y no un texto. Todo link asociado a una imagen tendrá menor relevancia que aquel asociado a un texto y por lo tanto que pueda ser indexado.</p>
<p style="text-align: left;"><strong>2. Evita scripts en la navegación</strong></p>
<p style="text-align: left;">Hay que evitar que la navegación por tu web funcione a través de scripts (por ejemplo menús activados por javascript, java o flash) pues dificultan la navegación a través de la web a los robots de los buscadores. Lo mejor es que los enlaces sean eso… enlaces con su etiqueta &lt;a&gt;&lt;/a&gt; y su texto descriptivo.</p>
<p style="text-align: left;"><strong>3. Parámetros en URL</strong></p>
<p style="text-align: left;">Aunque esto importa más a los programadores de la web que a los editores de información, hora de generar URLs (direcciones) a nuestros contenidos hay que evitar que se generen con parámetros extraños o confusos.<br />
Por ejemplo la dirección:</p>
<p style="text-align: left;">http://www.desarrollosweb.net/gestorContenidos/seccion.php?IDSeccion=6&amp;tag=tecnologia&amp;fecha=20090321&amp;tema=microcamaras</p>
<p style="text-align: left;">se indexa mucho peor que otra que sea</p>
<p style="text-align: left;">http://www.desarrollosweb.net/seccion/tecnologia/20090321/microcamaras</p>
<p style="text-align: left;">Si os fijáis, para el buscador la segunda url es mucho más descriptiva, pues a través de ella sabe que está en la sección tecnología, de la web www.desarrollosweb.net y que el 21 de marzo de 2009 se publicó un contenido sobre microcámaras.</p>
<p style="text-align: left;"><strong>4. Redirecciones</strong></p>
<p style="text-align: left;">Otro tema que le toca a los programadores, pero que es importante que lo sepan todos, el tema de las redirecciones. Una redirección es el acto de redirigir una URL (dirección) a otra distinta.</p>
<p style="text-align: left;">Esto se suele hacer cuando se cambia la dirección de un contenido, se borra, etc. Es algo que hay que evitar a toda costa y si no hay más remedio que hacerlo, lo mejor es informar al buscador con un código “301” que le indica que la página se ha redirigido para siempre a una nueva dirección. De esta forma, la próxima vez que el buscador vuelva a nuestra página irá directamente a la URL nueva.</p>
<p style="text-align: left;"><strong>5. No ocultes nada… que se acaba viendo</strong></p>
<p style="text-align: left;">Otro error que viene heredado del pasado. Hace unos años (cuando los buscadores estaban menos evolucionados) era muy fácil posicionar una web en primer lugar insertando en el código HTML de la página contenidos que no se mostraban en un navegador (la gente no podía leer esa información), pero sí que era visible a los motores de búsqueda.</p>
<p style="text-align: left;">Lo que se hacía normalmente era introducir cientos o miles de palabras clave no relevantes con el texto dentro de una etiqueta oculta, de forma que siempre aparecieses en los primeros resultados de las búsquedas.<br />
Actualmente esta técnica está prohibida y si la usas te arriesgas a que Google o cualquier otro buscador banee tu página y no vuelva a visitarte (ni indexarte) nunca. Es más, llegarás a perder todo el rankin que has conseguido.</p>
<p style="text-align: left;">Un tema relacionado con este es la práctica de crear enlaces asociados a píxeles transparentes que son invisibles a los usuarios y cuya finalidad es buscar que los robots de los motores de búsqueda los indexen.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2009/03/04/factores-relevantes-de-seo-en-una-web-o-como-hacer-paginas-google-friendly/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Cookbook ExtJS: Creando nuestros propios validadores</title>
		<link>http://www.desarrollosweb.net/2008/12/11/cookbook-crear-validadores-propios/</link>
		<comments>http://www.desarrollosweb.net/2008/12/11/cookbook-crear-validadores-propios/#comments</comments>
		<pubDate>Thu, 11 Dec 2008 12:39:39 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[formularios]]></category>
		<category><![CDATA[validación]]></category>
		<category><![CDATA[vTypes]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=58</guid>
		<description><![CDATA[Después de un puente largo, retomamos los artículos con trucos de ExtJS. En ExtJS tenemos la opción de crear nuestros propios validadores de datos para los campos de los formularios, lo que es útil si tenemos validaciones que se repiten a lo largo de nuestra aplicación. Muchos estaréis pensando que eso también lo podemos hacer [...]]]></description>
			<content:encoded><![CDATA[<p>Después de un puente largo, retomamos los artículos con trucos de ExtJS. En ExtJS tenemos la opción de crear nuestros propios validadores de datos para los campos de los formularios, lo que es útil si tenemos validaciones que se repiten a lo largo de nuestra aplicación.</p>
<p>Muchos estaréis pensando que eso también lo podemos hacer usando una función que llamemos dentro de un handler (lo cual es cierto), pero extendiendo los validadores de ExtJS conseguimos que nuestras reglas se integren automáticamente con los mecanismos de validación de formularios que trae &#8220;de serie&#8221; ExtJS</p>
<p>Vamos a crear una función de validación que chequee el valor de dos campos de forma que sólo uno de ellos tenga valor mayor que cero. Para ello lo primero que hacemos es añadir a la configuración de los campos del formulario una etiqueta propia &#8216;exclusionPair&#8217; (que nos indicará el id del otro campo que excluye el valor del actual) e indicamos que queremos usar nuestro validador</p>
<pre name="code" class="javascript">
{
    fieldLabel : 'Haber',
    xtype: 'numberfield',
    id: 'frm_mov_haber',
    name : 'mov_haber',
    // el campo exclusionPair indica cual es el id del otro campo que excluye a este
    // es un parametro de configuracion que nos hemos inventado
    exclusionPair: 'frm_mov_deber',
    vtype: 'exclusionNotZero', // función de validación que queremos usar
    allowBlank:false,
    width : 175
},{
    fieldLabel : 'Deber',
    xtype: 'numberfield',
    id: 'frm_mov_deber',
    name : 'mov_deber',
    // el campo exclusionPair indica cual es el id del otro campo que excluye a este
    // es un parametro de configuracion que nos hemos inventado
    exclusionPair: 'frm_mov_haber',
    vtype: 'exclusionNotZero', // función de validación que queremos usar
    allowBlank:false,
    width : 175
}
</pre>
<p>La forma de crear un validador es muy sencilla, lo único que hay que tener en cuenta es que tenemos que devolver un true cuando la validación sea correcta y un false si no ha pasado la validación</p>
<pre name="code" class="javascript">
/**
 * Extendemos los validadores de ExtJS que están definidos en Ext.form.VTypes
 *
 **/
Ext.apply(Ext.form.VTypes, {
    /**
     * Definimos un nuevo validador: exclusionNotZero
     *
     * Valida un par de campos numéricos para que no puedan tener valor positivo los dos ni
     * valor cero los dos.
     *
     **/
    exclusionNotZero: function(val, field) {
        /*
         * Si hay valor en el campo exclusionPair, es que tenemos que hacer la validación
         */
        if (field.exclusionPair) {
            // Nos quedamos con la instancia del objeto indicado en exclusionPair
            var pair = Ext.getCmp(field.exclusionPair);
            // Nos quedamos con el valor del campo
            var pairValue = pair.getValue();

            if ((pairValue > 0) &#038;&#038; (val > 0)) {
                /**
                 * Si los dos tienen un valor positivo, la regla de validación no se pasa
                 * marcamos el campo como no válido y un mensaje
                 */
                field.markInvalid('Un movimiento no puede tener a la vez haber y deber');
                return false;
            } else if ((pairValue == 0) &#038;&#038; (val == 0)){
                /**
                 * Si los dos tienen un valor cero, la regla de validación no se pasa
                 * marcamos el campo como no válido y un mensaje
                 */
                field.markInvalid('Un movimiento no puede tener haber y deber a cero');
                return false;
            } else {
                /**
                 * Si llegamos aquí es que la regla se ha pasado. Como anteriormente un campo
                 * pudo haberse marcado como no válido, los desmarcamos por si acaso
                 */
                field.clearInvalid();
                pair.clearInvalid();
                return true;
            }
        }
        /**
         * El campo no tiene valor en exclusionPair, devolvemos true para que se valide el campo
         */
        return true;
    }
});
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2008/12/11/cookbook-crear-validadores-propios/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Cookbook ExtJS: Cargar un registro de un GridPanel en un FormPanel</title>
		<link>http://www.desarrollosweb.net/2008/12/03/cookbook-extjs-cargar-un-registro-de-un-gridpanel-en-un-formpanel/</link>
		<comments>http://www.desarrollosweb.net/2008/12/03/cookbook-extjs-cargar-un-registro-de-un-gridpanel-en-un-formpanel/#comments</comments>
		<pubDate>Wed, 03 Dec 2008 09:40:14 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[Ext.Form]]></category>
		<category><![CDATA[Ext.GridPanel]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=51</guid>
		<description><![CDATA[¿Como hacemos para cargar los datos de un registro de un grid en un formulario? Bien, hay diversas opciones (siempre a gusto del programador) así que aquí vamos a explicar una de ellas: asociar el evento &#8216;rowdblclick&#8217; (doble click en una fila) a un handler que cargue el formulario con los datos del registro. GestorCuentas.movimientosGrid.on('rowdblclick',function( [...]]]></description>
			<content:encoded><![CDATA[<p>¿Como hacemos para cargar los datos de un registro de un grid en un formulario? Bien, hay diversas opciones (siempre a gusto del programador) así que aquí vamos a explicar una de ellas: asociar el evento &#8216;rowdblclick&#8217; (doble click en una fila) a un handler que cargue el formulario con los datos del registro.</p>
<pre name="code" class="javascript">
GestorCuentas.movimientosGrid.on('rowdblclick',function( grid, row, evt) {
        var movRecord = GestorCuentas.movimientosDataStore.getAt(row);
        GestorCuentas.movForm.getForm().load({
            url : 'cargaMovimiento.php',
            method: 'POST',
            params: {
                mov_id: movRecord.data.mov_id
            },
            waitMsg : 'Espere por favor'
        });
    });
</pre>
<p>En el API de ExtJS podemos comprobar que cuando asignamos una función al evento &#8216;rowdbclick&#8217; vamos a recibir tres parámetros: el grid que ha generado el evento, el índice del registro dentro del store y un objeto con datos adicionales del evento</p>
<p>Para quedarnos con el registro, sólo tenemos que solicitarle al store del grid que nos devuelva aquel cuyo índice hemos recibido por parámetro</p>
<pre name="code" class="javascript">
var movRecord = GestorCuentas.movimientosDataStore.getAt(row);
</pre>
<p>Ahora en movRecord tendremos el registro y podremos acceder a sus datos para quedarnos con el identificador del registro. Fijáos que en la definición del record del grid hemos incluído la Primary Key del registro en base de datos, aunque no lo estemos mostrando en el propio grid</p>
<p>Fijáos que el formulario que vamos a cargar tiene asignado un reader, de forma que podemos llamar al método load() del mismo para que automáticamente cargue los datos. Obviamente hay que indicarle que registro se va a cargar por lo que le pasamos como parámetro el mov_id del registro que hemos pedido al store</p>
<p>La instancia del formulario se la pedimos al FormPanel mediante el método getForm(). Esto nos devuelve un objeto Ext.form.BasicForm. Este método tiene un método load() al que le pasamos la configuración necesaria para hacer la carga</p>
<pre name="code" class="javascript">
    GestorCuentas.movForm.getForm().load({
            url : 'cargaMovimiento.php',
            method: 'POST',
            params: {
                // OJO, los datos del registro están en movRecord.data y no directamente en movRecord como podría pensarse
                mov_id: movRecord.data.mov_id
            },
            waitMsg : 'Espere por favor'
        });
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2008/12/03/cookbook-extjs-cargar-un-registro-de-un-gridpanel-en-un-formpanel/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>ExtJS CookBook: Código base</title>
		<link>http://www.desarrollosweb.net/2008/12/03/extjs-cookbook-codigo-base/</link>
		<comments>http://www.desarrollosweb.net/2008/12/03/extjs-cookbook-codigo-base/#comments</comments>
		<pubDate>Wed, 03 Dec 2008 09:16:17 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Zend Framework]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=49</guid>
		<description><![CDATA[Voy a iniciar una serie de artículos a los que voy a denominar &#8216;ExtJS Cookbook&#8217;, en los que iré explicando pequeñas acciones o trucos de ExtJS pero que por ser muy comunes merecen un poco de atención La primera parte de esta serie de artículos se van a basar en el código de una miniaplicación [...]]]></description>
			<content:encoded><![CDATA[<p>Voy a iniciar una serie de artículos a los que voy a denominar &#8216;ExtJS Cookbook&#8217;, en los que iré explicando pequeñas acciones o trucos de ExtJS pero que por ser muy comunes merecen un poco de atención</p>
<p>La primera parte de esta serie de artículos se van a basar en el código de una miniaplicación para gestión de un rudimentario libro de cuentas y que <a href="http://appsrv01.desarrollosweb.net/blog/extjs/cuentas/" target="_blank">tenéis disponible</a> para evaluar</p>
<p>Este ejemplo muestra cómo hacer varias cosas: cargar datos desde PHP en Extjs, cargar los datos de un FormPanel desde un GridPanel, filtrar un GridPanel desde un formulario externo, cómo obtener los datos de los campos de un FormPanel, etc.
<p>Espero que los artículos que publique estos días os sean de utilidad</p>
<h2>Fichero 1: Código Javascript de la miniaplicación</h2>
<pre name="code" class="javascript">
/*
 * Definimos nuestros propios validadores de datos
 * para los formularios
 */
Ext.apply(Ext.form.VTypes, {
    daterange : function(val, field) {
        var date = field.parseDate(val);

        if(!date){
            return;
        }
        if (field.startDateField &#038;&#038; (!this.dateRangeMax || (date.getTime() != this.dateRangeMax.getTime()))) {
            var start = Ext.getCmp(field.startDateField);
            start.setMaxValue(date);
            start.validate();
            this.dateRangeMax = date;
        }
        else if (field.endDateField &#038;&#038; (!this.dateRangeMin || (date.getTime() != this.dateRangeMin.getTime()))) {
            var end = Ext.getCmp(field.endDateField);
            end.setMinValue(date);
            end.validate();
            this.dateRangeMin = date;
        }
        return true;
    },
    /**
     * Definimos un nuevo validador: exclusionNotZero
     *
     * Valida un par de campos numéricos para que no puedan tener valor positivo los dos ni
     * valor cero los dos.
     *
     **/
    exclusionNotZero: function(val, field) {
        if (field.exclusionPair) {
            var pair = Ext.getCmp(field.exclusionPair);
            var pairValue = pair.getValue();

            if ((pairValue > 0) &#038;&#038; (val > 0)) {
                field.markInvalid('Un movimiento no puede tener a la vez haber y deber');
                return false;
            } else if ((pairValue == 0) &#038;&#038; (val == 0)){
                field.markInvalid('Un movimiento no puede tener haber y deber a cero');
                return false;
            } else {
                field.clearInvalid();
                pair.clearInvalid();
                return true;
            }
        }
        return true;
    }
});

Ext.onReady(function() {
    /*
     * Creamos un espacio de nombres
     */
    Ext.namespace('GestorCuentas');

    /*
     * Definimos el registro para un movimiento
     */
    GestorCuentas.movimientoRecord = new Ext.data.Record.create([
        {name: 'mov_id', type: 'int'},
        {name: 'mov_concepto', type: 'string'},
        {name: 'mov_fecha', type: 'date', dateFormat: 'Y-m-d'},
        {name: 'mov_deber', type: 'float'},
        {name: 'mov_haber', type: 'float'}
    ]);

    /*
     * Creamos el reader para el Grid de movimientos
     */
    GestorCuentas.movimientosGridReader = new Ext.data.JsonReader({
        root: 'data',
        totalProperty: 'total',
        id: 'mov_id'},
        GestorCuentas.movimientoRecord
    );

    /*
     * Creamos el DataProxy para carga remota de los datos
     */
    GestorCuentas.movimientosDataProxy = new Ext.data.HttpProxy({
        url: 'cargaMovimientos.php',
        method: 'POST'
    });

    GestorCuentas.movimientosDataStore = new Ext.data.Store({
        id: 'movimientosDS',
        proxy: GestorCuentas.movimientosDataProxy,
        reader: GestorCuentas.movimientosGridReader
    });

    /*
     * Creamos el modelo de columnas para el grid de movimientos de la cuenta
     */
    GestorCuentas.movimientosColumnMode = new Ext.grid.ColumnModel(
        [{
            header: 'Fecha',
            dataIndex: 'mov_fecha',
            width: 80,
            renderer: Ext.util.Format.dateRenderer('d/m/Y')
        },{
            header: 'Concepto',
            dataIndex: 'mov_concepto',
            width: 200
        },{
            header: 'Haber',
            dataIndex: 'mov_haber',
            width: 90
        },{
            header: 'Deber',
            dataIndex: 'mov_deber',
            width: 90
        }]
    );

    /*
     * Creamos el reader para el formulario de alta/modificación de movimientos
     */
    GestorCuentas.movimientosFormReader = new Ext.data.JsonReader({
        root : 'data',
        successProperty : 'success',
        totalProperty: 'total',
        id: 'mov_id'
        },GestorCuentas.movimientoRecord
    );

    /*
     * Creamos el grid de movimientos
     */
    GestorCuentas.movimientosGrid = new Ext.grid.GridPanel({
        id : 'mov-movimientos-grid',
        store : GestorCuentas.movimientosDataStore,
        cm : GestorCuentas.movimientosColumnMode,
        enableColLock : false,
        width : 750,
        height : 550,
        bbar : new Ext.PagingToolbar({
            pageSize: 20,
            store: GestorCuentas.movimientosDataStore,
            displayInfo: true
        }),
        selModel : new Ext.grid.RowSelectionModel({singleSelect:false})
    });

    /*
     * Creamos el formulario para filtrar el grid
     */
    GestorCuentas.filterForm = new Ext.FormPanel({
        id: 'form-filtro',
        region: 'north',
        split: false,
        frame: true,
        height: 100,
        width: 800,
        items: [{
            xtype: 'datefield',
            width: 200,
            fieldLabel: 'Desde',
            name: 'startdt',
            id: 'startdt',
            vtype: 'daterange',
            endDateField: 'enddt' // id of the end date field
        },{
            xtype: 'datefield',
            width: 200,
            fieldLabel: 'Hasta',
            name: 'enddt',
            id: 'enddt',
            vtype: 'daterange',
            startDateField: 'startdt' // id of the start date field
        }]
    });

    /*
     * Añadimos el botón para borrar el filtro
     */
    GestorCuentas.filterForm.addButton({
        text : 'Borrar filtro',
        disabled : false,
        handler : function() {
            GestorCuentas.filterForm.getForm().reset();
            GestorCuentas.movimientosDataStore.baseParams = {
                dateStart: '',
                dateEnd: ''
            };
            GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
        }
    });

    /*
     * Añadimos el botón para filtrar
     */
    GestorCuentas.filterForm.addButton({
        text : 'Filtrar',
        disabled : false,
        handler : function() {
            var startDate = GestorCuentas.filterForm.findById('startdt').getValue();
            var endDate = GestorCuentas.filterForm.findById('enddt').getValue();

            GestorCuentas.movimientosDataStore.baseParams = {
                dateStart: startDate.dateFormat('Y-m-d'),
                dateEnd: endDate.dateFormat('Y-m-d')
            };
            GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
        }
    });

    /*
     * Creamos el formulario de alta/modificación de movimientos
     */
    GestorCuentas.movForm = new Ext.FormPanel({
        id: 'form-movimientos',
        region: 'west',
        split: false,
        collapsible: true,
        frame: true,
        width: 300,
        minWidth: 300,
        height: 500,
        waitMsgTarget: true,
        reader: GestorCuentas.movimientosFormReader,
        items: [{
            fieldLabel : 'Haber',
            xtype: 'numberfield',
            id: 'frm_mov_haber',
            name : 'mov_haber',
            exclusionPair: 'frm_mov_deber',
            vtype: 'exclusionNotZero',
            allowBlank:false,
            width : 175
        },{
            fieldLabel : 'Deber',
            xtype: 'numberfield',
            id: 'frm_mov_deber',
            name : 'mov_deber',
            exclusionPair: 'frm_mov_haber',
            vtype: 'exclusionNotZero',
            allowBlank:false,
            width : 175
        }, {
            fieldLabel : 'Concepto',
            id: 'frm_mov_concepto',
            name : 'mov_concepto',
            allowBlank:false,
            xtype: 'textarea',
            width : 175,
            height: 225
        }, {
            fieldLabel : 'Fecha',
            id: 'frm_mov_fecha',
            name : 'mov_fecha',
            allowBlank:false,
            xtype: 'datefield',
            renderer: Ext.util.Format.dateRenderer('d/m/Y'),
            width : 175
        }, {
            id: 'frm_mov_id',
            name : 'mov_id',
            xtype: 'hidden'
        }]
    });

    /*
     * Añadimos el botón para borrar el formulario
     */
    GestorCuentas.movForm.addButton({
        text : 'Borrar formulario',
        disabled : false,
        handler : function() {
            GestorCuentas.movForm.getForm().reset();
        }
    });

    /*
     * Añadimos el botón para guardar los datos del formulario
     */
    GestorCuentas.movForm.addButton({
        text : 'Guardar',
        disabled : false,
        handler : function() {
            GestorCuentas.movForm.getForm().submit({
                url : 'salvaMovimientos.php',
                waitMsg : 'Salvando datos...',
                failure: function (form, action) {
                    Ext.MessageBox.show({
                        title: 'Error al salvar los datos',
                        msg: 'Error al salvar los datos.',
                        buttons: Ext.MessageBox.OK,
                        icon: Ext.MessageBox.ERROR
                    });
                },
                success: function (form, request) {
                    Ext.MessageBox.show({
                        title: 'Datos salvados correctamente',
                        msg: 'Datos salvados correctamente',
                        buttons: Ext.MessageBox.OK,
                        icon: Ext.MessageBox.INFO
                    });
                    responseData = Ext.util.JSON.decode(request.response.responseText);
                    GestorCuentas.movForm.getForm().reset();
                    GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
                }
            });
        }
    });

    /*
     * Añadimos el evento doble click en una fila para editar el registro correspondiente
     */
    GestorCuentas.movimientosGrid.on('rowdblclick',function( grid, row, evt) {
        var movRecord = GestorCuentas.movimientosDataStore.getAt(row);
        GestorCuentas.movForm.getForm().load({
            url : 'cargaMovimiento.php',
            method: 'POST',
            params: {
                mov_id: movRecord.data.mov_id
            },
            waitMsg : 'Espere por favor'
        });
    });

    /*
     * Creamos la ventana
     */
    GestorCuentas.contentWindow = new Ext.Window({
            id: 'libro-cuentas',
            title:'Gestor Libro de Cuentas',
            width:860,
            height:600,
            iconCls: 'icon-grid',
            shim:false,
            animCollapse:false,
            constrainHeader:true,
            layout: 'border',
            renderTo: document.body,
            items:[GestorCuentas.filterForm, GestorCuentas.movForm, {
                    id: 'col-grid-movimientos',
                    xtype: 'panel',
                    region: 'center',
                    width: 560,
                    layout: 'fit',
                    items:[GestorCuentas.movimientosGrid]
                }
            ]
    });

    /*
     * Mostramos ventana, la centramos y cargamos los datos iniciales en el grid
     */
    GestorCuentas.contentWindow.show();
    GestorCuentas.contentWindow.center();
    GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
});
</pre>
<h2>Fichero 2: PHP de carga de datos del grid</h2>
<pre name="code" class="php">
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

require_once 'Config/ConfigCuentas.php';

$db->setFetchMode(Zend_Db::FETCH_OBJ);

if (!empty($_POST['dateStart']) &#038;&#038; !empty($_POST['dateEnd'])) {
    $filterClause = "WHERE (mov_fecha >= '".$_POST['dateStart']."' AND mov_fecha <= '".$_POST['dateEnd']."')";
} else {
    $filterClause = ' ';
}

if (!empty($_POST['limit']) &#038;&#038; !empty($_POST['start'])) {
    $limit = $_POST['limit'];
    $start = $_POST['start'];
    $limitClause = "LIMIT $start,$limit";
} else {
    $limitClause = ' ';
}

$query = "SELECT * FROM mov_movimientos $filterClause ORDER BY mov_fecha ASC $limitClause";

$result = $db->fetchAssoc($query);

$resultados = array();

foreach ($result as $record) {
    $resultados[] = $record;
}

$retValue = array (
    'total' => count($result),
    'data' => $resultados
);

echo json_encode($retValue);
</pre>
<h2>Fichero 3: PHP de carga de datos del formulario</h2>
<pre name="code" class="php">
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

require_once 'Config/ConfigCuentas.php';

$query = "SELECT * FROM mov_movimientos WHERE mov_id=" . $_POST['mov_id'];

$db->setFetchMode(Zend_Db::FETCH_OBJ);
$result = $db->fetchAssoc($query);

$record = $result[$_POST['mov_id']];

$resultados = array();
$retValue = array (
    'total' => count($result),
    'success'=>true,
    'data' => array (
        array(
            'mov_id' => $_POST['mov_id'],
            'mov_concepto' => $record['mov_concepto'],
            'mov_haber' => $record['mov_haber'],
            'mov_deber' => $record['mov_deber'],
            'mov_fecha' => $record['mov_fecha']
        )
    )
);
echo json_encode($retValue);
</pre>
<h2>Fichero 4: PHP de guardado de datos desde el formulario</h2>
<pre name="code" class="php">
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

require_once 'Config/ConfigCuentas.php';

if (is_null($_POST['mov_id']) || empty($_POST['mov_id'])) {
    $query = "INSERT INTO mov_movimientos(mov_fecha,mov_concepto,mov_haber,mov_deber) "
            ."VALUES(STR_TO_DATE('". $_POST['mov_fecha'] ."', '%d/%m/%Y'),'". $_POST['mov_concepto'] ."',".$_POST['mov_haber'].",".$_POST['mov_deber'].")";
} else {
    $query = "UPDATE mov_movimientos SET "
            ."mov_fecha = STR_TO_DATE('". $_POST['mov_fecha'] ."', '%d/%m/%Y'),"
            ."mov_concepto = '" . $_POST['mov_concepto'] ."',"
            ."mov_haber = " . $_POST['mov_haber'] . ","
            ."mov_deber = " . $_POST['mov_deber'] . " "
            ."WHERE mov_id = " . $_POST['mov_id'];
}

$db->setFetchMode(Zend_Db::FETCH_OBJ);
$result = $db->query($query);
$lastInsert = $db->lastInsertId();

$resultados = array();

$retValue = array (
    'total' => count($result),
    'success'=>true,
    'data' => array (
        array(
            'mov_id' => $mov_id
        )
    )
);

echo json_encode($retValue);
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2008/12/03/extjs-cookbook-codigo-base/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>Depurando código con FirePHP y Zend Framework, o como decir adiós a los var_dump()</title>
		<link>http://www.desarrollosweb.net/2008/12/01/depurando-codigo-con-firephp-y-zend-framework-o-como-decir-adios-a-los-var_dump/</link>
		<comments>http://www.desarrollosweb.net/2008/12/01/depurando-codigo-con-firephp-y-zend-framework-o-como-decir-adios-a-los-var_dump/#comments</comments>
		<pubDate>Mon, 01 Dec 2008 11:02:34 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[FirePHP]]></category>
		<category><![CDATA[depuración]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[Firebug]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Zend Framework]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=46</guid>
		<description><![CDATA[Todos los que llevamos tiempo desarrollando en PHP sabemos que una de las tareas más tediosas es la depuración de nuestros scritps, y mucho más si estos están alimentando la respuesta de una llamada AJAX que tiene que devolver los datos en un cierto formato (JSON por ejemplo). Esta tarea se puede hacer más &#8220;agradable&#8221; [...]]]></description>
			<content:encoded><![CDATA[<p>Todos los que llevamos tiempo desarrollando en PHP sabemos que una de las tareas más tediosas es la depuración de nuestros scritps, y mucho más si estos están alimentando la respuesta de una llamada AJAX que tiene que devolver los datos en un cierto formato (JSON por ejemplo). Esta tarea se puede hacer más &#8220;agradable&#8221; si disponemos de un IDE con depuración integrada, como puede ser Zend Studio, o recientemente Eclipse con el módulo PDT</p>
<p>A pesar de la comodidad de tener el depurador integrado en el entorno de desarrollo, muchas veces se hace un poco engorroso tener que iniciar una sesión de depuración cada vez que quieres saber qué datos están pasando por tus variables</p>
<p>Pues bien, para facilitar la vida a los desarrolladores tenemos disponible un nuevo complemento para firefox: FirePHP. Este componente nos permite depurar directamente nuestros scripts PHP en la consola del firebug</p>
<p>La herramienta se basa en enviar unas ciertas cabeceras HTTP desde nuestros scripts PHP y que son capturadas por el plugin. De esta forma, la salida de nuestros scripts no se ve alterada (perfecto para llamadas AJAX que devuelvan un XML o un JSON) y ganamos rapidez en la depuración</p>
<p>FirePHP está ganando popularidad rápidamente entre los desarrolladores y de hecho muchos frameworks como Zend Framework o Symphony están incorporando en su API llamadas para depurar con esta herramienta</p>
<p>Si no estamos usando ninguno de estos frameworks, el equipo de desarrollo de FirePHP nos ofrece una pequeña librería en PHP5 que podemos integrar fácilmente en nuestros scripts</p>
<p>Lo primero que tenemos que hacer es instalar la extensión FirePHP en nuestro Firefox. Luego descargamos FirePHPCore, descomprimimos el contenido en el directorio donde estemos almacenando las librerías que usamos y ya sólo nos queda hacer las llamadas pertinentes tal y <a href="http://www.firephp.org/HQ/Use.htm" target="_blank">como se nos explica en este tutorial</a></p>
<p>Si estamos usando Zend Framework y su Modelo Vista Controlador (MVC) también podemos integrar FirePHPCore de forma fácil, si por un casual no nos gustase el API que nos ofrece el propio framework</p>
<p>Primero registramos el logger en el bootstrap.php de nuestra aplicación:</p>
<pre name="code" class="php">//Incluímos la librería
require_once('FirePHPCore/FirePHP.class.php');

// Instanciamos el logger y lo registramos
$firebugLogger = FirePHP::getInstance(true);
Zend_Registry::set('firebugLogger',$firebugLogger);</pre>
<p>Luego en cualquier parte de nuestro código podemos invocar al logger, como por ejemplo en una acción de un controlador:</p>
<pre name="code" class="php">    public function indexAction() {
        // Obtenemos la instancia del logger previamente registrada
        // en el bootstrap.php
        $fbLogger = Zend_Registry::get('firebugLogger');

        // Mensajes de log
        $fbLogger-&gt;log('Mensaje');
        $fbLogger-&gt;info('Mensaje nivel INFO');
        $fbLogger-&gt;warn('Mensaje nivel WARNING');
        $fbLogger-&gt;error('Mensaje niverl ERROR');

        // Mensaje de log con etiqueta adicional
        $fbLogger-&gt;log('Mensaje','Etiqueta Opcional');

        // Podemos mostrar una tabla de datos
        $table   = array();
        $table[] = array('Columna 1','Columna 2');
        $table[] = array('1:1','1:2');
        $table[] = array('2:1','2:2');
        $table[] = array('3:1','3:2');
        $fbLogger-&gt;table('Tabla de datos', $table);  // or FB::

        // También podemos mostrar información de traza
        $fbLogger-&gt;trace('Trace Label');
    }</pre>
<p>Aquí podemos ver cómo queda la salida en la consola del Firebug:</p>
<p style="text-align: center;"><a href="http://www.desarrollosweb.net/wp-content/captura_firephp.png"><img class="size-medium wp-image-47 aligncenter" title="captura_firephp" src="http://www.desarrollosweb.net/wp-content/captura_firephp-300x234.png" alt="" width="300" height="234" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2008/12/01/depurando-codigo-con-firephp-y-zend-framework-o-como-decir-adios-a-los-var_dump/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Introducción a formularios en ExtJS: Ext.FormPanel</title>
		<link>http://www.desarrollosweb.net/2008/11/21/introduccion-a-formularios-en-extjs-extformpanel/</link>
		<comments>http://www.desarrollosweb.net/2008/11/21/introduccion-a-formularios-en-extjs-extformpanel/#comments</comments>
		<pubDate>Fri, 21 Nov 2008 11:47:17 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[Ext.FormPanel]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=44</guid>
		<description><![CDATA[Definiendo un formulario Realmente la definición de un formulario que haga su carga de datos y guardado mediante Ajax, no dista mucho de la definición de un grid. Lo primero que hacemos es definir el registro que contendrá nuestros datos. Como se puede ver en este ejemplo hemos utilizado la misma definición de registro. var [...]]]></description>
			<content:encoded><![CDATA[<h2>Definiendo un formulario</h2>
<p>Realmente la definición de un formulario que haga su carga de datos y guardado mediante Ajax, no dista mucho de la definición de un grid.</p>
<p>Lo primero que hacemos es definir el registro que contendrá nuestros datos. Como se puede ver en este ejemplo hemos utilizado la misma definición de registro.</p>
<pre name="code" class="javascript">    var categoriesRecord = new Ext.data.Record.create([
        {name: 'cad_id', type: 'int'},
        {name: 'cad_name', type: 'string'},
        {name: 'cad_description', type: 'string'}
    ]);</pre>
<p>Para la carga de datos y respuestas del servidor, definimos también un reader (al igual que en los grids). Para este ejemplo vamos a seguir utilizando la codificación JSON, aunque podríamos haber elegido un reader XML</p>
<pre name="code" class="javascript">    var categoriesFormReader = new Ext.data.JsonReader(
            {
                root : 'data',
                successProperty : 'success',
                totalProperty: 'total',
                id: 'cad_id'
            },categoriesRecord
    );</pre>
<p>Como vemos, su definición es prácticamente igual que en el caso del reader del grid, salvo que en esta ocasión hay que definir una propiedad &#8216;successProperty&#8217; en la respuesta JSON que nos indique si ha tenido éxito o no la operación</p>
<p>Otra novedad respecto al grid, es que podemos indicar a un form desde el servidor si hubo algún error en algún campo. Esto lo podemos utilizar para hacer verificacion de campos en servidor (comprobar duplicados, caracteres no permitidos, etc.). Para ello se define una nueva clase que hereda de JsonReader: JsonErrorReader</p>
<pre name="code" class="javascript">Ext.form.JsonErrorReader = function() {
    Ext.form.JsonErrorReader.superclass.constructor.call(this, {
        root : 'data',
        successProperty : 'success',
        totalProperty: 'total'
     },
     [
        {name: 'id'},
        {name: 'msg'}
     ]);
    };</pre>
<p>Podemos ver que en este caso, en la definición del reader se cambia el registro por otro nuevo con sólo dos campos: &#8216;id&#8217; y &#8216;msg&#8217;. En &#8216;id&#8217; vendrá el identificador del campo en el que hay un error y en &#8216;msg&#8217; vendrá el error para ese campo. Es importante que activemos los mensajes superpuestos mediante la línea Ext.QuickTips.init();</p>
<p>La definición del form es sencilla. Como en cualquier otro panel, en la propiedad &#8216;items&#8217; vamos a introducir el array con todos los componentes del formulario, en este campos (texto, áreas de texto, combos, radios, etc.)</p>
<pre name="code" class="javascript">var formCategories = new Ext.FormPanel({
        frame : true,
        width : 624,
        height: 346,
        waitMsgTarget : true,
        reader: categoriesFormReader,
        errorReader: new Ext.form.JsonErrorReader(),
        items : [{
                fieldLabel : 'Categoría',
                xtype: 'textfield',
                name : 'cad_name',
                allowBlank:false,
                width : 430
            }, {
                fieldLabel : 'Descripción',
                name : 'cad_description',
                allowBlank:false,
                xtype: 'textarea',
                width : 430,
                height: 225
            }, {
                fieldLabel : 'Fecha',
                name : 'cad_date',
                allowBlank:false,
                xtype: 'datefield',
                renderer: Ext.util.Format.dateRenderer('d/m/Y'),
                width : 430
            }, {
                name : 'cad_id',
                xtype: 'hidden'
            }],
    });</pre>
<p>Acto seguido, vamos a añadir un botón. Esta vez en vez de indicarlo en el array de configuración, vamos a utilizar el método addButton y vamos a añadirle un poco de funcionalidad</p>
<pre name="code" class="javascript">    var submitFormCategories = formCategories.addButton({
        text : 'Guardar',
        disabled : false,
        handler : function() {
            formCategories.getForm().submit({
                url : 'formSaver.php',
                waitMsg : 'Salvando datos...',
                failure: function (form, action) {
                    Ext.MessageBox.show({
                        title: 'Error al salvar los datos',
                        msg: 'Error al salvar los datos.',
                        buttons: Ext.MessageBox.OK,
                        icon: Ext.MessageBox.ERROR
                    });
                },
                success: function (form, request) {
                    Ext.MessageBox.show({
                        title: 'Datos salvados correctamente',
                        msg: 'Datos salvados correctamente',
                        buttons: Ext.MessageBox.OK,
                        icon: Ext.MessageBox.INFO
                    });
                    responseData = Ext.util.JSON.decode(request.response.responseText);
                    formCategories.getForm().load({
                        url : 'formLoader.php',
                        method: 'GET',
                        params: {
                            cat_id: responseData.cat_id
                        },
                        waitMsg : 'Espere por favor'
                    });
                }
            });
        }
    });</pre>
<p>En el handler vemos que realizamos una llamda a la función formCategories.getForm().submit(), a la cual le pasamos un array de configuración. Esta llamada lo que hace es llamar al envío del formulario. Éste se configura pasándole una función para el manejo de error, una función para el manejo de una operación correcta y la URL de actualización.</p>
<p>El código todo unido quedaría de la siguiente forma</p>
<pre name="code" class="javascript">Ext.onReady(function() {
	Ext.QuickTips.init();
	/*
     * Creamos el registro de datos
     */
    var categoriesRecord = new Ext.data.Record.create([
        {name: 'cad_id', type: 'int'},
        {name: 'cad_name', type: 'string'},
        {name: 'cad_date', type: 'date', dateFormat: 'Y-m-d'},
        {name: 'cad_description', type: 'string'}
    ]);

	var categoriesFormReader = new Ext.data.JsonReader(
            {
                root : 'data',
                successProperty : 'success',
                totalProperty: 'total',
                id: 'cad_id'
            },categoriesRecord
    );

	Ext.form.JsonErrorReader = function() {
    Ext.form.JsonErrorReader.superclass.constructor.call(this, {
        root : 'data',
        successProperty : 'success',
        totalProperty: 'total'
     },
     [
        {name: 'id'},
        {name: 'msg'}
     ]);
    };

    Ext.extend(Ext.form.JsonErrorReader, Ext.data.JsonReader);

    var formCategories = new Ext.FormPanel({
        frame : true,
        width : 624,
        height: 346,
        waitMsgTarget : true,
        reader: categoriesFormReader,
        errorReader: new Ext.form.JsonErrorReader(),
        items : [{
                fieldLabel : 'Categoría',
                xtype: 'textfield',
                name : 'cad_name',
                allowBlank:false,
                width : 430
            }, {
                fieldLabel : 'Descripción',
                name : 'cad_description',
                allowBlank:false,
                xtype: 'textarea',
                width : 430,
                height: 225
            }, {
                fieldLabel : 'Fecha',
                name : 'cad_date',
                allowBlank:false,
                xtype: 'datefield',
                renderer: Ext.util.Format.dateRenderer('d/m/Y'),
                width : 430
            }, {
                name : 'cad_id',
                xtype: 'hidden'
            }],
    });

    var submitFormCategories = formCategories.addButton({
        text : 'Guardar',
        disabled : false,
        handler : function() {
            formCategories.getForm().submit({
                url : 'formSaver.php',
                waitMsg : 'Salvando datos...',
                failure: function (form, action) {
                    Ext.MessageBox.show({
                        title: 'Error al salvar los datos',
                        msg: 'Error al salvar los datos.',
                        buttons: Ext.MessageBox.OK,
                        icon: Ext.MessageBox.ERROR
                    });
                },
                success: function (form, request) {
                    Ext.MessageBox.show({
                        title: 'Datos salvados correctamente',
                        msg: 'Datos salvados correctamente',
                        buttons: Ext.MessageBox.OK,
                        icon: Ext.MessageBox.INFO
                    });
                    responseData = Ext.util.JSON.decode(request.response.responseText);
                    formCategories.getForm().load({
                        url : 'formLoader.php',
                        method: 'GET',
                        params: {
                            cat_id: responseData.cat_id
                        },
                        waitMsg : 'Espere por favor'
                    });
                }
            });
        }
    });

    var windowForm = new Ext.Window({
        closable: true,
        resizable: true,
        modal: false,
        border: true,
        plain: true,
        closeAction: 'hide',
        title: 'Layout Tab',
        width: 640,
        height: 380,
        renderTo: 'divRender',
        items: [formCategories]
    });

    formCategories.getForm().load({
        url : 'formLoader.php',
        method: 'GET',
        params: {
            cat_id: 1
        },
        waitMsg : 'Espere por favor'
    });
    windowForm.show();
});</pre>
<h2>Introduciendo un combo como campo</h2>
<p>El tratamiento de un combo requiere un poco más de trabajo. En este caso vamos a introducir el combo en el formulario como un objeto independiente.</p>
<pre name="code" class="javascript">    var categoriesComboWebs = new Ext.form.ComboBox({
        fieldLabel: 'Web',
        hiddenName: 'cad_web', // Este campo es importante, sin él no funciona el combo
        store: new Ext.data.SimpleStore({
            fields: ['cad_web', 'cad_web_text'],
            data : websData,
            id: 0
        }),
        valueField: 'cad_web',
        displayField: 'cad_web_text',
        typeAhead: true,
        mode: 'local',
        triggerAction: 'all',
        emptyText: 'Seleccione una web...',
        selectOnFocus: true,
        width: 430
    });</pre>
<p>En este caso hemos hecho que el combo sea local, y que los datos los saque de un Array. Para ello, hemos definido el store del mismo como un Ext.data.SimpleStore. En la propiedad data, hemos indicado cual es la variable javascript que contiene los valores a utilizar, y debe contener valores de la siguiente forma:</p>
<pre name="code" class="javascript">var websData = [
        ['1','http://www.3megapixels.com'],
        ['2','http://www.desarrollosweb.net'],
        ['3','http://www.axena.org'],
        ['4','http://www.javiercaride.es']
    ];</pre>
<p>Una propiedad importante, para que cuando carguemos los datos se seleccione bien el elemento del combo correspondiente y que además se envíe al servidor la variable correcta, es &#8216;hiddenName&#8217;. Debe estar inicializada al valor del registro que contiene la información, en este caso cad_web</p>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2008/11/21/introduccion-a-formularios-en-extjs-extformpanel/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Introducción a los Grids en ExtJS</title>
		<link>http://www.desarrollosweb.net/2008/11/18/introduccion-a-los-grids-en-extjs/</link>
		<comments>http://www.desarrollosweb.net/2008/11/18/introduccion-a-los-grids-en-extjs/#comments</comments>
		<pubDate>Tue, 18 Nov 2008 12:27:05 +0000</pubDate>
		<dc:creator>Javier Caride</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[desarrollo web]]></category>
		<category><![CDATA[Ext.EditorGridPanel]]></category>
		<category><![CDATA[Ext.GridPanel]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://www.desarrollosweb.net/?p=39</guid>
		<description><![CDATA[En ExtJS tenemos básicamente dos tipos de grids, los que únicamente son para listar información (los llamaremos Grids básicos) y los grids editables, que permiten editar la información que se lista dentro de ellos. Grid básico En los grids tenemos que distinguir 3 partes importantes: El store de datos El modelo de datos de las [...]]]></description>
			<content:encoded><![CDATA[<p>En ExtJS tenemos básicamente dos tipos de grids, los que únicamente son para listar información (los llamaremos Grids básicos) y los grids editables, que permiten editar la información que se lista dentro de ellos.</p>
<h2>Grid básico</h2>
<p>En los grids tenemos que distinguir 3 partes importantes:</p>
<ul>
<li>El store de datos</li>
<li>El modelo de datos de las columnas</li>
<li>La definición del grid en sí</li>
</ul>
<p><strong>El store de datos</strong>, es el objeto que se encarga de almacenar los datos que se van a mostrar en el grid. Básicamete se tratan de arrays de datos, pero su particularidad es que pueden leer los datos desde diversas fuentes:</p>
<ul>
<li>Estáticas: ficheros XML o variables de tipo array</li>
<li>Dinámicas: servicios de datos XML o JSON</li>
</ul>
<p>En la definición de un store tenemos que tener en cuenta dos aspectos importantes: el formato de los datos, que nos va a indicar qué tipo de reader necesitamos configurar y si el origen de dato es remoto, configurar el proxy de lectura de datos</p>
<p><strong>El modelo de datos de las columnas</strong> nos define qué datos se van a mostrar en el grid, con qué formato, y de donde se extraen los datos. Hay que tener en cuenta que aunque en un registro se lean muchos campos, podemos elegir cuales mostrar y cuales no.</p>
<h3>Pasos para configurar un grid</h3>
<p>Primero definimos el formato de cada registro, aquí configuramos cuantos campos hay y de que tipo</p>
<pre name="code" class="javascript">    /*
     * Creamos el registro de datos
     */
    var categoriesRecord = new Ext.data.Record.create([
        {name: 'cad_id', type: 'int'},
        {name: 'cad_name', type: 'string'},
        {name: 'cad_date', type: 'date', dateFormat: 'Y-m-d'},
        {name: 'cad_description', type: 'string'}
    ]);</pre>
<p>Después, configuramos el lector de datos. Como se dijo antes, el lector de datos será diferente dependiendo del formato de los datos que obtengamos. En este caso estamos definiendo que los datos nos vendrán codificados en JSON</p>
<pre name="code" class="javascript">    /*
     * Creamos el reader de datos
     */
    var categoriesGridReader = new Ext.data.JsonReader({
        root: 'data',
        totalProperty: 'total',
        id: 'cad_id'},
        categoriesRecord
    );</pre>
<p>La configuración del reader es fácil de entender:</p>
<ul>
<li>Mediante el campo root, indicamos la etiqueta bajo la que llegarán los datos, en este caso &#8216;data&#8217;</li>
<li>El campo totalProperty nos indica en qué etiqueta nos llega el número total de registros</li>
<li>El campo id, nos indica en qué campo nos llega la identificación del registro</li>
</ul>
<p>Una vez tenemos configurado el registro y el reader, definimos el proxy de datos (para una carga remota de los mismos).</p>
<pre name="code" class="javascript">    /*
     * Creamos el proxy para lectura remota de datos
     */
    var categoriesDataProxy = new Ext.data.HttpProxy({
        url: 'scripts/categoriesService.php',   // Servicio web
        method: 'POST'                          // Método de envío
    });</pre>
<p>En este caso, vemos que la carga la hacemos a través de un script php.El cual nos debe devolver un JSON como el siguiente:</p>
<pre name="code" class="javascript">{
    "total":3,
    "data":[
        {
            "cad_id":1,
            "cad_name":"Categoría 1",
            "cad_description":"Descripción de la primera categoría",
            "cad_date": "2008-10-01"
        },{
            "cad_id":2,
            "cad_name":"Categoría 2",
            "cad_description":"Descripción de la segunda categoría",
            "cad_date": "2008-10-11"
        },{
            "cad_id":3,
            "cad_name":"Categoría 3",
            "cad_description":"Descripción de la tercera categoría",
            "cad_date": "2008-10-20"
        },{
            "cad_id":4,
            "cad_name":"Categoría 4",
            "cad_description":"Descripción de la cuarta categoría",
            "cad_date": "2008-10-21"
        },
    ]
}</pre>
<p>Para ello, el php de prueba tiene este contenido</p>
<pre name="code" class="php">$retValue = array(
    'total' =&gt; 4,
    'data' =&gt; array(
        array(
            'cad_id' =&gt; 1,
            'cad_name' =&gt; 'Categoría 1',
            'cad_description' =&gt; 'Descripción de la primera categoría',
            'cad_date' =&gt; '2008-10-01'
        ),
        array(
            'cad_id' =&gt; 2,
            'cad_name' =&gt; 'Categoría 2',
            'cad_description' =&gt; 'Descripción de la segunda categoría',
            'cad_date' =&gt; '2008-10-11'
        ),
        array(
            'cad_id' =&gt; 3,
            'cad_name' =&gt; 'Categoría 3',
            'cad_description' =&gt; 'Descripción de la tercera categoría',
            'cad_date' =&gt; '2008-10-20'
        ),
        array(
            'cad_id' =&gt; 4,
            'cad_name' =&gt; 'Categoría 4',
            'cad_description' =&gt; 'Descripción de la cuarta categoría',
            'cad_date' =&gt; '2008-10-21'
        )
    )
);
echo json_encode($retValue);</pre>
<p>En el caso de scripts php, primero hay que crear en un array la estructura que queramos devolver y después codificar mediante al función json_encode (PHP5), o bien (en PHP4) utilizar la librería de codificación JSON que provee PEAR</p>
<p>Hay que tener en cuenta una cosa JSON, trabaja únicamente en UTF-8, por lo que si los datos los extraemos por ejemplo de una base de datos configurada en ISO-8859-1, <strong>HAY QUE CONVERTIRLOS A UTF-8</strong>, obligatoriamente.</p>
<p>Ahora ya tenemos todos los elementos necesarios para configurar el store del grid:</p>
<pre name="code" class="javascript">    /*
     * Creamos el datastore donde se van a almacenar los datos de la tabla
     */
    var categoriesDataStore = new Ext.data.Store({
        id: 'categoriesDS',
        //Indicamos de donde se va a leer los datos, en este caso un servicio web
        proxy: categoriesDataProxy,
        // Parámetros base que se enviarán al script
        baseParams: {
            language: "es_ES"
        },
        // Indicamos el reader, es decir el procesador de los datos
        reader: categoriesGridReader
    });</pre>
<p>Ya por último, antes de definir el grid en sí, definimos cuales van a ser nuestras columnas, su tipo, ancho, etc.</p>
<pre name="code" class="javascript">    var categoriesColumnMode = new Ext.grid.ColumnModel(
        [{
            header: '#',
            dataIndex: 'cad_id',
            width: 50,
            hidden: false
        },{
            header: 'Categoría',
            dataIndex: 'cad_name',
            width: 150
        },{
            header: 'Descripción',
            dataIndex: 'cad_description',
            width: 300
        },{
            header: 'Fecha',
            dataIndex: 'cad_date',
            width: 90,
            renderer: Ext.util.Format.dateRenderer('d/m/Y')
        }]
    );</pre>
<p>Ahora ya tenemos todos los elementos necesarios para configurar el grid:</p>
<pre name="code" class="javascript">    var categoriesGrid = new Ext.grid.GridPanel({
        id: 'cat_categoriesGrid',
        store: categoriesDataStore,
        cm: categoriesColumnMode,
        enableColLock:false,
        selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
    });</pre>
<p>Sólo nos falta poner todo dentro de un bloque Ext.onReady y definir una ventana para poder visualizar el grid.</p>
<h2>Grid editable</h2>
<p>Además de los grids básicos, ExtJS permite definir grids en los que se puedan editar valores. Para convertir un grid no editable a uno editable, tenemos que hacer cambios en dos lugares: en el ColumnModel y en la definición del grid En el Column mode, hay que incluir una etiqueta &#8216;editor&#8217; en aquellas columnas que queramos hacer editables. En esta etiqueta los que se pone es la definición de un campo editable cuyo objeto puede ser cualquiera de los que hay disponibles para los formularios (cajas de texto, combos, fechas, tiempo&#8230;)</p>
<pre name="code" class="javascript">    var categoriesColumnMode = new Ext.grid.ColumnModel(
        [{
            header: '#',
            dataIndex: 'cad_id',
            width: 50,
            hidden: false
        },{
            header: 'Categoría',
            dataIndex: 'cad_name',
            width: 150,
            editor: new Ext.form.TextField({})

        },{
            header: 'Descripción',
            dataIndex: 'cad_description',
            width: 300,
            editor: new Ext.form.TextField({})
        },{
            header: 'Fecha',
            dataIndex: 'cad_date',
            width: 90,
            renderer: Ext.util.Format.dateRenderer('d/m/Y'),
            editor: new Ext.form.DateField({})
        }]
    );</pre>
<p>La modificación del grid es &#8220;más fácil&#8221;. En este caso sólo hay que heredar de otra clase distinta: EditorGridPanel</p>
<pre name="code" class="javascript">    var categoriesGrid = new Ext.grid.EditorGridPanel({
        id: 'cat_categoriesGrid',
        store: categoriesDataStore,
        cm: categoriesColumnMode,
        enableColLock:false,
        selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
    });</pre>
<p>Al ejecutar el ejemplo, podemos ver que al hacer doble click en cualquiera de los campos editables, se nos permite cambiar el valor del campo y que dicho cambio queda marcado en la esquina superior izquierda con un pequeño icono rojo</p>
<h2>Tratando los cambios en un grid editable</h2>
<p>De nada nos sirve tener un grid editable si no tratamos los datos, pues todos los cambios que se hagan en un grid editable son cambios en el store local.</p>
<p>ExtJS no ofrece ningún mecanismo para automatizar el guardado de datos, dejando a elección del programador la elección del método. Por ejemplo, podemos hacer actualizaciones campo a campo, capturando el evento de cambio de una celda, o bien podemos enviar todos los cambios de una vez mediante la acción en algún botón o componente</p>
<p>Qué método utilizar queda a elección del desarrollador según las circunstancias en las que se  encuentre. En este caso vamos a explicar cómo se haría un envío de todos los cambios. Para ello vamos a coger el ejemplo anterior y primeramente añadir un botón de actualización de cambio situado en una barra de herramientas superior</p>
<pre name="code" class="javascript">    var categoriesGridTopbar = new Ext.Toolbar({
        id: 'categoriesGridTopbarId',
        items: [
            '-&gt;',
            {
                xtype:'button',
                text:'Guardar Cambios',
                id: 'portadas_ToolbarTop_btn0',
                handler:categoriesGridTopbarHnd,
                type:'button'
            }
        ]
    });</pre>
<p>Como se puede ver, se a asignado un handler al botón, que será el que tenderá la petición de guardado de cambios y que contendrá toda la lógica de actualización</p>
<p>En el handler, lo que hemos hecho es crear una llamada Ajax que envíe al servidor todos los cambios, para ello se pide al store un array con todos los registros que han cambiado, se recorre y se  almacena en un array los datos necesarios para enviar. Este array se codifica en JSON y se envía como una variable POST normal. Después es tarea del script php (o Java, o lo que sea) de tratar el array de cambios y realizar las actualizaciones pertinentes.</p>
<pre name="code" class="javascript">    var failureAjaxFn = function(response, request) {
        Ext.Msg.show({
            title: 'Error',
            msg: 'Cambios no guardados. Status:' + response.status,
            buttons: Ext.Msg.OK,
            animEl: 'elId',
            icon: Ext.MessageBox.ERROR
        });
    }

    var successAjaxFn = function(response, request) {
        /*
         * En response.responseText tenemos la respuesta del script, en este caso
         * la salida del script PHP es JSON con lo que tenemos que decodificarlo
         */
        var jsonData = Ext.util.JSON.decode(response.responseText);

        if (true == jsonData.success) {
            Ext.Msg.show({
                title: 'Operación completada',
                msg: jsonData.message,
                buttons: Ext.Msg.OK,
                animEl: 'elId',
                icon: Ext.MessageBox.INFO
            });

            /*
             * PASO IMPORTANTE
             * Para indicar al store que los cambios se han realizado correctamente
             * y eliminar los indicadores de que hay cambios pendientes de guardar
             * hay que llamar
             */
            categoriesDataStore.commitChanges();

        } else {
            Ext.Msg.show({
                title: 'Error',
                msg: jsonData.message,
                buttons: Ext.Msg.OK,
                animEl: 'elId',
                icon: Ext.MessageBox.ERROR
            });
        }
    }
var categoriesGridTopbarHnd = function (e,o) {
    	/*
    	 * Pedimos al store que nos devuelva los registros modificados
    	 */
        var modifiedRecords = categoriesDataStore.getModifiedRecords()

        if (modifiedRecords.length > 0) {
        	/*
             * Creamos un array con los datos de los registros que han cambiado
             */
        	var changes = new Array();
            for (var i=0; i &lt; modifiedRecords.length; i++){
                changes.push(modifiedRecords[i].data);
            }

            /*
             * Codificamos los cambios en JSON
             */
            changes = Ext.util.JSON.encode(changes);

            Ext.Ajax.request({
                url: 'salvarDatos.php',
                method: 'POST',
                success: successAjaxFn,
                failure: failureAjaxFn,
                timeout: 30000,
                params: {
                    operation: 'update',
                    changes: changes
                }
            });
        }
    }
</pre>
<h2>Grids paginados</h2>
<p>Uno de las barras de herramientas más utilizadas con los grids es la de paginación. Su utilización es<br />
bastante sencilla. Primero creamos la barra de paginación:</p>
<pre name="code" class="javascript">    var categoriesPagingBar = new Ext.PagingToolbar({
        pageSize: 10,
        store: categoriesDataStore,
        displayInfo: true
    });</pre>
<p>Su uso es sencillo, sólo tenemos que indicar cual es el store, y el tamaño de página. Después de definirlo<br />
se lo asociamos al grid, para lo que sólo tenemos que indicar en la configuración<br />
lo siguiente &#8220;bbar: categoriesPagingBar&#8221;:</p>
<pre name="code" class="javascript">    var categoriesGrid = new Ext.grid.EditorGridPanel({
        id: 'cat_categoriesGrid',
        store: categoriesDataStore,
        cm: categoriesColumnMode,
        enableColLock:false,
        tbar: categoriesGridTopbar,
        bbar: categoriesPagingBar,
        selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
    });</pre>
<p>Después en la carga inicial de datos, indicamos que queremos cargar la primera página de resultados<br />
y el número de ellos</p>
<pre name="code" class="javascript">categoriesDataStore.load({params: {start: 0, limit: 10}});</pre>
<p>Y después de esto ya no nos tenemos que preocupar de nada más, pues cada vez que hagamos click en los botones el store nos enviará dos variables nuevas: start y limit, al estilo de las paginaciones clásicas</p>
]]></content:encoded>
			<wfw:commentRss>http://www.desarrollosweb.net/2008/11/18/introduccion-a-los-grids-en-extjs/feed/</wfw:commentRss>
		<slash:comments>27</slash:comments>
		</item>
	</channel>
</rss>
