La dinámica de desarrollo en Flex está orientada a crear componentes independientes lo más atomizado posible, con el fin de componer interfaces ricas de forma ergonómica e independiente, lo que además, permite aislar problemas concretos dentro de las interfaces, además de contribuir a la reutilización de los mismos.
Para entender un poco mejor el párrafo anterior imaginemos una interfaz típica en la que hay un grid con el conjunto de datos, y además una ficha de registro. Podemos pensar en realizar todo en un mismo contenedor, ya sea en la misma visualización, activando desactivando opciones según se requieran, o bien crear estados para activar o desactivar el modo lista o el modo ficha.
Para ir un poco más finos, se puede crear un componente lista que se encargue de forma exclusiva de presentar los datos en un DataGrid, y operar con unos filtros para búsquedas más concretas de información.
Para ser lo más interactivos posibles, habría un botón "Agregar" para añadir un nuevo registro, con lo que presentaría la ficha vacía. Para editar o eliminar un dato concreto, cuando el usuario haga clic sobre una fila determinada se mostraría la ficha con los datos.
El componente ficha aparecería cuando se pulse el botón "Agregar" en modo edición y vacío. Si se hace clic sobre una fila, aparecería en modo visualización, con la opción de editarlo o eliminarlo. Como acciones tendría "Guardar", "Eliminar", "Cancelar" y "Cerrar". Su objetivo es tratar individualmente los datos de un registro específico.
El contenedor padre únicamente insertaría estos componentes y realizar las relaciones entre ambos según se requieran, pero los objetivos concretos están dentro de cada componente, y en el caso de depurar, corregir, modificar o agregar funcionalidades, el nivel de aislamiento permite claramente "tocar" sólo la parte responsable.
Espero que con este planteamiento esté claro la organización de una funcionalidad clásica de tratamiento y gestión de datos.
Ahora bien, un pensamiento típico para aquel que empieza a desarrollar con componentes es crear las relaciones con elementos (propiedades, variables o métodos) públicos). Esto, además de complicar el desarrollo, la sincronización entre las partes estaría forzada de forma implícita por código, lo que haría un sistema un tanto inestable e inseguro, amén de sacrificado por el esfuerzo que requiere después modificaciones o correcciones. A esto se le denomina acoplamiento fuerte, y todo depende de otras acciones u operaciones.
Es posible hacer más sencillo este desarrollo realizando un acoplamiento débil, y que no sea todo tan dependiente y preocuparnos de las relaciones cuando deban ocurrir, y de una forma menos forzosa. Para ello, podremos definir nuestros propios eventos en los componentes, y a través de ellos pasar información (como los datos de una fila o la acción a emprender).
Para empezar, recomiendo utilizar dos clases en ActionScript. La primera de ellas será una clase para almacenar la información que se pasará al evento, como los datos de la fila seleccionada o la acción solicitada por el usuario. Puede declararse en la carpeta o paquete que uno requiera.
package com.agenda
{
public class Agenda
{
public static const ACTION_NEW:int=0;
public static const ACTION_SELECT:int=1;
public var accion:int;
public var nombre:String;
public var direccion:String;
public var telefono:String;
public function Agenda()
{
accion=ACTION_NEW;
nombre="";
direccion="";
telefono="";
}
public function toString():String
{
return "accion:"+accion+"|"+
nombre+"|"+
direccion + "|" +
telefono;
}
}
}
Básicamente define los campos, un constructor por defecto (con una carga inicial de datos) y un método toString() que muestra la información de la instancia actual.
La segunda clase define el evento personalizado, y para ello se crea una clase que hereda de la superclase base Event y, al igual que la otra clase, podemos colgarla del paquete que creamos más oportuno (en este caso en el mismo paquete que la anterior):
package com.agenda
{
import flash.events.Event;
public class AgendaEvent extends Event
{
public var agenda:Agenda;
public function AgendaEvent(agenda:Agenda, type:String)
{
super(type);
this.agenda=agenda;
}
public override function clone():Event {
return new AgendaEvent(agenda, type);
}
}
}
La clase Event es la clase básica para cualquier evento, y por ello, esta clase hereda (extiende) aquella, añadiendo una funcionalidad propia. En primer lugar se crea una propiedad que contiene un objeto de tipo Agenda (definido en la clase anterior), conteniendo la información necesaria. A continuación se crea un constructor especial, en donde se pasa el objeto Agenda a tratar y el tipo de evento a construir (que será de este tipo). El constructor invoca a su superclase indicando este tipo, y asigna la información al objeto Agenda.
Se sobreescribe el método clone(), el cual crea y devuelve un objeto evento de sí mismo (mejor no entremos en detalle, pero es un punto importante a implementar).
El siguiente paso será ir al componente que va a generar el evento, en nuestro caso al componente del DataGrid, que es el que contiene la lista de datos. En este componente hay que declarar el evento para que sea visible por el resto de componentes que lo utilicen. Para ello, hay que crear este código justo después del comienzo de la declaración del componente (el contenedor que lo forma. Canvas, HBox, VBox...), que sea el primer código del mismo (no es esencialmente así, pero sí recomendable):
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas
xmlns:mx="http://www.adobe.com/2006/mxml"
width="450"
height="400"
>
<!-- EVENTS -->
<mx:Metadata>
[Event(name="selectAgenda",type="com.agenda.AgendaEvent")]
[Event(name="addAgenda",type="com.agenda.AgendaEvent")]
</mx:Metadata>
...
</Canvas>
El bloque Metadata declara dos eventos para este componente:
- selectAgenda -> cuando el usuario hace clic sobre una fila del DataGrid
- addAgenda -> cuando el usuario hace clic sobre el botón "Agregar"
Ambos eventos son del tipo AgendaEvent.
Esta parte sólo declara los eventos, para que sea visible por el componente padre que utiliza a éste (se puede probar a insertar el componente y con Ctrl+Espacio extraer las propiedades y métodos de este componente, donde aparecerán estos dos eventos). En realidad la declaración no hace que se produzcan, pues hay que controlar cuándo y cómo se lanzan estos dos eventos.
El primero de ellos se lanza cuando el usuario hace clic sobre una fila del DataGrid. Para ello, se captura el evento de la selección:
<mx:DataGrid id="dgAgenda"
itemClick="selectItemAgenda();"
...
El código correspondiente para despachar el evento "selectAgenda" es el siguiente:
private function selectItemAgenda():void
{
var miAgenda:Agenda = new Agenda();
miAgenda.action = Agenda.ACTION_SELECT;
miAgenda.nombre = dgAgenda.selectedItem.nombre;
miAgenda.direccion = dgAgenda.selectedItem.direccion;
miAgenda.telefono = dgAgenda.selectedItem.telefono;
var e:AgendaEvent = new AgendaEvent(agenda, "selectAgenda");
this.dispatchEvent(e);
}
Se instancia la clase Agenda para almacenar la información que se va a utilizar en el componente principal o padre. La información se extrae del DataGrid (dgAgenda), de la fila actualmente seleccionada (selectedItem) y de cada uno de los campos definidos en el DataGrid (nombre, direccion y telefono).
A continuación se crea un objeto de tipo evento AgendaEvent, pasando esta información, y dando el nombre del evento "selectAgenda" que ya fue declarado (bloque MetaData). Por último, se despacha el evento (dispatchEvent), que saltará en el componente padre cuando éste se produzca.
Para el evento addAgenda el código es similar. Al hacer clic sobre el botón se invoca al método que despachará el evento:
<mx:Button id="btnAdd" click="addItemAgenda();" />
El código a implementar sería el siguiente:
private function addItemAgenda():void
{
var agenda:Agenda = new Agenda();
var e:AgendaEvent = new AgendaEvent(agenda, "addAgenda");
this.dispatchEvent(e);
}
Cuando el usuario haga clic sobre el botón "Agregar" se creará el evento "addAgenda", que será lanzado hacia los componentes padre que utilicen este componente.
Ahora queda la parte en que estos eventos son capturados por el componente padre o que contiene a este componente. He de reconocer que me volví un poco loco porque creí que no me funcionaba todo lo anterior, ya que al incrustar el componente del DataGrid dentro del componente principal, al intentar ver los eventos, métodos y propiedaes con Ctrl+Espacio en el editor de Flex, no me aparecía nada. Incluso traté de declarar el evento para lanzar un método simple o lanzar un Alert, pero no funcionaba. Si esto os ocurre os contaré por qué.
Utilizo un paquete específico para los componentes. Si se ubica un componente en un paquete o carpeta distinto al del componente padre, parece no verlo, aunque se especifique la ruta completa de forma implícita y se vea el componente y aparentemente funciona (recoge datos y los visualiza, e incluso funciona el filtro). Pero los eventos no funcionaban. Al final, he ubicado todos los componentes, tanto padres e hijos en el mismo paquete, y así iba bien. Estoy de acuerdo que esta explicación no puede ser del todo convincente, pero el hecho es que así me funcionó a mi.
Ahora, en el componente padre, cuando se incrusta el componente hijo, al dar el espacio y al pulsar las teclas Ctrl+Espacio, se verán los eventos declarados y dispuestos para ser utilizados:
<components:ListaAgenda
selectBatch="doSelectAgenda(event);"
addBatch="doAddAgenda(event);"
/>
Al enviar el parámetro "event", éste contendrá la información contenida:
private function doSelectAgenda(e:AgendaEvent):void {
Alert.show("Evento selectAgenda: " + e.agenda.toString());
}
private function doAddAgenda(e:AgendaEvent):void {
Alert.show("Evento addAgenda: " + e.agenda.toString());
}
Espero que este tutorial básico sobre eventos personalizados os sea de utilidad.
Reflexiones, artículos, opiniones, recomendaciones, noticias... sobre la tecnología.
miércoles, 30 de septiembre de 2009
Flex/AIR: filtros de datos
Es muy habitual tener una colección de datos en nuestras interfaces de usuario. Cuando ésta es muy numerosa, es conveniente facilitar al usuario de medios para poder acceder más rápida y directa a una información concreta, y para ello se aplican filtros.
En el ejemplo que voy a exponer utilizaré un DataGrid, que es uno de los elementos de interfaz más utilizados para presentar colecciones de datos. Lo mismo puede aplicarse a otros elementos de interfaz, como las listas o AdvancedDataGrids.
Antes de iniciar el cómo vamos realizar un planteamiento. La mayor parte de las veces, los datos provendrán de fuentes externas o remotas, como un fichero, un HTTPService o un WebService. La invocación a un servicio de datos remoto, normalmente se obtiene en formato e4x, para poder trabajar cómodamente en XML. Pero este sistema carece de la cualidad de filtrar o realizar búsquedas concretas. Se puede realizar filtros manualmente e invocar a los servicios de datos con los parámetros del filtro, pero esto implicaría desperdiciar muchos recursos, ya que cada filtrado es una invocación remota (una llamada a un servicio remoto), con el tiempo que esto conlleva, ejecutar el código de invocación, carga y visualización de datos.
Así pues, nos queda otra opción: obtener la información y almacenarlo en un ArrayCollection, que posee métodos para filtrado y que puede ser enlazado a elementos de interfaz de usuario. En este caso, los datos son los mismos que en la primera carga, e internamente, en memoria, se realiza el filtrado, por lo que es mucho más óptimo en recursos (tiempo, ejecuciones, memoria, etc.)
Para ello, se define un ArrayCollection en la región dedicado al ActionScript (<mx:Script>...</mx:Script>):
[Bindable]
private var miAC:ArrayCollection;
En este post se pondrá un ejemplo con un HTTPService, que es el método más habitual de obtener la información. Recordemos que HTTPService invoca a una URL donde se retornará una fichero XML formado con un nodo padre y con varios hijos en forma de lista (cada uno como un registro o fila del DataGrid). Esta invocación puede realizarse a una página ASP, JSP, PHP, Servlet, etc, que en lugar de retornar una página web retorne un XML. También puede ser una URL con un fichero XML.
<mx:HTTPService
result="handleResultMetodo(event);"
fault="handleFaultMetodo(event);"
id="nombreServicio" resultFormat="object"
url="URLHTTPService"
useProxy="false">
<mx:request xmlns="">
<nombreParametro>valorParametro</nombreParametro>
...
</mx:request>
</mx:HTTPService>
Los parámetros a utilizar son:
- result -> Nombre del método que gestionará el resultado de la invocación
- fault -> Nombre del método que se ejecutará en caso de haber un error en la invocación
- id -> Nombre que se da al servicio
- url -> URL donde se encuentra el XML (ASP, JSP, PHP, Servlet, fichero XML...)
- resultFormat -> Formato del resultado. En este caso se ha usado un tipo "object" para que sea recogido por el ArrayCollection. En casos normales se utiliza el formato "e4x" que permite gestionar cómodamente cualquier XML.
El bloque mx:request es opcional, y se utiliza en el caso de que el recurso XML requiera de parámetros de entrada para formar el XML resultante.
En algún momento determinado del ciclo de vida de la aplicación, se invocará al HTTPService para recoger el XML con los datos. Habitualmente se realiza en el método creationComplete de la aplicación, o del componente Flex. En algunas ocasiones se realiza en respuesta a algún evento (como un clic sobre un botón). El código que realiza la invocación sería el siguiente:
nombreServicio.send();
Una vez invocado el servicio HTTPService generará un resultado (en caso de ir bien) o bien un error (en caso de ir mal). En ambos casos se invocará a los métodos respectivos declarados:
private function handleResultMetodo(event:ResultEvent):void
{
miAC = event.result.elementoPadre.elementoHijo;
}
private function handleFaultMetodo(event:FaultEvent):void
{
Alert.show(event.fault.faultString, "ERROR");
}
Cuando la invocación ha sido exitosa se ejecutará el primer método, pasando en el parámetro "event" el resultado del XML. Para agregar este XML en el ArrayCollection, hay que acceder a la propiedad "result" del mismo, e indicar cuál es el elemento padre y cuál el elemento hijo (registro o fila). Por ejemplo, un XML con esta estructura:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<producto>
<item>
<codigo>1</codigo>
<nombre>Boligrafo negro</nombre>
<precio>1.25</nombre>
</item>
<item>
<codigo>2</codigo>
<nombre>Boligrafo azul</nombre>
<precio>1.50</nombre>
</item>
<item>
<codigo>3</codigo>
<nombre>Boligrafo rojo</nombre>
<precio>1.75</nombre>
</item>
<producto/>
El elemento padre sería "producto", y el elemento hijo sería "item". El ArrayCollection contendría 3 registros (uno por cada elemento hijo), y cada uno de estos registros sería una fila en el DataGrid.
El ArrayCollection está cargado en este momento con la información. El ArrayCollection definido es enlazable (tiene la propiedad Bindable), lo que permite o un control visual, como un DataGrid o un List, enlazarse a los datos de este ArrayCollection, y cada cambio en áquel actualiza automáticamente los datos del control.
El DataGrid se define así:
<mx:DataGrid
id="miDataGrid">
DataProvider="{miAC}">
<mx:DataGridColumn dataField="codigo" headerText="CODIGO" />
<mx:DataGridColumn dataField="nombre" headerText="NOMBRE" />
<mx:DataGridColumn dataField="precio" headerText="PRECIO" />
</mx:DataGrid>
El control DataGrid es un control de datos en forma de tabla. El atributo "id" define el nombre para el DataGrid. El elemento "DataProvider" indica al DataGrid de dónde debe obtener los datos. En este caso, se indica entre llaves el nombre del ArrayCollection para que se lleve a cabo el enlace automático.
El DataGrid tiene tres columnas, que se definen con "DataGridColumn". El atributo "dataField" especifica el campo de donde va a obtener el valor ("codigo", "nombre" o "precio", definidos en el XML). El atributo "headerText" define el título que aparecerá en la cabecera superior de cada columna.
Ahora viene la parte interesante, y es el filtro. Para ello, se definen controles de usuario, como una caja de texto, una lista desplegable o un calendario para obtener una fecha. En cualquiera de los casos utilizados, se ha de invocar a una método cuando el valor de estos controles cambia (el que aplique según el caso). En este caso, vamos a poner que haya sido un campo de texto y un botón de anulación:
<mx:TextInput id="txtBuscar" change="buscarMetodo()" />
<mx:Button label="Reiniciar" click="reiniciarMetodo()" />
En el caso de que se introduzca un carácter en la caja de texto se producirá un cambio, que recogerá el evento "change", y aquí le indicamos que ejecute el método
buscarMetodo(), el cual contiene el siguiente código:
private function buscarMetodo():void {
miAC.filterFunction = filtroMetodo;
miAC.refresh();
}
El ArrayCollection tiene una propiedad que se encarga de la gestión de filtrado de los datos que contiene. Para ello, delega en un método que especifica cómo llevar a cabo este filtrado, y el cual, su nombre se especifica a esta propiedad, en este caso, "filtroMetodo". Este método contendrá el siguiente código:
private function filtroMetodo(item:Object):Boolean{
var encontrado:Boolean = false;
if(item.nombre.toLowerCase().search(txtBuscar.text.toLowerCase()) != -1){
encontrado = true;
}
return encontrado;
}
El ArrayCollection ejecutará este método por cada fila que contiene, enviando en el objeto "item" la fila que se está procesando. Aquí se compara la fila ("item") y el campo a comparar (en este caso "nombre"), anmbos igualados en minúsculas. En caso de que case esta comparación se retorna un valor true (se muestra) o en caso contrario retorna un valor false (no se muestra).
Otra forma de comparación sería así:
private function filtroMetodo(item:Object):Boolean{
var encontrado:Boolean = true;
var filter:String = item["nombre"]; // valor del campo "nombre"
if(filter.toLowerCase().indexOf(txtBuscar.text.toLowerCase())<0){
encontrado = false;
}
return encontrado;
}
Una vez se ha procesado todas las filas e identificadas cuáles cumplen los requisitos del filtro, la siguiente sentencia:
miAC.refresh();
Para anular los filtros y que el ArrayCollection (y por ende, el DataGrid) contenga toda la información, hay que anular la propiedad "filterFunction" y refrescar el Arraycollection:
private function reiniciarMetodo():void {
miAC.filterFunction = null;
miAC.refresh();
}
Refresca o actualiza el contenido del ArrayCollection, propagándose automáticamente hacia el DataGrid que actualizará la información a mostrar.
Un ejemplo muy sencillo es el siguiente:
En este caso, la información está en el propio código (por eso es tan extenso), y se carga directamente en el ArrayCollection sin usar un HTTPService. Pero el objetivo principal era demostrar el uso de los filtros.
Este ejemplo (el último) está en el siguiente enlace: http://www.boyzoid.com/blog/index.cfm/2006/10/19/Filtering-Data-in-Flex
Para terminar, indicar que los dos ejemplos expuestos anteriormente se han basado en un único campo, pero lo más normal es que un filtro pueda tener varios campos y se combinen entre sí.
A continuación voy a poner un ejemplo (no completo, si no únicamente en lo que concierne a este punto), con dos campos: uno en una combo y otro en un calendario.
El primero filtra un tipo de lotes. Sus valores pueden ser: * (todos), "I" (entrada o true) y "O" (salida o false).
Los dos campos de filtro, invocan a sus respectivos métodos cuando cambian su valor:
// User click on combo type of batch
private function changeType():void {
if (cboTipoLote.value!="*" || datLote.text!=null)
this.batchesList.filterFunction=filterBatches;
else
this.batchesList.filterFunction=null;
this.batchesList.refresh();
}
// User change date on date field
private function changeDate():void {
if (cboTipoLote.value!="*" || datLote.text!=null)
this.batchesList.filterFunction=filterBatches;
else
this.batchesList.filterFunction=null;
this.batchesList.refresh();
}
El ArrayCollection está definido en "batchesList".
El método delegado para el filtrado es el siguiente:
// Filter of batches
private function filterBatches(item:Object):Boolean
{
var match:Boolean = true;
var sDate:String="";
var sType:String="";
if (cboTipoLote.value=="I")
sType="true";
else if (cboTipoLote.value=="O")
sType="false";
else
sType="";
if (datLote.text !="") {
sDate =""+datLote.selectedDate.fullYear +"-";
var vValue:String=""+(datLote.selectedDate.month+1);
if (vValue.length==1)
vValue = "0" + vValue;
sDate+= vValue + "-";
vValue = ""+(datLote.selectedDate.date);
if (vValue.length==1)
vValue = "0" + vValue;
sDate += vValue;
}
else
sDate = "";
var filter1:String = item["input_batch"];
var filter2:String = item["date"];
if (sDate!="" && sType=="") {
if (!filter2 || filter2.indexOf(sDate)<0)
match=false;
}
else if (sDate=="" && sType!="") {
if (!filter1 ||
filter1.toLowerCase().indexOf(sType.toLowerCase())<0)
match=false;
}
else {
if (!filter1 ||
filter1.toLowerCase().indexOf(sType.toLowerCase())<0 ||
!filter2 || filter2.indexOf(sDate)<0)
match=false;
}
return match;
}
En este caso se contemplan 3 casos:
1) Se ha facilitado tipo pero no fecha
2) Se ha facilitado fecha pero no tipo
3) Se han facilitado tipo y fecha
Espero que estos ejemplos sean claros y útiles para desarrollar aplicaciones AIR y Flex más interactivas y optimizadas.
En el ejemplo que voy a exponer utilizaré un DataGrid, que es uno de los elementos de interfaz más utilizados para presentar colecciones de datos. Lo mismo puede aplicarse a otros elementos de interfaz, como las listas o AdvancedDataGrids.
Antes de iniciar el cómo vamos realizar un planteamiento. La mayor parte de las veces, los datos provendrán de fuentes externas o remotas, como un fichero, un HTTPService o un WebService. La invocación a un servicio de datos remoto, normalmente se obtiene en formato e4x, para poder trabajar cómodamente en XML. Pero este sistema carece de la cualidad de filtrar o realizar búsquedas concretas. Se puede realizar filtros manualmente e invocar a los servicios de datos con los parámetros del filtro, pero esto implicaría desperdiciar muchos recursos, ya que cada filtrado es una invocación remota (una llamada a un servicio remoto), con el tiempo que esto conlleva, ejecutar el código de invocación, carga y visualización de datos.
Así pues, nos queda otra opción: obtener la información y almacenarlo en un ArrayCollection, que posee métodos para filtrado y que puede ser enlazado a elementos de interfaz de usuario. En este caso, los datos son los mismos que en la primera carga, e internamente, en memoria, se realiza el filtrado, por lo que es mucho más óptimo en recursos (tiempo, ejecuciones, memoria, etc.)
Para ello, se define un ArrayCollection en la región dedicado al ActionScript (<mx:Script>...</mx:Script>):
[Bindable]
private var miAC:ArrayCollection;
En este post se pondrá un ejemplo con un HTTPService, que es el método más habitual de obtener la información. Recordemos que HTTPService invoca a una URL donde se retornará una fichero XML formado con un nodo padre y con varios hijos en forma de lista (cada uno como un registro o fila del DataGrid). Esta invocación puede realizarse a una página ASP, JSP, PHP, Servlet, etc, que en lugar de retornar una página web retorne un XML. También puede ser una URL con un fichero XML.
<mx:HTTPService
result="handleResultMetodo(event);"
fault="handleFaultMetodo(event);"
id="nombreServicio" resultFormat="object"
url="URLHTTPService"
useProxy="false">
<mx:request xmlns="">
<nombreParametro>valorParametro</nombreParametro>
...
</mx:request>
</mx:HTTPService>
Los parámetros a utilizar son:
- result -> Nombre del método que gestionará el resultado de la invocación
- fault -> Nombre del método que se ejecutará en caso de haber un error en la invocación
- id -> Nombre que se da al servicio
- url -> URL donde se encuentra el XML (ASP, JSP, PHP, Servlet, fichero XML...)
- resultFormat -> Formato del resultado. En este caso se ha usado un tipo "object" para que sea recogido por el ArrayCollection. En casos normales se utiliza el formato "e4x" que permite gestionar cómodamente cualquier XML.
El bloque mx:request es opcional, y se utiliza en el caso de que el recurso XML requiera de parámetros de entrada para formar el XML resultante.
En algún momento determinado del ciclo de vida de la aplicación, se invocará al HTTPService para recoger el XML con los datos. Habitualmente se realiza en el método creationComplete de la aplicación, o del componente Flex. En algunas ocasiones se realiza en respuesta a algún evento (como un clic sobre un botón). El código que realiza la invocación sería el siguiente:
nombreServicio.send();
Una vez invocado el servicio HTTPService generará un resultado (en caso de ir bien) o bien un error (en caso de ir mal). En ambos casos se invocará a los métodos respectivos declarados:
private function handleResultMetodo(event:ResultEvent):void
{
miAC = event.result.elementoPadre.elementoHijo;
}
private function handleFaultMetodo(event:FaultEvent):void
{
Alert.show(event.fault.faultString, "ERROR");
}
Cuando la invocación ha sido exitosa se ejecutará el primer método, pasando en el parámetro "event" el resultado del XML. Para agregar este XML en el ArrayCollection, hay que acceder a la propiedad "result" del mismo, e indicar cuál es el elemento padre y cuál el elemento hijo (registro o fila). Por ejemplo, un XML con esta estructura:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<producto>
<item>
<codigo>1</codigo>
<nombre>Boligrafo negro</nombre>
<precio>1.25</nombre>
</item>
<item>
<codigo>2</codigo>
<nombre>Boligrafo azul</nombre>
<precio>1.50</nombre>
</item>
<item>
<codigo>3</codigo>
<nombre>Boligrafo rojo</nombre>
<precio>1.75</nombre>
</item>
<producto/>
El elemento padre sería "producto", y el elemento hijo sería "item". El ArrayCollection contendría 3 registros (uno por cada elemento hijo), y cada uno de estos registros sería una fila en el DataGrid.
El ArrayCollection está cargado en este momento con la información. El ArrayCollection definido es enlazable (tiene la propiedad Bindable), lo que permite o un control visual, como un DataGrid o un List, enlazarse a los datos de este ArrayCollection, y cada cambio en áquel actualiza automáticamente los datos del control.
El DataGrid se define así:
<mx:DataGrid
id="miDataGrid">
DataProvider="{miAC}">
<mx:DataGridColumn dataField="codigo" headerText="CODIGO" />
<mx:DataGridColumn dataField="nombre" headerText="NOMBRE" />
<mx:DataGridColumn dataField="precio" headerText="PRECIO" />
</mx:DataGrid>
El control DataGrid es un control de datos en forma de tabla. El atributo "id" define el nombre para el DataGrid. El elemento "DataProvider" indica al DataGrid de dónde debe obtener los datos. En este caso, se indica entre llaves el nombre del ArrayCollection para que se lleve a cabo el enlace automático.
El DataGrid tiene tres columnas, que se definen con "DataGridColumn". El atributo "dataField" especifica el campo de donde va a obtener el valor ("codigo", "nombre" o "precio", definidos en el XML). El atributo "headerText" define el título que aparecerá en la cabecera superior de cada columna.
Ahora viene la parte interesante, y es el filtro. Para ello, se definen controles de usuario, como una caja de texto, una lista desplegable o un calendario para obtener una fecha. En cualquiera de los casos utilizados, se ha de invocar a una método cuando el valor de estos controles cambia (el que aplique según el caso). En este caso, vamos a poner que haya sido un campo de texto y un botón de anulación:
<mx:TextInput id="txtBuscar" change="buscarMetodo()" />
<mx:Button label="Reiniciar" click="reiniciarMetodo()" />
En el caso de que se introduzca un carácter en la caja de texto se producirá un cambio, que recogerá el evento "change", y aquí le indicamos que ejecute el método
buscarMetodo(), el cual contiene el siguiente código:
private function buscarMetodo():void {
miAC.filterFunction = filtroMetodo;
miAC.refresh();
}
El ArrayCollection tiene una propiedad que se encarga de la gestión de filtrado de los datos que contiene. Para ello, delega en un método que especifica cómo llevar a cabo este filtrado, y el cual, su nombre se especifica a esta propiedad, en este caso, "filtroMetodo". Este método contendrá el siguiente código:
private function filtroMetodo(item:Object):Boolean{
var encontrado:Boolean = false;
if(item.nombre.toLowerCase().search(txtBuscar.text.toLowerCase()) != -1){
encontrado = true;
}
return encontrado;
}
El ArrayCollection ejecutará este método por cada fila que contiene, enviando en el objeto "item" la fila que se está procesando. Aquí se compara la fila ("item") y el campo a comparar (en este caso "nombre"), anmbos igualados en minúsculas. En caso de que case esta comparación se retorna un valor true (se muestra) o en caso contrario retorna un valor false (no se muestra).
Otra forma de comparación sería así:
private function filtroMetodo(item:Object):Boolean{
var encontrado:Boolean = true;
var filter:String = item["nombre"]; // valor del campo "nombre"
if(filter.toLowerCase().indexOf(txtBuscar.text.toLowerCase())<0){
encontrado = false;
}
return encontrado;
}
Una vez se ha procesado todas las filas e identificadas cuáles cumplen los requisitos del filtro, la siguiente sentencia:
miAC.refresh();
Para anular los filtros y que el ArrayCollection (y por ende, el DataGrid) contenga toda la información, hay que anular la propiedad "filterFunction" y refrescar el Arraycollection:
private function reiniciarMetodo():void {
miAC.filterFunction = null;
miAC.refresh();
}
Refresca o actualiza el contenido del ArrayCollection, propagándose automáticamente hacia el DataGrid que actualizará la información a mostrar.
Un ejemplo muy sencillo es el siguiente:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="initData()">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var dataList:ArrayCollection ;
private function initData():void{
dataList= new ArrayCollection([
{name:"Atlantic City Medical Center-City Division", city:"Atlantic City"},
{name:"Atlantic City Medical Center-Mainland Division", city:"Pomona "},
{name:"Barnert Hospital", city:"Paterson"},
{name:"Bayonne Medical Center", city:"Bayonne"},
{name:"Bayshore Community Hospital", city:"Holmdel"},
{name:"Bergen Regional Medical Center. L.P.", city:"Paramus"},
{name:"Burdette Tomlin Memorial Hospital", city:"Cape May"},
{name:"Capital Health System - Fuld Campus", city:"Trenton"},
{name:"Capital Health System - Mercer Campus", city:"Trenton"},
{name:"CentraState Healthcare System", city:"Freehold"},
{name:"Chilton Memorial Hospital", city:"Pompton Plains"},
{name:"Christ Hospital", city:"Jersey City"},
{name:"Clara Maass Medical Center", city:"Belleville"},
{name:"Columbus Hospital", city:"Newark"},
{name:"Community Medical Center", city:"Toms River"},
{name:"East Orange General Hospital", city:"East Orange"},
{name:"Englewood Hospital and Medical Center", city:"Englewood"},
{name:"Hackensack University Medical Center", city:"Hackensack"},
{name:"Hackettstown Community Hospital", city:"Hackettstown"},
{name:"Holy Name Hospital", city:"Teaneck"},
{name:"Hospital Center at Orange", city:"Orange"},
{name:"Hunterdon Medical Center", city:"Flemington"},
{name:"Irvington General Hospital", city:"Irvington"},
{name:"Jersey Shore University Medical Center", city:"Neptune"},
{name:"JFK Medical Center", city:"Edison"},
{name:"Kennedy Memorial Hospitals/UMC Cherry Hill", city:"Cherry Hill"},
{name:"Kennedy Memorial Hospitals/UMC Stratford", city:"Stratford"},
{name:"Kennedy Memorial Hospitals/UMC Washington Twp", city:"Turnersville"},
{name:"Kessler Memorial Hospital", city:"Hammonton"},
{name:"Kimball Medical Center", city:"Lakewood"},
{name:"LibertyHealth-Greenville Hospital Campus", city:"Jersey City"},
{name:"LibertyHealth-Jersey City Medical Center Campus", city:"Jersey City"},
{name:"LibertyHealth-Meadowlands Hospital Campus", city:"Secaucus"},
{name:"Lourdes Medical Center of Burlington County", city:"Willingboro"},
{name:"Monmouth Medical Center", city:"Long Branch"},
{name:"MONOC", city:"Eatontown"},
{name:"Morristown Memorial Hospital", city:"Morristown"},
{name:"Muhlenberg Regional Medical Center", city:"Plainfield"},
{name:"Newark Beth Israel Medical Center", city:"Newark"},
{name:"Newton Memorial Hospital", city:"Newton"},
{name:"Ocean Medical Center", city:"Brick"},
{name:"Our Lady of Lourdes Medical Center", city:"Camden"},
{name:"Overlook Hospital", city:"Summit"},
{name:"Palisades Medical Center-New York Presbyterian ", city:"North Bergen"},
{name:"Pascack Valley Hospital ", city:"Westwood"},
{name:"PBI Regional Medical Center", city:"Passaic"},
{name:"Raritan Bay Medical Center - Old Bridge", city:"Old Bridge"},
{name:"Raritan Bay Medical Center - Perth Amboy", city:"Perth Amboy"},
{name:"Riverview Medical Center", city:"Red Bank"},
{name:"Robert Wood Johnson Univ Hospital", city:"New Brunswick"},
{name:"Robert Wood Johnson Univ Hospital at Hamilton", city:"Hamilton"},
{name:"Robert Wood Johnson Univ Hospital at Rahway", city:"Rahway"},
{name:"Saint Barnabas Medical Center", city:"Livingston"},
{name:"Saint Clares Hospital/Boonton Township", city:"Boonton"},
{name:"Saint Clares Hospital/Denville", city:"Denville"},
{name:"Saint Clares Hospital/Sussex", city:"Sussex"},
{name:"Saint James Hospital", city:"Newark"},
{name:"Saint Michaels Medical Center", city:"Newark"},
{name:"Saint Peters University Hospital", city:"New Brunswick"},
{name:"Shore Memorial Hospital", city:"Somers Point"},
{name:"Somerset Medical Center", city:"Somerville"},
{name:"South Jersey Healthcare-Bridgeton Hospital", city:"Bridgeton"},
{name:"South Jersey Healthcare-Elmer Hospital", city:"Elmer"},
{name:"South Jersey Healthcare-Newcomb Hospital", city:"Vineland"},
{name:"Southern Ocean County Hospital", city:"Manahawkin"},
{name:"St. Francis Medical Center", city:"Trenton"},
{name:"St. Josephs Regional Medical Center", city:"Paterson"},
{name:"St. Josephs Wayne Hospital", city:"Wayne"},
{name:"St. Mary Hospital", city:"Hoboken"},
{name:"St. Marys Hospital Passaic", city:"Passaic"},
{name:"Summit Hospital", city:"Summit"},
{name:"The Cooper Health System", city:"Camden"},
{name:"The Memorial Hospital of Salem County", city:"Salem"},
{name:"The Mountainside Hospital", city:"Montclair"},
{name:"The Valley Hospital", city:"Ridgewood"},
{name:"Trinitas Hospital", city:"Elizabeth"},
{name:"Underwood-Memorial Hospital", city:"Woodbury "},
{name:"Union Hospital", city:"Union"},
{name:"University Medical Center at Princeton", city:"Princeton"},
{name:"University of Medicine & Dentistry of NJ-Univ Hosp", city:"Newark"},
{name:"Virtua Memorial Hospital Burlington County", city:"Mount Holly"},
{name:"Virtua West Jersey Hospital-Berlin", city:"Berlin"},
{name:"Virtua West Jersey Hospital-Marlton", city:"Marlton"},
{name:"Virtua West Jersey Hospital-Voorhees", city:"Voorhees"},
{name:"Warren Hospital", city:"Phillipsburg"}
])
}
private function filterDemo():void{
dataList.filterFunction = searchDemo;
dataList.refresh();
}
private function searchDemo(item:Object):Boolean{
var isMatch:Boolean = false
if(item.name.toLowerCase().search(search.text.toLowerCase()) != -1){
isMatch = true
}
return isMatch;
}
private function clearSearch():void{
dataList.filterFunction = null;
dataList.refresh();
search.text = '';
}
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="Search" direction="horizontal">
<mx:TextInput id="search" change="filterDemo()" />
<mx:Button label="Clear Search" click="clearSearch()" />
</mx:FormItem>
</mx:Form>
<mx:DataGrid dataProvider="{dataList}" width="400" height="400">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="name" />
<mx:DataGridColumn headerText="City" dataField="city" />
</mx:columns>
</mx:DataGrid>
</mx:Application>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="initData()">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var dataList:ArrayCollection ;
private function initData():void{
dataList= new ArrayCollection([
{name:"Atlantic City Medical Center-City Division", city:"Atlantic City"},
{name:"Atlantic City Medical Center-Mainland Division", city:"Pomona "},
{name:"Barnert Hospital", city:"Paterson"},
{name:"Bayonne Medical Center", city:"Bayonne"},
{name:"Bayshore Community Hospital", city:"Holmdel"},
{name:"Bergen Regional Medical Center. L.P.", city:"Paramus"},
{name:"Burdette Tomlin Memorial Hospital", city:"Cape May"},
{name:"Capital Health System - Fuld Campus", city:"Trenton"},
{name:"Capital Health System - Mercer Campus", city:"Trenton"},
{name:"CentraState Healthcare System", city:"Freehold"},
{name:"Chilton Memorial Hospital", city:"Pompton Plains"},
{name:"Christ Hospital", city:"Jersey City"},
{name:"Clara Maass Medical Center", city:"Belleville"},
{name:"Columbus Hospital", city:"Newark"},
{name:"Community Medical Center", city:"Toms River"},
{name:"East Orange General Hospital", city:"East Orange"},
{name:"Englewood Hospital and Medical Center", city:"Englewood"},
{name:"Hackensack University Medical Center", city:"Hackensack"},
{name:"Hackettstown Community Hospital", city:"Hackettstown"},
{name:"Holy Name Hospital", city:"Teaneck"},
{name:"Hospital Center at Orange", city:"Orange"},
{name:"Hunterdon Medical Center", city:"Flemington"},
{name:"Irvington General Hospital", city:"Irvington"},
{name:"Jersey Shore University Medical Center", city:"Neptune"},
{name:"JFK Medical Center", city:"Edison"},
{name:"Kennedy Memorial Hospitals/UMC Cherry Hill", city:"Cherry Hill"},
{name:"Kennedy Memorial Hospitals/UMC Stratford", city:"Stratford"},
{name:"Kennedy Memorial Hospitals/UMC Washington Twp", city:"Turnersville"},
{name:"Kessler Memorial Hospital", city:"Hammonton"},
{name:"Kimball Medical Center", city:"Lakewood"},
{name:"LibertyHealth-Greenville Hospital Campus", city:"Jersey City"},
{name:"LibertyHealth-Jersey City Medical Center Campus", city:"Jersey City"},
{name:"LibertyHealth-Meadowlands Hospital Campus", city:"Secaucus"},
{name:"Lourdes Medical Center of Burlington County", city:"Willingboro"},
{name:"Monmouth Medical Center", city:"Long Branch"},
{name:"MONOC", city:"Eatontown"},
{name:"Morristown Memorial Hospital", city:"Morristown"},
{name:"Muhlenberg Regional Medical Center", city:"Plainfield"},
{name:"Newark Beth Israel Medical Center", city:"Newark"},
{name:"Newton Memorial Hospital", city:"Newton"},
{name:"Ocean Medical Center", city:"Brick"},
{name:"Our Lady of Lourdes Medical Center", city:"Camden"},
{name:"Overlook Hospital", city:"Summit"},
{name:"Palisades Medical Center-New York Presbyterian ", city:"North Bergen"},
{name:"Pascack Valley Hospital ", city:"Westwood"},
{name:"PBI Regional Medical Center", city:"Passaic"},
{name:"Raritan Bay Medical Center - Old Bridge", city:"Old Bridge"},
{name:"Raritan Bay Medical Center - Perth Amboy", city:"Perth Amboy"},
{name:"Riverview Medical Center", city:"Red Bank"},
{name:"Robert Wood Johnson Univ Hospital", city:"New Brunswick"},
{name:"Robert Wood Johnson Univ Hospital at Hamilton", city:"Hamilton"},
{name:"Robert Wood Johnson Univ Hospital at Rahway", city:"Rahway"},
{name:"Saint Barnabas Medical Center", city:"Livingston"},
{name:"Saint Clares Hospital/Boonton Township", city:"Boonton"},
{name:"Saint Clares Hospital/Denville", city:"Denville"},
{name:"Saint Clares Hospital/Sussex", city:"Sussex"},
{name:"Saint James Hospital", city:"Newark"},
{name:"Saint Michaels Medical Center", city:"Newark"},
{name:"Saint Peters University Hospital", city:"New Brunswick"},
{name:"Shore Memorial Hospital", city:"Somers Point"},
{name:"Somerset Medical Center", city:"Somerville"},
{name:"South Jersey Healthcare-Bridgeton Hospital", city:"Bridgeton"},
{name:"South Jersey Healthcare-Elmer Hospital", city:"Elmer"},
{name:"South Jersey Healthcare-Newcomb Hospital", city:"Vineland"},
{name:"Southern Ocean County Hospital", city:"Manahawkin"},
{name:"St. Francis Medical Center", city:"Trenton"},
{name:"St. Josephs Regional Medical Center", city:"Paterson"},
{name:"St. Josephs Wayne Hospital", city:"Wayne"},
{name:"St. Mary Hospital", city:"Hoboken"},
{name:"St. Marys Hospital Passaic", city:"Passaic"},
{name:"Summit Hospital", city:"Summit"},
{name:"The Cooper Health System", city:"Camden"},
{name:"The Memorial Hospital of Salem County", city:"Salem"},
{name:"The Mountainside Hospital", city:"Montclair"},
{name:"The Valley Hospital", city:"Ridgewood"},
{name:"Trinitas Hospital", city:"Elizabeth"},
{name:"Underwood-Memorial Hospital", city:"Woodbury "},
{name:"Union Hospital", city:"Union"},
{name:"University Medical Center at Princeton", city:"Princeton"},
{name:"University of Medicine & Dentistry of NJ-Univ Hosp", city:"Newark"},
{name:"Virtua Memorial Hospital Burlington County", city:"Mount Holly"},
{name:"Virtua West Jersey Hospital-Berlin", city:"Berlin"},
{name:"Virtua West Jersey Hospital-Marlton", city:"Marlton"},
{name:"Virtua West Jersey Hospital-Voorhees", city:"Voorhees"},
{name:"Warren Hospital", city:"Phillipsburg"}
])
}
private function filterDemo():void{
dataList.filterFunction = searchDemo;
dataList.refresh();
}
private function searchDemo(item:Object):Boolean{
var isMatch:Boolean = false
if(item.name.toLowerCase().search(search.text.toLowerCase()) != -1){
isMatch = true
}
return isMatch;
}
private function clearSearch():void{
dataList.filterFunction = null;
dataList.refresh();
search.text = '';
}
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="Search" direction="horizontal">
<mx:TextInput id="search" change="filterDemo()" />
<mx:Button label="Clear Search" click="clearSearch()" />
</mx:FormItem>
</mx:Form>
<mx:DataGrid dataProvider="{dataList}" width="400" height="400">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="name" />
<mx:DataGridColumn headerText="City" dataField="city" />
</mx:columns>
</mx:DataGrid>
</mx:Application>
En este caso, la información está en el propio código (por eso es tan extenso), y se carga directamente en el ArrayCollection sin usar un HTTPService. Pero el objetivo principal era demostrar el uso de los filtros.
Este ejemplo (el último) está en el siguiente enlace: http://www.boyzoid.com/blog/index.cfm/2006/10/19/Filtering-Data-in-Flex
Para terminar, indicar que los dos ejemplos expuestos anteriormente se han basado en un único campo, pero lo más normal es que un filtro pueda tener varios campos y se combinen entre sí.
A continuación voy a poner un ejemplo (no completo, si no únicamente en lo que concierne a este punto), con dos campos: uno en una combo y otro en un calendario.
El primero filtra un tipo de lotes. Sus valores pueden ser: * (todos), "I" (entrada o true) y "O" (salida o false).
Los dos campos de filtro, invocan a sus respectivos métodos cuando cambian su valor:
// User click on combo type of batch
private function changeType():void {
if (cboTipoLote.value!="*" || datLote.text!=null)
this.batchesList.filterFunction=filterBatches;
else
this.batchesList.filterFunction=null;
this.batchesList.refresh();
}
// User change date on date field
private function changeDate():void {
if (cboTipoLote.value!="*" || datLote.text!=null)
this.batchesList.filterFunction=filterBatches;
else
this.batchesList.filterFunction=null;
this.batchesList.refresh();
}
El ArrayCollection está definido en "batchesList".
El método delegado para el filtrado es el siguiente:
// Filter of batches
private function filterBatches(item:Object):Boolean
{
var match:Boolean = true;
var sDate:String="";
var sType:String="";
if (cboTipoLote.value=="I")
sType="true";
else if (cboTipoLote.value=="O")
sType="false";
else
sType="";
if (datLote.text !="") {
sDate =""+datLote.selectedDate.fullYear +"-";
var vValue:String=""+(datLote.selectedDate.month+1);
if (vValue.length==1)
vValue = "0" + vValue;
sDate+= vValue + "-";
vValue = ""+(datLote.selectedDate.date);
if (vValue.length==1)
vValue = "0" + vValue;
sDate += vValue;
}
else
sDate = "";
var filter1:String = item["input_batch"];
var filter2:String = item["date"];
if (sDate!="" && sType=="") {
if (!filter2 || filter2.indexOf(sDate)<0)
match=false;
}
else if (sDate=="" && sType!="") {
if (!filter1 ||
filter1.toLowerCase().indexOf(sType.toLowerCase())<0)
match=false;
}
else {
if (!filter1 ||
filter1.toLowerCase().indexOf(sType.toLowerCase())<0 ||
!filter2 || filter2.indexOf(sDate)<0)
match=false;
}
return match;
}
En este caso se contemplan 3 casos:
1) Se ha facilitado tipo pero no fecha
2) Se ha facilitado fecha pero no tipo
3) Se han facilitado tipo y fecha
Espero que estos ejemplos sean claros y útiles para desarrollar aplicaciones AIR y Flex más interactivas y optimizadas.
viernes, 25 de septiembre de 2009
Configurar NetBeans, Tomcat y un pool de conexiones a PostgreSQL
Este post va dedicado a crear un entorno de desarrollo para aplicaciones web, utilizando el lenguaje Java, la base de datos PostgreSQL y el IDE NetBeans. No me voy a detener en la instalación y configuración de Java ni de PostgreSQL, si no en el entorno en concreto.
PREPARATIVOS
El ejemplo que se ilustra aquí utiliza PostgreSQL 8.3 (http://www.postgresql.org, Java v6u14 (http://java.sun.com), Tomcat 6.0.20 (http://tomcat.apache.org) y NetBeans 6.7.1 (http://www.netbeans.org.
En primer lugar ha de estar instalado el SDK de Java y la base de datos PostgreSQL.
TOMCAT
Ahora, para instalar Tomcat, es preferible descomprimir la versión no instalable, ya que incorpora los archivos catalina.bat y catalina.sh, los cuales son gestionados por NetBeans para lanzar y depurar las aplicaciones. Yo recomiendo, para los usuarios de Windows, instalarlo en el directorio raíz del disco duro, pues en Windows Vista, Windows 7 y Windows Server 2008 utiliza el perfil de usuario para gestiones propias del entorno de sesión, y podría no funcionar.
Una vez instalado Tomcat, acceder al archivo conf/tomcat-users.xml y añadir el usuario administrador y el usuario manager:
<user username="admin" password="admin" roles="tomcat"/>
<user username="manager" password="manager" roles="manager"/>
El siguiente paso es descargarse el driver jdbc de PostgreSQL, el cual puede ser descargado de http://jdbc.postgresql.org. Descargar el driver correspondiente a jdbc3. Una vez descargado el archivo .jar, copiar éste en el directorio lib de Tomcat.
NETBEANS
Configuración del servidor
Una vez instalado NetBeans, hay que agregar el servidor Tomcat. Para ello:
- Menú "Tools" + Opción "Servers"
- Botón "Add Server..." (parte inferior izquierda)
- Seleccionar de la lista "Tomcat 6.0" y botón "Next"
- En el campo "Server Location", hacer clic en el botón "Browse..." y seleccionar la carpeta donde se aloja Tomcat.
- En el campo "Username" ingresar el nombre del usuario "manager"
- En el campo "Password" ingresar la contraseña del usuario "manager"
- Botón "Finish"
El servidor se habrá agregado a la lista de servidores configurados. Al seleccionar este servidor, aparecerán sus propiedades a la derecha. Clic en el botón "Close".
Crear proyecto web
Vamos a crear un proyecto web para configurar el pool de conexiones y mostrar un ejemplo de conexión:
- Menú "File" + opción "New Project..."
- En la lista "Categories" seleccionar "Java Web", y en la lista "Projects" seleccionar "Web Application"
- Botón "Next"
- En el campo "Project Name" dar el nombre del proyecto, por ejemplo "Prueba".
- Botón "Next"
- En el campo "Server" seleccionar "Tomcat 6.0"
- Botón "Finish"
Referencia a la librería de PostgreSQL
Creado el proyecto, lo primero que vamos a hacer es añadir una referencia a la librería de PostgreSQL. Para ello, en el panel "Project" (parte izquierda), aparecerá el proyecto con los diversos elementos del mismo. Hacer clic con el botón derecho sobre "Libraries" y seleccionar "Add JAR/Folder". Navegar hasta el directorio lib de Tomcat y seleccionar el archivo .jar de PostgreSQL. La librería aparecerá en el árbol subyacente.
Configurar pool de conexiones
A continuación vamos a configurar el pool de conexiones. Antes de empezar, comentar que en la versiones recientes de Tomcat, por motivos de seguridad y eficiencia, la configuración de contextos y recursos se realiza por aplicación, y en no en el contexto general de Tomcat. Aclarado ésto vamos a proceder a la configuración.
Desplegar, en el panel de proyecto, el elemento "Web pages" del proyecto. Desplegar ahora el elemento "WEB-INF" y hacer doble clic sobre el archivo "web.xml". Seleccionar ahora el botón "XML" (parte superior de la zona de trabajo), con lo que aparecerá el contenido del archivo. Aquí aparecerá un bloque principal encerrado entre los tags <web-app> y </web-app>. Situarse justo antes del cierre de este bloque y añadir las siguientes líneas:
<resource-ref>
<description>Referencia al pool de conexiones</description>
<res-ref-name>jdbc/[nombreJNDI]</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
El campo <res-ref-name> contiene el nombre JNDI que se utilizará como contexto para el pool de conexiones.
El siguiente paso es acceder al elemento "Web pages" del proyecto. Desplegar ahora el elemento "META-INF" y hacer doble clic sobre el archivo "context.xml". Se mostrará el contenido:
<Context antiJARLocking="true" path="/TrazalogicOperator"/>
Modificar esta línea para definir dos líneas (apertura y cierre) en lugar de una:
<Context antiJARLocking="true" path="/[NombreProyecto]">
</Context>
[NombreProyecto] es asignado automáticamente por NetBeans, y no es necesario cambiarlo.
Entre estas dos líneas añadir lo siguiente para crear el contexto del pool:
<Resource name="jdbc/[nombreJNDI]"
auth="Container"
type="javax.sql.DataSource"
username="[usuarioPostgreSQL]"
password="[passwordPostgreSQL]"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://[servidor]:[puerto]/[nombrebasedatos]"
maxActive="8"
maxIdle="4"/>
Un ejemplo de url sería:
url="jdbc:postgresql://localhost:5432/miBaseDatos"
Si la base de datos va a residir en la misma máquina que Tomcat y la aplicación web, se utilizaría "localhost". Si estuviera en otra máquina, especificar el nombre de la misma (si está configurado en un servidor DNS) o bien la dirección IP de la misma.
El puerto 5432 es el puerto por defecto de la base de datos PostgreSQL. Si se ha configurado otro puerto, modificar este parámetro.
Probar todo
Para probar tanto el servidor de aplicaciones como el pool de conexiones, vamos a crear un Servlet. Para ello:
- Menú "File" + opción "New File..."
- En la lista "Categories" seleccionar "Web", y en la lista "File Type" seleccionar "Servlet".
- Botón "Next"
- En el campo "Class Name" escribir el nombre del servlet, por ejemplo "ServletPrueba".
- En el campo "Package" escribir el nombre del paquete donde se ubicará el servlet, por ejemplo "com.prueba.servlet"
- Botón "Next"
- Botón "Finish"
Se creará el servlet con un código mínimo.
En la sección "import" añadir las siguientes importaciones:
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
Buscar el método processRequest y escribir el siguiente código:
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet ServletPrueba</title>");
out.println("</head>");
out.println("<body>");
out.println("INICIO...<br>");
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource)
envCtx.lookup("jdbc/[nombreJNDI]");
Connection conn = ds.getConnection();
//... Insertar aquí las consultas y actualizaciones a base de datos
conn.close();
out.println("...FIN<br>");
out.println("</html>");
} catch (Exception ex) {
out.println("Error: " + ex.toString();
} finally {
out.close();
}
}
A través del nombre JNDI se extrae el contexto para el pool de base de datos, retornando un objeto de tipo DataSource. Una vez obtenido el DataSource, es posible obtener la conexión (método getConnection()). A partir de ahí, con la conexión, procederemos a realizar las consultas a base de datos, o las actualizaciones pertinentes.
El código, tal y como está, servirá para verificar que se obtiene la conexión. Si todo va bien, devolverá una página Web, con el único texto "INICIO... / ...FIN". Si hubiera algún error, retornaría también el error producido.
Para ejecutar este servlet, en el panel de proyecto (parte izquierda), abrir la carpeta "Source Packages" + "com.prueba.servlet", y con el botón derecho hacer clic sobre el elemento "ServletPrueba.java" y seleccionar "Run File" (para ejecutar el servlet) o "Debug File" (para depurar el servlet, previo marcado un punto de interrupción).
Antes de finalizar, comentar qué me ha llevado a escribir este post, tras tres días devanándome la cabeza con Eclipse y con GlassFish, con los que he tenido multitud de problemas y una complejidad bastante elevada en cuanto a una configuración para trabajar. Soy de los que piensan que tenemos ya mucho trabajo por delante como para perder el tiempo descifrando o poniéndote delante de una bola de cristal para intentar explicar cómo configurar algo que debería ser automático y simple, y que en apenas unos segundos y de manera intuitiva debería dejarte empezar a trabajar. Ahí radica el éxito de Visual Studio de Microsoft, que facilita el trabajo multiplicando la productividad.
Con Eclipse, de forma inexplicable, los cambios en mi servlet no se actualizaba ni desplegaba en el servidor. Por más que he intentado saber por qué, aún no me lo explico.
Con GlassFish, que parece un servidor potente y muy bien diseñado, veía muchos pasos para configurar el pool de conexiones (primero una conexión, luego el pool y luego el resource, y luego configurar el context.xml y el web.xml). Pero aún así, mirando la documentaciónd e GlassFish, PostgreSQL no venía homologado en las especificaciones, aunque en realidad sí que permitía su configuración, y la conexión, de hecho me permitía mirar la estructura de la base de datos y ejecutar consultas desde el IDE. Pero a la hora de desarrollar, el DataSource era imposible de capturar, y desde el propio GlassFish, probar el DataSource me daba un error FATAL por que database devolvía null. El problema es que el driver de conexión es peculiar en PostgreSQL, y se obtiene mediante un DataSource "especial" llamado org.postgreSQL.ds.PGSimpleDataSource, el cual es incompatible o no se puede convertir a un DataSource normal. Asimismo, incluso intentando obtener un PGSimpleDataSource en lugar de un DataSource, también daba error porque internamente trabaja con DataSource. Vamos un galimatías que me hizo desistir.
Al final, me empapé la documentación de Tomcat (concretamente http://localhost:8080/docs/jndi-resources-howto.html) y trasteando con Tomcat, conseguí definir este procedimiento de configuración, simplificándolo al máximo y que funciona.
Espero que os sea de utilidad.

PREPARATIVOS
El ejemplo que se ilustra aquí utiliza PostgreSQL 8.3 (http://www.postgresql.org, Java v6u14 (http://java.sun.com), Tomcat 6.0.20 (http://tomcat.apache.org) y NetBeans 6.7.1 (http://www.netbeans.org.
En primer lugar ha de estar instalado el SDK de Java y la base de datos PostgreSQL.
TOMCAT
Ahora, para instalar Tomcat, es preferible descomprimir la versión no instalable, ya que incorpora los archivos catalina.bat y catalina.sh, los cuales son gestionados por NetBeans para lanzar y depurar las aplicaciones. Yo recomiendo, para los usuarios de Windows, instalarlo en el directorio raíz del disco duro, pues en Windows Vista, Windows 7 y Windows Server 2008 utiliza el perfil de usuario para gestiones propias del entorno de sesión, y podría no funcionar.
Una vez instalado Tomcat, acceder al archivo conf/tomcat-users.xml y añadir el usuario administrador y el usuario manager:
<user username="admin" password="admin" roles="tomcat"/>
<user username="manager" password="manager" roles="manager"/>
El siguiente paso es descargarse el driver jdbc de PostgreSQL, el cual puede ser descargado de http://jdbc.postgresql.org. Descargar el driver correspondiente a jdbc3. Una vez descargado el archivo .jar, copiar éste en el directorio lib de Tomcat.
NETBEANS
Configuración del servidor
Una vez instalado NetBeans, hay que agregar el servidor Tomcat. Para ello:
- Menú "Tools" + Opción "Servers"
- Botón "Add Server..." (parte inferior izquierda)
- Seleccionar de la lista "Tomcat 6.0" y botón "Next"
- En el campo "Server Location", hacer clic en el botón "Browse..." y seleccionar la carpeta donde se aloja Tomcat.
- En el campo "Username" ingresar el nombre del usuario "manager"
- En el campo "Password" ingresar la contraseña del usuario "manager"
- Botón "Finish"
El servidor se habrá agregado a la lista de servidores configurados. Al seleccionar este servidor, aparecerán sus propiedades a la derecha. Clic en el botón "Close".
Crear proyecto web
Vamos a crear un proyecto web para configurar el pool de conexiones y mostrar un ejemplo de conexión:
- Menú "File" + opción "New Project..."
- En la lista "Categories" seleccionar "Java Web", y en la lista "Projects" seleccionar "Web Application"
- Botón "Next"
- En el campo "Project Name" dar el nombre del proyecto, por ejemplo "Prueba".
- Botón "Next"
- En el campo "Server" seleccionar "Tomcat 6.0"
- Botón "Finish"
Referencia a la librería de PostgreSQL
Creado el proyecto, lo primero que vamos a hacer es añadir una referencia a la librería de PostgreSQL. Para ello, en el panel "Project" (parte izquierda), aparecerá el proyecto con los diversos elementos del mismo. Hacer clic con el botón derecho sobre "Libraries" y seleccionar "Add JAR/Folder". Navegar hasta el directorio lib de Tomcat y seleccionar el archivo .jar de PostgreSQL. La librería aparecerá en el árbol subyacente.
Configurar pool de conexiones
A continuación vamos a configurar el pool de conexiones. Antes de empezar, comentar que en la versiones recientes de Tomcat, por motivos de seguridad y eficiencia, la configuración de contextos y recursos se realiza por aplicación, y en no en el contexto general de Tomcat. Aclarado ésto vamos a proceder a la configuración.
Desplegar, en el panel de proyecto, el elemento "Web pages" del proyecto. Desplegar ahora el elemento "WEB-INF" y hacer doble clic sobre el archivo "web.xml". Seleccionar ahora el botón "XML" (parte superior de la zona de trabajo), con lo que aparecerá el contenido del archivo. Aquí aparecerá un bloque principal encerrado entre los tags <web-app> y </web-app>. Situarse justo antes del cierre de este bloque y añadir las siguientes líneas:
<resource-ref>
<description>Referencia al pool de conexiones</description>
<res-ref-name>jdbc/[nombreJNDI]</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
El campo <res-ref-name> contiene el nombre JNDI que se utilizará como contexto para el pool de conexiones.
El siguiente paso es acceder al elemento "Web pages" del proyecto. Desplegar ahora el elemento "META-INF" y hacer doble clic sobre el archivo "context.xml". Se mostrará el contenido:
<Context antiJARLocking="true" path="/TrazalogicOperator"/>
Modificar esta línea para definir dos líneas (apertura y cierre) en lugar de una:
<Context antiJARLocking="true" path="/[NombreProyecto]">
</Context>
[NombreProyecto] es asignado automáticamente por NetBeans, y no es necesario cambiarlo.
Entre estas dos líneas añadir lo siguiente para crear el contexto del pool:
<Resource name="jdbc/[nombreJNDI]"
auth="Container"
type="javax.sql.DataSource"
username="[usuarioPostgreSQL]"
password="[passwordPostgreSQL]"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://[servidor]:[puerto]/[nombrebasedatos]"
maxActive="8"
maxIdle="4"/>
Un ejemplo de url sería:
url="jdbc:postgresql://localhost:5432/miBaseDatos"
Si la base de datos va a residir en la misma máquina que Tomcat y la aplicación web, se utilizaría "localhost". Si estuviera en otra máquina, especificar el nombre de la misma (si está configurado en un servidor DNS) o bien la dirección IP de la misma.
El puerto 5432 es el puerto por defecto de la base de datos PostgreSQL. Si se ha configurado otro puerto, modificar este parámetro.
Probar todo
Para probar tanto el servidor de aplicaciones como el pool de conexiones, vamos a crear un Servlet. Para ello:
- Menú "File" + opción "New File..."
- En la lista "Categories" seleccionar "Web", y en la lista "File Type" seleccionar "Servlet".
- Botón "Next"
- En el campo "Class Name" escribir el nombre del servlet, por ejemplo "ServletPrueba".
- En el campo "Package" escribir el nombre del paquete donde se ubicará el servlet, por ejemplo "com.prueba.servlet"
- Botón "Next"
- Botón "Finish"
Se creará el servlet con un código mínimo.
En la sección "import" añadir las siguientes importaciones:
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
Buscar el método processRequest y escribir el siguiente código:
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet ServletPrueba</title>");
out.println("</head>");
out.println("<body>");
out.println("INICIO...<br>");
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource)
envCtx.lookup("jdbc/[nombreJNDI]");
Connection conn = ds.getConnection();
//... Insertar aquí las consultas y actualizaciones a base de datos
conn.close();
out.println("...FIN<br>");
out.println("</html>");
} catch (Exception ex) {
out.println("Error: " + ex.toString();
} finally {
out.close();
}
}
A través del nombre JNDI se extrae el contexto para el pool de base de datos, retornando un objeto de tipo DataSource. Una vez obtenido el DataSource, es posible obtener la conexión (método getConnection()). A partir de ahí, con la conexión, procederemos a realizar las consultas a base de datos, o las actualizaciones pertinentes.
El código, tal y como está, servirá para verificar que se obtiene la conexión. Si todo va bien, devolverá una página Web, con el único texto "INICIO... / ...FIN". Si hubiera algún error, retornaría también el error producido.
Para ejecutar este servlet, en el panel de proyecto (parte izquierda), abrir la carpeta "Source Packages" + "com.prueba.servlet", y con el botón derecho hacer clic sobre el elemento "ServletPrueba.java" y seleccionar "Run File" (para ejecutar el servlet) o "Debug File" (para depurar el servlet, previo marcado un punto de interrupción).
Antes de finalizar, comentar qué me ha llevado a escribir este post, tras tres días devanándome la cabeza con Eclipse y con GlassFish, con los que he tenido multitud de problemas y una complejidad bastante elevada en cuanto a una configuración para trabajar. Soy de los que piensan que tenemos ya mucho trabajo por delante como para perder el tiempo descifrando o poniéndote delante de una bola de cristal para intentar explicar cómo configurar algo que debería ser automático y simple, y que en apenas unos segundos y de manera intuitiva debería dejarte empezar a trabajar. Ahí radica el éxito de Visual Studio de Microsoft, que facilita el trabajo multiplicando la productividad.
Con Eclipse, de forma inexplicable, los cambios en mi servlet no se actualizaba ni desplegaba en el servidor. Por más que he intentado saber por qué, aún no me lo explico.
Con GlassFish, que parece un servidor potente y muy bien diseñado, veía muchos pasos para configurar el pool de conexiones (primero una conexión, luego el pool y luego el resource, y luego configurar el context.xml y el web.xml). Pero aún así, mirando la documentaciónd e GlassFish, PostgreSQL no venía homologado en las especificaciones, aunque en realidad sí que permitía su configuración, y la conexión, de hecho me permitía mirar la estructura de la base de datos y ejecutar consultas desde el IDE. Pero a la hora de desarrollar, el DataSource era imposible de capturar, y desde el propio GlassFish, probar el DataSource me daba un error FATAL por que database devolvía null. El problema es que el driver de conexión es peculiar en PostgreSQL, y se obtiene mediante un DataSource "especial" llamado org.postgreSQL.ds.PGSimpleDataSource, el cual es incompatible o no se puede convertir a un DataSource normal. Asimismo, incluso intentando obtener un PGSimpleDataSource en lugar de un DataSource, también daba error porque internamente trabaja con DataSource. Vamos un galimatías que me hizo desistir.
Al final, me empapé la documentación de Tomcat (concretamente http://localhost:8080/docs/jndi-resources-howto.html) y trasteando con Tomcat, conseguí definir este procedimiento de configuración, simplificándolo al máximo y que funciona.
Espero que os sea de utilidad.
Suscribirse a:
Entradas (Atom)