Mostrando las entradas con la etiqueta html. Mostrar todas las entradas
Mostrando las entradas con la etiqueta html. 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, marzo 26, 2014

GulpJS amistoso con AngularJS

Hace poco después de mi lectura diaria descubrí GulpJS. Es una simpática herramienta de línea de comando que permite optimizar muchas cosas del desarrollo web, desde la minimización de imágenes, hasta la "liposucción de código" como le llamamos en la oficina al proceso de eliminar comentarios y mensajes de debug.
El resultado normalmente es un archivo .js minimizado, un archivo .css también minimizado, e imágenes y archivos .html "optimizados".

No les voy a enseñar a usarlo, eso lo pueden ver en detalle en los siguientes enlaces (todos en inglés):
Los primeros problemas a los que me enfrenté fueron básicamente que las configuraciones por defecto son muy agresivas, por lo que mis HTMLs quedaron sin algunos atributos necesarios para que mis controladores Angular funcionaran, y que derechamente mis controladores Angular dejaron de funcionar.

Después de leer algo de documentación para revisar las opciones de los plugins de Gulp que decidí usar, y trás algunas pruebas, llegué al siguiente código, que me ha arrojado los mejores resultados:

// Include gulp
var gulp = require('gulp'); 

// Include Our Plugins
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var changed = require('gulp-changed');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var stripDebug = require('gulp-strip-debug');

// Lint Task
gulp.task('lint', function() {
    return gulp.src('js/*.js')
        .pipe(jshint())
        .pipe(jshint.reporter('default'));
});

// Concatenate & Minify JS
gulp.task('scripts', function() {
    return gulp.src(['js/angular.min.js', 'js/Directive/bindonce.min.js', 'js/jquery-2.0.3.min.js', 'js/bootstrap.min.js',  'js/app.js', 'js/*.js', 'js/Service/*.js', 'js/Filter/*.js', 'js/Directive/*.js', 'js/Controller/*.js'])
        .pipe(concat('appjs.js'))
        .pipe(stripDebug())
        .pipe(gulp.dest('dist'))
        .pipe(rename('app.min.js'))
        .pipe(uglify({ mangle: false }))
        .pipe(gulp.dest('dist'));
});

// include plug-ins
var minifyHTML = require('gulp-minify-html');
 
// minify new or changed HTML pages
gulp.task('htmlpage', function() {
  var htmlSrc = '*.html',
      htmlDst = 'dist';
 
  gulp.src(htmlSrc)
    .pipe(changed(htmlDst))
    .pipe(minifyHTML({ empty: true, spare:true, quotes: true }))
    .pipe(gulp.dest(htmlDst));
});

// include plug-ins
var autoprefix = require('gulp-autoprefixer');
var minifyCSS = require('gulp-minify-css');
 
// CSS concat, auto-prefix and minify
gulp.task('styles', function() {
  gulp.src(['css/bootstrap.min.css', 'css/*.css'])
    .pipe(concat('styles.min.css'))
    .pipe(autoprefix('last 2 versions'))
    .pipe(minifyCSS())
    .pipe(gulp.dest('dist'));
});

// default gulp task
gulp.task('default', ['htmlpage', 'scripts', 'styles'], function() {
});

Lo único que no pude resolver a través de parámetros (y si alguien lo sabe se agradece si comparten el dato) fue el hecho que el plugin minify-html se "come" mis espacios en blanco intencionales (a veces necesarios) y los atributos que no tienen valor (como el bindonce). Tan grave no es si sabes donde ponerlos de vuelta, pero eso ya es parte del trabajo manual.

martes, agosto 20, 2013

Diseño orientado al pixel vs Diseño web

Hace algún tiempo que me vengo relacionando con varios amigos diseñadores. Debo decirlo, sus trabajos gráficos son increíbles, pero lamentablemente tienen poca o nula idea de diseño web.

En cuanto a mi apreciación, ¿porqué tan radical? se preguntará el honorable lector. Muy simple, la mayoría de ellos dibuja muy bonito, sabe usar Photoshop (cosa que yo no sé), pero son todos obstinados en cuanto a su diseño. Cuando se ponen a diseñar sitios web, insisten en usar anchos fijos, ubicaciones estáticas, todo orientado al pixel.

Y esto es herencia de su escuela, muchos aprendieron diseño en papel, o diseño digital. Saben a la perfección de paletas de colores, colores complementarios, balance en la imagen, y muchos otros conceptos que claramente yo NO manejo ni conozco. Pero no saben de diseño web, y de como focalizar un trabajo gráfico increíble en la atención de los visitantes del sitio.

Con el tiempo he desarrollado la capacidad de poder pasar un diseño en PSD, o una simple imagen, a una versión web con HTML y CSS, sin demasiado esfuerzo. Pero ello implica saber, y sobretodo entender que el diseño orientado al pixel aplicado a web es contraproducente. Hay un desgaste no menor tratando de alinear todo a la perfección, y si el diseñador no está consciente de esto, y el cliente tampoco, entonces puede ser un factor bastante negativo para el proyecto.

Para web el diseño que sirve es netamente conceptual:

  • Paleta de colores adecuada
  • Ubicación referencial de los elementos importantes de cada página
  • Imágenes de fondo
  • Tipografías
Incluso muchos de esos elementos ya son la sintonía fina del diseño. 

No es tan necesario esforzarse tanto en que el diseño esté pensado para pantallas de 1024x768 con un contenedor de 980px y un espacio útil de 960px, y que todo encuadre perfectamente en esas dimensiones. Tarde o temprano alguien con otra resolución verá el sitio y este se verá mal, y lamentablemente, siempre pensando en diseño web,  no se puede satisfacer todas las necesidades de diseño. Pensemos solamente en los distintos dispositivos móviles en los cuales se podría ver el sitio, y la orientación del dispositivo (vertical u horizontal).

No digo que para el diseño web no sirva ajustarse a una resolución, pero no pensar en el pixel. Es hora de que, ustedes amigos diseñadores, empiecen a distinguir cada elemento y cuál es su importancia en el diseño web, más que solamente ver si debe estar 10px a la derecha de la imagen principal y cuadrar dentro de un espacio de 960px.

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.

lunes, mayo 21, 2012

Instalación de Phreeze: Framework MVC para PHP

Desde hace poco estoy desempolvando mis destrezas programando PHP. Estaba entre arreglar mi generador de código (algún día hablaré del mítico DbCommand y su generador de código) y crear unas plantillas Velocity para generar código  PHP o encontrar un Framework FRB (Fácil, Rápido y Bonito)  que me cree los CRUDs que necesito y me permita seguir usándolo sin demasiada dificultad (por eso lo fácil).

No puedo decir que evalué, sino más bien miré, varios Frameworks. CodeIgniter, Zend, CakePHP, y varios otros algo más desconocidos, todos son muy muy completos, pero son tan completos como complejos. Yo buscaba algo fácil. Y llegué a Phreeze.

Phreeze me convención en un video de 3 minutos. La aplicación genera un CRUD completo para bases de datos MySQL con sólo algunos clicks. Me dejé seducir por el video ya que Phreeze es bastante complejo y hay mucho de magia detrás.
A punta de prueba y error logré decifrar como instalarlo en un entorno "no preparado". Hay que decirlo la documentación es completa, pero hasta hace poco carecía de tutoriales, y no es precisamente un Framework para enfrentarse sin un buen ejemplo, o un buen tutorial (personalmente prefiero los 2dos).

Requerimientos previos:

  •  Apache
  •  MySQL, sino no tiene sentido haberlo bajado...
  •  PHP, sino no va a funcionar
  •  PEAR  para PHP
  •  Habilitar mod_rewrite en la configuración del Apache

En mi caso yo uso Ubuntu, así que la instalación la hice usando apt-get y Synaptic (ya se que el nuevo Software Center supuestamente es más simple, pero yo soy de la vieja escuela).

La habilitación de mod_rewrite tiene sus "ques":

  • Se debe editar el archivo de configuración de sitios de Apache reemplazando donde dice
    AllowOverride None
    por
    AllowOverride All
    para que  los directorios que deban ser afectados por este módulo.
    En Ubuntu el archivo se encuentra en:
    /etc/apache2/sites-enabled/000-default
  • Habilitar el módulo mod_rewrite copiando el archivo a la carpeta de módulos habilitados (comando en una sola línea):
    cp /etc/apache2/mods-available/rewrite.load/etc/apache2/mods-enabled/rewrite.load
  • Reiniciar Apache:
    service apache2 restart


Supongo que está de sobra decir que los comandos se ejecutan en modo root ( sudo su - , o derechamente con sudo) .


Instalación:

  • Bajar el archivo comprimido desde la página oficial y descomprimir en alguna carpeta que sea visible desde su servidor web, por ejemplo /var/www/phreeze (de hecho llamarla phreeze es lo recomendado).
  • Ajustar los permisos para la carpeta temporal de Phreeze, en 
    /var/www/phreeze/builder/temp
    ejecutando
    chmod -R 777 /var/www/phreeze/builder/temp
    Los permisos 777 suelen ser  "complicados", pero claramente no van a asignar todos los permisos en un servidor de producción.
    ¡HÁGANLO SOLAMENTE EN MÁQUINAS DE DESARROLLO NO EXPUESTAS!
    (Sí, les grité, pero fue sólo para asegurarme que entienden esto.)
  • Verificar que funcione visitando http://localhost/phreeze/builder
  • Generar sus CRUDs.

Instalación de los CRUDs:

  • Descompriman el archivo que les generó Phreeze en alguna carpeta. Procuren  respetar los parámetros de configuración que dieron en el builder.
  • Ajusten los permisos de la aplicación a 755
    chmod -R 755 *
  • Cambien los permisos de la carpeta template_c a 777
    chmod -R 777 templatec
    Esto es para permitir que las plantillas puedan ser compiladas a la carpeta local.
    OJO
    : Yo uso Smarty, Phreeze permite usar Smarty o Savant como motores de plantillas (templates), la carpeta template_c es de Smarty, si usan Savant es otra carpeta.

Los comandos los ejecutan en la carpeta donde descomprimieron su aplicación.

Creo que con eso tienen para entretenerse. Les recomiendo que vean los tutoriales en video de Phreeze, son muy ilustrativos de como se usa la aplicación.

martes, marzo 13, 2012

jQuery Mobile borrar item en listview usando swipe

En este momento estoy usando jQuery Mobile para desarrollar algunas interfaces para unas aplicaciones móviles. Gracias a PhoneGap puedo compilarlas para Android y pasan de ser un desarrollo 100% a una desarrollo que sigue siendo web, pero tiene olor a aplicación.

Dentro de la interfaz me enfrenté al problema que lo limitado del espacio en pantalla no me permite incluir demasiados botones, y requería de una manera para eliminar ítemes de una lista en modo inset. Quise copiar un poco la interfaz de webOS (de la difunta Palm)  para esto, una interfaz muy similar a la que provee iOS en el iPhone.


La idea:

  • Deslizar el dedo desde izquierda a derecha para eliminar un ítem.
  • Deben aparecer botones que permitan borrar el ítem o cancelar la operación de borrado.
  • Al seleccionar borrar se debe confirmar la eliminación antes de hacerla efectiva.


El código y el ejemplo:

Observaciones:
La variable onswipe marca como activa la acción de eliminación. Si se está eliminando un item, no se podrán eliminar otro.
Una vez que se hacen aparecer los botones para la eliminación es necesario desactivar los eventos tap y click sobre el ítem de la lista, sino va a tomar la acción por defecto y los botones no van a poder capturar el evento de click sobre ellos.
Se ha utilizado la etiqueta "button" en vez de "a" (para enlaces) para evitar que el framework aplique los estilos de lista (en este caso lista tipo inset).

Está probado en Android y en iOS y funciona. Seguramente alguien lo va a poder mejorar.
El código toma bastantes referencias de lo discutido en https://forum.jquery.com/topic/adding-an-iphone-style-swipe-to-delete-button-to-a-listview . Lamentablemente el código original no me funcionó.

lunes, diciembre 05, 2011

Combobox con Autocomplete & jQuery

Parte de las barbaridades que hago a diario requieren ciertas consultas HTTP recurrentes. Para poder atenderlas hice un formulario energizado por jQuery. Funciona, pero muchas veces tenía que referirme a datos estáticos y para no tener que llenarlos a cada rato los puse en un combobox.

El problema es que el combobox empezó a crecer y se empezó a volver tedioso encontrar el dato correcto en la cada vez más larga lista de registros.
La solución fue hacer que el combobox fuera editable y que desplegara los registros que respondían a una búsqueda parcial, tal como el ejemplo de jQuery UI para el componente autocomplete. Sin embargo el ejemplo provisto para los comboxes (select boxes) no me gustó. Demasiado complejo para algo "tan simple".

Mi solución (actualizada):

Dejé todo el código para que lo puedan ver y probar en http://jsfiddle.net/jsanta/CBjVz/3/

En mi opinión este código es mucho más fácil, más entendible y más ajustable que el del ejemplo original.
TO DO (pendiente) convertir esto a un plugin.