La validación de los datos de entrada es indispensable en cualquier aplicación. En las aplicaciones web tenemos que validar del lado del servidor y, es recomendable, validar del lado del cliente.

A continuación veremos como podemos validar del lado del cliente los campos de fecha de un formulario en SmartGWT.

En nuestro caso, tenemos un formulario sencillo donde se capturan dos fechas. Dado que estas fechas nos representan un rango, la fecha de fin siempre debe ser posterior a la fecha de inicio. En este caso se debe validar cuando cambie cualquiera de los dos campos. En SmartGWT lo podríamos realizar de la siguiente manera:

DynamicForm frmAudiencia = new DynamicForm();
DateTimeItem itmInicio = new DateTimeItem("inicio", "Inicio");
itmInicio.setType("datetime");
itmInicio.setShowPickerTimeItem(true);
itmInicio.setValidateOnChange(true);
DateTimeItem itmFin = new DateTimeItem("fin", "Fin");
itmFin.setType("datetime");
itmFin.setShowPickerTimeItem(true);
itmFin.setValidators(new CustomValidator() {
    {
        setDependentFields(new String[]{"inicio"});
    }
    @Override
    protected boolean condition(Object value) {
        return getRecord().getAttributeAsDate("inicio").before((Date) value);
    }
});
itmFin.setValidateOnChange(true);
frmAudiencia.setItems(itmNuc, itmTipoAudiencia, itmInicio, itmFin, itmJuez, itmSala);

De esta forma, cuando se modifique cualquiera de las dos fechas, se ejecuta el proceso de validación.

Fuentes:
http://smartclientexperience.wordpress.com/2011/10/19/introduction-to-smartclient-8-1smartgwt-2-5-validation-part-3/
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/widgets/form/validator/CustomValidator.html

Anuncios

Para organizar el código del cliente utilizo Overlay Types.

En un método intenté regresar un valor de tipo Integer, lo cuál me arrojaba una excepción de tipo IllegalArgumentException.

    public class Sala extends JavaScriptObject{
        public native final Integer getId()/*-{
            return this.id;
        }-*/; 

        public final String getNombre(){
            return JSOHelper.getAttribute(this, "nombre");
        }
        protected Sala() {
        } 

    }

esto es debido a que, en Javascript, el atributo ‘this.id’ es de tipo number y JSNI no puede hacer la conversión automáticamente.
Debemos realizar la conversión de la siguiente manera

    public native final Integer getId()/*-{
        return this.id ? @java.lang.Integer::valueOf(I)(this.id) : null;
    }-*/;

Fuentes:
https://code.google.com/p/google-web-toolkit/issues/detail?id=2972#c6
https://developers.google.com/web-toolkit/doc/latest/DevGuideCodingBasicsJSNI#methods-fields
http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16432

Actualmente en nuestros proyectos utilizamos CKEditor como editor de texto. Aquí explico como integrarlo a SmartGwt.

El problema es que para que el Editor ocupe el 100% del espacio disponible (verticalmente), debes conocer la altura disponible en píxeles. Esto debido a que la propiedad ‘height’ de la configuración no acepta valores porcentuales 😦

height : Number/String
The height of the editing area (that includes the editor content). This can be an integer, for pixel sizes, or any CSS-defined >length unit.

Note: Percent units (%) are not supported.

Cuando utilizamos Layouts, la altura esta disponible hasta que se ‘pinta’ el control. Por lo tanto tenemos que modificar el tamaño del Editor en ese momento. Para modificar el tamaño podemos utilizar JSNI de la siguiente forma.

    public class TextEditor extends Layout {
        private final static Logger log = Logger.getLogger(TextEditor.class.getName());</p><pre><code>    private static final int BASE_FLOAT_ZINDEX = 1000000;

    private CKEditor ckeEditor;

    private Canvas canvas;

    public TextEditor() {
        addResizedHandler(new ResizedHandler() {

            @Override
            public void onResized(ResizedEvent event) {
                log.info("Resizing editor: 100%, " + getHeightAsString());
                resizeEditor(ckeEditor, "100%", getHeightAsString());
            }
        });
        CKConfig config = new CKConfig();
        config.setBaseFloatZIndex(BASE_FLOAT_ZINDEX);
        config.setUseFormPanel(false);
        ckeEditor = new CKEditor(config);
        ckeEditor.setSize("100%", "100%");
        canvas = new Canvas();
        canvas.addChild(ckeEditor);
        setMembers(canvas);
    }

    @Override
    protected void onDraw() {
        super.onDraw();
        Timer timer = new Timer() {
            @Override
            public void run() {
                // after is drawn we could get height of component
                log.info("Resizing editor: 100%, " + getHeightAsString());
                resizeEditor(ckeEditor, "100%", getHeightAsString());
            }
        };
        timer.schedule(1 * 1000);
    }

    private native void resizeEditor(CKEditor editor, String width,
            String height) /*-{
                var e = editor.@com.axeiya.gwtckeditor.client.CKEditor::editor;
                if(typeof(e) != 'undefined' &amp;&amp; e != null){
                    e.resize(width, height);
                }
            }-*/;
}
</code></pre><p>

Notesé que en el método onDraw utilizamos un Timer para modificar el tamaño del Editor. De no hacerlo así, me arrojaba un error debido a que el CKEditor aún no era agregado al DOM -supongo-. También llamamos al método resizeEditor cuando el tamaño del Layout es modificado (ResizedHandler).

Fuentes:
http://docs.ckeditor.com/#!/api/CKEDITOR.editor
http://docs.ckeditor.com/#!/api/CKEDITOR.config
http://ckeditor.com/forums/CKEditor-3.x/Setting-editor-area-height-100?page=1
gwt-ckeditor source

Nunca le había prestado atención pero siempre estaba ahí, al ejecutar una aplicación GWT en el navegador (Chrome) aparecía el siguiente mensaje en la consola

[INFO] – Your *.gwt.xml module configuration prohibits the use of the current doucment rendering mode (document.compatMode=’ CSS1Compat’).
Modify your application’s host HTML page doctype, or update your custom ‘document.compatMode’ configuration property settings.

Intenté hacer lo que decía, modificar la etiqueta doctype en la página contenedora.

Cambié

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

por

<!DOCTYPE html>

pero no funcionó. Después encontré en est foro que la solución era de la siguiente manera

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

Con esto ya no apareció el molesto mensaje 🙂

GWT – JSON dates

marzo 11, 2013

JSON no tiene un tipo de dato Date, así que cuando transfieres fechas mediante JSON debes convenir un formato de fecha -por ejemplo, yyyy-MM-dd’T’hh:mm:ss.SZ- Un ejemplo de fecha en este formato sería “2013-03-08T16:23:35.000+0000”.

Para facilitar un poco las cosas, decidí utilizar un Overlay Type para la representación de mi objeto y acceder a sus propiedades mediante getter’s y setter’s. Estos métodos realizan la conversión de cadena a fecha, y viceversa, con la ayuda de la clase DateTimeFormat.

    public class Audiencia extends JavaScriptObject {</p><pre><code>    private final static DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat("yyyy-MM-dd'T'hh:mm:ss.SZ");

    public final native int getId()/*-{
        return this.id;
    }-*/;

    public final Date getInicio(){
        return dateTimeFormat.parseStrict(getInicioNative());
    };

    private final native String getInicioNative()/*-{
        return this.inicio;
    }-*/;

    public final Date getFin(){
        return dateTimeFormat.parseStrict(getFinNative());
    };

    private final native String getFinNative()/*-{
        return this.fin;
    }-*/;

    protected Audiencia() {
    }

    public static void getAudiencias(String idSala,
            final AsyncCallback&lt;Audiencia[]&gt; callback) {
        try {
            String queryParams = "?sala="+URL.encodeQueryString(idSala);
            RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,
                    Consts.REST_AUDIENCIAS + queryParams);
            rb.sendRequest(null, new RequestCallback() {

                @Override
                public void onResponseReceived(Request request,
                        Response response) {
                    if(response.getStatusCode() == 200){
                        JavaScriptObject jso = JSON.decode(response.getText());
                        DSResponse ds = new DSResponse(JSOHelper.getAttributeAsJavaScriptObject(jso, "response"));
                        if(ds.getStatus() == 0){
                            Record[] records = ds.getData();
                            Audiencia[] audiencias = new Audiencia[records.length];
                            for(int i=0;i&lt;records.length;i++){
                                audiencias[i] = (Audiencia) records[i].getJsObj();
                            }
                            callback.onSuccess(audiencias);
                        }else{
                            callback.onFailure(new Exception(ds.getAttributeAsString("data")));
                        }
                    }else{
                        callback.onFailure(new Exception(response.getStatusText()));
                    }
                }

                @Override
                public void onError(Request request, Throwable exception) {
                    callback.onFailure(exception);
                }
            });
        } catch (Exception e) {
            callback.onFailure(e);
        }
    }

}
</code></pre><p>

Esto es necesario porque el método JSON.decode) crea el objeto Javascript con propiedades (inicio y fin) de tipo String.

Para la implementación de un sistema de mensajes utilicé los llamados overlay types de GWT. Declaré una clase de la siguiente manera

    public class SystemMessage extends JavaScriptObject {</p><pre><code>    protected SystemMessage() {
    }

    public final native String getId()/*-{
        return this.id;
    }-*/;

    public final native String getHtml()/*-{
        return this.html;
    }-*/;
}
</code></pre><p>

Agregué a esta clase un método para obtener los mensajes del sistema desde un servicio REST

    public static void getMessages(final AsyncCallback<SystemMessage[]> callback) {
        try {
            RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,
                    URLRESTSYSTEMMESSAGES);
            rb.sendRequest(null, new RequestCallback() {</p><pre><code>            @Override
            public void onResponseReceived(Request request,
                    Response response) {
                if (response.getStatusCode() == 200) {
                    JavaScriptObject jsResponse = JSON.decode(response
                            .getText());
                    DSResponse dsResponse = new DSResponse(JSOHelper
                            .getAttributeAsJavaScriptObject(jsResponse,
                                    "response"));
                    if (dsResponse.getStatus() == 0) {
                        JavaScriptObject[] array = JSOHelper
                            .getAttributeAsJavaScriptObjectArray(dsResponse.getJsObj(),
                                        "data");
                        SystemMessage[] messages = (SystemMessage[]) array;
                        callback.onSuccess(messages);
                    }
                } else {
                    callback.onFailure(new Exception(response.getText()));
                }
            }

            @Override
            public void onError(Request request, Throwable exception) {
                callback.onFailure(exception);
            }
        });
    } catch (Exception e) {
        callback.onFailure(e);
    }
}
</code></pre><p>

El servidor nos regresa una respuesta en JSON parecida a la siguiente

    {"response":{ "status": 0, "data": [{"id":"1", "html": "Mensaje 1 
"},{"id":"2", "html": "Mensaje 2 
"}]}}

El problema es que este código no marca errores, incluso funciona correctamente cuando lo ejecuto en DevMode. Pero cuando hice el deploy en un servidor JBoss e intenté ejecutarlo, simplemente no me mostraba los mensajes pero tampoco registraba algún error en la consola. Después de muchísimas líneas de log.info() llegué a la conclusión que el problema era el casting del array. Intenté hacer un ‘cast’ de un array de tipo JavascriptObject[] a uno de tipo SystemMessage[]

    SystemMessage[] messages = (SystemMessage[]) array;

Para hacer el casting de un array en Java tenemos que hacer el casting elemento por elemento.

    SystemMessage[] messages = new SystemMessage[array.length];
    for(int i=0; i<array.length; i++){
        messages[i] = (SystemMessage) array[i];
    }

Nota: intenté utilizar el método Arrays.copyOf() pero éste no ha sido implementado en GWT. Si intentas utilizarlo te arrojará una error parecido al siguiente

The method copyOf(JavaScriptObject[], int, Class) is undefined for the type Arrays

Las aplicaciones desarrolladas con GWT deben ser compiladas para generar el código javascript que se ejecutará en el navegador. Este proceso puede ser muy tardado.

Normalmente cuando desarrollamos nuestra aplicación, sólo debemos actualizar el navegador (F5) para ver los cambios que hemos hecho al código. Sin embargo, hay ocasiones que necesitamos recompilar el código para que los cambios se vean reflejados. En estos casos podemos especificar un sólo navegador -el que utilicemos para probar la aplicación- para acelerar el proceso de compilación. Basta con agregar la siguiente línea al archivo gwt.xml del módulo

<set-property name="user.agent" value="safari"/>

En este caso, especificamos la compilación para Chrome. La lista completa de opciones la puedes consultar en el código fuente

Fuentes:

stackoverflow – how do i speed up the gwt compiler
stackoverflow – what are the possible user agent values in gwt xml