viernes, 8 de marzo de 2013

Como detectar burdamente que se ha dibujado una X sobre un JPanel.

Supongamos que queremos detectar si alguien dibuja sobre una pantalla táctil una X (Si utilizamos un ratón entendemos que dibujamos un trazo mientras mantenemos pulsado uno de sus botones).

Lo primero que tenemos que hacer es decidir cómo detectar si estamos dibujando el primer o segundo trazo de la X. Aunque se puede hacer de muchas maneras en este caso utilizaremos un intervalo de tiempo. Se dibuja el primer trazo (se pulsa, se arrastra y se suelta el botón)  y se espera al segundo trazo. Si se tarda mucho en hacer el segundo trazo entendemos que no estamos "escribiendo" una X si no que hemos hecho una raya y un rato después otra.

De cada trazo nos quedaremos sólo con el primer y último punto .

Para hacer lo anterior, en un JPanel, simplemente tenemos que agregarle un MouseListener para procesar los eventos del ratón.
Si se produce un evento pressed o entered llamaremos al método:

private void pressedOEntered(MouseEvent e) {
long tiempo = (System.nanoTime() - tiempoPrimeraRecta)/1000000;
System.out.println("Tiempo: " + tiempo);
if (tiempo > MAX_TIEMPO_ENTRE_RECTAS) {
// Es la primera recta
x0 = e.getX();
y0 = e.getY();
primeraRecta = true;
System.out.println("x0, y0: " + x0 + ", " + y0);
}
else {
// Es la segunda recta
x0p = e.getX();
y0p = e.getY();
primeraRecta = false;
System.out.println("x0p, y0p: " + x0p + ", " + y0p);
}
}

Si se produce un evento released o exited llamaremos al método:

private void releasedOExited(MouseEvent e) {
if (primeraRecta) {
x1 = e.getX();
y1 = e.getY();
tiempoPrimeraRecta = System.nanoTime();
System.out.println("x1, y1: " + x1 + ", " + y1);
}
else {
x1p = e.getX();
y1p = e.getY();
System.out.println("x1p, y1p: " + x1p + ", " + y1p);
comprobarX();
}
}

En el momento que se finaliza el segundo trazo llamaremos al método que comprueba si es o no una X. La comprobación que realizamos es la siguiente:
  • Son dos rectas con pendientes de signo distinto. No permitimos una recta totalmente vertical (pendiente infinita) ni X con rectas con la misma pendiente:

  • El punto de corte pertenece al dominio de los dos trazados:


La pendiente de una recta es su tangente:
El método que las calcula para nuestras rectas es:

private double getPendiente(double x0, double y0, double x1, double y1) {
   double pendiente;
if (x0 < x1) 
pendiente = (y1-y0)/(x1-x0);
else if (x0 > x1)
pendiente = (y0-y1)/(x0-x1);
else
throw new IllegalArgumentException("Pendiente infinita");
return pendiente;
}

Calculamos el punto b de cada recta partiendo de que ya tenemos las pendientes: y = mx + b => b = y – mx.

Una vez calculadas las rectas resolvemos el sistema de ecuaciones para calcular el punto de corte:

Quedando el método:

private void comprobarX() {
try {
double m = getPendiente(x0,y0,x1,y1);
System.out.println("Pendiente 1: " + m);
double mp = getPendiente(x0p,y0p,x1p,y1p);
System.out.println("Pendiente 2: " + mp);
if (m * mp >0)
System.out.println("No es X, pendientes del mismo signo");
else {
// Tenemos que ver si se cortan, y=mx+b => b = y-mx => b = y0-m * x0
double b = y0 - m * x0;
double bp = y0p - mp * x0p;
// y = mx + b, y=mp x + bp => mx+b = mp x + bp => x = (bp - b)/(m-mp), y= (m bp - mp b) / (m - mp)
double corteX = (bp - b)/(m - mp);
double corteY = (m * bp - mp * b) / (m - mp);
System.out.println("Punto de corte: " + corteX + ", " + corteY);
// Vemos si el punto de corte esta dentro del dominio de los segmentos trazados
if ( (Math.min(x0,x1) < corteX && Math.max(x0,x1) > corteX) && (Math.min(x0p, x1p) < corteX && Math.max(x0p,x1p)> corteX) && (Math.min(y0,y1) < corteY && Math.max(y0,y1) > corteY) && (Math.min(y0p, y1p) < corteY && Math.max(y0p,y1p)> corteY)) {
System.out.println("¡¡¡¡¡ Es x !!!!");
}
else 
System.out.println("No es x");
}
}
catch(IllegalArgumentException iae) {
// No vale;
System.out.print("Pendiente infinita, no vale como X");
}
}

El código java del ejemplo completo:

jueves, 7 de marzo de 2013

Apache POI en Apache Tapestry v.2.

Apache POI (http://poi.apache.org/) es un proyecto de Apache que nos permite trabajar, desde Java, con los documentos “office” de Microsoft. Tiene varias “sub-apis”, por un lado las que trabajan con los formatos antiguos de office (97-2007) y por otro las que trabajan con los nuevos formatos basados en xml. En este caso vamos a ver un ejemplo, muy por encima, de generación de un documento Excell utilizando el formato antiguo y cómo mostrarlo con Tapestry.

El enlace en la pantalla.
En apache tapestry una pantalla tiene dos componentes. Por un lado la parte visual (archivo .tml) y por otro lado su controlador, una clase java.  Tapestry no exige que la clase java extienda de otra o implemente tal o cual interfaz, simplemente utiliza convenios de nomenclatura. Si, por ejemplo, ponemos un enlace en nuestra página con un identificador “obtenerExcel”, lo que hará Tapestry al pulsar dicho enlace es instanciar la clase java asociada con esa pantalla (ambas relacionadas también por el nombre), ejecutar ciertos métodos de inicialización y buscar un método  llamado onActionFromObtenerExcel() para resolver la petición.

Para insertar un enlace en una pantalla utilizamos un componente de presentación de Tapestry que generará el código html necesario tanto para la presentación como para la petición que Tapestry debe recibir.  En nuestro caso queremos poner un enlace para generar un archivo Excel. El código a insertar en la pantalla será:

<t:actionlink t:id="obtenerExcel">
      <img src="${context:imagenes/excel.png}"/>
      Obtener Excel
</t:actionlink>

Cuyo resultado será:
El evento en la clase Java asociada con la pantalla.
Como he dicho antes al pulsar sobre ese enlace Tapestry ejecutará el método “onActionFromObtenerExcel()” de la clase java asociada con la pantalla.

En nuestro caso el archivo se genera al vuelo, no existirá físicamente en ninguna ubicación de nuestro servidor. Si estuviésemos enviándolo desde un servlet iríamos escribiendo sus bytes en el “response” habiéndole indicado previamente al navegador que lo que iba a recibir era un archivo Excel con la instrucción response.setContType(“application/vnd.ms-excel”).

Para que Tapestry envíe un “churro de bytes” al navegador debemos retornar un objeto del tipo StreamResponse desde nuestro método:

   StreamResponse onActionFromObtenerSabanaEnExcel() throws Exception {
         …
   }

Para indicar que el “churro de bytes” es un archivo Excel y su nombre debemos crear nuestro propio StreamResponse:

public class ExcelStreamResponse implements StreamResponse {
    private InputStream is;
    private String filename="default";
    public ExcelStreamResponse(InputStream is, String... args) {
        this.is = is;
        if (args != null) {
            this.filename = args[0];
        }
    }
    public String getContentType() {
        return "application/vnd.ms-excel";
    }
    public InputStream getStream() throws IOException {
        return is;
    }
    public void prepareResponse(Response arg0) {
        arg0.setHeader("Content-Disposition", "attachment; filename=" + filename + ".xls");
    }
}

Si nos fijamos en los métodos del StreamResponse vemos que tenemos que proporcionarle un InputStream. Esto es lógico puesto que Tapestry debe leer el “churro de bytes” para enviarlo al navegador. Esto contrasta con cualquier “parseador” que siempre necesita un outputstream sobre el que escribir. Después veremos cómo pasar ese outputstream (salida del generador del Excel) al inputstream que necesita Tapestry.

Generando el Excel con Apache POI.
Para compatibilizar el Excel generado con el office 2007 se debe utilizar el subapi
de POI, org.apache.poi.hssf, que tiene como descripción “Horrible SpreadSheet Format API's for reading/writting Excel files using pure Java”. Un par de ejemplos generados con POI son:


El primer ejemplo es muy sencillo. Primero creamos una plantilla con Excel y luego rellenamos algunos datos con POI accediendo a las filas del libro y luego a sus celdas para darles valor:

public static InputStream generarControlDeAsistencia(File plantilla, String cursoAcademico, String curso, int grupo, List<String> alumnos) throws Exception {        
        FileInputStream fis = new FileInputStream(plantilla);
        HSSFWorkbook libro = new HSSFWorkbook(fis);
        HSSFSheet hoja = libro.getSheetAt(0);
        HSSFRow fila = hoja.getRow(1);
        HSSFCell celda = fila.getCell(5);
        celda.setCellValue(cursoAcademico);
        celda = fila.getCell(8);
        celda.setCellValue(curso);
        celda = fila.getCell(11);
        celda.setCellValue(grupo);
        int totalFilas = Math.min(alumnos.size(), 36);
        for (int i=0; i<totalFilas; i++) {
            fila = hoja.getRow(7 + i);
            celda = fila.getCell(1);
            celda.setCellValue(alumnos.get(i));
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        libro.write(baos);
        baos.close();
        return new ByteArrayInputStream(baos.toByteArray());
}

El segundo ejemplo es más delicado ya que las filas y las celdas se crean dinámicamente. Para ello se debe hacer uso de métodos para crear hojas, filas y celdas y se deben definir estilos de celdas o fuentes a aplicar.

Devolviendo un InputStream. 
Como se ve al final del método generarControlDeAsistencia del apartado anterior se retorna un InputStream que necesitamos para nuestro ExcelStreamREsponse. Una vez creado éste ya lo podemos retornar como respuesta a la pulsación de enlace de la pantalla.

martes, 30 de octubre de 2012

Reestructurar el html de una tabla dinámicamente tras cambios de rowspan o colspan.

Dentro de mi editor quiero que se puedan cambiar los atributos colspan y rowspan de cualquier celda de la tabla y que esta se adapte a esos cambios. En Firefox, por ejemplo, si a una celda existente le modificas uno de estos atributos la tabla mostrara el exceso o defecto de celdas (td o th) ya que el navegador se limita a pintar lo que figura en el html y en él los tds por defecto o por exceso existen.

El primer intento ha sido intentar escribir una función que modifique el colspan de una celda directamente. El resultado ha sido una función que no me da muchas garantías en las que no tengo claro si cubro todos los casos y con limitaciones del tipo “si la celda adyacente tiene un rowspan, no se puede modificar el colspan de la celda previa”. El punto culminante ha sido al intentar duplicar esa función para tratar cambios del rowspan, con lo que podría suceder que “para modificar el rowspan la celda no debe tener un colspan”. En definitiva podría darse un caso recursivo que impidiese el realizar ciertas estructuras.  Al menos, me ha ayudado a ir viendo los distintos casos y a ver más claramente el problema.

El siguiente intento ha sido simplificar el problema a un primer paso, incrementos del colspan en una única celda y del rowspan en una única celda. Con esto se simplifican los casos y, posteriormente,  iterando se llega al mismo resultado (más costoso para el ordenador pero mucho más sencillo, más mantenible, y mucho más fiable).

Colspan + 1
1. Partimos de una celda seleccionada y queremos añadir 1 a su colspan dejando la tabla html en condiciones y teniendo en cuenta que las celdas adyacentes pueden tener muchas disposiciones posibles:
El asterisco representa la celda seleccionada que queremos ampliar con un colspan +1. Las celdas rojas son las afectadas. A la derecha de la flecha aparece el resultado final, la celda amplia su colspan, la roja desaparece o se reduce y pueden aparecer celdas nuevas (amarillas).

2. Creación de un modelo para la tabla. La mejor forma, en informática, de representar algo es mediante números. En este caso, he decidido asignar un número a cada color, o a cada celda. Con este modelo en mente, el dibujo anterior se traduce en:
Hay que tener en cuenta, también, que la celda inicial puede tener su colspan y su rowspan y que debe seguir funcionando.

3. Creamos, primero, unas funciones para representar las dos tablas, el modelo y su representación con colspans y rowspans y la página html. Las pintamos utilizando jquery. Cada celda de la tabla tiene como id su id en el modelo.

4. Dada una celda seleccionada (click) en la tabla pulsamos el botón de colspan+1. El proceso, mirando en los ejemplos es el siguiente:
  • Cogemos el id de la celda seleccionada y calculamos su cuadrado (filaIni, columnaIni, filaFin, columnaFin) utilizando para ello los valores almacenados en el modelo para ese id.
  • Recorremos la columna adyacente a la columnaFin (si no es la última, columnaFin+1) desde la filaIni hasta la filaFin.
    • Hacemos idAEliminar = modeloTabla[filaFin+1][j];
    • Si el identificador de la siguiente columna (columnaFin+2) es igual al que vamos a eliminar entonces debemos eliminar ese identificador de la columna columnaFin+1.
  • Ponemos el id de la celda seleccionada en lugar del idAEliminar.
El código de ejemplo y su funcionamiento (probado en Firefox 15) está en:

Colspan - 1
El colspan -1 es bastante más sencillo:
Simplemente se crean nuevos ids para la columna que se pierde. Si la celda tiene colspan 1 no se permite reducirlo.
El ejemplo:
Rowspan + 1 y Rowspan -1
Tal y como debería haber sido desde el principio si cambiamos col por row, i por j, y filas por columnas, tendremos los métodos para modificar el rowspan directamente desde los anteriores, sin necesidad de comernos el tarro. 
El ejemplo:
Iterando
Aquí dejo el resultado final. He modificado las funciones anteriores  para que retornen un booleano indicando si el incremento o decremento en uno se puede o no hacer en lugar de mostrar un mensaje de error. La iteración se realizará hasta que se llegue al final o se obtenga un false en una de las iteraciones. Simplemente, calculamos la diferencia entre el valor actual y el introducido para saber cuantos pasos tenemos que realizar.
El código final en:

viernes, 14 de septiembre de 2012

Propiedad toJSON de un objeto Javascript

A la hora de serializar un objeto javascript con JSON utilizando la función
JSON.stringify(value[, replacer [, space]]) 
aparte de poder utilizar el "replacer" para "customizar" (personalizar) la salida JSON de ese objeto podemos incluir la función (propiedad) toJSON en el propio objeto. El parseador la verá y hará uso de ella para crear el JSON. Esta propiedad no debe devolver el string JSON ya creado sino que deberá devolver qué propiedades del objeto se deben o no incluir en el string JSON final. Si, por ejemplo, sólo queremos que aparezca la propiedad "foo" del objeto en nuestra serialización haríamos:
     MiObjeto.prototype.toJSON = function() {
          return { foo: this.foo };
     };
Si queremos varias propiedades pero no todas, podríamos escribir:
     MiObjeto.prototype.JSON = function() {
          return { prop1: this.prop1,
                       prop2: this.prop2,
                       propQueEsArray: this.propQueEsArray };
     }

jueves, 24 de mayo de 2012

Utilizando JSON para serializar y des-serializar objetos de javascript incluyendo sus métodos (Parte 2).


Tras ojear el libro "Professional Javascript for web developers" (tercera edición), en mi opinión bastante bueno, he visto que tenía bastantes cosas que corregir en la parte 1 de este tema.
Primero, tal y como se indica allí, si codificamos nuestros objetos con el "patrón siguiente":

     function Direccion(calle, codigoPostal) {
          this.tipo = "Direccion";
         this.calle = calle,
         this.codigoPostal = codigoPostal;
         this.getCodigoPostal = function() {
              return this.codigoPostal;
         }
     }

Al crear dos instancias del objeto "Dirección" se crearán también dos instancias distintas de la función (método, objeto) getCodigoPostal. Obviamente, esto es bastante malo si queremos aprovechar memoria. Aunque en el libro se comentan otros modos para codificar nuestras "clases" parece ser que lo más correcto es:

     function Direccion(calle, codigoPostal) {
          this.tipo = "Direccion";
         this.calle = calle,
         this.codigoPostal = codigoPostal;
     }

     Direccion.prototype = {
          constructor : Direccion,
         getCodigoPostal : function() {
              return this.codigoPostal;
         }
     };

Es decir, utilizar un constructor para los atributos y luego definir los métodos extendiendo el prototipo. De este modo se consigue que cada objeto tenga sus propios atributos y que todos los del mismo tipo compartan los mismos (objetos) métodos.

En ECMASCRIPT 6 aparecerá el concepto de clase.

Si, además, consideramos que estamos ejecutando ECMASCRIPT 5 entonces ya dispondremos de JSON (sin necesidad de incluir json2.js) y del método Array.isArray(value) para comprobar si "value" es un array o no.
Con estos cambios la página queda así:
Resumiendo, si incluimos el atributo “tipo” con el nombre de la clase de nuestros objetos en cada uno de ellos, con la función siguiente podremos des-serializar nuestros objetos almecenados con JSON, conservando su tipo y sus métodos.

     function recrearObjetoDesdeJSON(objetoDesdeJSON) {
          if (!objetoDesdeJSON.tipo)
              return objetoDesdeJSON;
         var nuevoObjeto = new this[objetoDesdeJSON.tipo];
         var valorAtributo;
         for(var atributo in objetoDesdeJSON) {
              valorAtributo = objetoDesdeJSON[atributo];
              if (typeof valorAtributo == "object") {
                   if (Array.isArray(valorAtributo)) {
                       var nuevoArray = new Array();
                      for (var i=0; i<valorAtributo.length; i++) {
                           nuevoArray[i] = recrearObjetoDesdeJSON(valorAtributo[i]);
                      }
                      nuevoObjeto[atributo] = nuevoArray;
                  }
                  else
                       nuevoObjeto[atributo] = recrearObjetoDesdeJSON(valorAtributo);
}
else
                  nuevoObjeto[atributo] = valorAtributo;
}
return nuevoObjeto;
}

Debemos seguir teniendo cuidado para no referenciar un atributo desde otro:
this.atr1 = ….
this.atr2 = new Array(this.atr1, …)
Ya que el atributo del array se creara como un objeto distinto al atributo de "la clase".

miércoles, 23 de mayo de 2012

Aplicar una transformación xsl desde javascript cogiendo el archivo xml del ordenador del cliente.


Aunque podría poner una xsl  general en este ejemplo voy a utilizar la siguiente (por_un_clavo.xsl):

<?xml version="1.0"?>
     <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:template match="/">
          <xsl:apply-templates/>
     </xsl:template>
     <xsl:template match="por_un_clavo">
          <ul>
       <xsl:apply-templates/>
  </ul>
     </xsl:template>
     <xsl:template match="linea">
          <li>
       <xsl:value-of select="."/>
  </li>
     </xsl:template>
</xsl:stylesheet>

Para procesar, con ella, el xml siguiente (por_un_clavo.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!-- <?xml-stylesheet type="text/xsl" href="por_un_clavo.xsl"?> -->
<por_un_clavo>
     <linea>Por un clavo se perdió una herradura,</linea>
     <linea>por una herradura, se perdió un caballo,</linea>
     <linea>por un caballo, se perdió una batalla,</linea>
     <linea>por una batalla, se perdió el Reino.</linea>
</por_un_clavo>

Si descomentamos la línea comentada podemos abrir el archivo xml con Internet Explorer y éste realizará la transformación directamente. Esto nos servirá para validar rápidamente nuestra xsl.

Si no queremos tener problemas con los acentos, las ñ y demás deberemos poner el encoding dentro del xml con el que realmente hayamos guardado nuestro archivo (trabajar con utf-8 es la mejor elección, olvidaros del ISO-8859-1 por muy latín que sea). Una forma de hacerlo, desde Windows, es escribiendo el xml en Word, guardando como texto y allí eligiendo la codificación (en Windows, por defecto y para variar, la codificación es propietaria de Microsoft y no es utf-8):


Una vez seleccionado el archivo de disco y transformado con nuestra xsl el resultado, con Firefox, será:


La página, con el código completo, se puede ver en:

El body de la página es, simplemente:

<input id="ficheroxml" type="file" name="ficheroxml" onchange="procesarXml();"/>
<div id="resultado"></div>

En él tenemos un input para seleccionar el archivo y un div que albergará el resultado de la transformación. En el momento en el que se selecciona el archivo se producirá el evento “onchange” y se llamará a la función javascript procesarXml().

Dentro del javascript hemos incluido la xsl como una variable de texto con todo su contenido:

     var xsl = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> …

y hemos creado una instancia de un lector de ficheros:

     var reader = new FileReader();

Cuando se llama a la función procesarXml se ejecuta el siguiente código:

     if (document.getElementById("ficheroxml").files.length == 0) return;
     var fichero = document.getElementById("ficheroxml").files[0];
     reader.readAsText(fichero);

donde validamos si se ha seleccionado algún archivo, cogemos el primero de ellos y le pedimos al lector que lo lea como un archivo de texto.
Si se produce algún error se lanzará el evento onerror en el lector que definimos del modo siguiente:

     reader.onerror = function(event) {
           alert("Error leyendo el archivo, code: " + reader.error.code);
     }

Si todo va bien se lanzará el evento onload sobre el reader:

      reader.onload = function(event) {
           xml = event.target.result;
   parsearXml();
      }

Aquí guardaremos el resultado de la lectura en la variable xml y llamaremos a la función que realiza el parseo e introduce el resultado en el div "resultado":

function parsearXml() {
      var xsltProcessor = new XSLTProcessor();
      var parser = new DOMParser();
      xsltProcessor.importStylesheet(parser.parseFromString(xsl, "text/xml"));
      var resultDocument = xsltProcessor.transformToFragment(parser.parseFromString(xml, "text/xml"),                  
                                       document);
      document.getElementById("resultado").appendChild(resultDocument);
}

Para que esto funcione en Chrome (18.0.1025.162 m) es necesario arrancar la instancia de  Chrome con el switch “--allow-file-access-from-files” ya que, si no, no se nos permitirá, por razones de seguridad, acceder al archivo del  disco.

martes, 22 de mayo de 2012

Hora de aventuras.

Hora de aventuras según el peque (7 añitos), con Finn, Jake, la princesa chicle, Lady Arcoiris, la princesa bultos y demás ...


Utilizando JSON para serializar y des-serializar objetos de javascript incluyendo sus métodos (Parte 1).

Ver Parte 2 de este tema.
En el desarrollo real del editor que estoy realizando me he visto en la necesidad de serializar mis objetos javascript a disco para lo que pensaba que seria suficiente con convertir mis objetos al formato JSON utilizando json2.js de https://github.com/douglascrockford/JSON-js) :

localStorage.setItem(nombredelapantalla, JSON.stringify(pantalla));

¿Cual ha sido mi sorpresa cuando he visto que este formato no admite que los objetos javascript tengan funciones? Es decir, si tenemos un objeto (función) del tipo:

function Persona(nombre, trabajo, nacimiento) {
     this.nombre = nombre;
     this.trabajo = trabajo;
     this.nacimiento = nacimiento;
     this.getNombre = function() {
          return this.nombre;
     }
}

Su JSON será:
{"nombre":"Pedro Picapriedra","trabajo":"Picapedrero","nacimiento":-1000}. 
Aquí no existe ninguna referencia a la clase, objeto o tipo “Persona”. Si des-serializamos este objeto tendremos un nuevo objeto con los atributos nombre, trabajo, y nacimiento pero sin el método getNombre() (Sabremos que su nombre es Pedro Picapiedra, que es Picapedrero pero no sabremos si es una Persona y no le podremos preguntar su nombre).
Tras varias vueltas por internet y revisar arriba y abajo JQuery sólo he visto el método toSource que convierte un objeto a un formato similar al de JSON con referencias al método y al tipo del objeto. Lástima que, una vez más, este método no esté disponible en todos los navegadores y sólo sea válido para Mozilla.
En definitiva, toca hacer en javascript algo que muchos otros lenguajes hacen de serie.

1ª Prueba: Introducimos un atributo para identificar el tipo del objeto en nuestra “clase” y lo utilizamos para regenerar el objeto copiando los valores de los atributos del objeto des-serializado por json2:

if (objetoSinMetodos.tipo == "persona") {
     var nuevoPedro = new Persona();
     // Copiamos los valores de los atributos
     for(var member in objetoSinMetodos)
          nuevoPedro[member] = objetoSinMetodos[member];
     alert(nuevoPedro.getNombre() + ", " + nuevoPedro.trabajo + "," + nuevoPedro.nacimiento);
}

El ejemplo en:

2ª Prueba: Introducimos el objeto "Direccion" con sus atributos y métodos como atributo del objeto Persona. Al des-serializar queremos poder llamar a objPersona.getDireccion().getCodigoPostal(). 
El código siguiente se explica por sí mismo, si un atributo es un objeto le metemos la propiedad “tipo” y así sabemos de qué tipo es (aunque en el código lo creemos a capón en esta versión):
var valorAtributo;

if (objetoSinMetodos.tipo == "persona") {
     var nuevoPedro = new Persona();
     // Copiamos los valores de los atributos
     for(var atributo in objetoSinMetodos) {
          valorAtributo = objetoSinMetodos[atributo];
    if (typeof valorAtributo == "object") {
         var nuevaDireccion = new Direccion();
         for (var atributo2 in valorAtributo)
              nuevaDireccion[atributo2] = valorAtributo[atributo2];
         nuevoPedro[atributo] = nuevaDireccion;
    } 
    else
         nuevoPedro[atributo] = valorAtributo;
     }
     alert(nuevoPedro.getDireccion().getCodigoPostal());
}

El ejemplo completo en:

3.- Quitando el “a capón”: Algo muy bueno que tiene Javascript es que a partir de una variable, un string, un nombre, podemos hacer casi todo. Podemos, en este caso, crear un nuevo objeto del tipo persona o del tipo dirección a partir de los valores, string, de los atributos tipo:

function recrearObjetoDesdeJSON(objetoDesdeJSON) {
     var nuevoObjeto = new this[objetoDesdeJSON.tipo];
     var valorAtributo;
     for(var atributo in objetoDesdeJSON) {
          valorAtributo = objetoDesdeJSON[atributo];
    if (typeof valorAtributo == "object")
         nuevoObjeto[atributo] = recrearObjetoDesdeJSON(valorAtributo);
    else
         nuevoObjeto[atributo] = valorAtributo;
     }
     return nuevoObjeto;
}

El ejemplo completo en:

4. Incluyendo arrays: Al ir a introducir el código anterior en mi estructura de objetos he visto que algunos de ellos tenían atributos que eran arrays por lo que la función dejaba de funcionar. Para resolver esto, debemos saber si un objeto de Javascript es un array o no. En 
nos explican la solución:

function isArray(a) {
     return Object.prototype.toString.apply(a) === '[object Array]';
}

Quedando, finalmente:

function recrearObjetoDesdeJSON(objetoDesdeJSON) {
  if (!objetoDesdeJSON.tipo)
return objetoDesdeJSON;
var nuevoObjeto = new this[objetoDesdeJSON.tipo];
var valorAtributo;
for(var atributo in objetoDesdeJSON) {
valorAtributo = objetoDesdeJSON[atributo];
if (typeof valorAtributo == "object") {
if (isArray(valorAtributo)) {
var nuevoArray = new Array();
for (var i=0; i<valorAtributo.length; i++) {
nuevoArray[i] = recrearObjetoDesdeJSON(valorAtributo[i]);
}
nuevoObjeto[atributo] = nuevoArray;
}
else
nuevoObjeto[atributo] = recrearObjetoDesdeJSON(valorAtributo);
}
else
nuevoObjeto[atributo] = valorAtributo;
}
return nuevoObjeto;
}

Recordar que para que esto funcione cada uno de nuestros objetos deberá disponer de un atributo “tipo” con el nombre de la clase.
Es muy probable que en el caso de un atributo que sea un array de arrays el código no funcione correctamente. No es mi caso ;D. 

¡CUIDADO! En mi caso tenia varios atributos que luego volvia a referencias desde otro que era un array:
this.atr1
this.atr2
this.atr3 = new Array(this.atr1, this.atr2);
Al des-serializarse el objeto con mi función los atributos del array no son los mismos objetos (son objetos del mismo tipo pero son instancias distintas).  En mi caso la solución ha sido poner una función:
this.getAtributos = function() {
return new Array(this.atr1, this.atr2);
}

Estoy leyendo un buen libro de javascript "Professional javascript for web developers", tercera edición, en el que comentan el problema de codificar los objetos javascript tal y como lo estoy haciendo yo aquí, definiendo las funciones dentro del objeto con "this.funcion". Parece ser que en este caso cada vez que se crea un objeto se crea tambien uno por cada una de las funciones que aparecen en ese objeto de modo tal que si creamos dos instancias de uno de mis objetos se crearan también instancias nuevas de cada una de las funciones. En definitiva debería escribir mis "clases" de otro modo y cuando lo haga deberé buscar el modo de serializar mis objetos de nuevo. En ese momento, espero que pronto, pondré aquí la parte 2.
Por cierto, con ECMASCRIPT 6 parece ser que Javascript resolverá muchos de sus problemas. Se prodrán definir clases, constantes, etc.

Ver Parte 2 de este tema.

jueves, 29 de marzo de 2012

Un editor html WISIWING con JQuery.

Aquí dejo el código de una pequeña prueba para hacer un editor de html (ver código fuente del enlace). Aunque para mis necesidades no es necesario que fuese WISIWING (lo que ves es lo que tienes) si quería ver cómo podría hacer algo que diese una imagen real del resultado final.
Ahora mismo con JQuery (simplifica el trabajo de javascript), con los nuevos navegadores (esto no funcionara en cualquiera si no estamos actualizados), lo tenemos más fácil aunque tenemos que pasar por el aro de trabajar con javascript. Manipulando el dom de la página podemos agregar y eliminar elementos, podemos introducir contenedores (div, párrafos, tablas, …), podemos modificar las propiedades css de los elementos y podemos jugar con sus atributos mientras vemos el resultado directamente en pantalla. Lo mejor, es que el código apenas llega a las 100 líneas e introducir el resto de propiedades y atributos para completar el editor es tremendamente sencillo. 
El enlace al editor:
Una imagen:

Para introducir los elementos en un contenedor, seleccionaremos el elemento y pulsaremos dentro del contenedor. Si, por ejemplo, queremos introducir un botón como el de la imagen, pondremos un elemento input (por defecto se incluye de tipo texto), pulsaremos sobre él, e iremos a los atributos y cambiaremos a tipo "button". Despues en el value podemos poner el texto a mostrar.

sábado, 31 de diciembre de 2011

Colisiones y sonido

Ver artículo anterior de esta serie
Puff, poco tiempo para estas cosas y con el peque ya dándole a la psp y a la ps3. Llego tarde.
Dejo aquí un ejemplillo sobre cómo montar las colisiones (muy burdamente pero suficientes si se ajustasen mejor los rectángulos) y sobre cómo incluir los sonidos. Algo curioso es que si un sonido esta en uso hay que esperar a que termine antes de que se puede volver a utilizar. Para resolver esto hay que tener en el código distintas variables apuntando al mismo sonido. En el caso de las flechas tengo dos variables así que si uno esta sonando se utiliza el otro. Si se dispara muy rápido serian necesarias más variables.
Para probar esto lo mejor es ir a la segunda pantalla y dedicarse a cazar al pájaro. Una vez que se le ha dado con una flecha, cambia el estado del sprite para que este caía hasta donde corresponda en función de la pantalla, al llegar al suelo se convierte en "premio" y cuando el soldado colisiona con el premio se lo lleva.
El ejemplo:

jueves, 3 de noviembre de 2011

Un arco sin caza que cazar :(

Ver el artículo anterior de esta serie.
El ejemplo:

Esto me ha llevado un rato. De nuevo si lo hubiese pensado antes no me habría liado con la parte de apuntar con el arco. ¡Sir Arthur siempre ha tirado los cuchillos “pa lante”!.
Ahora podemos disparar pero nada de tiros parabólicos, hazte a la idea de que la flecha cae después de que sale de la pantalla o de que tienes una fuerza increíble.
Aunque he revisado el código se me ha guarreado un poco. La trayectoria de las flechas es, cómo no, y=mx+b, donde m es la pendiente de la recta, m=tangente del ángulo, y b es el punto de corte con el eje y.
En función de la escala del prota (si te vas a la tercera pantalla es más pequeño) y en función del ancho del sprite o de hacia dónde apunta hay que echar una serie de cálculos en los que no me he parado demasiado. He probado utilizando la intuición y me veo, cuando intente tratar las colisiones con objetos (siguiente paso), revisando todo el código y parándome a pensar, lápiz en mano, las trayectorias y demás. El hecho de que el eje “y” esté invertido en el canvas y de que yo no tenga muy claro hacia dónde van los ángulos complica las cosas y más si añadimos que he modificado el mapa de los sprites para que sólo aparezcan los que miran hacia la izquierda y debo, entonces, girarlos para tratar el caso de los que miran a la derecha:


Por cierto, las flechas van por el canvas intermedio y, por ejemplo, en el dibujo de arriba, si no pongo algo en el primer plano van por delante del desfiladero, ¡un efecto a lo Michael Escher!

miércoles, 26 de octubre de 2011

Subir escaleras molara pero disparar un arco muchísimo más.

Ver el artículo siguiente de esta serie.
Ver el artículo anterior de esta serie.
Primera prueba para el disparo con el arco (Sí ya sé que estas son horas para darle al Batman Arkham City pero el deber es el deber y, además, el peque se ha puesto a darle al Braid y aunque es “mu” complicado para cuando esté esto ya se es capaz de pasarse el Halo sin problemas).
La prueba es muy sencilla, una pequeña animación para ver cómo se va a mover el arco a la hora de apuntar. El mapa del “sprite” es el siguiente:
 
Primero dibujamos el brazo de atrás, girando el contexto el ángulo adecuado, luego pintamos el cuerpo del muñeco (casi a lo Johnny cogió su fusil) y, finalmente, encima de lo dibujado, volvemos a girar el contexto y pintamos el otro brazo y el arco.

El increíble hombre menguante – I.

Ver artículo siguiente de esta serie.
Ver artículo anterior de esta serie.
Lo del “I” en el título es porque después de ver el ejemplo siguiente está claro que tiene que haber un “II” para continuar con este tema y corregirlo:


Este es el primer intento para ir integrando cosas. Principalmente el cambio de pantallas y el cambio en la escala (dos “sprites”, pequeño y grande).
Poco a poco voy definiendo variables globales que se van utilizando en la mayoría de los scripts. Las más destacadas son un el “sprite”, personaje pequeño, el grande y la pantalla actual en la que tenemos definidos los puntos iniciales, finales, el recorrido posible, de dónde venimos y a donde vamos.
Uno de los cambios más destacados es “el bucle principal del juego” que se intenta ejecutar a razón de 30 frames por segundo:

this.actualizarJuego = function() {
        this.sprite.actualizarEstado();
        //
        var siguientePantalla = pantalla.hayQueCambiarDePantalla(this.sprite.posX, this.sprite.posY);
        if (siguientePantalla != 0) {
            var numPantalla = pantalla.numero;
            pantalla = getPantalla(siguientePantalla);
            if (pantalla.escalaReducida)
                this.sprite = spriteEscalaReducida;
            else
                this.sprite = spriteEscalaNormal;
            if (numPantalla < pantalla.numero)   
                this.sprite.setPosicion(pantalla.getPosicionInicial());
            else
                this.sprite.setPosicion(pantalla.getPosicionFinal());
        }
        //                       
        this.drawFrame();
}

Aquí:
  • Actualizamos el estado del sprite (si hay una tecla pulsada se actualiza la posición en función de ella si es que se puede).  
  • Comprobamos si hay que cambiar de pantalla y si es así colocamos el sprite (pequeño o grande, dependiendo de la pantalla) en su posición inicial o final si venimos de la anterior o la siguiente, respectivamente.
  • Dibujamos todo.
El otro cambio destacable es la inclusión de una función “cargarImagenesPantalla” en el script:
Esta función es la encargada de cambiar las imágenes en función de la pantalla a mostrar. Primero pondrá una ventanita de “cargando …”  y cuando se han cargado las imágenes del fondo y del primer plano (si existe ésta) las mostrará. Aquí tenemos dos problemas que creo que son sencillos de resolver:
  • Cuando la pantalla se está cargando si seguimos pulsando las teclas el muñeco se sigue moviendo y cuando se muestran el muñeco no está al principio. Esto se corrige no aceptando la pulsación de las teclas si estamos cargando.
  • La carga de las pantallas va lenta. Esto se debe corregir cargando previamente las imágenes tanto de la pantalla anterior como de la siguiente al llegar a una determinada. El navegador debería “cachear” (guardar temporalmente) la imagen y al necesitarla ya la deberíamos tener disponible en local (directorio temporal del disco duro).

martes, 25 de octubre de 2011

Controlando al personajillo II.

A un árbol me subí
donde manzanas había.
Si manzanas no comí
y manzanas no dejé...
¿cuántas manzanas había?.

El ejemplo:

Mi idea inicial era que el personaje consiguiese, en un momento determinado del juego, una cuerda que podría lanzar para poder subir y bajar. He programado el lanzamiento de la cuerda pero al ir a integrarlo con el movimiento del muñeco he visto que se complicaba mucho. ¿Cuándo recoger la cuerda? ¿Difícil acertar para que se enganche? ¿Hacia dónde está mirando el muñeco?. En definitiva, ¿qué pasa, si como en la mayoría de los juegos, dejo las cuerdas ya colocadas?. Así que descartado el lanzamiento y cuerdas colocadas. ¿Qué pasa con las cuerdas? ¿Por qué los juegos utilizan escaleras? Si engancho las cuerdas en los troncos de arriba el muñeco al subir quedará en el aire, subiendo sin cuerda, ya que no tiene sentido que las cuerdas sobresalgan por encima de los troncos. Para resolver esto nada más sencillo que poner escaleras en lugar de cuerdas ya que las escaleras sí sobresalen. En muchos juegos las escaleras se ven de frente. Después de programar esta parte está claro que esa solución es muchísimo más sencilla ya que no hay que tener en cuenta hacia qué lado mira el personaje y no hay que tener más que una animación con el muñeco de espaldas. En mi caso he dibujado la animación de subir y bajar en la escalera de la derecha (con el personaje mirando hacia la izquierda). Así las manos coinciden con la escalera. Al reflejar el personaje para que suba por el otro lado las manos ya no quedan en la escalera, si no que vuelan. Debería retocar las animaciones, cosa que no hubiera sucedido con la escalera de frente. Además si hubiésemos descartado la poca profundidad que tiene mi escenario también nos hubiésemos evitado el tener que estar mirando qué queda delante y qué queda detrás. Necesitamos poner un plano en el frente para ocultar las manos cuando se sube por la escalera detrás de los troncos.
Para manejar por donde se mueve el personaje y para definir la pantalla he creado el javascript pantalla.js. Se crean segmentos por donde se puede mover el personaje, fuera de ellos no se permite el movimiento (hay que llegar al final del segmento para poder cambiar de dirección, quizás sea necesaria una pequeña ayuda aquí). Además los segmentos verticales se marcan para saber si el personaje debe mirar  hacia la derecha o hacia la izquierda. Esta definición es automática y sólo hay que proporcionar los puntos que unen los segmentos de izquierda a derecha. En el caso de esta pantalla, el recorrido se define como:
Pantalla([0,255,180,255,180,54,546,54,546,255,736,255]); 

El recorrido a realizar es el mostrado en negro pero teniendo en cuenta la posición y el tamaño de las imágenes del sprite y que se su posición es la esquina superior izquierda, nos queda el recorrido mostrado en rojo.
Además dentro de la Pantalla hemos metido un par de métodos que nos indicarán si se llega al final del recorrido (siguiente pantalla) o si se va al inicio (pantalla anterior).
He ampliado el script “listeners” para tener en cuenta las teclas para subir y bajar. Aunque por el momento las teclas son las flechas tendré que cambiarlas ya que se utilizan para mover el scroll y si éste existe es un problema (ahora lo eliminado impidiendo en el html que aparezca).
El código de sprite.js también lo he modificado adaptándolo a los nuevos movimientos y “generalizándolo” para sprites que se mueven con el teclado en todas direcciones (¡sólo uno!).
¿El tamaño “de la cámara”? No tengo muy claro qué tamaño del personaje elegir. En las pantallas donde va haber conversación con otros personajes lo ideal es lo mostrado en los primeros ejemplos, de cerca, pero para otras pantallas, como la primera de este ejemplo, da mucho más juego poner “la cámara” más alejada. Esto nos obliga a meter más imágenes con distintos tamaños o a escalarlas en el canvas. Otra opción podría ser elegir un tamaño intermedio pero, a mí, el cambio de escala me resulta más atractivo de cara al peque.

martes, 18 de octubre de 2011

Apache POI en Apache Tapestry.

Apache POI (http://poi.apache.org/) es un proyecto de Apache que nos permite trabajar, desde Java, con los documentos “office” de Microsoft. Tiene varias “sub-apis”, por un lado las que trabajan con los formatos antiguos de office (97-2007) y por otro las que trabajan con los nuevos formatos basados en xml. En este caso vamos a ver un ejemplo, muy por encima, de generación de un documento Excell utilizando el formato antiguo y cómo mostrarlo con Tapestry. El paquete a utilizar es org.apache.poi.hssf que tiene como descripción “Horrible SpreadSheet Format API's for reading/writting Excel files using pure Java”.
Empezamos viendo cómo generar el documento siguiente:

Dentro de nuestra página en Tapestry colocamos un par de enlaces para la generación de los documentos Excell:
El código, dentro del archivo “.tml”, para generar los enlaces:

<t:actionlink t:id="obtenerSabanaEnExcel" style="margin-right:10px;">
      <img src="${context:imagenes/excel.png}"/>
      Sábana
</t:actionlink>
<t:actionlink t:id="excelParaMoodle" style="margin-right:10px;">
      <img src="${context:imagenes/excelparamoodle.png}"/>
      Excel para Actas en Moodle
</t:actionlink>

El código, dentro de la clase Java asociada con la página, que se ejecuta al pulsar sobre obtenerSabanaEnExcell (onActionFrom):

StreamResponse onActionFromObtenerSabanaEnExcel() throws Exception {
      getAlumnos();
      SabanaAsignatura sabanaAsignatura = new SabanaAsignatura(getCodigoAsignatura(), getSistema(),                         alumnos, parteExamenDAO, resultadoExamenDAO, getUsuario().getCursoAcademico());
      GeneradorSabanaExcelCompleta generador = new
                  GeneradorSabanaExcelCompleta(getNombreAsignatura(), sabanaAsignatura);
      InputStream is = generador.generarSabana(getSistema());
      return new ExcelStreamResponse(is, "Resultados");           
}

Donde lo único que hacemos es cargar los datos para la generación del Excell, generarlo y devolver un objeto del tipo ExcelStreamResponse con el InputStream y que se llamará “Resultados.xls” en el cliente.
La clase ExcelStreamResponse es utilizada por Tapestry para hacer saber al navegador el tipo de contenido (en este caso un documento Excell de Microsoft) que le va a ser servido:

public class ExcelStreamResponse implements StreamResponse {
    private InputStream is;
    private String filename="default";
    public ExcelStreamResponse(InputStream is, String... args) {
        this.is = is;
        if (args != null) {
            this.filename = args[0];
        }
    }
    public String getContentType() {
        return "application/vnd.ms-excel";
    }
    public InputStream getStream() throws IOException {
        return is;
    }
    public void prepareResponse(Response arg0) {
        arg0.setHeader("Content-Disposition", "attachment; filename=" + filename + ".xls");
    }
}

Dentro de la clase GeneradorSabanaExcelCompleta se crea la hoja (u hojas) Excell.

      HSSFWorkbook libro = new HSSFWorkbook();
      hoja = libro.createSheet(nombreAsignatura);

Se crean las filas:

      HSSFRow fila0 = hoja.createRow(0);
      fila0.setHeightInPoints(19f);

Se crean los estilos:

      Font fuenteBlanca;
      fuenteBlanca = libro.createFont();
      fuenteBlanca.setColor(IndexedColors.WHITE.getIndex());
      //
     CellStyle estiloGris = libro.createCellStyle();
     estiloGris.setAlignment(CellStyle.ALIGN_CENTER);
     estiloGris.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
     estiloGris.setFillPattern(CellStyle.SOLID_FOREGROUND);
     estiloGris.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
     estiloGris.setFont(fuenteBlanca);

Y se crean y se rellenan las celdas con los distintos estilos:

      HSSFCell celdaAsignatura = fila0.createCell(0);
      celdaAsignatura.setCellValue(nombreAsignatura);
      celdaAsignatura.setCellStyle(estiloGrisOscuro);

Finalmente, devolvemos el InputStream:

      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      libro.write(baos);
      baos.close();
      return new ByteArrayInputStream(baos.toByteArray());

Utilización de plantillas.

Apache POI también nos permite utilizar plantillas (vale un xls sin rellenar) con lo que nuestro código se reduce bastante. Un ejemplo completo:

public static InputStream generarControlDeAsistencia(File plantilla, String cursoAcademico, String curso, int                                                                               grupo, List<String> alumnos) throws Exception {       
        FileInputStream fis = new FileInputStream(plantilla);
        HSSFWorkbook libro = new HSSFWorkbook(fis);
        HSSFSheet hoja = libro.getSheetAt(0);
        HSSFRow fila = hoja.getRow(1);
        HSSFCell celda = fila.getCell(5);
        celda.setCellValue(cursoAcademico);
        celda = fila.getCell(8);
        celda.setCellValue(curso);
        celda = fila.getCell(11);
        celda.setCellValue(grupo);
        int totalFilas = Math.min(alumnos.size(), 36);
        for (int i=0; i<totalFilas; i++) {
            fila = hoja.getRow(7 + i);
            celda = fila.getCell(1);
            celda.setCellValue(alumnos.get(i));
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        libro.write(baos);
        baos.close();
        return new ByteArrayInputStream(baos.toByteArray());
}