miércoles, 30 de mayo de 2012

Composite Widgets


En la publicación anterior Panels y Widgets en GWT vimos que GWT provee una gran variedad de widgets que permiten diseñar una interface de usuario de acuerdo con especificaciones propias de la aplicación. Sin embargo, a medida que vaya utilizando nuevas técnicas de desarrollo con GWT, encontrará la necesidad de manejar un grupo de widgets como si fuera uno solo. Es aquí cuando comenzará a construir sus propios widgets basados en otros existentes. Estos nuevos widgets se denominan composite widgets.

Composite widgets no son más que una colección de widgets, ya sea provistos dentro del conjunto de paquetes de GWT o que hayan sido creados por otros usuarios o compañías expertas en este campo. Normalmente un composite widget agrupa otros widgets pero se comporta como si fuera una sola entidad que ofrece una funcionalidad específica. Por ejemplo, un composite widget muy común corresponde a una interface de login, la cual requiere como entrada un usuario y una contraseña para ingresar a un sistema en particular. Además sus componentes tienen la capacidad de disparar eventos de acuerdo a las acciones hechas sobre ellos y aprueba o desaprueba la transición a otro estado de la página o a otra página de la aplicación. Esta interface de usuario se construye utilizando widgets tales como Labels, TextBoxes, Buttons, Images y Panels que facilitarán la distribución de los widgets. La interface también podrá realizar algunos efectos como desplegar un mensaje de error cuando el usuario ha entrado un usuario o contraseña inválidos, es decir, aquí habrá implementado un efecto sobre la interface.

GWT ofrece la facilidad de construir componentes de usuario de tal suerte que esto ha permitido que otras compañías construyan librerías completas que se pueden integrar a sus proyectos. De manera que anímese para crear las suyas y así obtener un ingreso adicional.

Estos nuevos componentes se construyen de tres maneras diferentes:

·         Creando un widget conteniendo widgets existentes. Este es un composite widget.
·         Creando un nuevo widget escrito completamente en Java.
·         Creando un widget que contiene JavaScript  utilizando métodos de JSNI (JavaScript Native Interface).

En este artículo solamente cubriremos la primera forma para crear widgets: composite widgets.


Construyendo un Composite widget

La construcción de un composite requiere que la nueva clase extienda la clase Composite, la cual es tiene la capacidad de contener otros widgets escondiendo los métodos del widget contenido.

Típicamente un composite se conforma de un panel que a su vez contiene otros panels y widgets. Algunos de los widgtes proporcionados por GWT son en realidad composites. Por ejemplo, TabPanel es un composite compuesto de un TabBar y un DeckPanel.

El siguiente ejemplo que he denominado LoginComposite, es una aplicación que ilustra paso a paso el procedimiento para crear un composite. Construiremos una interface de login para acceder a alguna aplicación. El contenedor principal será un DialogBox, que a su vez contendrá un VerticalPanel que  permitirá ubicar los demás widgets en una sola columna. Los botones los pondremos dentro de un HorizontalPanel para ubicarlos uno al lado del otro en la misma línea. El HorizontalPanel también será parte del VerticalPanel. Es recomendable que diseñe primero su aplicación en papel o utilizando la tableta de su preferencia, esto facilitará la labor de codificación y visualizará con anticipación los panesl y widgets que se ajustarán a su diseño. Con estas indicaciones pasemos entonces a crear el proyecto.


Cree un nuevo proyecto GWT

Desde el menú principal de Eclipse seleccione:

File à New à Web Application Project



Deje la opción Sample Code activa para generar la estructura de un proyecto cliente/ servidor, aunque tan sólo utilizará la aplicación del cliente, facilitará la tarea de crear las clases y la página html desde el principio. También tendrá que cambiar parte del código generado por el wizard de GWT con su propio código.

En el navegador de Eclipse observe la estructura del proyecto:



Los archivos resaltados son los principales archivos que se utilizarán para crear la interface. Tan solo queda pendiente crear la clase de usuario que extenderá la clase Composite.


Cree una clase Entry Point

Abra la aplicación Login.java, borre el código original y copie el siguiente:

package com.example.compositelogin.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.HTML;

public class Login implements EntryPoint {

            @Override
            public void onModuleLoad() {
                        // TODO Auto-generated method stub
                        RootPanel.get().add(new LoginComposite());                 
            }
}

Esta clase implementa la interface EntryPoint y será la clase a invocarse desde la siguiente línea de la página Login.html:

<script type="text/javascript" language="javascript" src="login/login.nocache.js"></script> 

La clase Login.java declara RootPanel como el contenedor principal de la aplicación. A través del método get(), RootPanel consigue donde insertar la interface dentro de la página HTML. Si la página HTML define una etiqueta div con un identificador, el método get(“id”) de Rootpanel accede a esa sección de la página y posiciona allí la interface construida en LoginComposite. La siguiente línea de código se encarga de hacer todo este trabajo:

                        RootPanel.get().add(new LoginComposite());                 

A continuación creará la clase que implementará la interface. Seleccione el paquete com.example.login.client, click derecho y seleccione New à Class.

Entre el nombre LoginComposite:



Click en Finish.

Abra el archivo LoginComposite.java e inserte la siguiente línea de código después de la sentencia de definición del paquete:

import com.google.gwt.user.client.ui.Composite;

Esta clase se utilizará en la declaración de la clase LoginComposite, tal y como se muestra en la siguiente línea de código.

public class LoginComposite extends Composite {

Ahora incluya las sentencias para instanciar los panels y widgets que se utilizarán en la interface. Después de la línea de identificación de la clase, defina las siguientes variables:

            private DialogBox dbLogin = new DialogBox();
            private VerticalPanel vpContainer = new VerticalPanel();
            private Label lblError = new Label("Usuario o Contrase\u00F1a inv\u00E1lidos");
            private HorizontalPanel hpError = new HorizontalPanel();
            private Button btnEntrar = new Button("Entrar");
            private Button btnCancel = new Button("Cancelar");
            private Label lblUsr = new Label("Usuario:");
            private Label lblPasswd = new Label("Contrase" + "\u00F1" + "a:");
            private TextBox txtUsr = new TextBox();
            private PasswordTextBox txtPasswd = new PasswordTextBox();
            private HorizontalPanel hpButtons = new HorizontalPanel();

El editor de Eclipse notará que los tipos de las variables declaradas no han sido importados. Para solucionar este inconveniente, importe las siguientes clases a la clase LoginComposite:

import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.Window;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ClickEvent;

La clase DialogBox, la cual es un derivado de la clase Panel, definirá el marco de referencia de la interface de usuario. Este panel tiene la propiedad setText que permite definir un título a la interface. Agregue el título “Entrada al Sistema” o sustitúyalo por otro de su preferencia:

                        dbLogin.setText("Entrada al Sistema");

Es hora de indicarle a la clase Composite, cuál es el widget que contendrá la totalidad de la interface:

                        initWidget(dbLogin);

initWidget es un método de la clase Composite que permite al panel dbLogin ser envuelto (wrapped) por esta clase. Debe incluirse en el constructor de la clase y antes de invocar otro widget o panel. Utilice initWidget solamente una vez dentro de la clase.

dbLogin será el contenedor del panel vertical vpContainer en donde se ubicarán los widgets y panels que conformarán la interface.

El label lblError se ha declarado previamente con el mensaje “Usuario o contraseña inválidos”, se utilizará como primer elemento del contenedor vertical vpContainer. Asigne el valor false a la propiedad setVisible del label para que la interface no lo despliegue cuando se cargue por primera vez en el navegador de internet:

                        vpContainer.setSpacing(4);
                        vpContainer.add(lblError);
                        lblError.setVisible(false);

Ahora incluya los labels, cajas de texto y los botones en el panel vertical vpContainer:

                        vpContainer.add(lblUsr);
                        vpContainer.add(txtUsr);
                        vpContainer.add(lblPasswd);
                        vpContainer.add(txtPasswd);
                        hpButtons.setSpacing(12);
                        hpButtons.add(btnEntrar);
                        hpButtons.add(btnCancel);
                        vpContainer.add(hpButtons);

Se ha creado el panel horizontal hpButtons como contenedor de los botones btnEntrar y btnCancel, así quedarán posicionados horizontalmente en la misma línea o fila. El panel hpButtons se alineará verticalmente dentro del panel vpContainer.

Los botones responden a los eventos que actúan sobre ellos. Inserte el código que implementa los manejadores de eventos. La clase Button hereda de la clase FocusWidget el método addClickHandler el cual procesa los eventos sobre los botones. En principio, incluya el código en el botón btnEntrar:

                        //Manejadores de click
                        btnEntrar.addClickHandler(new ClickHandler() {
                                    @Override
                                    public void onClick(ClickEvent event) {
                                                onbtnEntrarClick();
                                    }
                        });

El anterior  fragmento de código hará que se ejecute el método onbtnEntrarClick() cuando el usuario de un click sobre el botón btnEntrar.

Como ejercicio cree el código para el botón btnCancel.

Por último, adicione el código para el método onbtnEntrarClick(). En él se analizará si la combinación usuario/contraseña es válida.

Por ahora el usuario y la contraseña serán valores estáticos dentro de la aplicación, aunque no es la mejor forma de autenticación, el ejemplo ilustra cómo implementar el código en respuesta a los eventos. En este caso, el usuario admin y la contraseña admin serán los valores para una correcta autenticación. Si la autenticación es válida, la interface desplegará una ventana de bienvenida, en caso contrario hará visible el label lblError:

            void  onbtnEntrarClick() {
                        if (txtUsr.getText().equalsIgnoreCase("admin") && txtPasswd.getText().equalsIgnoreCase("admin")) {
                                    lblError.setVisible(false);
                                    Window.alert("Hola, Bienvenido a mi aplicaci\u00F3n!");
                        } else {
                                    lblError.setVisible(true);
                        }
            }

Una vez haya terminado los anteriores pasos, el código completo de la clase LoginComposite se verá así:

package com.gwt.tests.client;

import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.Window;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ClickEvent;

public class LoginComposite extends Composite {
            private DialogBox dbLogin = new DialogBox();
            private VerticalPanel vpContainer = new VerticalPanel();
            private Label lblError = new Label("Usuario o Contrase\u00F1a inv\u00E1lidos");
            private Button btnEntrar = new Button("Entrar");
            private Button btnCancel = new Button("Cancelar");
            private Label lblUsr = new Label("Usuario:");
            private Label lblPasswd = new Label("Contrase" + "\u00F1" + "a:");
            private TextBox txtUsr = new TextBox();
            private PasswordTextBox txtPasswd = new PasswordTextBox();
            private HorizontalPanel hpButtons = new HorizontalPanel();
           
            public LoginComposite() {
                        dbLogin.setText("Entrada al Sistema");
                        initWidget(dbLogin);

                        vpContainer.setSpacing(4);
                        vpContainer.add(lblError);
                        lblError.setVisible(false);
                       
                        vpContainer.add(lblUsr);
                        vpContainer.add(txtUsr);
                        vpContainer.add(lblPasswd);
                        vpContainer.add(txtPasswd);

                        hpButtons.setSpacing(12);
                        hpButtons.add(btnEntrar);
                        hpButtons.add(btnCancel);
                        vpContainer.add(hpButtons);
                       
                        //Manejadores de click
                        btnEntrar.addClickHandler(new ClickHandler() {
                                    @Override
                                    public void onClick(ClickEvent event) {
                                                onbtnEntrarClick();
                                    }
                        });
                       
                        dbLogin.setWidget(vpContainer);
            }
           
            void onbtnEntrarClick() {
                        if (txtUsr.getText().equalsIgnoreCase("admin") && txtPasswd.getText().equalsIgnoreCase("admin")) {
                                    lblError.setVisible(false);
                                    Window.alert("Hola, Bienvenido a mi aplicaci\u00F3n!");
                        } else {
                                    lblError.setVisible(true);
                        }
            }
}


Modifique la página HTML

El proyecto se creó activando la opción que genera código de ejemplo. Por esta razón, el código de la clase Entry Point se remplazó por uno propio. Del mismo modo la página Login.html debe ser modificada.

En primer lugar remplace el título:

    <title>Web Application Starter Project</title>

Por:

    <title>Login Composite</title>

Borre la línea:

    <h1>Web Application Starter Project</h1>

Y borre también las etiquetas que crean la siguiente tabla:

    <table align="center">
      <tr>
        <td colspan="2" style="font-weight:bold;">Please enter your name:</td>       
      </tr>
      <tr>
        <td id="nameFieldContainer"></td>
        <td id="sendButtonContainer"></td>
      </tr>
      <tr>
        <td colspan="2" style="color:red;" id="errorLabelContainer"></td>
      </tr>
    </table>

Al final, el código completo de la página Login.html lucirá así:

<!doctype html>
<!-- The DOCTYPE declaration above will set the     -->
<!-- browser's rendering engine into                -->
<!-- "Standards Mode". Replacing this declaration   -->
<!-- with a "Quirks Mode" doctype is not supported. -->

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    <!--                                                               -->
    <!-- Consider inlining CSS to reduce the number of requested files -->
    <!--                                                               -->
    <link type="text/css" rel="stylesheet" href="Login.css">

    <!--                                           -->
    <!-- Any title is fine                         -->
    <!--                                           -->
    <title>Login Composite</title>
   
    <!--                                           -->
    <!-- This script loads your compiled module.   -->
    <!-- If you add any GWT meta tags, they must   -->
    <!-- be added before this line.                -->
    <!--                                           -->
    <script type="text/javascript" language="javascript" src="login/login.nocache.js"></script>
  </head>

  <!--                                           -->
  <!-- The body can have arbitrary html, or      -->
  <!-- you can leave the body empty if you want  -->
  <!-- to create a completely dynamic UI.        -->
  <!--                                           -->
  <body>

    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
   
    <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>

  </body>
</html>

Es tiempo de probar la aplicación.


Pruebe la aplicación

Abra la clase Login.java y desde el menú principal seleccione:

Run à Run As à Web Application

Copie el URL que aparece en el tab Development Mode:


Abra una página en el navegador de internet de su preferencia y pegue el URL. Presione enter. Si no hay errores de codificación en la aplicación la siguiente página se desplegará en el navegador de Internet:


En el campo usuario entre el nombre cathy y como contraseña la palabra cathy123. Click en el botón Entrar. El label lblError se hará visible en la parte superior de la interface mostrando el mensaje de error:


Ahora pruebe de nuevo la aplicación entrando los valores admin, admin en cada campo. La respuesta será la ventana de bienvenida a la aplicación:


En una aplicación ya terminada, en lugar de la ventana de bienvenida, tendría que aparecer la página principal de su aplicación.

Como podrá observar, la interface ha tomado los estilos definidos por defecto en el archivo descriptor Login.gwt.xml. Pero con seguridad usted querrá crear y aplicar sus propios estilos a la interface para darle un toque más personalizado. Por ejemplo, El título de de la interface podría ser más grande, con otro color. Por otra parte, el mensaje de error podría aparecer enmarcado, con letras de color rojo y con un fondo amarillo.

En la siguiente publicación se describirá el procedimiento para aplicar estilos a los widgets, panels y composites utilizados en su aplicación.
Autor de El Efecto Margarita