Configurando el VIM para que suba por FTP

Tanto en mi trabajo como en mis proyectos personales utilizo como editor de texto y entorno de desarrollo el vim. Me gustan los entornos livianos y que respondan con rapidez cuando escribo.

Al usar únicamente el vim, cuando tengo que subir un fichero al servidor, me apoyo un un cliente de FTP, en este caso, el Filezilla. Y claro, pierdo tiempo entre :w!, CMD + TAB y por último arrastrar el fichero.

Para ser más productivo, decidí que ya era hora de mirar la manera de poder subir un fichero directamente desde el propio vim con alguna combinación de teclas.

Me puse manos a la obra y después de un rato buscando, encontré los plugins que me harían falta para lograrlo.

El primero de ellos y el más importante fue el netrw. Este plugin permite realizar operaciones de lectura y escritura en red, es decir permite editar ficheros en remoto y subir ficheros por FTP, SFTP, SCP e incluso RSYNC.

El segundo que hace falta es el localvimrc. Este plugin permite definir una configuración especifica para cada proyecto. En mi caso lo uso para definir el comando que voy a usar para subir el fichero que estoy editando al servidor.

Una vez instalados ambos plugins, lo que hay que hacer es ir a la carpeta local donde tienes el proyecto y crear el fichero .lvimrc, dentro del cual vamos a meter un comando similar a éste:

map <leader>S :execute 'Nwrite ftp://www.javray.com//home/miproyecto/' . expand("%")

Yo he definido <leader>S para subir el fichero que estoy editando, pero se puede definir la combinación que os sea más comoda. También se pueden definir distintos comandos.

Eso sí, al usar el protocolo FTP para que no me pida la clave he tenido que usar el fichero .netrc, en el cual he definido el servidor, el login y el password. Una opción más segura sería subir el fichero por SCP y meter la clave publica de mi ordenador en el servidor.

¿Lo siguiente?

Crear un comando que me suba al repositorio el fichero, a ver si investigo un poco y os lo cuento.

Debug remoto

Estoy probando una aplicación en el iPad que básicamente es una UIWebView que accede a una página de Internet que es la que realmente tiene la aplicación.

En el navegador de escritorio todo funciona perfectamente, pero a la hora de probarlo en el iPad he detectado un comportamiento distinto y necesito saber qué es lo que pasa y por dónde va el flujo de ejecución.

Para ello existen muchas soluciones, bastante complicadas desde mi punto de vista, que pueden servir para ver qué es lo que está pasando como por ejemplo: Enabling Remote Debugging via Private APIs in Mobile Safari.

Pero no me acaba de convencer, así que he optado por otra solución, que leí hace un tiempo y no recuerdo dónde.

La idea es redefinir el objeto console para que realice una petición a un servidor enviando el contenido con el que se le ha invocado, algo así como un log remoto.

Para ello con el siguiente código se puede conseguir:

(function(window, undefined) {
    var console_old = window.console,
        console = {};

    console.log = function() {
        var xhr = new XMLHttpRequest();
        
        xhr.open('POST', '/console.php', true);
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        xhr.send('log=' + JSON.stringify(arguments[0]));

        console_old.log(arguments[0]);
    };

    console.info = console.log;
    console.warn = console.log;

    window.console = console;

})(window);

El código en el servidor sería el siguiente:

 exec("echo '".$_POST['log']."' >> /tmp/console.log");    

Con todo lo anterior ya podemos poner debugs por el código y ver en tiempo real por dónde está pasando la aplicación y qué problemas tiene.

Ni que decir tiene que aplicar esto a un desarrollo web o aplicación nos puede servir para poder saber qué tipos de errores les da a los usuarios, eso sí habría que mandar un poquito más de información para poder resolver incidencias que les ocurran.

Github

Hace un tiempo que me hice una cuenta en Github para aprender a utilizar git, ya que me parece una mejor opción que SVN. De momento lo he usado bastante poco, he subido tres proyectos: dos de ellos muy sencillos y un tercero algo más complejo.

Empezando por el más complejo, Galert, es un aplicación para Android hecha en python usando SL4A, la tengo publicada en Google Play, como es gratuita y es más un experimento que otra cosa, he decidido liberar lo que he hecho:

https://github.com/javray/Galert

La siguiente es un sencillo script en PHP que coge un RSS y lo transforma en comandos SHELL para que se vea con colores en el terminal:

https://github.com/javray/RSS-Colorize

Y la última, es una única página que contiene HTML y Javascript, la cual sirve para realizar un sorteo entre varios participantes. La idea es que tienes los participantes en un fichero de texto, arrastras éste a la aplicación, eliges cuántos ganadores quieres y le das a sortear:

https://github.com/javray/Sorteo

Espero seguir publicando más aplicaciones que se me vayan ocurriendo, ya que es bueno compartir el código para aprender.

Números y rendimiento

Sigo buscando que mis aplicaciones hechas en javascript se ejecuten más rápido, para ello recientemente he descubierto unos trucos para para poder acelerar la conversión de strings en números.

Para ello, en vez de usar parseInt y parseFloat que son increiblemente lentos, uso el operador unario ‘+’:

//var numero = parseInt('12');
var numero = +('12');

//var numero = parseFloat('12.1');
var numero = +('12.1');

La razón de que el operador uniario sea más rápido es que éste no realiza una comprobación de los límites superiores e inferiores del número, eso sí hay que tener cuidado de que el string sea un número válido.

Referencias:

http://monkeyandcrow.com/blog/js_coercion_performance/
http://jsperf.com/parsing-vs-coercion

Expansión de objetos con variables y métodos privados

Continuando con el apasionante mundo de la creación de objetos en javascript y después de haber aprendido a usar los getters y los setters, me he puesto a definir objetos y quería que todos ellos tuvieran la función de vincularse unos con otros.

Al hacer esto me he visto repitiendo una y otra vez el mismo código para realizar esa vinculación, lo cual no es buen sintoma. Así que me he parado a pensar cómo podría hacer para crear un objeto genérico con esas propiedades para que los demás objetos, por así decirlo, hereden esas propiedades.

Me he puesto manos a la obra y lo primero que he hecho ha sido crear la definición de ese objeto genérico que va a contener, de momento, solo la funcionalidad de vincular.


var Objeto = function() {
    var self = this;

    self._binds = {};

    self.clase = {
      bind: function(v, o, f) {
        self._binds[v] || (self._binds[v] = []);
        self._binds[v].push({
          obj: o,
          func: f
        });
      }
    };

    self._bind = function() {
      self._binds['value'] && self._binds['value'].forEach(function(e) {
        e.func.apply(e.obj, [self.clase]);
      });
    };
  };

Después de tener el objeto genérico y sabiendo que no voy a instanciar ningún objeto real con él, sino que lo voy a usar para crear otro objeto el cual sí será la clase que usaré para instanciar los demás objetos, me he puesto a buscar información sobre constructores, prototipos y demás.

Al cabo de un buen rato he dado con una solución válida para mi problema, ya que yo quiero mantener ciertas variables y ciertos métodos privados; y con la información que había obtenido de mi búsqueda siempre tenía todas las variables a la disposición de la instancia que creaba.

La solución es la siguiente:

var miObjeto = function() {
    var self = this,
        _value = 0;

    // Cogemos las propiedades genéricas

    Objeto.apply(this);

    // Definimos los getters y los setters

    self.clase.__defineGetter__('value', function() {
      return _value;
    });
    self.clase.__defineSetter__('value', function(v) {
      _value = v;
      self._bind(); // Llamamos a la función de vinculación
    });

    return self.clase; // Devolvemos únicamente la clase
  }

Como los constructores en javascript son funciones y todas las funciones tienen el método call, lo que se me ha ocurrido es llamar a la función/constructor Objeto sobre miObjeto, con lo que se consigue que se definan dentro de miObjeto todas las propiedades y métodos que estaban presentes en Objeto.

Luego utilizo la otra manera de definir getters y setters, que tienen javascript, para definir las demás propiedades de miObjeto y por último devuelvo el objeto clase que contiene los métodos públicos que deseo que tenga la instancia que utilice dicha clase.

El uso de estas nuevas clases es igual que en el artículo anterior, con la diferencia de que si ahora creo, por ejemplo, un miObjeto2, no tengo que volver a implementar la funcionalidad de vincular ya que la puedo heredar de Objeto.

var miObjeto2 = function() {
    var self = this,
        _enable = 0;

    // Cogemos las propiedades genéricas

    Objeto.apply(this);

    // Definimos los getters y los setters

    self.clase.__defineGetter__('enable', function() {
      return _enable;
    });
    self.clase.__defineSetter__('enable', function(e) {
      _enable = e;
      self._bind(); // Llamamos a la función de vinculación
    });

    return self.clase; // Devolvemos únicamente la clase
  }
  var miObjetoReal = new miObjeto();
  var miObjetoReal2 = new miObjeto2();
  miObjetoReal.bind('value', miObjetoReal2, function(e) {
    (e.value != '') && (this.enable = 1) || (this.enable = 0);
  });

También a estos objetos se les podría añadir la vinculación con un elemento DOM, como en el artículo anterior, y tendríamos unos objetos con los que trabajar sin preocuparnos de las actualizaciones de la interfaz.

Referencias

http://www.webdeveloper.com/forum/showthread.php?t=243971
http://www.3site.eu/doc/
http://stackoverflow.com/questions/6039676/copying-javascript-getters-setters-to-another-prototype-object

Getters, Setters y vinculación de objetos

Recientemente he estado leyendo algunos artículos de javascript en los que se explicaban las bondades de los getters y los setters y cómo podemos utilizarlos para darles más funcionalidad a nuestras clases.

Un Ejemplo sencillo sería el siguiente:

  var MiClase = function() {
    var _value = 0;
    var clase = {
      get value() {
         return _value;
      },
      set value(v) {
         _value = v;
      },
    };
    return clase;
  };

Lo definido arriba es equivalente a esto:

  var MiClase = function() {
    this.value = 0;
  };

Pero claro, ¿para qué hacer algo más complicado si no se obtiene ningún beneficio?, la respuesta está en que al ser los getters y los setters funciones, podemos añadir más acciones dentro de estas a parte de las de devolución y asignación de variables.

Uno de los usos que se le puede dar es vincular un objeto que creemos a través de la clase con un objeto DOM, como por ejemplo una caja de texto:

  var MiClase = function(t) {
    var _value = 0,
        _obj = document.getElementBy(t);

    var clase = {
      get value() {
         return _value;
      },
      set value(v) {
         _value = v;
         _obj.value = v;
      },
    };

    _obj.addEventListener('blur', function() {
      clase.value = this.value;
    }, false);

    return clase;
  };

  var miObjeto = new MiClase('micaja');

Con el código de arriba conseguimos vincular el objeto miObjeto con la caja de texto de identificador micaja, de manera que siempre que introduzcas algo en la caja de texto se actualizará la propiedad value del objeto miObjeto y viceversa.

Podemos complicar un poco más la definición de la clase para añadir vinculación entre objetos, es decir, yo quiero vincular dos objetos a través de una propiedad de uno de ellos, de manera que cuando actualice la propiedad en el objeto origen en el objeto destino se realicen una serie de acciones. Para todo esto, la clase quedaría de la siguiente manera:

var MiClase = function(t) {
    var _value = 0,
        _obj = document.getElementBy(t),
        _binds = {};

    var clase = {
      get value() {
         return _value;
      },
      set value(v) {
         _value = v;
         _obj.value = v;
         var self = this;
         _binds['value'] && _binds['value'].forEach(function(e) {
           e.func.apply(e.obj, [self]);
         });
      },
      bind: function(v, o, f) {
        _binds[v] || (_binds[v] = []);
        _binds[v].push({
          obj: o,
          func: f
        });
      }
    };

    _obj.addEventListener('blur', function() {
      clase.value = this.value;
    }, false);

    return clase;
  };

Lo que hemos hecho ha sido añadir un objecto que contiene un array de vinculaciones, _binds, y una función bind que nos permite asociar objetos. También hemos añadido en el setter la ejecución de las funciones de vinculación que pueda tener el objeto pasando como parametro el propio objeto para que puedan interactuar con él.

Ahora el código para realizar las vinculaciones:

  var miObjeto1 = new MiClase('caja1');
  var miObjeto2 = new MiClase('caja2');

  miObjeto1.bind('value', miObjeto2, function(e) {
    this.value = e.value + '(copia)';
  });

Con el código de arriba lo que hemos hecho ha sido vincular las cajas de texto caja1 y caja2 con los objetos miObjeto1 y miObjeto2 respectivamente y después hemos vinculados los dos objetos entre sí por medio de la propiedad value de miObjeto1 de manera que cuando ésta cambie se llamará a la función de vinculación. En este ejemplo sencillo, cada vez que se cambia la propiedad value de miObjeto1 se le asigna a la propiedad value de miObjeto2 el mismo valor más el texto “(copia)”.

Dentro de la función de vinculación, this hace referencia al objeto destino, en nuestro ejemplo, miObjeto2 y el parámetro e hace referencia al objeto origen, en el ejemplo, miObjeto1.

Referencias
JavaScript getters and setters: varying approaches
Javascript getters and setters for dummies?
Working with objects

Copia de un array en Javascript

En casi todos los desarrollos es común el uso del tipo array para almacenar elementos. Muchas veces queremos guardar una copia de una array por si lo necesitamos restaurar de nuevo a su estado inicial. En tal caso haríamos algo similar a esto:

var a = [1, 2];
var b = a;

Ahora si modificamos un elemento de a:

a[0] = 3;

Podremos restaurar de nuevo nuestro array que teníamos en b, ¿No?

a = b;

La respuesta es NO, ya que si vemos el contenido de b comprobaremos que el elemento 0 también se ha modificado. Lo que ha sucedido con la asignación entre dos variables, una de las cuales es de tipo array, es que hemos creado un puntero al mismo objeto y por tanto no hemos guardado un array con los valores iniciales.

Para conseguir nuestro objetivo tendremos que recurrir a un pequeño truco que crea un nuevo array:

var b = [].concat(a);

El código de arriba lo que hace es crear un nuevo array que es el resultado de concatenar a un array vacío el array a. De esta manera, modificando los valores del array a conseguimos que los valores del array b queden inalterados.

querySelector / querySelectorAll

Estamos tan acostumbrados a los frameworks tipo jQuery que se nos olvida que también existe una forma dentro de javascript para realizar una búsqueda de elementos dentro del DOM mediante un selector. La manera de hacerlo es mediante los métodos querySelector y querySelectorAll.

La forma de usarlos es igual que con jQuery:

document.querySelector('#mi_id')
document.querySelector('.mi_classe')

La diferencia entre querySelector y querySelectorAll es que el primero nos devuelve el primer elemento que cumple el selector y el segundo nos devuelve una lista de nodos.

¿Para que nos puede ser útil esto? Por ejemplo, para realizar ciertos selectors en un proyecto sin tener que cargar jQuery o cuando no nos es posible cargar jQuery porque lo que estamos modificando es de una tercera persona.

Otro uso un poco más rebuscado sería el que le he dado yo hoy: Estaba trabajando con una BBDD desde phpMyAdmin y quería seleccionar todas las tablas que empezarán por ps_ y claro estamos hablando de más de 100 tablas y hacerlo a mano es un poco rollo.

Así que para facilitar mi trabajo, he abierto la consola del Chrome y que tecleado el siguiente código:

for (var i=0, lista = window.frames[1].document.querySelectorAll('input[value*=ps_]'), limite = lista.length; i < limite; i += 1) { lista[i].setAttribute('checked', 'checked');}

Con esto consigo que se me seleccionen todos los checkbox y puedo borrarlos todos de una.

Prevenir el bloqueo de la UI

Con el auge de las llamadas asíncronas a través de peticiones AJAX me he acostumbrado a que todas las operaciones con javascript no bloqueen la interfaz gráfica de usuario y que cuando los datos estén listos ésta se actualice sin que el usuario se vea perjudicado por la obtención y procesado de los datos.

Pero claro, ¿qué pasa si los datos ya los tenemos cacheados y tenemos que procesarlos? Lo que ocurre por lo general es que se nos bloquea la interfaz de usuario y mientras se procesan el navegador o el dispositivo móvil se queda sin responder.

Una solución sencilla a este problema es provocar una llamada asincrona de la función que procesa los datos. Para ello podemos utilizar la función de setTimeout de javascript de la siguiente manera:

setTimeout(function() {
    procesadoDeDatos();
}, 100);

Con esto conseguimos que la ejecución de la función procesadoDeDatos se haga de manera asincrona y no se bloquee la interfaz esperan a que acabe el procesado.

Así podemos poner el típico mensaje de procesando, para que el usuario sepa que se está procesando su petición y éste a su vez si quiere puede seguir interactuando con la interfaz mientras espera que se le devuelvan los datos.

Cache de imágenes

Sigo con el desarrollo de mi aplicación para iPad y el siguiente muro con el que me he topado ha sido el tema de cachear las imágenes. Mi aplicación accede a muchas imágenes que están en Internet y por tanto el consumo de ancho de banda es considerable. Lo que quería es que una vez que se accediera a la imagen, está se almacenara en cache y no se tuviera que volver a pedir.

Después de mucha investigación y muchas pruebas, he dado con dos soluciones: una para cuando el número de imágenes a almacenar no es muy grande (este no es mi caso) y otra que de momento promete guardar todas la imágenes que quiera.

LocalStorage, Web DB SQL y Canvas

La primera solución consiste en utilizar una combinación de localStorage y Web DB SQL para almacenar las imágenes y Canvas para obtener la versión base64 de la misma.

Lo primero que necesitamos por tanto es una función a la que le pasamos una URL de una imagen y nos devuelve una representación en base64 de la misma:

function getImgBase64(imagen, callback) {
    var img = document.createElement('img'),
        canvas = document.createElement('canvas'),
        ctx = canvas.getContext('2d');
    img.src = imagen;
    
    img.addEventListener('load', function() {
        canvas.width = this.width;
        canvas.height = this.height;
        ctx.drawImage(this, 0, 0);
        callback(canvas.toDataURL('image'));
    }, false);
}

Ahora que tenemos la representación de la imagen en base64 necesitamos almacenarla de manera persistente, para ello tenemos localStorage y Web DB SQL. Toca enfrentarse a los límites de tamaño, localStorage permite almacenar 5M y Web DB SQL permite 50M que los puedes repartir en unos 10 BBDD de 5M sin tener que preguntar al usuario si quiere permitir crear una BBDD de tamaño mayor. Cuando estos límites se alcanzan se deja de guardar información.

Si tenemos claro que las imágenes que queremos guardar van a ocupar menos de 5M usaremos localStorage, si es mayor que 5M e inferior a 50M usaremos una combinación de ambas, para minimizar el acceso al Web DB SQL ya que a parte de ser asincrono tiene un retardo, menor que si accediéramos a Internet, pero un retardo que puede quitar fluidez a la aplicación.

La función que almacena en el localStorage sería la siguiente:

function setImg(ref, img) {
    getImgBase64(img, function(base64) {
        var imagenes = localStorage.imagenes && JSON.parse(localStorage.imagenes) || [];
        imagenes[ref] = base64;   
        localStorage.imagenes = JSON.stringify(imagenes);
    });
}

Para obtener la imagen del localStorage:

function getImg(ref) {
     var imagenes = localStorage.imagenes && JSON.parse(localStorage.imagenes) || [];
     return imagenes[ref] || '';
}

La función completa que se encarga de hacer una pre-carga de las imágenes quedaría de la siguiente manera:

function preLoadImg(ref, img) {
   var imagen = new Image(),
       data = getImg(ref);
   
   imagen.addEventListener('load', function() {
        // Acciones una vez que tenemos cargada la imagen.
   }, false);

   if (data === '') {
      imagen.src = img;
      setImg(ref, img);
   } 
   else {
      imagen.src = data; 
   }
}

Para la versión mixta entre localStorage y Web DB SQL, lo que vamos a hacer es almacenar las referencias y estado en localStorage y la imagen codificada en base64 en el Web DB SQL.

Primero vamos a definir qué es lo que guardamos en el localStorage: las referencias a las imágenes que tenemos guardas en Web DB SQL y el número y el tamaño que llevamos en la BBDD actual.

Al empezar, las referencias serán un array vacío y el número y el tamaño serán 0.

Para inicializar la variables y crear la BBDD haremos una función de inicialización:


var DB = [];

function init() {
    var referencias = localStorage.referencias && JSON.parse(localStorage.referencias) || [],
        actual = localStorage.actual && JSON.parse(localStorage.actual) || {n: 0, t:0},
        DBI;
    
    for (var i = 0; i <= actual.n; i+=1) {
         DBI = openDatabase('Imagenes_' + i, '1.0', 'Imagenes ' + i, 4.9 * 1024 * 1024);
         DBI.transaction(function(t) {
                t.executeSql('CREATE TABLE IF NOT EXISTS Imagenes(ref TEXT NOT NULL, img TEXT NOT NULL, PRIMARY KEY(ref))', []);
            });

         DB.push(DBI);
    }
    
}

En la función de inicialización, la primera vez que se inicia se crea la BBDD Imagenes_0 y las demás veces se abren las BBDD que llevemos usadas hasta el momento.

La función que guarda una imagen en BBDD es algo más complicada que su versión únicamente con localStorage ya que tenemos que tener en cuenta el tamaño que llevamos en la BBDD actual y la creación de una nueva BBDD en caso de que sea necesario.

Como podemos ver defino una variable global DB donde voy a ir almacenando las conexiones a las distintas BBDD.

function setImgDB(ref, img) {
    getImgBase64(img, function(base64) {
        var actual = localStorage.actual && JSON.parse(localStorage.actual) || {n: 0; t: 0},
            referencias = localStorage.referencias && JSON.parse(localStorage.referencias) || [];
        
        if (actual.t >= 1810000) {
            
            actual.n += 1;
            actual.t = base64.length;
            
            referencias[ref] = actual.n;

            localStorage.actual = JSON.stringify(actual);
            localStorage.referencias = JSON.stringify(refrencias);
            
            DBI = openDatabase('Imagenes_' + actual.n , '1.0', 'Imagenes ' + actual.n, 4.9 * 1024 * 1024);
            DBI.transaction(function(t) {
                    t.executeSql('CREATE TABLE IF NOT EXISTS Imagenes(ref TEXT NOT NULL, img TEXT NOT NULL, PRIMARY KEY(ref))', [], function(t, r) {
                            t.executeSql('REPLACE INTO Imagenes(ref, img) VALUES(?, ?)', [ref, base64], function(t, r) {
                                console.log('Insertamos la imagen: ' + ref + ' en BBDD ' + actual.n);
                            });
                    });
                });

            DB.push(DBI); 
        }
        else {
            
            actual.t += base64.length;
            
            referencias[ref] = actual.n;

            localStorage.actual = JSON.stringify(actual);
            localStorage.referencias = JSON.stringify(refrencias);
     
            DB[actual.n].transaction(function(t) {
                    t.executeSql('REPLACE INTO Imagenes(ref, img) VALUES(?, ?)', [ref, base64], function(t, r) {
                        console.log('Insertamos la imagen: ' + ref + ' en BBDD ' + actual.n);
                    });
                });

        }
    });
}

El número 1810000 es el tamaño máximo aproximado de imágenes que se pueden almacenar, es un tamaño totalmente empirico y que se podría afinar un poco más.

Ahora necesitamos la función para obtener una imagen de la BBDD:

function getImgDB(ref, callback) {
     var referencias = localStorage.referencias && JSON.parse(localStorage.referencias) || [],
         n = referencias[ref] || -1;
     
     if (n !== -1) {
         DB[n].transaction(function(t) {
                    t.executeSql('SELECT img FROM Imagenes WHERE ref = ?', [ref], function(t, r) {
                        if (r.rows.length > 0) {
                            callback(r.rows.item(0).img);
                        }
                        else {
                           callback('');
                        }
                    });
                });
     }
     else {
         callback('');
     }
}

Y ahora la función de pre-carga de imágenes:

function preLoadImg(ref, img) {
   var imagen = new Image();
   
   getImgDB(ref, function(data) {
       if (data === '') {
           imagen.src = img;
           setImgDB(ref, img);
       } 
       else {
          imagen.src = data; 
       }
    });
   
   imagen.addEventListener('load', function() {
        // Acciones una vez que tenemos cargada la imagen.
   }, false);
}

Como he dicho antes, estás dos soluciones son para un número pequeño y controlado de imágenes.

En el siguiente apartado veremos cómo hacerlo cuando no sabemos el número de imágenes a cachear.

API de Phonegap

En está solución necesitamos acceder al dispositivo, por lo tanto necesitamos una API que nos proporcione acceso al mismo, en este caso yo he escogido Phonegap. A parte de la API de Phonegap, necesitamos un Plugin que nos permite descargar ficheros a nuestro dispositivo.

No voy a meterme en cómo se instala el Phonegap ni el plugin, voy a suponer que ya está todo hecho y vamos directamente al código.

Primero una función de inicialización, necesaria para usar las API's de Phonegap


var fileSystem = {};

function init() {
    document.addEventListener("deviceready", function() {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
            fileSystem = fs;
        },
        function() {
            console.log('Error de inicialización');
        });
    }, false);
}

Ahora la función para almacenar una imagen en el dispositivo:

function setImgFile(ref, img) {
    var ext = img.split('.')[3],
        fullPath = fileSystem.root.fullPath + '/',
        prev = 'file://' + fullPath + ref + '.' + ext,
        image = new Image();

   image.addEventListener('error', function() {
       var downloader = new Downloader();

        downloader.downloadFile(
            img,
            fullPath,
            ref + '.' + ext,
            function(file) {
                console.log(file);
            },
            function() {
                console.log('Error al subir la imagen');
        });
   }, false);

   image.src = prev;
}

Para la función anterior, suponemos que la imagen está ya en nuestro dispositivo, y por tanto le asignamos el path que suponemos que tiene, en caso de error es cuando nos la bajamos al dispositivo, con esto hacemos que sea más rápida la búsqueda de una imagen y no tenemos que acceder al sistema de archivos que es mucho más lento.

Con esta solución no nos hace falta una función para obtener la imagen ya que las imagenes se guardan en el dispositivo en una ubicación conocida, por tanto vamos con la función de pre-carga de imagenes que usará el mismo principio de suponer que la imagen ya está en nuestro dispositivo antes de pedirla a Internet.

function preLoadImg(ref, img) {
    var ext = img.split('.')[3],
        fullPath = fileSystem.root.fullPath + '/',
        prev = 'file://' + fullPath + ref + '.' + ext,
        image = new Image();
    
    image.addEventListener('load', function() {
        // Acciones una vez que tenemos cargada la imagen.
    }, false);

   image.addEventListener('error', function() {
       this.src = img;
   }, false);
  
   image.src = prev;
}

Conclusión

Está claro que cuanto menos ancho de banda consuma una aplicación y cuanto más rápida sea en mostrar el contenido al usuario, éste se va a sentir más satisfecho y valorará mejor nuestra aplicación, por tanto debemos de ser cautos a la hora de cachear de manera que no se vea afectado el rendimiento de la aplicación por el acceso y la actualización de la misma.

Quiero destacar que las dos primeras soluciones son perfectamente válidas para una aplicación WEB, la segunda al depender del dispositivo solo servirá para dispositivos móviles, en este caso iPad y iPhone.

Referencias

http://stackoverflow.com/questions/934012/get-image-data-in-javascript
http://docs.phonegap.com/en/1.2.0/phonegap_file_file.md.html#File
https://github.com/aflx/pg_downloader_plugin_ios