Mostrando las entradas con la etiqueta json. Mostrar todas las entradas
Mostrando las entradas con la etiqueta json. Mostrar todas las entradas

martes, julio 01, 2014

AngularJS Case & accent insensitive filter

Siguiendo con AngularJS, requería de un filtro que me permitiera rescatar objetos desde un arreglo, pero con la particularidad de ignorar capitalización y caracteres especiales. Como no encontré nada terminé haciendo mi propio filtro, que comparto con los honorables lectores.

Básicamente la necesidad surgió ya que revisando un proyecto que está casi listo para su entrega final, nos percatamos que en uno de los infinitos procesos de re-creación de base de datos (que la bajaban y la volvían a crear), los datos de algunos conjuntos de combos de selección relacionados entre si (como puede ser país, región, estado, provincia, condado) no eran consistentes.

En este caso (sumamente particular), la relación (lo que podríamos decir "llave foránea") es un descriptor en texto, y el comité creativo por algún misterioso motivo puso en algunos combos datos en mayúsculas con acentos, en otros en minúsculas y todas las variaciones que se puedan imaginar.

El resultado de esto fue que el proceso de filtro que cargaba "el siguiente" combo relacionado, traía todos los datos, ignorando cualquier relación esperable.
Para evitar esto, y suponiendo posible inconsistencia en los datos, se debía construir un filtro personalizado que hiciera que cadenas de caracteres como "REPUBLICA     DE MÉXICO" y "República de México" fueran comparables, y que el resultado fuera true.

Para ello lo más sano era eliminar acentos y espacios, pasar todo a mayúsculas y comparar.

El código:


app.filter('cleancompare', function(){
    return function(items, filterObj){
        if (filterObj === false) {
          return items;
        }
        
        if ((filterObj || angular.isUndefined(filterObj)) && angular.isArray(items)) {
            var newItems = [];
            angular.forEach(items, function (item) {
                var matches = true;                             
                var kArr = Object.keys(filterObj);
                for(key in kArr){                    
                    var cleanItemAttr = removeDiacritics(item[kArr[key]]).replace(/[^\w]/gi, '').toUpperCase();
                    var cleanObjAttr  = removeDiacritics(filterObj[kArr[key]]).replace(/[^\w]/gi, '').toUpperCase();
                    matches = (cleanItemAttr != cleanObjAttr)?false:matches;  
                    // Descomentar la línea únicamente si es estrictamente necesario depurar,
                    // ya que el rendimiento se ve seriamente perjudicado para conjuntos muy 
                    // grandes de datos.
                    /*if(matches){ console.log('Comparacion realizada: ', cleanItemAttr, cleanObjAttr); }*/
                }
                if(matches){
                    newItems.push(item);
                }
            });
            
            items = newItems;
        }
        
        return items;
    }
});
 
La función removeDiacritics() la saqué de http://stackoverflow.com/questions/18123501/replacing-accented-characters-with-plain-ascii-ones
Disfruten ;) 

miércoles, mayo 28, 2014

Spring REST Services y el error Required String parameter is not present

Desarrollando una API REST utilizando Spring MVC, y consumiéndola con AngularJS, me enfrenté al problema "Required String parameter is not present". Así es como lo solucioné.

Hace poco "vendí" (en rigor sólo convencía  mi jefe que es buena idea tomar este enfoque) una idea para abordar nuevos desarrollos. Consiste en desarrollar una API REST, utilizando el generador de código Telosys, y generar las interfaces con HTML5, CSS3 y JavaScript de modo de consumir los servicios con AngularJS.
Nada nuevo dirán, pero ofrece la ventaja que el mismo desarrollo web puede ser portado sin demasiado esfuerzo a una aplicación móvil usando Phonegap, o bien a una aplicación de escritorio utilizando Node-Webkit.

Este tipo de desarrollo resulta bastante rápido, y si logras una combinación entre Angular-UI-router y plantillas web con controladores propios (los controllers de AngularJS), se obtiene una modularidad bastante atractiva.

El problema

El código no tenía problemas, pero al tratar de realizar una llamada POST con Restangular a pesar de que el JSON con los datos que debía recibir la petición estaba bien construido, la llamada arrojaba el error:
Required String parameter 'usr' is not present

Soluciones propuestas

De las soluciones propuestas, después de realizar muchas búsquedas, ninguna funcionó:
  • Forzar que todas las peticiones POST indicaran en sus headers
    Content-Type = 'application/x-www-form-urlencoded' con el código:
  • $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';   
  • Agregar el atributo enctype="application/x-www-form-urlencoded" a la etiqueta form.
  • Realizar la llamada con AngularJS nativo vs usar Restangular.

Hasta pensé en cambiar el enfoque y usar HATEOAS, pero significaba realizar demasiados cambios a la aplicación (en rigor regenerar el código, Telosys se encarga del trabajo sucio).

Solución

Dí con este artículo de StackOverflow, donde explican que el servicio debe recibir un objeto que tenga todos los parámetros de la petición. Algo como:
@RequestMapping(value = "events/add", method = RequestMethod.POST)
public void addEvent(@RequestBody CommandBean commandBean){
    //some code
}

donde se debe indicar que el objeto (puede ser un POJO) CommandBean es el  @RequestBody. Y Spring se encargará de capturar los parámetros adecuadamente.
Y eso funcionó sin problemas.

lunes, julio 01, 2013

Acceso vía REST a listas de Sharepoint 2013

Por un proyecto que estamos siguiendo en mi actual trabajo tenemos que realizar una serie de integraciones con MS Sharepoint 2013. Entre ellas una interfaz "algo" compleja para las búsquedas, que demandó realizar un WebPart particular que se encarga de realizar la búsqueda con una toxi-consulta CAML, y luego se rescata el detalle de cada registro usando el  plugin jQuery SPWebServices.

Debido a un error que prefiero no mencionar para no perjudicar a los inocentes, estuvimnos mirando que maneras alternativas teníamos para rescatar esta misma información, y en esta búsqueda llegamos a la API REST de Sharepoint 2013.
Esta API nos provee de una vía bastante intuitiva para abordar temas como la recuperación de registros, sobretodo si hay conocimiento de la lista a la que pertenece y su correspondiente Id (siempre del registro).

Nosotros hicimos algunas trampas, en la lista de origen teníamos en las etiquetas data de los enlaces de cada registro tanto la lista como el id que he mencionado.

Básicamente los pasos a seguir son:

  • Identificar las listas disponibles, de modo de poder identificar el nombre de la lista (su título). Para ello pueden abrir la URL:
    http://[servidor sharepoint]/_vti_bin/listdata.svc
  • Recuperar el registro utilizando la interfaz REST, abriendo la URL:
    http://[servidor sharepoint]/_api/Web/lists/getByTitle('[nombre de la lista]')/Items([id numérico])
  • Otra manera de hacer exactamente lo mismo es usando la URL:
    http://[servidor sharepoint]/_vti_bin/listdata.svc/[nombre de la lista]([id numérico])

Notar los paréntesis en la llamada.

Con esto se recuperará un XML bastante tóxico y sin embargo bastante legible, sobre el cual pueden realizar las operaciones que determinen convenientes según sus requerimientos.
Si esto lo combinamos con un poco de jQuery, AJAX, y eventualmente algo de JSON (entiendo que haciendo la llamada adecuadamente se puede recuperar el resultado en JSON, pero por tiempo no lo verifiqué) se podrían hacer cosas bastante entretenidas.

Eso lo dejamos o para el pedido del público o para otro artículo más adelante.
Dudas, comentarios, aportes y observaciones son siempre bienvenidas.

Cabe advertirles que mi paso por Sharepoint es netamente circunstancial debido a este proyecto que espero se termine pronto.