Screenshot from 2015-11-20 09:10:35.png

Dependiendo la versión de SmartGWT que estes utilizando, puede que los selectores de fecha (calendarios) no te permitan seleccionar más allá de 2015. Una forma de evitar esta limitante es ir a Diciembre de 2015 y -aunque este en gris y aparentemente desactivado- dar clic en el día 1 de Enero de 2016 que es visible. Una vez hecho esto, el calendario da el salto a 2016 y podemos seleccionar cualquier mes de ese año. Obviamente no debemos obligar al usuario a tener que hacer esto cada que quiera seleccionar una fecha posterior.

Para establecer por default -para todos los selectores de fecha- un rango de fechas más amplio podemos hacer lo siguiente en nuestro punto de entrada a la aplicación.

DateItem defaultDateItemProperties = new DateItem();
defaultDateItemProperties.setStartDate(new Date(2000-1900, 01, 01));
defaultDateItemProperties.setEndDate(new Date(2025-1900, 12, 31));
DateItem.setDefaultProperties(defaultDateItemProperties);
DateChooser defaultDateChooserProperties = new DateChooser();
defaultDateChooserProperties.setStartYear(2000);
defaultDateChooserProperties.setEndYear(2025);
DateChooser.setDefaultProperties(defaultDateChooserProperties);

Fuentes:
http://forums.smartclient.com/forum/smart-gwt-technical-q-a/22639-datepicker-not-allowing-before-1995-and-2015

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

Es común encontrar combos que sólo deben mostrar algunas opciones dependiendo de la selección hecha en otro combo, esto es, una selección dependiente.

En el Showcase de SmartGwt viene un ejemplo de cómo hacer esto. El problema con el ejemplo es que no nos dejará filtrar (en realidad autocompletar) los valores que contiene el combo. Esto se debe a que, en el código del ejemplo, sobreescribimos el método getPickListFilterCriteria que es utilizado cuando escribimos algún valor en el combo.

Por ejemplo, si tenemos que mostrar las Colonias que pertenecen a un Municipio -y además permitir que el usuario filtre/busque al escribir el nombre de la colonia- lo haríamos de la siguiente manera.

        SelectItem itmMunicipio = new SelectItem("municipio", "Municipio");
        itmMunicipio.addChangedHandler(new ChangedHandler() {
            @Override
            public void onChanged(ChangedEvent event) {
                ComboBoxItem item = (ComboBoxItem) frmPartes.getItem("colonia");
                item.setPickListCriteria(new Criteria("municipio", form.getValueAsString("municipio")));
                item.fetchData();
            }
        });
        itmMunicipio.setOptionDataSource(MunicipiosDS.singleton());
        itmMunicipio.setValueField("id");
        itmMunicipio.setDisplayField("nombre");
        itmMunicipio.setDefaultValue("014"); // Querétaro
        ComboBoxItem itmColonia = new ComboBoxItem("colonia", "Colonia");
        itmColonia.setOptionDataSource(ColoniasDS.singleton());
        itmColonia.setValueField("id");
        itmColonia.setDisplayField("nombre");
        itmColonia.setAddUnknownValues(false);
        itmColonia.setTextMatchStyle(TextMatchStyle.SUBSTRING);
        itmColonia.setPickListCriteria(new Criteria("municipio", "014")); // esto es para el valor inicial
        itmColonia.setDefaultToFirstOption(true);
        itmColonia.setWidth(200);
        itmColonia.setPickListWidth(300);

Fuentes:
http://www.smartclient.com/smartgwt/showcase/#dep_selectects_db_combobox_category
http://www.smartclient.com/smartgwt/showcase/#combobox_multifield_search
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/docs/ComboBoxFiltering.html
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/widgets/form/fields/ComboBoxItem.html#setTextMatchStyle(com.smartgwt.client.types.TextMatchStyle)

SmartGwt especifica 2 tipos de errores: de validación e irrecuperables.

Los errores de validación se representan en JSON de la siguiente manera

    {    "response":
           {   "status": -4,
           "errors":
               {   "field1": [
                       {"errorMessage": "First error on field1"},
                       {"errorMessage": "Second error on field1"}
                   ]
               }
           }
    }

de acuerdo a la documentación un error irrecuperable se representa de la siguiente forma

    {"response": {"status": -1, "data": "Error message..."}}

Pero cuando utilicé un RestDataSource en un SelectItem y ocurría un error en el servidor, la petición se reintentaba de forma cíclica. La solución fue agregar la propiedad totalRows a la respuesta.

    {"response": {"status": -1, "data": "Error message...", "totalRows": 0 }}

Fuentes:
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/docs/ErrorHandling.html
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/data/RestDataSource.html
http://forums.smartclient.com/showthread.php?t=22975

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

SmartGwt tiene el widget Calendar para mostrar, crear y editar eventos. Por default, tiene un editor con controles para modificar las propiedades básicas de un evento: startDate, endDate, name y description.
Si quieres agregar más propiedades a tus Eventos, puedes utilizar el método setEventEditorFields)
Pero que pasa cuando quieres modificar por completo, o mejor dicho, sustituir el Editor por defecto. Desde la versión 2.5 de SmartGwt lo puedes hacer de manera fácil utilizando el evento BackgroundClick) de la siguiente manera

Para crear un evento cuando el usuario selecciona un rango de tiempo mediante un click o arrastrando el mouse

    final Calendar calendar = new Calendar();
    calendar.setShowQuickEventDialog(false);
    calendar.setShowAddEventButton(false);
    calendar.addBackgroundClickHandler(new BackgroundClickHandler() {
        @Override
    public void onBackgroundClick(BackgroundClickEvent event) {
        event.cancel();
        clearCalendarSelection(calendar);
        CalendarEvent evnt = new CalendarEvent();
        evnt.setStartDate(event.getStartDate());
        evnt.setEndDate(event.getEndDate());
        editEvent(evnt); // show custom Event Editor
    }
    };);

Para crear un evento cuando se da clic en una fecha en la vista de “Mes”

    calendar.addDayBodyClickHandler(new DayBodyClickHandler() {
        @Override
        public void onDayBodyClick(DayBodyClickEvent event) {
            event.cancel();
            CalendarEvent evnt = new CalendarEvent();
            evnt.setStartDate(event.getDate());
            evnt.setEndDate(event.getDate());
            editEvent(evnt);
        }
    });

Para modificar un Evento existente

    calendar.addEventClickHandler(new EventClickHandler() {
        @Override
        public void onEventClick(CalendarEventClick event) {
            event.cancel();
            clearCalendarSelection(calendar);
            editEvent(event.getEvent());
        }
    });

Notesé que es necesario llamar el método clearCalendarSelection para evitar que al dar clic en un Evento después de seleccionar un rango se dispare también el evento BackgroundClick, además del evento EventClick

private native final void clearCalendarSelection(Calendar calendar)/*-{
    obj = calendar.@com.smartgwt.client.widgets.BaseWidget::getJsObj()();
    obj.clearTimeSelection();
}-*/;

Fuentes:
http://forums.smartclient.com/showthread.php?p=102142#post102142
https://code.google.com/p/smartgwt/issues/detail?id=267
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/widgets/calendar/events/BackgroundClickHandler.html
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/widgets/calendar/Calendar.html
http://forums.smartclient.com/showthread.php?p=102139#post102139

Si necesitas un control para desplegar una lista de opciones, puedes utilizar un ListGrid y activar el modo de selección con casillas (checkbox).

Debes tener en cuenta, si quieres ejecutar código cuando cambie la selección, que el evento SelectionChangedHandler se manda llamar para cada elemento seleccionado.

    ListGrid grdOpciones = new ListGrid();
    grdOpciones.setSelectionType(SelectionStyle.SIMPLE);
    grdOpciones.setSelectionAppearance(SelectionAppearance.CHECKBOX);
    grdOpciones.addSelectionChangedHandler(new SelectionChangedHandler() {
        @Override
        public void onSelectionChanged(SelectionChangedEvent event) {
            updateOpciones();
        }
    });

Esto es ineficiente cuando el usuario marca la casilla del encabezado para seleccionar todos los elementos. En este caso, convendría utilizar el evento SelectionUpdatedHandler que se dispara una sola vez.

    grdOpciones.addSelectionUpdatedHandler(new SelectionUpdatedHandler() {
        @Override
        public void onSelectionUpdated(SelectionUpdatedEvent event) {
            updateOpciones();
        }
    });

Fuentes:
http://forums.smartclient.com/showthread.php?t=15971