jueves, 16 de febrero de 2012

Basic Grid with GXT3


En el siguiente ejemplo uso I am using GWT 2.4, AppEngine 1.7.0



A continuación muestro un pantallazo del ejemplo. Next is the screenshot of the example
El codigo Fuente lo encuentran aqui:The source code is here:  https://github.com/poseidonjm/GAE-GXT3
Demo http://gae-gxt3-basic.appspot.com/
La estructura del proyecto:The project organization:

Introducción:
 A continuación voy a mostrar un ejemplo básico de como hacer un CRUD utilizando GXT3 RequestFactoryEditorDriver teniendo en el servidor a Objectify como medio de persistencia. Al igual que en posts anteriores voy a utilizar AppEngine, pero igualmente podria utilizar una base de datos relacional y JPA para la persistencia.
Introduction:
In this post I show a basic example of how to build a CRUD using GXT3 and RequestFactoryEditorDriver with Objectify and AppEngine, but I could use a relational database and JPA for the ORM. EXT GWT 2 a GXT3
La nueva versión de GXT3 tiene como objetivo hacerlo más compatible con GWT. Por ejemplo utilizar los eventos de GWT, soporte para UIBinder, hacer el binding con el Editor Framework, etc.Más información lo pueden encontrar en la presentación de senchacon2011 http://www.slideshare.net/senchainc/migrating-from-ext-gwt-2x-to-30-10013673
y el video http://vimeo.com/33640855.
Debido a esta actualización varios componentes de gxt2 fueron removidos para ser reemplazados por componentes propios de GWT. Por ejemplo El FormBinding fue removido para se reemplazado por el Editor de GWT. En GXT2 los modelos debían heredar del ModelData para poder se mostrados en los componentes de datos. El objetivo del uso del ModelData era para poder acceder a las propiedades de los objetos ya que GWT no soporta Java Reflection. En la versión de GXT3 los objetos son simples POJOS (Objetos planos) y para acceder a las propiedades de los objetos utiliza una interfaz llamada PropertyAccess<T> . Para lograr esto el equipo de EXT GWT de Sencha tuvo que reescribir los stores desde cero.
Otro cambio importante es el renderizado de componentes. En GWT los componentes se renderizan cuando se llama al constructor del objeto. lo que no ocurría en GXT2. Pero en GXT3 ya es posible acceder al DOM desde el constructor ya que se renderiza al igual que un componente de GWT.
En GXT3 se hizo una cambio en los Layouts http://www.slideshare.net/senchainc/ext-gwt-30-layouts . Por ejemplo ahora existen un ContentPanel y un FramedPanel y el método setFramed fue removido.



EXT GWT 2 to GXT3
The new GXT3 is going to be more compatible with GWT. Will use system events of GWT, UiBinder support, binding with Editor Framework. For More information you can see the slides of senchacon2011 http://www.slideshare.net/senchainc/migrating-from-ext-gwt-2x-to-30-10013673 and on Vimeo http://vimeo.com/33640855.
Because this is the major upgrade of GXT, some GXT2 components was removed in GXT3 and replaced with GWT components. For example: The FormBinding was replaced by The Editor Framework. GXT2 models have to extend ModelData because is a requirement for data components. The use of ModelData is for get access to object properties because GWT does not support Java Reflection. In GXT3 models are simple POJOS and for get access to object properties use PropertyAccess<T> interface. And The EXT GWT team had to build stores from scratch.
The renderized of GXT2 components was called Lazy Rendered and you couldn't access to DOM in constructor. In GXT3 you could access to DOM in constructor and the renderizer is the same as GWT.
In GXT3 Layouts was modified http://www.slideshare.net/senchainc/ext-gwt-30-layouts . Now we have a ContentPanel and FramedPanel and the method setFramed was removed.
Los metodos como setTopComponent y setBottomComponent fueron removidos del ContentPanel
y ahora se utilizara el VerticalLayoutContainer para posicionar los toolbars.
En GXT3 es posible utilizar el ClientBundle de GWT para optimizar la descarga de tus imágenes y estilos. Por ejemplo ahora todas las imágenes de tu sitio estarán en un paquete de tu código fuente ya no en la carpeta pública. En GXT2 era necesario hacer un link en el html al archivo css de gxt2 y copiar la carpeta resources con las imágenes requeridas a la carpeta pública e tu sitio. Pero en GXT3 gracias al uso del ClientBundle todos los archivos de recursos están incluidos en el jar de GXT.
The methods like setTopComponent and setBottomComponent were removed and replaced by VerticalLayoutContainer for toolbars.
In GXT3 you can use ClientBundle  of GWT for images and styles. All images should be in a package. And all resources of GXT3 like images and styles are in jar of GXT.

Adicionalmente ahora GXT3 tiene su propia librería para la generación de gráficos estadísticos http://www.sencha.com/blog/ext-gwt-3-drawing-and-charting/ a diferencia de GXT2 que utilizaba una librería de un tercero basado en flash llamado OpenFlashChart.

Algunos Inconvenientes en GXT3 ?
En GXT2 he utilizado mucho la característica del FormButtonBinding para que el botón guardar de un formulario permaneciera desactivado mientras existan campos inválidos. Pero en GXT3 aún no se si esta característica se mantendra http://www.sencha.com/forum/showthread.php?177964-FormButtonBinding-is-there-a-replacement


Some troubles in GXT3?
I used FormButtonBinding feature a lot, to make buttons disable while the fields are invalid. But in GXT3 I don't know how can I do that  http://www.sencha.com/forum/showthread.php?177964-FormButtonBinding-is-there-a-replacement
ExampleRF por dentro
ExampleRF in detail

La aplicación de ejemplo usa AppEngine y Objectify para el almacenamiento de datos y RequestFactory para la comunicación con el servidor.
The example was build using AppEngine and Objectify for persistence and  RequestFactory for interact with the server side.

Archivo de Módulo GWT ExampleRF.gwt.xml
Module file ExampleRF.gwt.xml

<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='examplerf'>  
  <inherits name='com.google.gwt.user.User'/>
  <inherits name="com.googlecode.objectify.Objectify" />
  <inherits name='com.google.web.bindery.requestfactory.RequestFactory' /> 
  <inherits name='com.sencha.gxt.ui.GXT'/>
  <inherits name="com.sencha.gwt.uibinder.UiBinder" />  
  <entry-point class='com.examplerf.client.ExampleRF'/>  
  <source path='client'/>
  <source path='shared'/>
</module>

Aquí el único mudulo necesario es com.sencha.gxt.ui.GXT adicionalmente necesita de otro módulo com.sencha.gwt.uibinder.UiBinder para poder declarar atributos personalizados en UiBinder según sencha ya no será necesario en GWT 2.5.
The module needed is com.sencha.gxt.ui.GXT and for custom attributes in UiBinder com.sencha.gwt.uibinder.UiBinder is required and not will need in GWT 2.5

Archivo html ExampleRF.html
Html file ExampleRF.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="reset.css">
    <link type="text/css" rel="stylesheet" href="ExampleRF.css">

    <title>Web Application Starter Project</title>    
    <script type="text/javascript" language="javascript" src="examplerf/examplerf.nocache.js"></script>
  </head>
  <body>
    
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
    
  </body>
</html>

Aquí mi archivo ExampleRF.css es un archivo en blanco. Según sencha el único archivo de recursos necesario es reset.css
ExampleRF.cc is an empty file. And the resource file needed is reset.css

/**
 * Ext GWT 3.0.0-SNAPSHOT - Ext for GWT
 * Copyright(c) 2007-2011, Sencha, Inc.
 * licensing@sencha.com
 *
 * http://sencha.com/license
 */
html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}img,body,html{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;}q:before,q:after{content:'';}

Mi Clase DAO ColaboradorDao
My Class DAO ColaboradorDao

package com.examplerf.server.service;

import java.util.List;

import com.examplerf.server.domain.Colaborador;
import com.examplerf.server.domain.ColaboradorListLoadResultBean;

public class ColaboradorDao extends ObjectifyDao<Colaborador> {
 
 public void save(Colaborador colaborador){
  this.put(colaborador);
 }
 
 public void remove(Colaborador colaborador){
  this.delete(colaborador);
 } 

 public ColaboradorListLoadResultBean list() {
  List<Colaborador> list = listAll();  
  return new ColaboradorListLoadResultBean(list);
 }
}
Aquí la clase de dominio es Colaborador y ColaboradorListLoadResulBean es una clase adicional requerida para poderse mostrar en los componentes de datos de GXT3.
The domain class is Colaborador and ColaboradorListLoadResulBean is required for data components.

Colaborador.java
package com.examplerf.server.domain;

import com.googlecode.objectify.annotation.Entity;

@Entity
public class Colaborador extends DatastoreObject {
 private String nombres;
 private String apellidos;
 private Integer edad;
 public String getNombres() {
  return nombres;
 }
 public void setNombres(String nombres) {
  this.nombres = nombres;
 }
 public String getApellidos() {
  return apellidos;
 }
 public void setApellidos(String apellidos) {
  this.apellidos = apellidos;
 }
 public Integer getEdad() {
  return edad;
 }
 public void setEdad(Integer edad) {
  this.edad = edad;
 } 
}
ColaboradorListLoadResulBean.java
package com.examplerf.server.domain;

import java.util.List;

import com.sencha.gxt.data.shared.loader.ListLoadResultBean;

@SuppressWarnings("serial")
public class ColaboradorListLoadResultBean extends ListLoadResultBean<Colaborador> {
    public ColaboradorListLoadResultBean(List<Colaborador> list) {
      super(list);
    }
    public ColaboradorListLoadResultBean() {
        super();
    }
}
Mi clase RequestContext ColaboradorService
My class RequestContext ColaboradorService

package com.examplerf.shared.service;

import java.util.List;

import com.examplerf.server.domain.ColaboradorListLoadResultBean;
import com.examplerf.server.locator.DaoServiceLocator;
import com.examplerf.server.service.ColaboradorDao;
import com.examplerf.shared.proxy.ColaboradorProxy;
import com.google.web.bindery.requestfactory.shared.ProxyFor;
import com.google.web.bindery.requestfactory.shared.Request;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.google.web.bindery.requestfactory.shared.Service;
import com.google.web.bindery.requestfactory.shared.ValueProxy;
import com.sencha.gxt.data.shared.loader.ListLoadResult;

@Service(value = ColaboradorDao.class, locator = DaoServiceLocator.class)
public interface ColaboradorService extends RequestContext {
 
 Request<Void> save(ColaboradorProxy colaborador); 
 Request<Void> remove(ColaboradorProxy colaborador);
  
 @ProxyFor(value = ColaboradorListLoadResultBean.class)
 public interface ColaboradorListLoadResultProxy extends ValueProxy, ListLoadResult<ColaboradorProxy> {
     @Override
     public List<ColaboradorProxy> getData();
 }
 
 Request<ColaboradorListLoadResultProxy> list(); 
}
En GWT 2.4 es necesario validar los RequestContext, cada vez que se modifica este archivo se debe hacer un clean al proyecto Project->Clean . Aquí la unica interfaz especial es ColaboradorListLoadResultProxy y es necesaria para poder llenar el Grid con datos. Nuestro método list() debe retornar objetos de este tipo.
RequestContest in GWT 2.4 have to be validated, every time is modified you have to clean the project Project->Clean . ColaboradorListLoadResultProxy interface is needed for data components.

Mi Proxy ColaboradorProxy
My Proxy ColaboradorProxy

package com.examplerf.shared.proxy;

import com.examplerf.server.domain.Colaborador;
import com.examplerf.server.locator.ObjectifyLocator;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
import com.google.web.bindery.requestfactory.shared.ProxyFor;

@ProxyFor(value = Colaborador.class, locator = ObjectifyLocator.class)
public interface ColaboradorProxy extends EntityProxy {
 String getNombres();
 void setNombres(String nombres);
 String getApellidos();
 void setApellidos(String apellidos);
 Integer getEdad();
 void setEdad(Integer edad); 
 Long getId();
 void setId(Long id);
}
La interfaz ColaboradorProxy.java representa a la clase de dominio Colaborador.java y sirve para hacer la persistencia desde el cliente. Más información aquí http://vivagwt.blogspot.com/2011/09/requestfactory-con-objectify.html .
ColaboradorProxy.java interface is like domain class Colaborador.java but in the client side. More about RequestFactory http://vivagwt.blogspot.com/2011/09/requestfactory-con-objectify.html .


Diseñando mi Formulario con UiBinder: ColaboradorEditor.ui.xml
Designing my Form with UiBinder: ColaboradorEditor.ui.xml

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
 xmlns:g="urn:import:com.google.gwt.user.client.ui"
 xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
 xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form"
 xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
 xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button" >
 
 <ui:with type="com.examplerf.client.images.ExampleRFImages" field="images" />
 
 <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="verticalLayoutData">
     <ui:attributes width="1" height="-1" />
   </ui:with>
      
 <ui:style>
  .important {
   font-weight: bold;
  }
 </ui:style>
 
   <gxt:FramedPanel ui:field="form" headerVisible="false" buttonAlign="END" >
     <form:FieldSet ui:field="fieldSet" headingText="User Information" collapsible="false"  >
       <container:VerticalLayoutContainer>
         <container:child layoutData="{verticalLayoutData}">
           <form:FieldLabel text="First Name">
                <form:widget>
                  <form:TextField ui:field="nombres" allowBlank="false" />
                </form:widget>
              </form:FieldLabel>
         </container:child>
         <container:child layoutData="{verticalLayoutData}">
           <form:FieldLabel text="Last Name">
                <form:widget>
                  <form:TextField ui:field="apellidos" allowBlank="false" />
                </form:widget>
              </form:FieldLabel>              
         </container:child>
         <container:child layoutData="{verticalLayoutData}">
           <form:FieldLabel text="Age">
                <form:widget>
                  <form:NumberField ui:field="edad" />
                </form:widget>
              </form:FieldLabel>
         </container:child>
       </container:VerticalLayoutContainer>
     </form:FieldSet>
     
     <gxt:button>
       <button:TextButton text="Save" ui:field="save" icon="{images.save}" />
     </gxt:button>
     <gxt:button>
       <button:TextButton text="Cancel" ui:field="cancel" icon="{images.cancel}" />
     </gxt:button>
          
   </gxt:FramedPanel>

</ui:UiBinder>
Para Poder usar el FramedPanel es necesario importar el namespace
xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
igualmente se han importato el resto de namespaces.
Para poder utilizar las imágenes en el código UiBinder es necesario referenciar al ClientBundle con la etiqueta <ui:with>
<ui:with type="com.examplerf.client.images.ExampleRFImages" field="images" />
For FramedPanel is needed import the namespace
xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
likewise import the rest of namespaces.
For imáges in ClientBundle and UiBinder is needed put  <ui:with>
<ui:with type="com.examplerf.client.images.ExampleRFImages" field="images" />

Lo siguiente es una forma de declarar los layout data en UiBinder (necesita el jar adicional). También e posible hacerlo por código pero yo prefiero el xml
The following code show a way to declare custom attributes like layout data inn UiBinder (aditional jar is required). You could declare layout data in code but I prefer xml

<ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="verticalLayoutData">
    <ui:attributes width="1" height="-1" />
</ui:with>

El ClientBundle ExampleRFImages.java
The ClientBundle ExampleRFImages.java

package com.examplerf.client.images;

import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;

public interface ExampleRFImages extends ClientBundle {

 public ExampleRFImages IMAGES = GWT.create(ExampleRFImages.class);
 
 @Source("add.gif")
 ImageResource add();
 
 @Source("delete.gif")
 ImageResource delete();
 
 @Source("update.png")
 ImageResource update();
 
 @Source("save.png")
 ImageResource save();
 
 @Source("cancel.png")
 ImageResource cancel();
}
Las imágenes están en el mismo paquete:
The images are in the same package:




La Clase Manejadora ColaboradorEditor.java
Handler Class ColaboradorEditor.java

package com.examplerf.client;

import com.examplerf.client.events.SaveEvent;
import com.examplerf.client.events.SaveEventHandler;
import com.examplerf.shared.proxy.ColaboradorProxy;
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.widget.core.client.FramedPanel;
import com.sencha.gxt.widget.core.client.Window;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.form.NumberField;
import com.sencha.gxt.widget.core.client.form.NumberPropertyEditor;
import com.sencha.gxt.widget.core.client.form.TextField;

public class ColaboradorEditor implements Editor<ColaboradorProxy>, HasHandlers {

 private HandlerManager handlerManager;
 
 private static ColaboradorUiBinder uiBinder = GWT.create(ColaboradorUiBinder.class);

 interface ColaboradorUiBinder extends UiBinder<Widget, ColaboradorEditor> {
 }
 @UiField
 FramedPanel form;
 
 @UiField
 TextField nombres;
 
 @UiField
 TextField apellidos;
 
 @UiField(provided = true)
 NumberField<Integer> edad;
  
 Window panel;
 
 public ColaboradorEditor() {
  handlerManager = new HandlerManager(this);
  panel = new Window();
  panel.setResizable(false); 
  
  edad = new NumberField<Integer>(new NumberPropertyEditor.IntegerPropertyEditor());
  //edad.setAllowDecimals(false);
  
  panel.setWidget(uiBinder.createAndBindUi(this));
  form.getHeader().setVisible(false);  
 }
  
 public void show(String title){
  panel.setHeadingText(title);
  panel.show();
 }  
 
 @UiHandler("save")
 public void onSave(SelectEvent event){  
  fireEvent(new SaveEvent());
 }
 
 @UiHandler("cancel")
 public void onCancel(SelectEvent event){
  hide();
 }

 @Override
 public void fireEvent(GwtEvent<?> event) {
  handlerManager.fireEvent(event);  
 }
 
 public HandlerRegistration addSaveEventHandler(
            SaveEventHandler handler) {
        return handlerManager.addHandler(SaveEvent.TYPE, handler);
    }

 public void clearFields() {
  nombres.clearInvalid();
  apellidos.clearInvalid();  
 }
 
 public void hide() {
  panel.hide();
 }
 
 public boolean isValid() {
  boolean n = nombres.isValid();
  boolean a = apellidos.isValid();
  return n && a;
 }

}
Este Editor esta implementando la interfaz HasHandler para poder enviar eventos personalizados. en este caso el evento SaveEvent. Es importante recordar que si se quiere hacer el binding de un objeto ColaboradorProxy con los campos de texto, estos campos de texto deben existir como variables en el código java. No basta con declarar la caja de texto en UiBinder. Si el TextField nombres sólo se encuentra en el archivo UiBinder y no como variable de clase el GWT Editor no podra hacer el binding.
This Editor is immlementing HasHandler interface for send custom events. The event is SaveEvent. Is important remember that Editor Framework need the fields like class attributes. Is not enough put the fields in UiBinder is needed declare the fields like class attributes.

@UiField
 TextField nombres;
 


Resumen de Eventos en GWT:
Summary of Events in GWT:

Extraido de http://stackoverflow.com/questions/2951621/gwt-custom-events
Los eventos son siempre enviados para informar sobre algo(por ejemplo el cambio de un estado). Los eventos existen en todos los sistemas y frameworks. Para poder enviar y recibir eventos es necesario lo siguiente:
Extracted from http://stackoverflow.com/questions/2951621/gwt-custom-events
Events are always sent to inform about something (e.g. a change of state). This is how events work in general in every system or framework (not only in GWT). In order to send and receive events in such systems you have to define:
  1. Que es lo que es va a enviar (algun objeto). Esto es opcional.
  2. Quién recibe el evento.
  3. Quién envía el evento.
  1. What is sent (some object). It is not required.
  2. Who receives events (event receivers).
  3. Who sends events (event senders).
Evento SaveEvent
Event SaveEvent

Este evento se encargara de ejecutar una acción cuando el usuario pulsa en el botón Save. Las clases de eventos los puse en un paquete client.events
This event will do something when the user to click in button Save. The events class are in client.events package.

La Clase SaveEvent.java
Class SaveEvent.java

package com.examplerf.client.events;

import com.google.gwt.event.shared.GwtEvent;

public class SaveEvent extends GwtEvent<SaveEventHandler> {

 public static Type<SaveEventHandler> TYPE = new Type<SaveEventHandler>();
 
 @Override
 public Type<SaveEventHandler> getAssociatedType() {
  return TYPE;
 }

 @Override
 protected void dispatch(SaveEventHandler handler) {
  handler.onSave(this);
 }

}
Toda clase que representa un evento GWT debe extender GwtEvent esta clase contiene dos metodos abstractos que deben ser implementados getAssociatedType y dispatch.
Every class representing a GWT event has to extend GwtEvent class. This class contains two abstract methods which must be implemented: getAssociatedType and dispatch.

La Interfaz SaveEventHandler.java
Interface SaveEventHandler.java

En GWT el que recibe el evento es llamado Handler. En este ejemplo la interfaz que recibe el evento es SaveEventHandler.
In GWT receivers are called handlers. In the example an event receiver interface will be named SaveEventHandler.

package com.examplerf.client.events;

import com.google.gwt.event.shared.EventHandler;

public interface SaveEventHandler extends EventHandler {

 void onSave(SaveEvent saveEvent);

}
Todo Handler debe extender la interfaz EventHandler. También debe definir el método que será llamado cuando el evento ocurra.
Each handler has to extend EventHandler interface. It should also define a method which will be invoked when an event occurs.

Quien envia el evento ColaboradorEditor.java
Define event senders ColaboradorEditor.java

package com.examplerf.client;

import com.examplerf.client.events.SaveEvent;
import com.examplerf.client.events.SaveEventHandler;
import com.examplerf.shared.proxy.ColaboradorProxy;
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.widget.core.client.FramedPanel;
import com.sencha.gxt.widget.core.client.Window;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.form.NumberField;
import com.sencha.gxt.widget.core.client.form.NumberPropertyEditor;
import com.sencha.gxt.widget.core.client.form.TextField;

public class ColaboradorEditor implements HasHandlers {

 private HandlerManager handlerManager; 
 
 public ColaboradorEditor() {
  handlerManager = new HandlerManager(this);
 }
   
 
 @UiHandler("save")
 public void onSave(SelectEvent event){  
  fireEvent(new SaveEvent());
 } 

 @Override
 public void fireEvent(GwtEvent<?> event) {
  handlerManager.fireEvent(event);  
 }
 
 public HandlerRegistration addSaveEventHandler(
            SaveEventHandler handler) {
        return handlerManager.addHandler(SaveEvent.TYPE, handler);
    }

}
Todo el que envía un evento debe implementar la interfaz HasHandlers. El elemento más importante aquí es HandlerManager como el nombre lo dice gestiona los Event Handler (manejadores de eventos). Todo evento recibidor que quiere recibir eventos debe registrarse asi mismo como interesado. Para eso son los handler managers. Ellos hacen posible registrar los manejadores de eventos (event handlers) para que puedan enviar un evento en particular a todos los manejadores de eventos registrados.
Cuando un HandlerManager es creado toma un argumento en su constructor. Todo un evento tiene una fuente de origen y este parametro sera usado como origen para todos los eventos enviados por este handler manager en el ejemplo es this como el origen de eventos es ColaboradorEditor.
El metodo fireEvent esta definido en la interfaz HasHandler y es responsable de enviar eventos. Como puede ver sólo usa un handler manager para enviar(fire) un evento.
addSaveEventHandler es usado por todos los recibidores de eventos para registrarse asi mismos como interesados en recibir eventos. De nuevo handler manager es usado para esto.
aEvery event sender has to implement HasHandlers interface.

The most important element here is a HandlerManager field. In GWT HandlerManager as the name suggest manages event handlers (event receivers). As it was said at the beginning every event receiver that wants to receive events must register itself as interested. This is what handler managers are for. They make it possible to register event handlers an they can send a particular event to every registered event handler.

When a HanlderManager is created it takes one argument in its constructor. Every event has a source of origin and this parameter will be used as a source for all events send by this handler manager. In the example it is this as the source of events is ColaboradorEditor.

The method fireEvent is defined in HasHandlers interface and is responsible for sending events. As you can see it just uses a handler manager to send (fire) and event.

addSaveEventHandler is used by event receivers to register themselves as interested in receiving events. Again handler manager is used for this.

Vincular el recibidor de eventos con el que envía el evento.
Bind event receivers with event senders.

public class ExampleRF implements EntryPoint{
              private ColaboradorEditor editor;
              public void onModuleLoad() {
  
  editor = new ColaboradorEditor();  

  editor.addSaveEventHandler(new SaveEventHandler() {
   
   @Override
   public void onSave(SaveEvent saveEvent) {
    
   }
  });
}
}
Como parámetro de addSaveEventHandler estoy pasando una clase anónima que es el recibidor de eventos y se registra asi mismo en el enviador de eventos ColaboradorEditor.
When everything is defined event receivers must register themselves in event senders. Now all events sent by   ColaboradorEditor will be received by anonymous class.

Enviar eventos
Send events

Para enviar un evento ColaboradorEditor debe crear una instancia de evento y enviarlo usando el método fireEvent.
To send an event, ColaboradorEditor must create an event instance and send it using fireEvent method

 @UiHandler("save")
 public void onSave(SelectEvent event){  
  fireEvent(new SaveEvent());
 }

El archivo  ExampleRF.ui.xml
File  ExampleRF.ui.xml

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    xmlns:g="urn:import:com.google.gwt.user.client.ui"
    xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
    xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
    xmlns:toolbar="urn:import:com.sencha.gxt.widget.core.client.toolbar"
    xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button"
    xmlns:grid="urn:import:com.sencha.gxt.widget.core.client.grid" >
    
    <ui:with type="com.examplerf.client.images.ExampleRFImages" field="images" />
    
    <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="verticalLayoutData">
     <ui:attributes width="1" height="-1" />
   </ui:with>
   
   <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="centerLayoutData">
     <ui:attributes width="1" height="1" />
   </ui:with>
   
   
    <ui:style>
      .background {
        background-color: white;
      }
    </ui:style>
    <g:VerticalPanel spacing="10">
      <gxt:FramedPanel ui:field="panel" collapsible="true" headingText="RequestFactory Grid Example" pixelSize="550, 300">
        <container:VerticalLayoutContainer borders="true" addStyleNames="{style.background}">
          <container:child layoutData="{verticalLayoutData}">
            <toolbar:ToolBar>
              <button:TextButton text="Add" ui:field="add" icon="{images.add}" />
              <toolbar:SeparatorToolItem />
              <button:TextButton text="Edit" ui:field="edit" icon="{images.update}" enabled="false" />
              <toolbar:SeparatorToolItem />
              <button:TextButton text="Delete" ui:field="delete" icon="{images.delete}" enabled="false" />
              <toolbar:FillToolItem />
            </toolbar:ToolBar>
          </container:child>
          <container:child layoutData="{centerLayoutData}" >
            <grid:Grid ui:field="grid" />                        
          </container:child>          
        </container:VerticalLayoutContainer>
      </gxt:FramedPanel>
    </g:VerticalPanel>
</ui:UiBinder>

El EntryPoint ExampleRF.java
The EntryPoint ExampleRF.java

package com.examplerf.client;

import java.util.ArrayList;
import java.util.List;

import com.examplerf.client.events.SaveEvent;
import com.examplerf.client.events.SaveEventHandler;
import com.examplerf.shared.proxy.ColaboradorProxy;
import com.examplerf.shared.service.ColaboradorService;
import com.examplerf.shared.service.ExampleRFRequestFactory;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.web.bindery.event.shared.SimpleEventBus;
import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.sencha.gxt.core.client.Style.SelectionMode;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.data.shared.loader.ListLoadConfig;
import com.sencha.gxt.data.shared.loader.ListLoadResult;
import com.sencha.gxt.data.shared.loader.ListLoader;
import com.sencha.gxt.data.shared.loader.LoadResultListStoreBinding;
import com.sencha.gxt.data.shared.loader.RequestFactoryProxy;
import com.sencha.gxt.widget.core.client.Dialog;
import com.sencha.gxt.widget.core.client.box.ConfirmMessageBox;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.event.HideEvent;
import com.sencha.gxt.widget.core.client.event.HideEvent.HideHandler;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.info.Info;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent.SelectionChangedHandler;

public class ExampleRF implements EntryPoint, IsWidget {
 interface ColaboradorProxyProperties extends PropertyAccess<ColaboradorProxy> {
     ModelKeyProvider<ColaboradorProxy> id();
     ValueProvider<ColaboradorProxy, String> nombres();
     ValueProvider<ColaboradorProxy, String> apellidos();
     ValueProvider<ColaboradorProxy, Integer> edad();
 }
 
 interface MyUiBinder extends UiBinder<Widget, ExampleRF> {
 }
 
 private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
 
 private final ExampleRFRequestFactory myRF = GWT.create(ExampleRFRequestFactory.class);
  
 interface Driver extends RequestFactoryEditorDriver<ColaboradorProxy, ColaboradorEditor>{  
 }
 Driver driver = GWT.create(Driver.class);
 
 private ColaboradorEditor editor;
 
 private ColaboradorService cs;
 private ColaboradorProxy colaborador;
 private ListStore<ColaboradorProxy> store;
 private ListLoader<ListLoadConfig, ListLoadResult<ColaboradorProxy>> loader; 
 
 @UiField(provided = true)
 Grid<ColaboradorProxy> grid;
 
 @UiField
 TextButton edit;
 
 @UiField
 TextButton delete;
 
 public void onModuleLoad() {
  myRF.initialize(new SimpleEventBus());
  editor = new ColaboradorEditor();  
  driver.initialize(myRF, editor);
  editor.addSaveEventHandler(new SaveEventHandler() {
   
   @Override
   public void onSave(SaveEvent saveEvent) {
    
    RequestContext context = driver.flush();
    if(!driver.hasErrors()&&editor.isValid()){
     editor.hide();
     context.fire(new Receiver<Void>() {
      
      @Override
      public void onSuccess(Void response) {
       Info.display("ExampleRF", "Se guardo correctamente");      
       loader.load();
      }
     });
    }
      
   }
  });  
  
  RootPanel.get().add(this);  
  
 } 

 @Override
 public Widget asWidget() {
  RequestFactoryProxy<ListLoadConfig, ListLoadResult<ColaboradorProxy>> proxy = new RequestFactoryProxy<ListLoadConfig, ListLoadResult<ColaboradorProxy>>() {
   
   @Override
   public void load(ListLoadConfig loadConfig,
     Receiver<? super ListLoadResult<ColaboradorProxy>> receiver) {
    ColaboradorService cs = myRF.colaboradorService();
    cs.list().to(receiver);
    cs.fire();    
   }
  };
  
  loader = new ListLoader<ListLoadConfig, ListLoadResult<ColaboradorProxy>>(proxy);
  ColaboradorProxyProperties props = GWT.create(ColaboradorProxyProperties.class);
  
  store = new ListStore<ColaboradorProxy>(props.id());
  loader.addLoadHandler(new LoadResultListStoreBinding<ListLoadConfig, ColaboradorProxy, ListLoadResult<ColaboradorProxy>>(store));
    
     
     ColumnConfig<ColaboradorProxy, String> nombresColumn = new ColumnConfig<ColaboradorProxy, String>(props.nombres(), 150, "Nombres");
     ColumnConfig<ColaboradorProxy, String> apellidosColumn = new ColumnConfig<ColaboradorProxy, String>(props.apellidos(), 150, "Apellidos");
     ColumnConfig<ColaboradorProxy, Integer> edadColumn = new ColumnConfig<ColaboradorProxy, Integer>(props.edad(), 80, "Edad");
     
     List<ColumnConfig<ColaboradorProxy, ?>> l = new ArrayList<ColumnConfig<ColaboradorProxy, ?>>();
     l.add(nombresColumn);
     l.add(apellidosColumn);
     l.add(edadColumn);
     
     ColumnModel<ColaboradorProxy> cm = new ColumnModel<ColaboradorProxy>(l);
     
     grid = new Grid<ColaboradorProxy>(store, cm) {
         @Override
         protected void onAfterFirstAttach() {
           super.onAfterFirstAttach();
           Scheduler.get().scheduleDeferred(new ScheduledCommand() {
             @Override
             public void execute() {
               loader.load();
             }
           });
         }
     };
     
     grid.setLoader(loader);
     grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);     
     
     grid.getSelectionModel().addSelectionChangedHandler(new SelectionChangedHandler<ColaboradorProxy>() {

   @Override
   public void onSelectionChanged(
     SelectionChangedEvent<ColaboradorProxy> event) {
    int size = event.getSelection().size();
    if(size == 0){     
      edit.setEnabled(false);
      delete.setEnabled(false);
    }else if(size == 1){      
      edit.setEnabled(true);
      delete.setEnabled(true);
    }else if(size > 1){
      edit.setEnabled(false);
      delete.setEnabled(true);
    }       
    
   }
  });

     return uiBinder.createAndBindUi(this);
 }
 
 @UiHandler("add")
 public void onAdd(SelectEvent event){
  
  cs = myRF.colaboradorService();
  colaborador = cs.create(ColaboradorProxy.class);
  cs.save(colaborador);
  driver.edit(colaborador, cs);
  editor.clearFields();
  editor.show("Nuevo Colaborador");
 }
 
 @UiHandler("edit")
 public void onEdit(SelectEvent event){
  cs = myRF.colaboradorService(); 
  
  colaborador = grid.getSelectionModel().getSelectedItem();
  cs.save(colaborador);
  driver.edit(colaborador, cs);
  editor.clearFields();
  editor.show("Editar Colaborador");
 }
 
 @UiHandler("delete")
 public void onDelete(SelectEvent event){
  ConfirmMessageBox box = new ConfirmMessageBox("ExampleRF", "Esta seguro que desea eliminar?");
  box.addHideHandler(new HideHandler() {
   
   @Override
   public void onHide(HideEvent event) {
    Dialog btn = (Dialog) event.getSource();
    if("Yes".equals(btn.getHideButton().getText())){
     cs = myRF.colaboradorService(); 
     colaborador = grid.getSelectionModel().getSelectedItem();
     cs.remove(colaborador).fire(new Receiver<Void>() {
      
      @Override
      public void onSuccess(Void response) {
       Info.display("ExampleRF", "Se elimino correctamente");
       loader.load();
      }
     });
    }
   }
  });
  box.show();  
 } 
 
}
Eso es todo por hoy espero les sea de ayuda.
That's all for now, I hope it helps someone

2 comentarios:

  1. Hi there, your blog helped me a lot kickstarting my first gwt-gxt-requestfactory app. Thank you!

    But I have a problem after extending your code. I'd like to access a nested property in the interface ColaboradorProxyProperties (ExampleRF.java) like that:

    interface ColaboradorProxyProperties extends PropertyAccess {
    ModelKeyProvider id();
    ValueProvider nombres();
    ValueProvider apellidos();

    @Path("address.city")
    ValueProvider address_city();
    }

    But the Requestfactory-Servlet Resultset doesn't contain the nested entity "address".
    I suspect it has something to do with the PagingLoadResultBean (ColaboradorDao.java). Can You confirm this behavior?

    ResponderEliminar
  2. RequestFactory does not brings the object graph by default just primitive types. You should explicitly specify the properties you need using with("address") now in gxt the list is wrapped in other object LoadResultBean then you should use:
    with("data.address") e.g.
    ColaboradorService cs = myRF.colaboradorService();
    cs.list().with("data.address").to(receiver);
    cs.fire();

    ResponderEliminar