martes, 7 de abril de 2009

Obtener etiquetas de un .properties con JavaScript, DWR y Spring

Hoy por fin retomo la publicación en el blog...¿la razón? Tengo algo interesante que comentar y compartir =).

Tenia asignada una tarea donde tenia que usar una lista dinámica como command de un controller, con Spring, claro.

Ese problema menos mal ya lo habia solucionado con anterioridad. Pero sabia que la parte fuerte estaba en las validaciones y que estas tendrian que ser via JavaScript. ¡Look and Feel!

Bueno eso tampoco era problema...de no ser porque la aplicación maneja muchos idiomas. Aunque por ahora solo nos han pedido Ingles y Español.

¿Y cual es el problema? Pues que los mensajes debian ser estandarizados y mostrados según el idioma que el usuario hubiera escogido desde la primera ventana.

Estamos usando archivos para los mensajes (.properties). Asi que tambien estaban estandarizados los textos de los mensajes.

Entonces...el problema era invocar esas etiquetas desde JavaScript enviando simplemente el key de las mismas...y quizás algún argumento adicional.

Pensando, consultando y leyendo algo...finalmente tome partecitas de algunos consejos, de algunas clases ya implementadas en nuestro framework y un poco de imaginación.

Les presento la solución que le di a este pequeño inconveniente de llamar etiquetas de un archvio de mensajes desde un método JavaScript...usando DWR y Spring =).

Usemos el ejemplo siguiente para mostrar como se podria solucionar la cuestión planteada: deseamos registrar una a una un listado de personas asistentes a un evento. Sin embargo, debemos cuidar que el nombre de la persona siempre sea ingresado.

La solución fue planteada basandome en un método que tiene el SimpleFormController:

[sourcecode language='java']
public String getText(String msgKey, Locale locale) {
return getMessageSourceAccessor().getMessage(msgKey, locale);
}
[/sourcecode]

Entonces la idea para llamar a este método desde algún metodo JavaScript era al menos pasar el Key y el Locale (por el idioma).

Sabia que el Key dependia de lo que quisiera mostrar asi que ello no era problema. Sin embargo el Locale...bueno lo podia conseguir haciendo uso del HttpServletRequest...entonces el foquito empezo a prenderse. Si requeria un objeto HttpServletRequest lo podria obtener a través del Controller.

Entonces, conversando con un compañero de trabajo acerca de ello me sugirió que podria inyectar el request en alguna clase que hiciera un llamado al metodo getText u otro método que hiciera lo mismo. Entonces la idea empezó a tener consistencia:

1. Debia crear una clase a la que pudiera inyectar un objeto HttpServletRequest a través del Controller.
2. Crear un método en la clase mencionada que hiciera lo mismo que el getText del SimpleFormController.
3. Llamar a este método desde otro JavaScript, para lo cual podría usar DWR.
4. Mostrar el mensaje que requiera.

Manos a la obra.

1. class MessagePropertiesLoader: esta clase nos permitirá obtener un objeto HttpServletRequest para obtener el Locale que nos dirá con que idioma se está trabajando en la aplicación. De esta manera al llamarlo desde la vista podremos obtener el mensaje en el idioma correcto.
[sourcecode language='java']
public class MessagePropertiesLoader {

private HttpServletRequest request;
...

public void setRequest(HttpServletRequest request) {
this.request = request;
}
....
}
[/sourcecode]
2. getMessageByResourceBundle: este será el método de MessagePropertiesLoader que será llamado desde la vista y que nos devolverá el mensaje que requrimos.
[sourcecode language='java']
public String getMessageByResourceBundle(String key, String before, String after)
{
String filename = Constants.BUNDLE_KEY;
ResourceBundle bundle = ResourceBundle.getBundle(filename, this.request.getLocale());
String message = bundle.getString(key).trim();

if(ValidationUtil.validateString(before))
{
before = bundle.getString(before).trim();
message = before + " "+message;
}

if(ValidationUtil.validateString(after))
{
after = bundle.getString(after).trim();
message = message + " "+ after;
}
return message;
}
[/sourcecode]
Donde:

[sourcecode language='java']String key [/sourcecode] es el nombre de la etiqueta que deseamos obtener.

[sourcecode language='java']String filename = Constants.BUNDLE_KEY [/sourcecode] nos da el nombre del archivo de propiedades que usamos en la aplicación, en este caso el valor es "resources.properties" para inglés y "resources_es.properties" para español.

[sourcecode language='java']ResourceBundle bundle = ResourceBundle.getBundle(filename, locale) [/sourcecode] nos permitirá acceder al archivo que necesitamos.

[sourcecode language='java']String message = bundle.getString(key).trim() [/sourcecode]una vez abierto el archivo podemos obtener la etiqueta requerida.

Las otras líneas son poder agregar algun parámetro antes o despues del mensaje. Finalmente devolvemos la cadena requerida.

3. Usar la clase implementada: teniendo nuestro controller:
[sourcecode language='java']
public class PeopleFormController extends BaseFormController {

private MessagePropertiesLoader messagePropertiesLoader;
...

protected Object formBackingObject(HttpServletRequest request) throws Exception {

if (!isFormSubmission(request)) {

messagePropertiesLoader.setRequest(request);
People people = new People();
return people;
}
return super.formBackingObject(request);
}
...
}
[/sourcecode]
Donde:
[sourcecode language='java']messagePropertiesLoader.setRequest(request) [/sourcecode]es la sentencia que nos permite acceder al objeto HttpServletRequest.

En nuestro applicationContext.xml:
[sourcecode language='java']


[/sourcecode]

En nuestro action-servlet.xml:
[sourcecode language='java']





[/sourcecode]

Y finalmente para poder acceder a la clase via DWR, en el dwr.xml:
[sourcecode language='java']



[/sourcecode]

4. Solo nos quedaria llamar a la clase y al método desde nuestra vista.
[sourcecode language='html']




Ejemplo Properties, DWR, JS y Spring




Registro de personas asistentes











:
















... ...




[/sourcecode]
Donde:

[sourcecode language='html'] [/sourcecode] nos permitirá acceder a la clase donde estará nuestro método que nos devolverá el valor de la etiqueta requerida.

[sourcecode language='html']input type="button" onclick="addPerson();" value="+"[/sourcecode] es el botón que disparará la acción de agregar una persona a las lista, pero antes validará que se haya ingresado el nombre.

[sourcecode language='html']MessagePropertiesLoader.getMessageByResourceBundle("errors.requiredField", "common.name", "",
function(data) {
alert(data);
}
); [/sourcecode] es el método que finalmente nos arroja?a el mensaje correspondiente al error hayado.

Por último lo que deberian mostrar las etiquetas se encuentran en los archivos .properties:

resources.properties:
[sourcecode language='java']
errors.requiredField= is required.
common.name= Name
common.address=Address
[/sourcecode]

resources_es.properties para español:
[sourcecode language='java']
errors.requiredField= es un campo obligatorio.
common.name= Nombre
common.address=Dirección
[/sourcecode]

Finalmente la aplicación se veria asi para espalñol:

[caption id="attachment_128" align="aligncenter" width="518" caption="Aplicación en Español"]Aplicación en Español[/caption]

Y para el inglés:

dwr_en

Definitivamente esto puede mejorar y se puede llegar a imitar realmente la funcionalidad del getText, que puede recibir argumentos en arreglos de Objetos y más. Pero por ahora me sirve y espero le sirva a alguien más que requiera lo mismo.

Saludos, hasta la próxima.