lunes, 29 de octubre de 2012

Aplicando Estilos - Parte I


Ubicación de los archivos de estilos (CSS)


En el artículo anterior, Composite Widgets, Composite Widgets, se describió paso a paso la construcción de un widget propio utilizando los widgets provistos por GWT. El resultado fue un widget básico en el que no se utilizaron estilos propios para darle otra apariencia al widget sino que se utilizó uno estándar proporcionado por GWT. Generalmente un proyecto GWT utiliza un archivo estándar de estilos definido en el descriptor del proyecto (proyecto.gwt.xml). Este archivo de estilos no es más que un archivo CSS (Cascading Style Sheets) de los cuatro temas estándar que GWT 2.4 provee: Clean, Standard, Chrome y Dark.

Ya es hora de darle un estilo o una apariencia propia a su proyecto para que adquiera una personalidad bien definida dentro del mundo de las aplicaciones web.

Para entender un poco mejor como GWT selecciona el estilo para su proyecto, entre a Eclipse, abra el proyecto Login que desarrollamos en Composite Widgets y abra el archivo Login.gwt.xml dentro del paquete com.example.login:

<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='login'>
  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>

  <!-- Inherit the default GWT style sheet.  You can change       -->
  <!-- the theme of your GWT application by uncommenting          -->
  <!-- any one of the following lines.                            -->
  <inherits name='com.google.gwt.user.theme.clean.Clean'/>
  <!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/>     -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>                -->

  <!-- Other module inherits                                               -->

  <!-- Specify the app entry point class.                            -->
  <entry-point class='com.example.login.client.Login'/>

  <!-- Specify the paths for translatable code                    -->
  <source path='client'/>
  <source path='shared'/>

</module>

Preste especial atención a la sección Inherit the default GWT style sheet:

  <inherits name='com.google.gwt.user.theme.clean.Clean'/>
  <!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/>     -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>                -->

Este es un archivo XML y por lo tanto sigue las especificaciones para crear archivos de este tipo. Recuerde que dentro de un archivo XML los comentarios se definen entre los símbolos <!— y -->.

Como podrá observar, el único tema de estilos habilitado para este proyecto es el definido como com.google.gwt.user.theme.clean.Clean. Los demás temas están encerrados entre los símbolos de definición de comentarios, es decir, están deshabilitados.

Revise la definición de este archivo:

Desde Eclipse y dentro del proyecto CompositeLogin, abra la carpeta war, luego la carpeta login y por último la carpeta clean. Allí encontrará el archivo clean.css. Abra este archivo y observe las definiciones de estilos estándar que GWT ofrece.

No es necesario que sea un experto en CSS, sin embargo, algunas definiciones requerirán de algún análisis de su parte.

Es importante que no haga modificaciones sobre estos archivos estándar de GWT, aunque no hay ninguna restricción, la mejor alternativa, si es que ninguno de los otros temas es de su preferencia, es crear su propio archivo de estilos CSS e incluirlo en el proyecto. Recuerde que en el momento de crear el proyecto, GWT genera un archivo básico CSS, cuyo nombre es dado por el nombre del proyecto y la extensión css. Este archivo puede ser editado y cambiado de acuerdo con sus preferencias de estilos. Por ejemplo, en el proyecto CompositeLogin, GWT creó el archivo Login.css.

La siguiente figura muestra la ubicación de los archivos de estilos dentro de su proyecto:


Ahora se estará preguntando, ¿Qué pasa si deseo incluir un nuevo archivo de estilos? o ¿Cómo se remplaza un archivo CSS existente? Estas preguntas pueden surgir porque usted ya tiene definidos sus propios estilos utilizados en proyectos anteriores o que consiguió a través de terceros y por lo tanto no desea invertir tiempo en la composición de estilos nuevos.

Bien, el archivo Login.css es asociado a su proyecto en la página host HTML, en este caso, Login.html.

Abra la página host Login.html:

<!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>

Observe la línea resaltada. Incluya la definición de los otros archivos de estilos que tenga a disposición para que sean utilizados en el proyecto. Salve el archivo y no olvide copiar los archivos de sus propios estilos dentro de la estructura de carpetas o directorios del proyecto.

Tip: Si incluye un nuevo archivo de estilos en su proyecto y los aplica a los widgets de las interfaces en el código fuente Java, es conveniente refrescar la página web en ejecución así haya reiniciado la ejecución del proyecto. De esta manera se asegura que la página HTML y el descriptor GWT están utilizando el nuevo archivo de estilos.


Probando Otros Estilos Estándar de GWT

Si el tema estándar de estilos Clean no es de su preferencia, pase a probar los otros temas estándar de GWT. Por ejemplo, pruebe la aplicación utilizando el tema Standard. Abra el archivo descriptor Login.gwt.xml y cambie los símbolos de comentarios de la siguiente manera:

<!--   <inherits name='com.google.gwt.user.theme.clean.Clean'/> -->
<inherits name='com.google.gwt.user.theme.standard.Standard'/>

De este modo, Clean se ha deshabilitado para dar paso al tema Standard. Salve al archivo. Pruebe de nuevo la aplicación y observe las diferencias.

Utilizando el tema Clean:



Utilizando el tema Standard:



Si ya hizo el cambio y aun sigue viendo la interface de usuario con el mismo tema Clean, entonces quiere decir que no leyó el tip de este artículo.

Si usted es curioso, probará los restantes temas estándar Chrome y Dark ofrecidos por GWT.

En el siguiente artículo veremos cómo crear estilos propios y como aplicarlos a los widgets de nuestros proyectos. 

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