Geek & Roll » programacion

Cómo no programar una encuesta Web

Cesar May 22nd, 2011 programacion, tips 3 comentarios

Frecuentemente me encuentro con sitios que deciden atraer más visitantes, creando concursos en donde la gente vota por su participante favorito. No hay nada de malo con eso, sólo que son fácilmente abusadas si no se tiene cuidado al programarlos. Al final podemos tener un ganador que no fue la elección popular, sino el más hábil con los bits.

Supongamos que hay una encuesta para votar por la flor más bella del ejido, y tenemos a las siguientes candidatas:
La Flor Mas Bella del Ejido

De izquierda a derecha las numeramos como Flor 1, 2, 3 ,4, 5 y 6.

El programador del sitio se siente muy confiado, ya que guarda una cookie en el navegador para identificar de manera única a los usuarios y evitar que voten repetido. Empieza la votación y por alguna razón la flor #3 va en la última posición, pero esta flor tiene amigos que le saben a eso de las computadoras y les pide algo de ayuda. Lo que hacen los amigos de la flor #3 es lo siguiente (en PHP):

function xrun($email,$name){

//set POST variables
$url = ‘http://www.laflormasbelladelejido.com.mx/votar.php’;
$fields = array(
‘Nombre’=>$name,
‘Email’=>$email,
‘Voto7′=>’VOTO POR LA FLOR #3′
);

$fields_string =”";

//url-ify the data for the POST
foreach($fields as $key=>$value) { $fields_string .= $key.’=’.$value.’&'; }
rtrim($fields_string,’&');

//open connection
$ch = curl_init();

//set the url, number of POST vars, POST data
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POST,count($fields));
curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);

//execute post
$result = curl_exec($ch);

//close connection
curl_close($ch);

}

Lo que hace el script anterior es enviar un HTTP POST a la URL de votación, previa preparación de las variables que espera la forma. Esto es muy fácil saberlo utilizando Firebug por ejemplo, o la consola para desarrolladores de Chrome. Para enviar el POST, están utilizando curl que es la neta del planeta cuando se trata de transferir datos por medio de HTTP, en este caso los datos del voto.

Pero ejecutar el script manualmente para aumentar el número de votos por la flor #3 es muy tedioso, así que los emprendedores amigos de esta pobre flor decidieron llevarlo un paso más alla:

$nombres = file_get_contents(”nombres.txt”);
$correos = file_get_contents(”correos.txt”);
$nom = split(”\n”,$nombres);
$ema = split(”\n”,$correos);
$r = count($nom);

for($x=0;$x<$r;$x++){

if($ema[$x] && $nom[$x]){
xrun( $ema[$x], $nom[$x]);
}

}

Ahora basado en un archivo de nombres y correos, se van a enviar tantos votos como nombres existan en el archivo. Este pedazo de código se puede mejorar muchísimo, por ejemplo sólo enviar tantos votos como pares nombre – correo existan, en vez de basarse en el número de nombres. Otra posibilidad es enviar votos en un ciclo pero restringido en tiempo, es decir simular que ha pasado cierto tiempo entre voto y voto, para que no sea tan obvio que es un programa y no gente real la que está votando.

Y ya con eso la flor más bella del ejido es la #3, como podemos verla en la imagen tan contentota.

¿Y la cookie? Obviamente se la pasa por el arco del triunfo. El problema es que el programador supuso que los votos siempre vendrían del navegador, es decir confió de la entrada del usuario. ¿Y cual es la primera regla de la seguridad programación Web?

  1. Desconfía en la entrada del usuario.

Lo repetiremos de nuevo solo para asegurarnos de que quedó claro:

  1. Desconfía en la entrada del usuario.

Así es, desconfía de la entrada del usuario. Desconfía como desconfías del que te quiere vender un tiempo compartido, o del informe de gobierno.

¿Qué podemos hacer para evitarlo?
Lo más obvio es guardar la IP, pero eso deja por fuera a muchísimas personas que se conectan usando NAT. Me explico: en una casa puede que existan 3 personas que comparten una conexión a Internet. Estas tres personas quieren votar por la flor mas bella del ejido, pero la persona 1 ya se les adelantó y su IP fue guardado en el servidor del sitio por lo que al querer votar la persona 2 ya no puede, le aparece un mensaje que su voto ya fue registrado. La decisión de qué hacer dependerá de que tan flexible y seguro necesitamos que sea la encuesta.

Si se esta votando por la flor mas bella del ejido, probablemente sea suficiente con guardar la IP por 30 minutos o más y validar contra eso. Si se está votando por el presidente de un país, probablemente sea necesario un poco más de seguridad.

Programando para BlackBerry en OS X

Si quieres programar la siguiente killer application en BlackBerry, y eres usuario de OS X, tus opciones no son muchas. RIM ofrece un plugin beta para Eclipse en OS X, con la limitante de que no puedes usar simuladores para probar tu código. La única manera es utilizando un BlackBerry conectado por USB a la mac, y solo para aquellos con OS 6 (9800, 9670 y 9780)

Pero gracias a la virtualización, es posible ejecutar Eclipse y el plugin de BlackBerry para Windows que si tiene soporte para simuladores. En mi caso, no solo necesitaba programar bajo OS X sino también poder cambiarme entre OS X y Windows, dependiendo del lugar donde me encuentre. Para lograr el escenario anterior esto es lo que hice, en resumen:

  1. Instalar Windows virtualizado con VirtualBox en OS X
  2. Compartir la carpeta del workspace entre el Windows virtualizado y el host OS X
  3. Instalar Eclipse y el plugin de BlackBerry en Windows virtualizado
  4. Sincronizar la carpeta compartida con Dropbox
  5. Configurar Eclipse en la otra computadora (física) con Windows para que use el workspace sincronizado con Dropbox

Windows virtualizado
Esta es la parte fácil. VirtualBox es multiplataforma, open source, y muy sencillo de utilizar. Pueden encontrar el manual en línea aquí, pero no es necesario para instalar Windows virtualizado en OS X ya que VirtualBox es muy intuitivo.

Comaprtir la carpeta del workspace
Esto nos va a servir para dos cosas: que Dropbox pueda sincronizar la carpeta, y para compartir archivos entre Windows virtualizado y OS X. En la pantalla principal de VirtualBox damos click en Shared Folders, y configuramos el folder. En mi caso compartí el folder Public de mi /home como se indica en la siguiente imagen.

Shared Folders en VirtualBox

Al iniciar Windows virtualizado, este debe mostrar una unidad de red con el nombre del folder (Public en este caso). De no ser así, hay que asegurarse de que la opción de “Automount” se encuentre seleccionada al crear el folder compartido desde VirtualBox. Cualquier cosa que pongamos en el folder Public desde OS X, va a estar accesible para Windows virtualizado.

Eclipse y el plugin de BlackBerry en Windows virtualizado
Con el folder compartido podemos descargar desde OS X Eclipse (para Windows), el plugin para BlackBerry y tantos simuladores como necesitemos. Todos estos ejecutables los ponemos en el folder Public, vamos a Windows virtualizado y los instalamos como de costumbre en Windows.

Una vez con todo instalado, podemos ejecutar Eclipse y correr un proyecto de prueba solo para asegurarnos que efectivamente nuestra instalación funciona. En este punto podemos activar el modo Seamless de VirtualBox, para no tener todo el escritorio de Windows abierto, solo la ventana de Eclipse. Cuestión de preferencias.

Sincronizar la carpeta compartida con Dropbox
Si trabajáramos solo con OS X, hasta aquí ya podríamos crear y probar programas para BlackBerry. Pero en mi caso necesito poder moverme entre OS X y otra computadora con Windows, y tener el proyecto sincronizado en ambas. Para lograrlo hay que hacer dos cosas:

  1. Mover el workspace de Eclipse virtualizado al folder compartido
  2. Crear un link simbólico en OS X, para que Dropbox sincronice el folder

Vamos por partes, primero cerramos Eclipse, copiamos el folder del workspace de donde este actualmente a la carpeta compartida. Después reabrimos Eclipse y nos va a preguntar que si en donde está el workspace así que le indicamos el nuevo lugar y listo.

Ahora para que Dropbox sincronice ese folder aunque se encuentre fuera del folder de Dropbox, hay que hacer un link simbólico. Un link simbólico es como un atajo, un nombre que apunta hacia otra parte pero sin hacer una copia del contenido. Para eso tenemos que abrir la terminal, que en OS X podemos abrirla buscándo Terminal en spotlight. En la terminal tecleamos lo siguiente:

ln -s /Users/usuario/Public ~/Dropbox/destino/

De la línea anterior, reemplazar usuario por nuestro propio usuario, o la ruta completa si no usaron el folder Public. Lo importante es que la ruta NO DEBE terminar en / de lo contrario no funcionará. La segunda ruta es el folder de Dropbox donde queremos que se sincronice. El caracter ~ significa tu directorio home actual, y para ponerlo en el teclado de una mac es ALT + N.

En el folder de Dropbox debe aparecer el ícono del folder con una flecha negra, indicando que es un link como se muestra en la imagen.
Dropbox

Configurar Eclipse en la otra computadora (física) con Windows
Para esto basta con abrir Eclipse y seleccionar File | Switch Workspace, indicándole la ruta al workspace dentro del folder de Dropbox. En mi caso además de cambiar el workspace, tuve que importar el proyecto desde File | Import.

En este momento, cualquier cambio que hago en la computadora con Windows o OS X se sincroniza y puedo trabajar desde cualquiera sin problemas. Encima de todo esto uso git para llevar control de versiones, lo que funciona de maravilla.

Adiós Everlasting Flame

Cesar November 9th, 2010 FLOSS, aplicaciones, noticias, programacion 26 comentarios

Everlasting Flame icon

Hace aproximadamente 2 años (el 1ro de Diciembre se cumplen) inicié un proyecto, como empiezan la mayoría de estos: para rascar una comezón propia. A lo largo de los meses se fue desarrollando, hasta que se convirtió en algo útil no solamente para mi, sino para muchas otras personas en mi misma situación.

Se creó un sitio para que la gente pudiera descargarlo, probarlo, donar al proyecto pero más importante, contribuír. Como en cualquier proyecto Open Source, las contribuciones pueden ser de muchos tipos, no solamente en código. Con Everlasting Flame, recibí contribuciones muy importantes:

  1. La página fue hecha por mi compañero Geek&Roller Rafyta, además de estar hosteada en sus servidores
  2. Ideas de nueva funcionalidad
  3. Numerosas contribuciones monetarias por medio de paypal y depósitos directos

Pero llegó el momento de decir adiós.

¿Porque?
La razón principal es que ya no tengo el tiempo necesario para continuarlo como hobbie. No hay nadie que tome la batuta, nunca llegó otro programador a querer contribuir con el desarrollo. Un tiempo comencé a darle vueltas a la idea de cobrar por distribuir los binarios, o ponerlo en el App World de paga, pero en ese entonces no se podían comprar aplicaciones en la App World desde México. Finalmente llegó la empresa Innox y me compró el proyecto, asegurando su desarrollo futuro.

¿Qué va a pasar con Everlasting Flame?
Será responsabilidad de Innox continuar con su desarrollo, pero se vuelve closed source. Como parte del acuerdo yo dejo de distribuír los binarios desde mi sitio.

¿Y la tan prometida versión 2.0?
Toda la funcionalidad de esta versión estará presente en la primera que se libere por parte de Innox.

¡Cerdo capitalista!
Hay que comer pues…

Iconos de Eclipse y Netbeans para Docky

Netbeans

Ya que andamos en eso de hacer que Eclipse se vea bien en GNU/Linux, ahora le toca el turno a los iconos tanto de Eclipse como de Netbeans.

Normalmente tengo los dos IDEs instalados, y sus iconos de aplicación se ven pésimo a resoluciones altas como las que maneja Docky. Afortunadamente la solución es simple, reemplazarlos por unos de mayor resolución.

Para Eclipse, hay un excelente icono en formato SVG que sigue las especificaciones del proyecto Tango. Este icono lo encuentran en Gnome-Look.org.

Para Netbeans es más interesante, se puede usar el mismo icono que se usa en OSX. Resulta que cuando instalas Netbeans en OSX el icono se ve muy bien, pero usuarios de otros sistemas operativos nos tenemos que conformar con una versión de menor resolución. Afortunadamente podemos tomar el icono de OSX en formato icns y usar icns2png (disponible en tu distro favorita) para convertirlo a PNG. Ahora que si se quieren ahorrar esos pasos, solo click derecho y guardar como. Gracias este blog por el tip.

Cómo hacer que Eclipse se vea más bonito en Ubuntu

Tradicionalmente Ubuntu no le ha dado la prioridad que debería a eclipse, mi IDE de preferencia a la hora de programar. La diferencia es más notable si, como yo, constantemente te encuentras cambiando entre sistemas operativos.

Eclipse IDE en Windows 7
eclipse_windows

Eclipse IDE en Ubuntu
eclipse_ubuntuhuge

Notese como en Ubunto en el mismo espacio se muestra mucho menos área útil, ya que los botones y la interfaz en general ocupan más espacio del que deberían. El problema real está en GTK y sus limitantes, no en eclipse o SWT (el toolkit gráfico utilizado por eclipse). Para arreglarlo, al lanzar eclipse es posible especificarle a gnome que use un tema diferente, modificado especialmente para que eclipse use mejor el espacio disponible.

Lo primero es asegurarnos que las fuentes liberation-fonts se encuentran instaladas (en mi caso ya estaban instaladas):

sudo aptitude install ttf-liberation

Después descargamos este zip y lo descomprimimos en el directorio raíz donde se encuentra eclipse. El zip contiene dos archivos:

  • gtkrc-sar es el archivo de propiedades para el tema de GTK que vamos a utilizar específicamente con eclipse.
  • ec es un shell script muy sencillo que se encarga de crear la variable de entorno para el tema de eclipse y finalmente lanzarlo

Ahora hacemos el shell script ejecutable:

chmod 755 ec

Y finalmente ejecutamos ec en vez de eclipse:

./ec

Eclipse en Ubuntu después de las modificaciones
eclipse_ubuntubetter

El resultado es un eclipse con un espacios mucho más normales en las tabs y los iconos de la barra de herramientas, aprovechando mejor el espacio y mejorando la experiencia de usar este IDE en Ubuntu.

Si se quiere modificar la entrada del menú para lanzar eclipse, lo cual a su vez tiene el efecto de permitirnos de lanzar eclipse con el nuevo tema desde Gnome Do, o Docky por ejemplo, lo que se tiene hacer es editar el script ec y dejarlo de la siguiente manera:

#!/bin/sh
GTK2_RC_FILES=/home/usuario/eclipse/gtkrc-sar /home/usuario/eclipse/eclipse

Lo que estamos haciendo es utilizar rutas absolutas en vez de relativas. En este caso estoy suponiendo que eclipse se encuentra instalado en /home/usuario/eclipse/ hay que editar el archivo para que concuerde con el sistema de cada quien.

Con esta modificación al script, en el menú principal podemos agregar una entrada para eclipse (si no es que ya tenemos una) poniendo como comando lo siguiente:

sh /home/usuario/eclipse/ec

Ahora sí, tanto Gnome Do como un lanzador de Docky creado a partir del menú principal deberán de iniciar eclipse con el nuevo tema.

Gracias a Sarath Chandra por el post original.

Cargando recursos binarios desde JavaScript

Desde que Google mostró al mundo su port de Quake 2 a JavaScript usando GWT, no me podía sacar una pregunta de la cabeza: ¿Cómo hacen para cargar los recursos (como por ejemplo los modelos de los jugadores, o los niveles) desde el servidor al cliente, para su uso por el motor del juego en JavaScrip? ¿Es posible obtener archivos binarios del servidor, guardarlos en el sistema de archivos local, y ejecutarlos finalmente por una aplicación en JavaScript? La respuesta obviamente era sí, pero no sabía cómo.

Pero una consulta al código fuente del proyecto quake2-gwt aclaró las dudas. El secreto es sobreescribir un tipo MIME, lo que le permite al objeto XMLHttpRequest recibir respuestas binarias en vez de texto, para lo cual es más comúnmente utilizado.

function load_binary_resource(url) {
  var req = new XMLHttpRequest();
  req.open('GET', url, false);
  //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
  req.overrideMimeType('text/plain; charset=x-user-defined');
  req.send(null);
  if (req.status != 200) return '';
  return req.responseText;
}

En GWT necesitamos hacer uso de una funcion JavaScript nativa (JSNI) para lograr lo mismo:

private native void overrideMimeType(XMLHttpRequest req, String mimeType) /*-{
    req.overrideMimeType(mimeType);
}-*/;

Y finalmente crear un objeto XMLHttpRequest, hacer la petición y procesar los datos binarios en el cliente:

XMLHttpRequest req = XMLHttpRequest.create();
overrideMimeType(req, "text/plain; charset=x-user-defined");

req.setOnReadyStateChange(new ReadyStateChangeHandler(){
    //procesar la respuesta binaria
});

req.open("GET", "archivo.binario");
req.send();

Para convertir la respuesta en un InputStream, en este caso utilizando GWT y clases de emulación io y nio que tomé prestadas del proyecto quake2-gwt:

response = xhr.getResponseText();
byte[] responseBytes = new byte[sbb.stringToByteBuffer(response).remaining()];
InputStream inputStream = new ByteArrayInputStream(responseBytes);

En este punto pueden usar el InputStream como lo harían en un programa Java tradicional, por ejemplo si cargaron los bytes de una imagen, pueden aplicarle filtros a-la-photoshop, en tiempo real, en el cliente, sin necesidad de procesamiento en el servidor mas allá de descargar la imagen.

El CPU del Nintendo Entertainment System

Soy un geek de la emulación desde que supe se podía emular el SNES en mi modesta Pentium 2. Desde entonces he utilizado varios emuladores y hasta programado un simulador por mi cuenta (para el microcontrolador 68HC11 de Motorola).

Lo que me lleva a este post: el NES. El NES es interesante ya que es el sistema más emulado que hay, aún así crear un emulador del NES no es tarea fácil. Además las computadoras (y otros gadgets) de hoy tienen lo suficiente para emularlo sin penalización en la velocidad. También, su CPU (MOS 6502 ligeramente modificado) de 8 bits fue el CPU elegido para la mítica Commodore 64, el Atari 2600, Terminator y Bender. Leer el resto de este post.

Cuando Hibernate cascade te juega bromas

Cesar March 23rd, 2010 programacion 7 comentarios

Ah, Hibernate. ¿Que podemos decir? Te atrae con promesas de cerrar la brecha entre lo relacional y el mundo de los objetos, pero luego te deja a que trates de entender por qué demonios no funciona como tu lo esperas. En cierto modo es como C++ que evita que te dispares en el pie, pero cuando lo haces te vuelas la pierna completa.

De lo que quiero hablar en este post es de las operaciones en cascada. Hibernate te permite configurar operaciones en cascada, de tal manera que si así tu lo deseas, cuando creas un objeto que a su vez tiene relación con otro objeto, ambos pueden ser guardados con una sola operación. Por ejemplo:

public class Member(){
    private ExpertiseArea expertiseArea;
}

En este caso, Member tiene una relación uno a uno con ExpertiseArea, y sería deseable que al guardar Member también se guardara ExpertiseArea. Para lograrlo, le decimos a Hibernate cuando debe realizar las operaciones en cascada:

<one-to-one name="expertiseArea"
    class="ExpertiseArea"
    cascade="persist,save-update" />

Con lo anterior le decimos a Hibernate “Cuando ejecutes las operaciones de crear (persist) y de actualizar (save-update) en Member, también hazlo para ExpertiseArea”.

Creamos una prueba para verificar que lo anterior funciona como debería:

	@Test
	public void testMemberRetrieval(){
		Member m = buildMember();
		md.makePersistent(m);
		md.flush();

		Member retrievedMember = md.findById(m.getId(), false);
		assertNotNull("Entity should not be null",retrievedMember);
		Member retrievedMember = md.findById(m.getId(), true);
		assertNotNull("Entity should not be null",retrievedMember);
		assertNotNull("ExpertiseArea should be persistent",
                    retrievedMember.getExpertiseArea());
	}

	private Member buildMember(){
		ExpertiseArea ea = new ExpertiseArea("Singer");
		Member m = new Member("Britney Spears", ea, "Queen of Pop");
		return m;
	}

¿Qué estamos haciendo aquí? Creamos un nuevo Member, le asignamos un ExpertiseArea, y lo guardamos todo en una sola instrucción makePersistent. Como tenemos configuradas las operaciones en cascada, esperamos que se guarden en la base de datos tanto la instancia de Member, como la de ExpertiseArea. Efectivamente, la prueba pasa y podemos revisar en los logs lo que Hibernate hace tras bambalinas:

Hibernate: insert into Member (VERSION, name, tagLine, id) values (?, ?, ?, ?)
Hibernate: insert into ExpertiseArea (VERSION, name, id) values (?, ?, ?)

Pass

No esperaba menos. Leer el resto de este post.

Formas dinámicas con GWT

Cesar March 8th, 2010 javascript, programacion Haz un comentario

Mi relación con GWT es una de amor – odio. Por un lado, tenemos a un toolkit que me permite utilizar mis conocimientos de Java para crear aplicaciones Web dinámicas, aplicando mejores prácticas en la arquitectura del código del cliente, mejorando el tiempo de respuesta gracias al uso de técnicas como agrupar imágenes para usarlas en el cliente con una sola descarga y código segmentado.

Por el otro lado, algunas tareas se vuelven más complicadas. Estoy convencido que las herramientas como GWT son el futuro, más no me queda claro si la actual implementación sea adecuada para toda la programación del lado del cliente. Además la curva de aprendizaje es más inclinada de lo que debería de ser; existen muchos gotchas todavía que hacen que el programador tenga que estar consciente de que esto es Web y no una aplicación regular.

En esta ocasión el requerimiento era el siguiente: tengo una aplicación que hace uso extensivo de formas para captura de datos. Necesito una manera de declarar dichas formas y que la aplicación automáticamente las ordene en diferentes tabs cuando estas crezcan demasiado. Las formas generadas deben poder ser insertadas en cualquier sitio, además de poder aplicarles estilos con CSS.

Como dije anteriormente, lo que me gusta de GWT es que puedo generar la solución a mis requerimientos de la misma manera en la que estoy acostumbrado a hacerlo. En este caso, creo una clase DynaForm que representa mi forma dinámica:

/*Represents the client side DynaForm*/
public class DynaForm {
	private String formSource;
	private ArrayList<String> panelTitles;
	private ArrayList<HTML> panels;
	private Node root;

	public DynaForm(String formSource){
		setFormSource(formSource);
	}

Ahora viene algo interesante. El método setFormSource(). Es aquí donde vamos a parsear el código de la forma para extraer los valores que nos interesan, y descomponerla en sus partes para después volverla a construir pero con las propiedades que nos interesan, en este caso los tabs.

public void setFormSource(String formSource){
	try{
		Document d = XMLParser.parse(formSource);
		root = d.getFirstChild();
		XMLParser.removeWhitespace(root);
		setPanels(root, formSource);
	}catch(Exception e){
		System.out.println(e.getMessage());
		e.printStackTrace();
	}

	this.formSource = formSource;
}

Como vemos, parseamos la fuente y de esa manera nos aseguramos que es código válido. Aquí podríamos aplicar otras reglas, como por ejemplo eliminar cualquier tag o propiedad maliciosa como una llamada a JavaScript. El método setPanels() es el que se encarga de construir los páneles necesarios.

/*
 * This method creates an array of HTML panels. Both panels and panelTitles must
 * contain the same number of elements. In case a <span> element doesn't provide
 * a 'name' attribute, it is replaced by an empty string.
 *
 * The number of HTML panels created is = # of <span> elements. All elements inside <form>
 * without a <span> are ignored.
 * */
private void setPanels(Node root, String formSource){
	panelTitles = new ArrayList<String>();
	panels = new ArrayList<HTML>();
	NamedNodeMap attribs;

	NodeList allElements = root.getChildNodes();

	/*
	 * Start looping all elements, and create new HTML panels as needed
	 * */
	for(int i=0; i<allElements.getLength(); i++){
		Node n = allElements.item(i);
		//if it's a span, create a new empty HTML panel and set its name.
		if(n.getNodeName().equals("span")){
			//Set the panel title entry
			attribs = n.getAttributes();
			panelTitles.add(getAttribValue(attribs, "name"));
			panels.add(new HTML());
		}else{
			try{
				//get the last panel
				HTML panel = panels.get(panels.size()-1);
				//add the node source to the panel HTML
				panel.setHTML(panel.getHTML() + n.toString());
			}catch(Exception e){
				System.out.println(e.getMessage());
			}
		}
	}
}

El método setPanels() toma todos los elementos de la forma y los analiza de la siguiente manera: Si es un elemento span, quiere decir que es el inicio de un nuevo tab. Toma la propiedad name del span y lo convierte en el título de la nueva tab. Si el elemento no es un span, entonces lo agrega a la lista de elementos que forman el tab actual.

Después, en el punto de entrada de la aplicación:

TabPanel tabPanel = new TabPanel();
dynaForm = new DynaForm(formSource);

//All each HTML panel to the tabPanel
for(int i=0; i<dynaForm.getPanels().size(); i++){
	quirksTabPanel.add(dynaForm.getPanels().get(i), dynaForm.getPanelTitles().get(i));
}

RootPanel.get("dynaForm").add(tabPanel);

Utilizando el siguiente código HTML como fuente de la forma:

<form method='POST' action='contactengine.php' name='contact'>
	<span name='Datos' />
	<label for='Nombre'>Nombre:</label>
    	<input type='text' name='Nombre' id='Nombre' />"

    	<label for='Correo'>Correo:</label>
    	<input type='text' name='Correo' id='Correo' />

	<label for='Mensaje'>Mensaje:</label>
	<textarea name='Mensaje' rows='20' cols='20' id='Mensaje'></textarea>

	<span name='Enviar' />
	<input type='submit' name='submit' value='Enviar' class='submit-button' />
</form>

El resultado es:
DynaForm

Claro que se deben usar estilos para hacer que la forma se vea más presentable. También creo que se puede lograr exactamente el mismo resultado utilizando una combinación de jQuery, selectores CSS y jQuery UI.

Controles custom en BlackBerry

Cesar February 12th, 2010 gadgets, programacion Haz un comentario

Crear un control custom para una aplicación de BlackBerry es muy similar a crearlo para una aplicación Java con Swing. La arquitectura del toolkit de Swing es una de las cosas que más me gustan de Java, haciendo muy claro el cómo reemplazar algo por una implementación propia.

En este caso, mi requerimiento era tener una gran “palomita” o checkmark cuando cierta variable era verdadera, y una gran cruz o tache cuando la misma variable era falsa. En otras palabras, un checkbox pero con otra interfaz, más bonita (aunque bonita es una variable que depende del observador, y como eso no lo puedo controlar…)

Como muchas cosas en Java, todo comienza extendiendo una clase, en este caso la clase CheckboxField

public class CustomCheckBox extends CheckboxField

Este checkbox, contrario al checkbox normal, no va a tener un texto asociado, solo la parte gráfica. Así que sobreescribimos el constructor:

public CustomCheckBox(){
	super();
}

public CustomCheckBox(boolean check){
	super("",check);
}

Similar a Swing, hay que sobreescribir el método paint para ahí pintar lo que nosotros queremos. Cuando el checkbox esta check dibujamos la palomita, y cuando esta uncheck, dibujamos la cruz:

public void paint(Graphics g){
	if(getChecked()){
		drawCheck(g);
	}else{
		drawCross(g);
	}
}

Esos dos métodos drawCheck y drawCross son los responsables de pintar el control. El método getChecked heredado de CheckboxField nos dice si el control esta checked o unchecked.

Primero veamos drawCheck

private void drawCheck(Graphics g){
	int left = getBorderLeft();
	int top = getBorderTop();

	int x0 = left, x1 = x0, x2 = x1+20, x3 = x2+50, x4 = x3,
		x5 = x4-50, x6 = x5-20;
	int y0 = top, y1 = y0+30, y2 = y1+15, y3 = y2-45,
		y4 = y3+25, y5 = y4+45, y6 = y5-15;

	g.setColor(0x5B9058);
	g.setBackgroundColor(isFocus() ? 0xEEEEEE : 0xFFFFFF);
	drawBackground(g);
	g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, true);

	int[] xPts = {x1, x2, x3, x4, x5, x6};
	int[] yPts = {y1, y2, y3, y4, y5, y6};
	g.drawFilledPath(xPts, yPts, null, null);

	g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, false);
}

De antemano perdonen ustedes el uso de números mágicos. Lo interesante es el método drawFilledPath, que toma dos arreglos con coordenadas XY y dibuja, como su nombre lo indica, un polígono. Es algo como dibujar con Logo, uno de mis primeros lenguajes de programación muy similar a Lisp. Primero preparamos las coordenadas, después verificamos si nuestro control tiene el foco para dibujarle un fondo distintivo.

El otro método, drawCross, es muy parecido solo que las coordenadas cambian para formar una cruz:

private void drawCross(Graphics g){
	int left = 0;
	int top = 10;

	int x0 = left, x1=x0, x2=x1+25, x3=x2+10, x4=x3+10, x5=x4+25,
		x6=x4+5, x7=x5, x8=x4, x9=x3, x10=x2, x11=x1, x12=x2-5;
	int y0 = top, y1=y0,y2=y1, y3=y2+15, y4=y2, y5=y4, y6=y5+25,
		y7=y6+25, y8=y7, y9=y8-15, y10=y8, y11=y10, y12=y6;

	g.setColor(0xB54E41);
	g.setBackgroundColor(isFocus() ? 0xEEEEEE : 0xFFFFFF);
	drawBackground(g);
	g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, true);

	int[] xPts = {x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12};
	int[] yPts = {y1,y2,y3,y4,y5,y6,y7,y8,y9,y10,y11,y12};
	g.drawFilledPath(xPts, yPts, null, null);

	g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, false);
}

Y finalmente la clase completa

public class CustomCheckBox extends CheckboxField{
	public CustomCheckBox(){
		super();
	}

	public CustomCheckBox(boolean check){
		super("",check);
	}

	public void paint(Graphics g){
		if(getChecked()){
			drawCheck(g);
		}else{
			drawCross(g);
		}
	}

	protected void drawFocus(Graphics g, boolean focus){
		//do nothing
	}

	protected void onFocus(int direction){
		super.onFocus(direction);
		invalidate();
	}

	protected void onUnfocus(){
		super.onUnfocus();
		invalidate();
	}

	protected void layout(int width, int height){
		width = 71;
		height = 71;
		setExtent(width, height);
	}

	private void drawCheck(Graphics g){
		int left = getBorderLeft();
		int top = getBorderTop();

		int x0 = left, x1 = x0, x2 = x1+20, x3 = x2+50, x4 = x3,
			x5 = x4-50, x6 = x5-20;
		int y0 = top, y1 = y0+30, y2 = y1+15, y3 = y2-45,
			y4 = y3+25, y5 = y4+45, y6 = y5-15;

		g.setColor(0x5B9058);
		g.setBackgroundColor(isFocus() ? 0xEEEEEE : 0xFFFFFF);
		drawBackground(g);
		g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, true);

		int[] xPts = {x1, x2, x3, x4, x5, x6};
		int[] yPts = {y1, y2, y3, y4, y5, y6};
		g.drawFilledPath(xPts, yPts, null, null);

		g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, false);
	}

	private void drawCross(Graphics g){
		int left = 0;
		int top = 10;

		int x0 = left, x1=x0, x2=x1+25, x3=x2+10, x4=x3+10, x5=x4+25,
			x6=x4+5, x7=x5, x8=x4, x9=x3, x10=x2, x11=x1, x12=x2-5;
		int y0 = top, y1=y0,y2=y1, y3=y2+15, y4=y2, y5=y4, y6=y5+25,
			y7=y6+25, y8=y7, y9=y8-15, y10=y8, y11=y10, y12=y6;

		g.setColor(0xB54E41);
		g.setBackgroundColor(isFocus() ? 0xEEEEEE : 0xFFFFFF);
		drawBackground(g);
		g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, true);

		int[] xPts = {x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12};
		int[] yPts = {y1,y2,y3,y4,y5,y6,y7,y8,y9,y10,y11,y12};
		g.drawFilledPath(xPts, yPts, null, null);

		g.setDrawingStyle(Graphics.DRAWSTYLE_AAPOLYGONS, false);
	}

	private void drawBackground(Graphics g){
		g.clear();
	}

	public boolean isFocusable(){
		return true;
	}
}

El resultado:
customcheckbox

Look ma! custom controls with no images!

Posts anteriores