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

Screenshot from 2014-04-16 12:11:30

Para dar formato a una fecha, y muchas cosas más, existe una librería llamada Moment.js. Para utilizar sus métodos desde una plantilla encontré este post que nos explica cómo crear un filtro para lograrlo.

El filtro quedaría de la siguiente manera

angular.module('myModule').
  filter('fromNow', function() {
    return function(dateString) {
      return moment(dateString).fromNow()
    }
  })

y el template

{{ reply.createdDate | fromNow }}

Funciona muy bien, pero tendríamos que crear un filtro para cada función de la librería que quisieramos utilizar. La ventaja es que la implementación del filtro queda independiente de la vista (template).

Otra opción sería poner el objeto moment disponible en la vista de la siguiente manera

angular.module('myModule', []).
controller('myController', ['$scope', function($scope){
 $scope.moment = moment
}])

y en la plantilla

{{ moment(reply.createdDate).fromNow() }}

Fuentes:

http://www.34m0.com/2012/07/angularjs-user-friendly-date-display.html
http://stackoverflow.com/questions/12466661/using-helper-methods-while-templating-with-angular-js
http://momentjs.com/

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

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.

En Apache CXF JAX-RS existen los ParameterBeans que nos permiten crear una instancia de un objeto a partir de los parámetros enviados al servicio.

@POST
public String create(@FormParam("")Empleado empleado){
 try {
 DAO dao=new DAO();
 dao.ofy().put(empleado);
 Map<String, Object> response = new HashMap<String, Object>();
 Map<String, Object> body = new HashMap<String, Object>();
 body.put("status", 0);
 body.put("data", empleado);
 response.put("response", body);
 return json.writeValueAsString(response);
 } catch (Exception e) {
 log.error("", e);
 }
 return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al guardar el Empleado\"}}";
}

En el ejemplo anterior se contruye un objeto Empleado a partir de los parámetros @FormParam enviados al servidor.

Los parámetros pueden ser de tipo String o cualquier tipo que tenga un constructor que reciba como parámetro una cadena o un método estático valueOf(String s). Adicionalmente CXF JAXRS puede utilizar el método fromString(String s) si existiera.


public class Empleado {

 Long id;
 String nombre;
 Date fechaNacimiento;

 public Empleado() {
 }

//getters y setters
}

Al utilizar la clase Empleado cómo parámetro, me arrojaba el siguiente error
ERROR [org.apache.cxf.jaxrs.utils.InjectionUtils] – Class java.util.Date can not be instantiated using a constructor with a single String argument
El mensaje del error es muy explícito. Pero entonces, ¿cómo le indicamos a Apache CXF que genere un objeto de tipo Date a partir de una cadena?
Bueno, pues tenemos que utilizar un ParameterHandler como el siguiente


/**
 * Converts ISO8601 parameters into date objects.
 *
 */
@Provider
public class ISO8601DateParamHandler implements ParameterHandler<Date> {

/**
 * Coerce an ISO8601 time string to a date object.
 *
 * @param s the string to parse.
 * @return the date object or null if the string cannot be parsed.
 */
 @Override
 public Date fromString(String s) {
 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
 try {
 return new Date(dateFormat.parse(s).getTime());
 } catch (ParseException e) {
 return null;
 }
 }
}

y configurarlo en nuestro archivo beans.xml

<jaxrs:server id="rest-api" address="/rest/gwt-audiencias">
 <jaxrs:providers>
 <bean class="com.mycompany.server.api.rest.ISO8601DateParamHandler" />
 </jaxrs:providers>
 <jaxrs:serviceBeans>
 <bean id="empleados"
 class="com.mycompany.server.api.rest.EmpleadosResource"></bean>
 </jaxrs:serviceBeans>
</jaxrs:server>

Fuentes:
http://cxf.547215.n5.nabble.com/ParameterHandler-not-invoked-for-Date-parameter-td2267734.html
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Parameterbeans
http://cxf.apache.org/docs/jaxrs-services-configuration.html

Estoy probando la base de datos PostgreSQL. Uno de mis primeros dolores de cabeza fue como obtener la fecha y hora actual del servidor. En Oracle ejecutas el comando ‘select sysdate from dual’ pero en PostgreSQL marca el error:

ERROR:  relation “dual” does not exist

LINE 1: select sysdate from dual;

                            ^

********** Error **********

ERROR: relation “dual” does not exist

SQL state: 42P01

Character: 21

En PostgreSQL tienes que hacerlo de la siguiente manera:

select now();

o

select CURRENT_TIMESTAMP;

Fuentes:
http://www.postgresql.org/docs/7.4/static/functions-datetime.html