3 abr. 2012

MiniSeries - Vaadin Parte I


Vaadin “Thinking in U and I”, o lo que es lo mismo como hacer una interfaz gráfica atractiva y ligera, con programación java pura y disponer de todos los mecanismos necesarios para poder realizar binding de datos sin dolores de cabeza.
Vaadin es otro de estos frameworks que hacen la vida del desarrollador de software mucho más confortable. Se apoya en GWT para la capa de presentación pero no es necesario que tengamos conocimientos profundos de esta tecnología, pero si conviene tener unos conocimientos básicos, para temas de persistencia se puede utilizar cualquier mecanismo JPA, Hibernate, MyBatis, Stored Procedures, … ya que el mecanismo de contenedores estándar y de data binding de la plataforma se basa en Items lo cual hace que sea muy flexible.

Comencemos...

Preparar el entorno de trabajo

Para empezar a trabajar con Vaadin necesitaremos 4 cosas fundamentalmente:
  1. Una instalación de JDK5 o JDK6 de Sun, JRocket o IBM y la variable de entrono $JAVA_HOME establecida. 
  2. Apache Maven 2.0.9 o superior instalado y con el PATH configurado correctamente. 
  3. Eclipse IDE, preferiblemente la versión JEE. 
  4. Vaadin Eclipse plugin, esto no es totalmente necesario, pero para próximas miniguías será muy interesante disponer de el, ademas con la interfaz gráfica WYSIWYG que han incluido hará muy simple la composición de pantallas (una de las grandes pegas desde mi punto de vista de este framework)

Creación del proyecto

Para la creación del proyecto usaremos el arquetipo maven que está disponible en http://vaadin.com/addons
 
Una vez instalado el arquetipo ejecutaremos en una shell:

mvn archetype:generate
-DarchetypeGroupId=com.vaadin
-DarchetypeArtifactId=vaadin-archetype-clean
-DarchetypeVersion=1.3.0
-DgroupId=org.agenda
-DartifactId=agenda.vaadin
 
Esto producirá una salida por consola similar a la siguiente:
 
[INFO] Scanning for projects...
[INFO]
[INFO]----------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO]----------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.0-alpha-3:generate (default-cli) @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.0-alpha-3:generate (default-cli) @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.0-alpha-3:generate (default-cli) @ standalone-pom ---
[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] Generating project in Interactive mode
[WARNING] No archetype repository found. Falling back to central repository (http://repo1.maven.org/maven2).
[WARNING] Use -DarchetypeRepository=<your repository> if archetype's repository is elsewhere.
Define value for version: 1.0-SNAPSHOT: :
Confirm properties configuration:
groupId: org.agenda
artifactId: agenda.vaadin
version: 1.0-SNAPSHOT
package: org.agenda
Y: :
[INFO]----------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO]----------------------------------------------------------------
[INFO] Total time: 28.536s
[INFO] Finished at: Fri Sep 02 07:10:46 CEST 2011
[INFO] Final Memory: 7M/81M
[INFO]----------------------------------------------------------------

Usando Eclipse como IDE

Para empezar a usar eclipse tenemos 2 opciones, la primera es instalar el plugin de maven en eclipse e importar un proyecto maven, y la segunda es ejecutar en una consola:

mvn eclipse:eclipse -DdownloadJavadocs=true -DdownloadSources=true

Ahora se importa el proyecto a eclipse usando el mecanismo de importación estándar, o bien si se tiene instalado un plugin de maven se importa como proyecto maven.

Creación de estructura base para la aplicación

El arquetipo de vaadin ya nos ha creado la estructura básica de la aplicación, con una clase MyVaadinApplication.java que extiende de Application y que tiene una implememtación básca del método init que sirve para iniciar la aplicación y cargar el contenido deseado.

Esta implementación es la siguiente:

@Override
public void init()
{
     window = new Window("My Vaadin Application");
     setMainWindow(window);
     window.addComponent(new Button("Click Me"));
}

Vamos a probar que muestra en un navegador, para ello mvn tomcat:run y después abrimos el navegador e introducimos como dirección http://localhost:8080/agenda.vaadin

El resultado será similar a este:

Creación de la capa de presentación UI

Vamos a crear la estructura básica que tendrá la aplicación. Esta estructura está basada en una tabla para visualizar los contactos y un formulario para poder dar de alta nuevos contactos, editarlos o eliminarlos.
 
El aspecto final de la aplicación será similar a este:
 
Vamos a empezar modificando el método init() de la aplicación La nueva implementación de este método quedaría como sigue:

@Override
public void init() {
     window = new Window("Aplicación de Agenda con Vaadin");
     setMainWindow(window);
     buildContents();
}

Ahora vamos a ver la implementación del método buildContents(), que construye la interfaz general de la aplicación, esta sería la siguiente: 
 
private void buildContents() { 
    centerPaneLayout = new HorizontalLayout(); 
    centerPaneLayout.setSizeFull(); 
    centerPaneLayout.setMargin(true); 
    centerPaneLayout.setSpacing(true); 
    centerPaneLayout.addComponent(buildTableAndForm()); 
    window.setContent(centerPaneLayout); 
}
Una vez que tenemos montada la carcasa principal, vamos a construir la tabla y el formulario, para la creación de la tabla usaremos la clase com.vaadin.ui.Table y para la creación del formulario la clase com.vaadin.ui.Form. El código del método buildTableAndForm() en esta primera iteración sería la siguiente: 
 
private Component buildTableAndForm() { 
    VerticalLayout vLayout = new VerticalLayout(); 
    vLayout.setSpacing(true); 
    vLayout.setSizeFull(); 
    vLayout.addComponent(buildContactsTable()); 
    vLayout.addComponent(buildContactsForm()); 
    return vLayout; 
} 
private Component buildContactsTable() { 
    contactsTable = new Table("Listado de Contactos"); 
    contactsTable.setSelectable(true); 
    contactsTable.setImmediate(true); 
    return contactsTable; 
} 
private Component buildContactsForm() { 
    BeanItem<Contacto> contact = new BeanItem<Contacto>(new Contacto()); 
    contactForm = new Form(); 
    contactForm.setItemDataSource(contact); 
    contactForm.setWriteThrough(false); 
    contactForm.setImmediate(true); 
    // Creación del panel de botones 
    HorizontalLayout footerLayout = new HorizontalLayout(); 
    footerLayout.setSpacing(true); 
    Button btnSave = new Button("Salvar"); 
    btnSave.setData(Buttons.SAVE); 
    btnSave.setStyleName(Reindeer.BUTTON_DEFAULT); 
    footerLayout.addComponent(btnSave); 
    btnDelete = new Button("Eliminar"); 
    btnDelete.setData(Buttons.DELETE); 
    btnDelete.setStyleName(Reindeer.BUTTON_DEFAULT); 
    btnDelete.setEnabled(false); 
    footerLayout.addComponent(btnDelete); 
    Button btnClear = new Button("Cancelar"); 
    btnClear.setStyleName(Reindeer.BUTTON_DEFAULT); 
    btnClear.setData(Buttons.CLEAR); 
    footerLayout.addComponent(btnClear); 
    // Adición del panel de botones al formulario 
    contactForm.setFooter(footerLayout); 
    return contactForm; 
}

Data Binding

Para el data binding la gente de Vaadin ha pensado mucho y muy bien, como podéis ver en el capitulo 9 de su libro (disponible en la web de vaadin).

Para este primer ejemplo vamos a usar un BeanItemContainer, para almacenar nuestros contactos. Crearemos nuestro bean contacto con los siguientes atributos y sus correspondientes getter y setters:
    • Nombre
    • Apellidos
    • Dirección
    • Telefono
    • Movil
    • Email
Nuestro Contacto quedaría de la siguiente forma:

package org.agenda;

public class Contacto {
    private static final String TO_STRING_FORMAT = "%s %s.
                       Direccion:%s. Fijo/Movil:%s/%s. Email: %s";
    String nombre = "";
    String apellidos = "";
    String direccion = "";
    String telefonoFijo = "";
    String telefonoMovil = "";
    String email = "";

    public Contacto() {
        super();
    }

    public Contacto( String nombre, String apellidos,
                               String direccion, String telefonoFijo,
                               String telefonoMovil, String email) {
        super();
        this.nombre = nombre;
        this.apellidos = apellidos;
        this.direccion = direccion;
        this.telefonoFijo = telefonoFijo;
        this.telefonoMovil = telefonoMovil;
        this.email = email;
    }

    /* Getters y Setters */

    @Override
    public String toString() {
        return String.format(TO_STRING_FORMAT, nombre, apellidos,
                                          Direccion, telefonoFijo, telefonoMovil,
                                          email);
    }
}

Ahora creamos el contenedor de contactos, para ello crearemos una nueva clase que extienda com.vaadin.data.util.BeanItemContainer, llamaremos a nuestra clase ContactosContainer, en este primer mini tutorial, no aplicaremos persistencia de datos, simplemente realizaremos DataBinding de datos en componentes estándar de Vaadin.

Nuestra clase ContactosContainer sería de la siguiente forma:

package org.agenda;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;

import com.vaadin.data.util.BeanItemContainer;

public class ContactosContainer extends BeanItemContainer<Contacto> {
    private static final long serialVersionUID = 1L;
    private static final String[] NOMBRES = new String[] {
                "Andrew", "Ann", "Johanna", "Steve", "Mark", "Jane"
    };
    private static final String[] APELLIDOS = new String[] {
                "Martin", "Adams", "Jobs", "Rowling", "Doe", "Knowles"
    };
    private static final String[] DIRECCIONES = new String[] {
                "Market Road", "Wall Street", "Abbey Road",
                "5th Street", "Les Champs de France", "Little Tokio"
    };
    private static final String[] TELEFONOS = new String[] {
                "987654321", "976543218", "965432187",
                "954321876", "943218765", "932187654"
    };
    private static final String[] MOVILES = new String[] {
                "698765432", "619876543", "612987654",
                "612398765", "612349876", "612345987"
    };
    private static final String[] EMAILS = new String[] {
                "email@email.com", "email@email.org", "email@email.net",
                "email@email.info", "email@email.biz", "email@email.co.uk"
    };


    public ContactosContainer(Class<? super Contacto> type)
    throws IllegalArgumentException {
        super(type);
    }

    public ContactosContainer getAllContacts() {
        addAll(createDummyContacts());
        return this;
    }

    private Collection<? extends Contacto> createDummyContacts() {
        List<Contacto> listaContactos = new ArrayList<Contacto>();
        Random random = new Random();
        for (int i =0; i< 10000; i++){
            Contacto contacto = new Contacto(
                    NOMBRES[random.nextInt(5)],
                    APELLIDOS[random.nextInt(5)],
                    DIRECCIONES[random.nextInt(5)],
                    TELEFONOS[random.nextInt(5)],
                    MOVILES[random.nextInt(5)],
                    EMAILS[random.nextInt(5)]);
            listaContactos.add(contacto);
        }
        return listaContactos;
    }
}

He añadido un método para alimentar el container con 100 contactos creados aleatoriamente a partir de los datos de los Arrays estáticos definidos, en siguientes entregas, como he dicho anteriormente, añadiremos persistencia de datos.

Ahora vamos a añadir el container a la tabla para que se muestren los contactos generados aleatoriamente, para esto modificamos el método buildContactsTable, quedando este método así:

            private Component buildContactsTable() {
                contactsTable = new Table("Listado de Contactos");
                ContactosContainer contactsContainer =
                                new ContactosContainer(Contacto.class);
                ContactsContainer = contactsContainer.getAllContacts();
                contactsTable.setContainerDataSource(contactsContainer);
                contactsTable.setVisibleColumns(VISIBLE_COLS );
                contactsTable.setColumnHeaders(COL_NAMES);
                contactsTable.setSelectable(true);
                contactsTable.setImmediate(true);
                return contactsTable;
            }

Como podéis ver en el nuevo código, he añadido 5 líneas nuevas:
  1. ContactosContainer contactsContainer = new ContactosContainer(Contacto.class);
    Con esta línea creamos el contenedor de Contactos
  2. ContactsContainer = contactsContainer.getAllContacts();
    Generamos los contactos aleatoriamente
  3. contactsTable.setContainerDataSource(contactsContainer);
    Se establece el contenedor como origen de datos.
  4. contactsTable.setVisibleColumns(VISIBLE_COLS );
    Se establecen cuales son las columnas que deben estar visibles
  5. contactsTable.setColumnHeaders(COL_NAMES);
    Se establecen cuales son los nombres de las columnas de la tabla

      
    Las constantes VISIBLE_COLS y COL_NAMES serán las siguientes:
          private final static Object[] VISIBLE_COLS = new Object[] {
                    "nombre","apellidos","direccion",
                    "telefonoFijo","telefonoMovil","email"
          };
           private final static String[] COL_NAMES = new String[] {
                    "Nombre","Apellidos","Direccion",
                    "Teléfono Fijo","Teléfono Movil","Email"
          };

Con esto ya tenemos la tabla alimentada, vamos a probar el aspecto que tiene ahora la aplicación, para esto ejecutamos lo siguiente:

mvn clean install tomcat:run

La aplicación tendrá ahora este aspecto:


Ahora que tenemos la aplicación casi terminada, vamos a dar “vida” a la tabla y los botones. Para esto Vaadin ofrece Listeners estándar para eventos sobre los componentes base.

Para añadir un listener a la tabla solamente debemos hacer lo siguiente:

        contactsTable.addListener(new ItemClickEvent.ItemClickListener() {
            public void itemClick(ItemClickEvent event) {
                // Código para gestionar el click en una fila
            }
        });

Vamos a alimentar el formulario con la información del Item seleccionado en la tabla, el código para gestionar este evento será como sigue:

        contactsTable.addListener(new ItemClickEvent.ItemClickListener() {
            public void itemClick(ItemClickEvent event) {
                // Código para gestionar el click en una fila
                contactForm.setItemDataSource( event.getItem(),
                                               Arrays.asList(VISIBLE_COLS) );
                contactForm.setCaption("Edición de Contacto");
                btnDelete.setEnabled(true);
            }
        });

A partir de este momento, al hacer click sobre una fila el formulario se alimenta con los datos del Item de la tabla seleccionado, se cambia el caption del formulario a Edición y se habilita el botón de eliminar.

El resultado será el siguiente:


Ahora vamos a dar funcionalidad a los botones, para ello existe un Listener en la clase Button que nos facilita bastante la vida, para no crear demasiadas clases anónimas vamos a hacer que nuestra aplicación implemente este Listener, para ello cambiamos la definición de nuestra clase Agenda para que tenga el siguiente aspecto:

    public class Agenda extends Application implements ClickListener {

Esto nos obliga a implementar un método, que quedará de la siguiente forma:

        public void buttonClick(ClickEvent event) {
            Buttons btnData = (Buttons)event.getButton().getData();
            switch (btnData) {
                case SAVE:
                    commit();
                break;
                case CLEAR:
                    discard();
                break;
                case DELETE:
                    delete();
                break;
            }
        }
 
Los métodos commit(), discard() y delete() serán de la siguiente forma:

        protected void commit() {
            contactForm.commit();
            Contacto contact = ((BeanItem<Contacto>)
            contactForm.getItemDataSource()).getBean();
            if (!contactsTable.getContainerPropertyIds().contains(contact)) {
                contactsTable.addItem(contact);
                contactsTable.select(null);
                message(String.format("Contacto %s añadido correctamente.",
                                       contact));
                contactForm.setItemDataSource(new BeanItem<Contacto>(
                        new Contacto()), Arrays.asList(VISIBLE_COLS));
            } else {
                message(String.format(
                    "El contacto %s ya está entre los contactos almacenados.",
                    contact.getNombre()));
            }
        }
        protected void discard() {
            contactForm.discard();
            BeanItem<Contacto> contact = new BeanItem<Contacto>( new Contacto());
            contactForm.setItemDataSource(contact, 
                                          Arrays.asList(VISIBLE_COLS));
            btnDelete.setEnabled(false);
            contactsTable.select(null);
            contactForm.setCaption("Crear Nuevo Contacto");
        } 
        protected void delete() {
            Contacto contact = ((BeanItem<Contacto>)
                    contactForm.getItemDataSource()).getBean();
            if (contactsTable.removeItem(contact)) {
                message(String.format("Contacto %s eliminado correctamente",
                                       contact));
                contactForm.setItemDataSource(new BeanItem<Contacto>(
                        new Contacto()), Arrays.asList(VISIBLE_COLS));
                btnDelete.setEnabled(false);
                contactsTable.select(null);
                contactForm.setCaption("Crear Nuevo Contacto");
            }
        }

El método message(String) lo usaremos para mostrar una notificación al usuario de la acción realizada:

        private void message(String msg) {
            getMainWindow().showNotification(msg);
        }

Con esto ya tenemos finalizado el primer mini tutorial de Vaadin, ahora solo queda...

Probar la aplicación

Para probar la aplicación ejecutamos la siguiente orden en una shell

mvn clean install tomcat:run

Abrimos navegador y ponemos la siguiente dirección:

http://localhost:8080/agenda.vaadin

Espero que os sirva de ayuda para empezar a usar este framework, que hace bastante simple la realización de aplicaciones con una presentación muy buena.

Un saludo y a disfrutar!!!

No hay comentarios: