lunes, febrero 09, 2015

Validaciones con HTML5 sin necesidad de form.submit

Como parte de HTML5 existe la posibilidad de agregar información a los inputs de un form, para realizar validaciones; podemos indicar si queremos que sea requerido, con el tipo de datos; number, email, etc restringimos los valores que pueden ser agregados, podemos usar alguna mascara para validaciones, colocar mensajes de error custom, etc (en la red existen muchos ejemplos acerca de como customizar formularios).

Ahora bien pongamos en contexto, tengo un formulario como este:

<form name="managerForm"  id="managerForm">
             <p>
                 Name:
                <input id="managerNameText" required="required" placeholder="Write here the new manager name" size="40"/>
             </p>

            <p>
                Email:
                <input id="emailText" required="required" placeholder="[email protected]" type="email" />
            </p>

             <br/>
             <a href="#" id="addManagerLink">Add a new Manager to the List</a>
         </form>


Si presta atención al markup, podrá ver que se cuenta con dos inputs; un text normal y un email, el text es requerido al igual que el email, adicionalmente el email precisa que el texto introducido sea acorde a una dirección de correo.

Si hiciéramos submit al formulario con algún campo vacío veríamos como el browser se encarga de hacer las validaciones y mostrar los popups con los mensajes de errores.

Ahora bien, el tema es; que sucede si el requerimiento es que el formulario no se envié pero que los errores continúen funcionando de la misma forma. Para ello debemos usar las siguientes funciones en javascript:


 var managerForm =
                    document.getElementById("managerForm");

            if (managerForm.checkValidity()) {

                  // hacer algo
            } else {

                 managerForm.reportValidity();
            }


checkValidity retorna true si no existe ningún error en el formulario, de lo contrario retorna false.

reportValidity muestra los mensajes de error en los popups sobre los inputs correspondientes en caso que existan, similar a cuando hacemos submit.


JavaScript: Removiendo hijos

Supongamos que se desea eliminar una lista de nodos hijos en un elemento particular en el dom; regularmente seguiríamos la siguiente forma


// Bajo parent Id los elementos que deseo eliminar tienen un className en común.
var nodes =
    document.getElementsByClassName(className);

var parentNode =
    document.getElementById(parentId);

            if (nodes) {

                for (var i = 0; i < nodes.length; ++i) {

                    var node = nodes[i];

                    if (node) {

                        if (parentNode.contains(node)) {

                            parentNode.removeChild(node);
                        }
                    }
                }
            }



El problema con el código anterior es que no se eliminan todos los elementos, si no alrededor de la mitad y esto sucede porque el valor nodes.length es alterado cada vez que eliminamos un nodo (o al menos es lo que creo, tampoco me detuve mucho tiempo a comprobar mi teoría), el cambio siguiente me funciono bien (simplemente cambie el siglo del for para que decremente en lugar de incrementar)

for (var i = nodes.length - 1; i >= 0; --i)


domingo, enero 12, 2014

Cache y estrategias, parte 1

Cache, en una aplicación medianamente escalable, ni que decir en una altamente escalable, la cache es un elemento clave en el desempeño y tiempo de respuesta. Regularmente utilizamos la cache de una manera muy simple, basado en un conjunto de entradas determinamos si el objeto se encuentra en cache, si no se encuentra ejecutamos la lógica de negocios para obtener la información y volver a introducirla en la cache para  futuros llamados. Regularmente este esquema es lo suficientemente bueno para la mayoría de sistemas de tamaño regular, pero un sistema con muchas consultas, un nivel de concurrencia mayor, etc, debemos empezar analizar el comportamiento de nuestra memoria y determinar las mejores estrategias de almacenamiento cache.

Que cachear, supercache

En lo personal creo que una estrategia cache muy eficaz (sin embargo no siempre eficiente) es almacenar a fuerza bruta si es posible toda la información en memoria, es decir si tenemos un cluster de cache con memoria RAM lo suficientemente grande como para que  podamos guardar por ejemplo el catalogo de productos, mi recomendación sería guardar todo ahí, sistemas como los de redes sociales, suelen tener clusters gigantes con información guardada en memoria y hace que las aplicaciones sean más rápidas, en este esquema por lo regular solo tenemos que tener cuidado con la invalidación de la cache (cuando algo cachado cambia, debe eliminarse el registro actual de la cache para que  obtenga los últimos cambios). Podríamos reducir entonces la estrategia a, si tiene mucho espacio en memoria, simplemente úselo.

Los más populares

En algunos modelos de negocios conocemos cuales objetos son los más populares, tanto por conteo estadísticos como heurísticas de negocio; tomemos por ejemplo el caso de un sistema de comercio electrónico, un agente de negocios puede conocer de antemano cuales productos son los más vendidos, más populares, etc. Así pues con este conocimiento podríamos levantar un listado de productos y al inicio de la aplicación hacer un “warm up” de la cache con estos objetos, adicionalmente si no se tienen muchas áreas de cache yo recomiendo crear estos objetos en un área aparte del resto de productos comúnmente cachados con el fin de monitorear en el futuro el comportamiento de estos objetos en cache.

La segunda estrategia siempre dentro del esquema de los más populares es calcularla programáticamente mediante el análisis estadístico de la cache (sencillo cuales productos pasan más tiempo en memoria a razón de mayor cantidad de hits), así pues podemos guardar esta lista en algún mecanismo de persistencia y cuando se requiera o cuando se inicia la aplicación solicitar que se caliente la cache con estos elementos también.

Por último se me ocurre una tercera estrategia, esta de la mano de la heurística; un producto nuevo puede de pronto volverse popular (incluso uno impopular puede volverse popular), regularmente la gente de marketing tiene métricas sobre sus expectativas de ventas, por ejemplo una promoción (un producto nuevo), un sale (productos que se van a colocar en oferta), un combo (grupo de productos a mejor precio), producto posicionado (un producto que se va poner por ejemplo en la página de home page o alguna otra página de alto alcance, etc). Estas acciones tienen un motivo claramente conocido por marketing y en conjunto, se pueden crear listas de productos para ser cachados previamente y así hacer la aplicación mas rápida y eficiente (cache implica menos tiempo de CPU, I/O etc), ante la probabilidad de mayores consultas sobre estos objetos.

Impopulares y filtros

Regularmente dependiendo de las facilidades de hardware con que contamos (como dijimos antes si tiene mucha memoria y puede cacharlo todo, simplemente hágalo), filtramos los objetos que vamos a introducir a memoria. La estrategia más sencilla es limitar el tamaño de los  objetos, por ejemplo nada mayor a 100kb o un megabyte, etc. Esto con el motivo de no saturar nuestra cache con objetos gigantes de manera muy rápida.

Si bien es cierto tener algo en cache ahorra tiempo de CPU y regularmente I/O, latencia y por ende tiempo de respuesta al usuario, pero también debemos tener en cuenta que si nuestro espacio de memoria para cache es limitado debemos evitar incluir objetos que consumirán memoria en forma de paracito, me explico; la idea de la cache es almacenar un objeto en ella, de tal manera que reciba al menos un hit antes de que este expire y sea eliminado de la cache (depende de la estrategia pero regularmente por prioridad o TTL, un objeto es eliminado de la cache cuando no es invocado por mucho tiempo y la cache está llena o cuando se vence un el tiempo designado en TTL), así pues agregar objetos a la cache que nunca son leídos resultará en una mala gestión de nuestros recursos de hardware.

Por ende, lo que se propone es identificar que objetos son los más impopulares y filtrar su adicción a la cache, por ejemplo los frameworks de cache regularmente proporcionan estadísticas acerca del comportamientos de los entries, pero nosotros podemos hacer un servicio que sea el helper mediante el cual se entra a la cache y guardar estadísticas propias acerca de que objetos se llaman y hacen hit, cuales se llaman y no hacen hit y así determinar cuáles son populares o no, o inclusive determinar si el tiempo de TTL para un objeto debe ser mayor, por ejemplo si tenemos un TTL parejo de 10 minutos y ciertos objetos son invocados cada 15 minutos, entonces estos vivirán 10 minutos en cache, serán eliminados pasados esos 10 minutos, para que 5 minutos después sean agregados de nuevo en la cache de forma infortunada.

Así pues, tendríamos implementados sobre la capa de abstracción de cache, una capa de estadísticas y otra de filtrado que nos ayude a determinar el comportamiento de los objetos en la cache.
Como vemos, existen algunas alternativas para hacer que nuestra estrategia de cache sea más asertiva y eficiente, mas adelante espero presentar un diagrama de clases e implementaciones de cómo lograr algunas de estas para que puedan darse una idea más clara de cómo implementarlas.

Pura vida,
J

lunes, diciembre 09, 2013

Ordenamiento de numeros flotantes en sistemas de persistencia

Contexto

Necesidad de almacenar numeros flotantes en un sistema de persistencia java (en el caso particular es para Lucene/Solr, pero podria bien ser para MySql, Mongo o cualquier otro sistema de persistencia) Una vez almacenados esos flotantes, debemos hacer ordenamientos por ellos y a su vez obtenerlos y formatearlos de tal manera que se muestren en una presicion de 2 decimales.

Es decir un numero como 12.542323
Debera ser presentado como 12.54

Solución inicial

Lo normal sería almacenar el valor tal cual en el sistema de persistencia y despues con un objeto Decimal Format darle formato con precisión 2.
Sin embargo con numeros flotantes, Java no asegurá total exactitud en este calculo y fue constatado pues a la hora de pasar un flotante cuya mantiza se encuentra cerca de .99 se redonde al siguiente numero, lo cual resultaba en un comportamiento perjudicial.
Una solución sería cambiar todo por numero double, estos funcionan bastante bien y el Decimal Format también funciona de forma decente, sin embargo existe una solución un poco mas estable y segura que comentamos acontinuación.

Solución alternativa

Pasar el numero flotante a un entero cuyos ultimos dos digitos serían los dos decimales de precisión que se requieren desplegar.
Veamos como sería el asunto:
Si se tienen numeros como:

12.546676
12.073847
guardariamos numeros tales como:

1254
1207
A la hora de formatear estos numeros, simplemente sacariamos los ultimos de digitos y se mostrarían como:

12.54
12.07
La solución es muy segura, y para los flotantes de 30 bits o menos funciona bien (pues se ocupan dos para los decimales) así que si no tienes numeros muy grandes una buena solución.

J

jueves, junio 13, 2013

Canvas - Dibujando poligonos con Javascript (Herencia y pseudo polimorfismo)

En el ejemplo anterior definimos de una manera muy basica varios poligonos, bueno decidi buscar la manera de pintarlos en un lienzo (canvas) y aqui el resultado (ojo necesitas un browser que soporte HTML 5)

Lo primero el HTML


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Figure Example</title>
</head>
<body>

    <canvas id="myCanvas" width="300" height="300" style="border: solid 1px black"></canvas>


    <script type="text/javascript" src="figure.js"></script>

</body>
</html>

Note q al javascript le hemos dado el nombre de figure.js

Seguidamente el codigo con el canvas, es bastante sencillo para mas detalles busque la documentacion de cada metodo.



/**
 * User: jsanca
 * Date: 6/12/13
 * Time: 11:10 PM
 */

// Defines a point
function Point (x, y) {

    this.x = x;
    this.y = y;
} // Point


// Basic definition of a Figure, just a point in nowhere.
function Figure (point, name) {

    this.point = point;
    this.name = name;

    this.render = function (context) {

        console.log("figure: " + this.name + " " +this.point);
    }
} // Figure.

// the points defines some of the sides
function Square (point1, point2, name) {

    Figure.call(this, point1, name);
    this.point2 = point2;

    this.side = function () {

        var side =
            (this.point2.x == this.point.x)?
                this.point2.y - this.point.y:
                this.point2.x - this.point.x;

        return Math.abs(side);
    }

    this.area = function () {

        var side = this.side();

        return Math.pow(side, 2);
    }

    this.render = function (context) {

        // render out the square.
        var side = this.side();

        context.fillStyle = "rgb(0,0,255)";
        context.fillRect (this.point.x, this.point.y, side, side);

        console.log("square: " + this.name + ", area = " + this.area());
    }
} // Square.

Square.prototype.parent = Figure.prototype;
Square.prototype.constructor = Square;

// the points defines the point angles
function Triangle (point1, point2, point3, name) {

    Figure.call(this, point1, name);
    this.point2 = point2;
    this.point3 = point3;

    this.area = function () {

        var base = 0;
        var verticalHeight = 0;

        if (this.point2.x == this.point.x) {

            base = this.point2.y - this.point.y;
            verticalHeight = this.point.x - this.point3.x;
        } else {

            base = this.point2.x - this.point.x;
            verticalHeight = this.point.y - this.point3.y;
        }


        return 0.5 * Math.abs(base) * Math.abs(verticalHeight);
    }

    this.render = function (context) {

        console.log("triangle: " + this.name + ", area = " + this.area());

        // the triangle tooks a base point and them move to the rest of the angle point
        context.fillStyle = "rgb(255,0,0)";

        context.beginPath();
        context.moveTo(this.point.x,this.point.y);
        context.lineTo(this.point2.x,this.point2.y);
        context.lineTo(this.point3.x,this.point3.y);

        context.fill();
    }
} // Triangle.

Triangle.prototype.parent = Figure.prototype;
Triangle.prototype.constructor = Triangle;

// the points you pass are the diagonal of the rectangle.
function Rectangle (point1, point2, name) {

    Figure.call(this, point1, name);
    this.point2 = point2;

    this.width = function () {

        return Math.abs(this.point2.x - this.point.x);
    }

    this.height = function () {

        return Math.abs(this.point2.y - this.point.y);
    }


    this.area = function () {

        return this.width() * this.height();
    }

    this.render = function (context) {

        console.log("rectangle: " + this.name + ", area = " + this.area());

        context.fillStyle = "rgb(0,255, 0)";
        context.fillRect (this.point.x, this.point.y, this.width(), this.height());

    }
} // Rectangle.

Rectangle.prototype.parent = Figure.prototype;
Rectangle.prototype.constructor = Rectangle;

// the points defines the radius
function Circle (point1, point2, name) {

    Figure.call(this, point1, name);
    this.point2 = point2;

    this.radius = function () {

        var radius =
            (this.point2.x == this.point.x)?
                this.point2.y - this.point.y:
                this.point2.x - this.point.x;

        return Math.abs(radius);
    }

    this.circumference = function () {

        var radius = this.radius();

        return 2 * Math.PI * radius;
    }

    this.area = function () {

        var radius = this.radius();

        return Math.PI * Math.pow(radius, 2);
    }

    this.render = function (context) {

        var radius = this.radius();

        console.log("circle: " + this.name + ", area = " + this.area() + ", circumference = " + this.circumference() + ", radius = " + radius);

        context.fillStyle = "rgb(255,255, 0)";

        context.beginPath();
        context.arc(this.point.x, this.point.y, radius , 0,2 * Math.PI);
        context.stroke();
    }
} // Circle.

Circle.prototype.parent = Figure.prototype;
Circle.prototype.constructor = Circle;

console.log("Running");
var figuresArray = new Array(
    new Square  (new Point(10,10), new Point(10,30), "Cuadrado 1"),
    new Square  (new Point(10,50), new Point(20,50), "Cuadrado 2"),
    new Triangle(new Point(10,10), new Point(10,20), new Point(0,15), "Mi Triangulito"),
    new Triangle(new Point(100,100), new Point(60,160), new Point(160,160), "Triangulo grande"),
    new Rectangle(new Point(80,50), new Point(90,80), "Mi Rectangulo"),
    new Circle(new Point(200,200), new Point(200,225), "Mi Circulo")
);

var canvas = document.getElementById('myCanvas');
var context =  (canvas.getContext)?
    canvas.getContext('2d'):null;


    figuresArray.forEach(
    function(entry) {

        console.log(entry);
        entry.render(context);
    }
);


Ejecutalo para ver el resultado :) cada figura tiene un color diferente