Monthly Archives: September 2008

Validación de campos con Prototype

Para desarrollar aplicaciones basadas en formularios, además de la manipulación dinámica de las filas de las tablas, es muy necesaria la validación de los campos del lado del cliente, es decir, impedir que un formulario se envie y se muestren al usuario los mensajes de error necesarios.  Obviamente esto no reemplaza a la validación del lado del servidor.

Para esto y utilizando a Prototype voy a describir a continuación como utilizar la mejor (única ?) librería para este fin, desarrollada por Andrew Tetlaw.

Really easy field validation requiere que se incluya en el <head> de las páginas web el archivo validation.js además del prototype.js.

Tan pronto y se carga en el navegador el árbol del documento, se activa explícitamente la validación para cada uno de los formularios.  Para este ejemplo se activa la validación del <form> cuyo id es formulario.

       %MINIFYHTMLaed5e60067d5fc009c2540a6b085de762%

Después de garantizado esto, se crea el <form> definiendo el tipo de validación que se realizará sobre sus campos, agregando las clases CSS que correspondan.

       
                                             

Para este caso, el campo name es requerido y sólo admite letras, el campo age es requerido y sólo admite números y el campo greeting es requerido y debe seleccionarse una de las opciones válidas.

Otros validadores predefinidos corresponden con la siguiente lista.

  • required (no vacío)
  • validate-number (un número válido)
  • validate-digits (sólo digitos)
  • validate-alpha (sólo letras)
  • validate-alphanum (sólo letras y números)
  • validate-date (una fecha válida)
  • validate-email (una dirección de correo válida)
  • validate-url (un URL válido)
  • validate-date-au (una fecha con formato; dd/mm/yyyy)
  • validate-currency-dollar (un valor de moneda)
  • validate-selection (una opción válida seleccionada)
  • validate-one-required (al menos un textbox/radio seleccionado)

Es posible definir nuevos validadores personalizados.  Para esto consultar la información del método Validation.add.

Los mensajes de error (advices) que aparecen corresponden con los predefinidos por la librería, sin embargo es posible modificar este comportamiento de dos maneras.

1. Definir el bloque (p, div, etc.) que se desea se muestre en caso de no cumplirse la validación del campo.

<div id='advice-required-name' style="display: none">
    Mensaje personalizado
</div>

El id de este bloque deberá estar compuesto de la siguiente manera:

advice-NOMBRE_VALIDACIÓN-ID_ELEMENTO

El ejemplo anterior del mensaje personalizado corresponde con la validación required del campo name.

2. Mostrar como mensaje de error el contenido de los atributos title de los campos del formulario.  Para activar esta opción es necesario especificar la siguiente opción cuando se está activando la verificación del formulario.

useTitles   : true

Es posible evitar que la verificación del formulario se realice automáticamente durante el evento onSubmit y permitir un mayor control del evento de envío del formulario.  Para hacer esto se debe desactivar esta opción especificando el siguiente código cuando se está activando la verificación del formulario.

onSubmit    : true

Posteriormente es posible solicitar la validación del formulario (val.validate()), reiniciar los validadores (val.reset()), validar un campo específico con los validadores definidos (Validation.validate(campo)) o con cualquier validador arbitrario (Validation.get('validator-name').test(elemento)).

Con respecto a la presentación, es posible manipular las siguientes clases CSS.

  • validation-failed: Campos que no ya fueron verificados y no pasaron la validación.
  • validation-passed: Campos que pasaron la validación.
  • required: Campos requeridos, independientemente si han sido verificados o no.
  • validation-advice: Mensaje de validación, no incluye a los mensajes personalizados (forma #1).
  • advice-NOMBRE_VALIDACIÓN-ID_ELEMENTO: Mensaje de validación personalizado para un elemento y un tipo de validacion específicos.

Enlaces:

Manipulación fácil del DOM de una tabla con Prototype

El día de hoy realicé la migración del pequeño script de manipulación DOM de tablas de su versión de Mootools a Prototype.  La versión 0.1 está basada en la versión 0.2 del primero y fue desarrollado con la versión 1.6.0.2 de la librería.

Básicamente es el mismo JavaScript, permite agregar y remover filas de una tabla dinámicamente desde el lado del cliente.

La distribución incluye a las siguientes funciones.

  • TDAgregarRegistro(tablaId): agrega una nueva fila a la tabla identificada con el id tablaId.
  • TDRemoverRegistro(tablaId): remueve a las filas seleccionadas de la tabla identificada con el id tablaId.
  • TDValidarDato(valor, tipo, opciones): determina si un dato es válido según el tipo de verificación elegido.

La implementación de esta funcionalidad requiere que se implementen las siguientes funciones en la página cliente.

  • TDObtEstructuraRegistro(tablaId): determina la estructura de las filas o registros de la tabla cuyo id es tablaId.
  • TDObtPropiedadesCampo(tablaId): determina las propiedades o atributos de un campo de la tabla cuyo id es tablaId.
  • TDObtPropiedadesFila(tablaId): determina las propiedades o atributos de una fila de la tabla cuyo id es tablaId..

Al igual que la versión 0.2 con Mootools, incluí un validador sencillo para los datos de entrada basado en el campo validador de la especificación y que utiliza directamente a la función TDValidarDato.  Ver el campo edad del ejemplo de demostración.

Por ahora la validación continúa con un problema que no he logrado solucionar: el evento incluye el código de la tecla presionada sin importar si era en mayúsculas o minúsculas, dificultando validar esta sensibilidad.  Esto es particularmente importante para el caso de los números ya que 2 y @ estarían significando lo mismo para el validador.

Enlaces:

Introducción a PrototypeJS

  • Prototype es creado en marzo de 2005 por Sam Stephenson.
  • Parte de Ruby on Rails, sin embargo puede ser utilizado con cualquier lenguaje/ambiente de desarrollo.
  • Su misión es la de mejorar, reemplazar y ampliar las capacidades de Javascript.
  • Es interpretado del lado del cliente por el navegador web, es parte de la vista.
  • Incluye facilidades para el manejo del DOM, eventos, elementos, arreglos, formularios, AJAX, JSON, etc.
  • Permite desarrollar con independencia de navegador web.
  • Se acopla muy bien a Scriptaculous, librería de efectos creada por Thomas Fuchs en junio de 2005.

  • Introducción.
  • Inclusión de la librería.
  • Función $().
  • Función $w().
  • Función $A().
  • Función $F().
  • Función $H().
  • Función $R().
  • Función $$().
  • Enlaces de interés.

Enlace: Prototype Javascript Framework.

OpenArena con Ubuntu

OpenArena es la versión opensource (GPL) de Quake 3 Arena, con todos los recursos: gráficos, texturas, sonidos, etc. reemplazados por unos completamente libres.

Para probar OpenArena en Linux Ubuntu es necesario desactivar el Compiz, de lo contrario el ratón funciona incorrectamente con el rango de movimientos limitado.

Para solucionar se debe ejecutar a OA con el siguiente script que hace lo mencionado, desactivar Compiz, ejecutar la aplicación y activar Compiz nuevamente al terminar la ejecución.

#!/bin/bash
metacity --replace &
cd /{OPENARENA_PATH}/
./openarena.i386
compiz --replace &

Enlace: OpenArena FAQ.

Departamentos y municipios de Colombia (Actualización 20080915)

He venido actualizando la información de los departamentos y municipios de Colombia publicada anteriormente.

Ahora se incluye a los municipios creados desde entonces (1102 registros actualmente).  Además me he dado a la tarea de georreferenciar (latitud/longitud) esta información para ser utilizada en futuros proyectos.  De momento me faltan 19 municipios cuyos nombres no coinciden.

La información se puede descargar de manera gratuita teniendo en cuenta que este es un trabajo en progreso por lo que no es necesariamente preciso.  Agradecería me informen las correcciones si alguien nota algún error en los datos.

Los archivos a continuación se encuentran en formato CSV y su codificación es UTF-8 lo que debe tenerse en cuenta para su correcta visualización.

La información se puede de la base de datos se puede consultar de manera gráfica utilizando el siguiente mapplet de mi autoría: Mapa de departamentos y municipios colombianos.

Enlace: Sistema de consulta de la división pollítico-administrativa de Colombia.

Serialización de objetos en XML con C#.NET

De manera análoga a como en Java es posible realizar la serialización de clases a XML, encontré que también es posible hacerlo en C#.NET.  Para esto es necesario que la clase a ser serializada exponga los atributos que desea publicar a través de propiedades.

Para la demostración creé una clase XMLSerializable de la cual heredarán las otras que requieran de este tipo de serialización.  Esta clase abstracta provee los métodos load y save para realizar automáticamente la serialización de sus hijos.

La serialización de los objetos se realiza con el siguiente código.

        public void save(String filename)
        {
            // Creates the writing stream
            StreamWriter w = new StreamWriter(filename);
            // Creates the XML serializer with its type
            XmlSerializer s = new XmlSerializer(this.GetType());
            // Serializes the object (this self -> son)
            s.Serialize(w, this);
            // Closes the output stream
            w.Close();
        }

La carga de objetos serializados requiere de un par de verificiones adicionales.

        public bool load(String filename)
        {
            bool success = false;
            // Checks if the source file exists, otherwise there is nothing to do
            if (File.Exists(filename))
            {
                // Creates the reading stream
                StreamReader sr = new StreamReader(filename);
                // Creates the text reader from the stream
                XmlTextReader xr = new XmlTextReader(sr);
                // Creates the XML (de)serializer with its type
                XmlSerializer xs = new XmlSerializer(this.GetType());
                // Temporary storage object
                object c;
                // Checks if the received data can be deserialized
                if (xs.CanDeserialize(xr))
                {
                    // Deserialize the incoming data
                    c = xs.Deserialize(xr);
                    // Gets the type (definition) of the object
                    Type t = this.GetType();
                    // Retrieve the exposed attributes by properties
                    PropertyInfo[] properties = t.GetProperties();
                    // Walks thru all properties and loads its values on
                    // the local attributes
                    foreach (PropertyInfo p in properties)
                    {
                        p.SetValue(this, p.GetValue(c, null), null);
                    }
                    success = true;
                }
                // Closes the reader and the stream
                xr.Close();
                sr.Close();
            }
            return success;
        }

Con estas facilidades se crea una clase Empleado con atributos básicos.

    public class Empleado : XMLSerializable
    {
        ////////////////////////////////////////////////////////////////
        private int      codigo;
        private String   nombre;
        private float    salario;
        private DateTime fechaNacimiento;
        ////////////////////////////////////////////////////////////////
        // Resto del codigo
    }

Es necesario para la serialización que el objeto tenga propiedades de los atributos a exponer.

        public int Codigo
        {
            get { return codigo; }
            set { codigo = value; }
        }
        public String Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }
        public float Salario
        {
            get { return salario; }
            set { salario = value; }
        }
        public DateTime FechaNacimiento
        {
            get { return fechaNacimiento; }
            set { fechaNacimiento = value; }
        }

También es necesario que la clase cuente con un constructor sin parámetros además de los que requiera normalmente.

        public Empleado()
        {
            this.codigo          = -1;
            this.nombre          = "Sin nombre";
            this.salario         = -1;
            this.fechaNacimiento = new DateTime(1990, 01, 01);
        }
        ////////////////////////////////////////////////////////////////
        public Empleado(int codigo, String nombre, float salario, DateTime fechaNacimiento)
        {
            this.codigo          = codigo;
            this.nombre          = nombre;
            this.salario         = salario;
            this.fechaNacimiento = fechaNacimiento;
        }

Por facilidad también se sobreescribió el método ToString() para generar su representación en cadena.

        override public String ToString()
        {
            return String.Format("[Empleado: codigo = {0}, nombre = \"{1}\", salario = ${2}, fechaNacimiento = {3}]",
                                    this.codigo, this.nombre, this.salario, this.fechaNacimiento);
        }

Con todo esto, la aplicación de prueba crea en el primer paso a un objeto Empleado y le da valores iniciales a sus atributos, después de esto solicita su serialización.

            Empleado empleado = new Empleado(123, "Pepito Pimentón", 123456.789f, DateTime.Now);
            empleado.save("empleado.xml");

Posteriormente se crea un segundo Empleado que almacenará la información recuperada de la serialización.  Si la prueba tiene éxito, la información de los dos Empleados deberá coincidir.

            Empleado empleado2 = new Empleado();
            empleado2.load("empleado.xml");

La información presentada a través de salida estándar al ejecutar la aplicación de demostración deberá ser algo similar a lo siguiente.

Writing Object: [Empleado: codigo = 123, nombre = "Pepito Pimentón", salario = $123456,8, fechaNacimiento = 14/09/2008 11:12:10 p.m.]
Read Object:    [Empleado: codigo = 123, nombre = "Pepito Pimentón", salario = $123456,8, fechaNacimiento = 14/09/2008 11:12:10 p.m.]

El documento XML generado es bastante explícito y fácil de realizar su manipulación manual si se llegara a requerir.



  123
  Pepito Pimentón
  123456.789
  2008-09-14T22:39:52.888-05:00

Enlaces:

GeoIPTool – de donde es esta IP ?

El servicio GeoIPTool permite identificar de manera gráfica de donde proviene cualquier dirección IP que se desee.

Enlace: http://geoiptool.com/.

Evolución de los teléfonos móviles

Esta es una perspectiva intersante de como ha sido la evolución de los teléfonos móviles desde sus comienzos hasta practicamente la actualidad.

Cuáles ha tenido ?

Obtener lecturas de un GPS USB desde Java/C#

La tarea del día de hoy era obtener la posición actual desde un GPS utilizando Java o C#.  Para esto contaba con un GPS Garmin eTrex Summit HC que por supuesto cuenta con un puerto USB.

Extrañamente concluyo que no hay un método confiable ni portable para leer información desde los puertos USB para ninguno de los dos lenguajes.  Para Java encontré varias librerías pero aún inmaduras o con soporte para sólo un sistema operativo, prefiero el soporte para Windows y Linux por lo menos.  Para C# igual horizonte, nativamente aún no es soportado como si lo es el SerialPort y la mejor opción parece ser #usblib de los mismos creadores de SharpDevelop, sin embargo no pude encontrar un ejemplo que indicara su viabilidad.

Con este oscuro panorama terminé explorando un estilo de solución que no es de mi mayor agrado: el ejecutar aplicaciones de terceros para obtener y procesar su salida estándar.

Para esto instalé un software muy útil llamado GPSBabel el cual se puede descargar para Windows, Linux y Mac, incluyendo su código fuente escrito en lenguaje C.  La distribución de Linux está basada en RPM los cuales no fueron instalables en mi estación de trabajo basada en Ubuntu, sin embargo se encuentra disponible el paquete para instalarse desde la distribución de paquetes de Ubuntu.

El comando gpsbabel permite obtener desde línea de comando la información contenida en el GPS, incluyendo por supuesto, su ubicación actual.

$ sudo /usr/bin/gpsbabel.exe -i garmin,get_posn -f usb:

El resultado es algo de este estilo: (probado desde Armenia/Qundío)

4.536692N 75.669346W Position/Position

Extrañamente la distribución de Windows incluye además la fecha/hora de la ejecución, hecho que en Linux no sucede.

Wed Sep 10 22:42:46 2008
4.536692N 75.669346W Position/Position

La aplicación intermedia fue desarrollada en C# debido a la afinad de mi compañero de desarrollo con ese lenguaje.  Fue interesante para aprender algunos detalles particulares del lenguaje a los cuales nos vimos enfrentados durante el desarrollo.

Lo que hice fue crear un método estático (AccesoGPSBabel.obtUbicacionActual) que basado en la ubicación del comando gpsbabel retorna un Hashtable con la latitud/longitud actual o null en caso de suceder algún problema.

La ejecución del programa externo se realiza mediante un objeto System.Diagnostics.Process al cual se le indica la ubicación del archivo ejecutable, los argumentos de línea de comando y se le informa que estamos interesados en obtener su salida estándar.  La aplicación (o un enlace a) gpsbabel deberá estar ubicada en el mismo directorio del ejecutable de la aplicación principal, incluyendo el archivo libexpat.dll para la versión de Windows.

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.EnableRaisingEvents = false;
            proc.StartInfo.FileName = aplicacion;
            proc.StartInfo.Arguments = "-i " + protocolo + ",get_posn -f usb:";
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.UseShellExecute = false;
            proc.Start();

Para recibir la información proveniente de la salida estándar se obtiene su StreamReader de la siguiente manera.

            String datos = proc.StandardOutput.ReadToEnd().Trim();

Su procesamiento no se aleja de la carpintería normal del manejo de cadenas con String.Split junto con un detalle adicional, la información aparece con el posfijo N/S, W/E; yo prefería que fuera un solo valor numérico siendo positivo o negativo según su ubicación con respecto a la referencia, tal y como lo maneja GoogleMaps y otros paquetes similares.

Para esto, al procesar las líneas recibidas de la salida estándar recorté la subcadena de manera que no tomara el último carácter de las coordenadas.

            latitud     = secciones[0].Trim().Substring(0, secciones[0].Length - 1);
            latitudDir  = secciones[0].Trim().Substring(secciones[0].Length - 1, 1);
            longitud    = secciones[1].Trim().Substring(0, secciones[1].Length - 1);
            longitudDir = secciones[1].Trim().Substring(secciones[1].Length - 1, 1);

Y después las multipliqué por su inverso aditivo en los casos en que correspondiera.

            Double Latitud = Double.Parse(latitud) * ((latitudDir.Equals("S")) ? -1 : 1);
            Double Longitud = Double.Parse(longitud) * ((longitudDir.Equals("W")) ? -1 : 1);

Sucedió un problema.  El método Double.Parse estaba convirtiendo erróneamente los valores porque el GPS los enviaba con el punto como separador decimal y la Configuración Regional de la máquina Windows que estabamos utilizando usaba la coma.

Como la intención era que la aplicación no sólo funcionara entre diferentes configuraciones regionales sino que lo hiciera también entre diferentes sistemas operativos (Linux con Mono) me dediqué a buscar una solución portable.

Para esto se obtiene al separador decimal basado en la configuración regional del sistema operativo.

            String separadorDecimal = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;

Se realiza la corrección de separadores (de punto al utilizado por la configuración regional local) antes de manipular los datos con el Double.parse.

            latitud = secciones[0].Trim().Substring(0, secciones[0].Length - 1).Replace('.', separadorDecimal[0]);
            longitud = secciones[1].Trim().Substring(0, secciones[1].Length - 1).Replace('.', separadorDecimal[0]);

Con esto se solucionó el problema.  Para terminar, se utilizó un Hashtable para almacenar la información y retornársela al objeto que la solicitó.

            resultado.Add("latitud", Latitud);
            resultado.Add("latitudDireccion", latitudDir);
            resultado.Add("longitud", Longitud);
            resultado.Add("longitudDireccion", longitudDir);

Esta no es la solución que pensaba darle al problema, sin embargo la implementación fue interesante y gracias a la flexibilidad que provee gpsbabel, espero que sea funcional con una amplia gama de dispositivos, no solamente con los de la marca Garmin.

De todas formas me queda pendiente la tarea de buscar la forma de acceder directamente al puerto USB para leer su informacion en forma de cadenas NMEA, la cual me parece la solución menos dependiente posible de su entorno.

Enlace: Distribución con las fuentes del proyecto (C# basado en MonoDevelop).

Demostración de georreferenciación al estilo web 2.0

La demostración de georreferenciación al estilo web 2.0 que permite visualizar el mapa de cualquier ubicación del mundo y ubicar sobre él marcadores georreferenciados.

Para esto utiliza los servicios de Google Maps para la generación de las imágenes y GeoNames para la georreferenciación, es decir, para convertir las ubicaciones (Manizales, Caldas, Colombia) en sus respectivas duplas latitud/longitud para poder ser relacionadas geográficamente.

En la parte izquierda se aprecia la lista con las ubicaciones seleccionadas que corresponden con las marcas rojas en el mapa, en la parte derecha está el mapa junto con tres barras de desplazamiento que regulan su presentación y en la parte inferior se encuentran los botones de opciones.

El demo le permite al usuario Agregar y Remover ubicaciones como puntos seleccionados.  Como se mencionó, estas se ingresan con los nombres de las ubicaciones y el sistema obtiene su ubicación geográfica a través de la consulta de un servicio web.  Es posible en cualquier momento, Centrar el mapa al rededor de cualquiera de los puntos seleccionados almacendos.

La manipulación del mapa se realiza con las barras de desplazamiento.  La naranja (derecha) corresponde con el zoom: hacia abajo aumenta, haciendo mayor el acercamiento del mapa.  La horizontal, azul, corresponde con la longitud y la vertical, verde, corresponde con la latitud.  Cuando se modifica cualquiera de estos valores se deberá solicitar la actualización del mapa presionando el botón Refrescar.

La aplicación ha sido desarrollada en Java por lo que su código es muy claro y modular.  En términos de la implementación, el acceso al webservice de GeoNames utiliza su propio API para el cual se descargaron dos archivos: geonames-1.0-java5.jar y jdom-1.0.jar.

La georrefernciación no podría ser más fácil.

    public static GeoLocation locate(String location) throws Exception
    {
        GeoLocation result = new GeoLocation();
        // Creates the toponym searcher
        ToponymSearchCriteria searchCriteria = new ToponymSearchCriteria();
        // Sets the criteria based on the specified location
        searchCriteria.setQ(location);
        // Request the geolocalization to the webserver
        ToponymSearchResult searchResult = WebService.search(searchCriteria);
        // Gets the results
        List toponyms = searchResult.getToponyms();
        // Checks if there were results
        if(toponyms == null || toponyms.size() == 0)
            return null;
        // Gets the first result of all (could be many)
        Toponym first = toponyms.get(0);
        // Prepares the result with its information
        result.put("geoNameId",   first.getGeoNameId()   + "");
        result.put("name",        first.getName()        + "");
        result.put("latitude",    first.getLatitude()    + "");
        result.put("longitude",   first.getLongitude()   + "");
        result.put("countryCode", first.getCountryCode() + "");
        result.put("countryName", first.getCountryName() + "");
        return result;
    }

La clase GeoLocation que utilizo para manejar el resultado no es mas que un Hashtable<String, String> con algunos adendos para facilitar su uso.

Por otro lado, la generación del mapa requiere aún menos ciencia aunque la realizo en dos pasos discretos que en general no incluyen nada extraño.

    public String prepareUrl()
    {
		String markers = "";
		// Walks thru all the points to create its markers
		for(int i=0; i

En el primer paso preparo el URL del consulta al servicio basado en información como la coordenada centro del mapa, el nivel de zoom, el tamaño de la imagen, los puntos seleccionados (marcadores) y la llave del API que debe ser privada y es única para cada sitio web (FQDN), aunque para el caso específico de aplicaciones de escritorio no es muy relevante a pesar de ser obligatoria.  La llave del API puede ser obtenida de manera gratuita por cualquier desarrollador desde esta dirección.

    public Image prepareImage(String url) throws Exception
    {
        Image image = ImageIO.read(new URL(url));
        return image;
    }

El segundo paso se relaciona con consultar el servicio de Google utilizando el URL recién generado, leer los bytes que conforman la imagen y crear con ellos un objeto Image para ser mostrado posteriormente en la interfaz de usuario.  Con Java, este procedimiento es extremadamente sencillo: 1 línea de código.

Para su uso se deberá tener cuidado en el manejo de las posibles excepciones que pueda lanzar el requerimiento como por ejemplo, producto de un fallo de red.

        // Gets the map image
        Image imageMap = mapService.getMap();
        // Checks if was received
        if(imageMap == null)
        {
            JOptionPane.showMessageDialog(this, "El mapa no se pudo recuperar.",
                    "Error recuperando mapa", JOptionPane.ERROR_MESSAGE);
            return;
        }
        // Puts the map on the gui
        map.setIcon(new ImageIcon(imageMap));

Teniendo el objeto Image lo podemos poner en cualquier componente de AWT/Swing.  Por facilidad, yo utilizo un JLabel que incluye el soporte de íconos (ImageIcon) los cuales se basan en objetos de imagen.

Como se puede apreciar, la complejidad de la aplicación es muy baja por lo que reitero: la imaginación es el límite.

Es posible acceder a la aplicación desde web sin instalar ningún archivo local desde el siguiente enlace o ejecutando el siguiente comando en una consola/símbolo del sistema operativo:

   $ javaws http://demo.jorgeivanmeza.com/Java/DemoGeoReferenceMap/0.1/DemoGeoReferenceMap.jnlp

Enlace: