IndexedDB es también un ejemplo muy ilustrativo de cómo evolucionan los estándares. Por medio de grupos de trabajo de estándares y el HTML5 Labs (un sitio web que publica prototipos de implementación de diversas especificaciones de HTML5 que podemos probar y comentar), IndexedDB pronto va a estar lista para su uso en los principales sitios web.
Si aún no conoces IndexedDB, puedes empezar por aquí:

La Preliminar de Plataforma de Internet Explorer 10 ya viene con soporte para IndexedDB. También puedes hacerte con alguna de las últimas versiones de Google Chrome o Firefox y con esto tenemos ya todo lo necesario.
Desde el punto de vista del modelo de datos, no puede ser más simple. La aplicación permite al usuario escribir notas de texto y etiquetarlas con ciertas palabras clave. Cada nota tendrá un identificador exclusivo que le sirve de clave y, aparte del texto de la nota, se asociará con una serie de cadenas de texto que son las etiquetas.
Este es un ejemplo de objeto de nota representado en la notación concreta de objeto en JavaScript:
var note = {
id: 1,
text: "Note text.",
tags: ["sample", "test"]
};
Vamos a crear un objeto NotesStore con la siguiente interfaz:
var NotesStore = {
init: function(callback) {
},
addNote: function(text, tags, callback) {
},
listNotes: function(callback) {
}
};
Se entiende perfectamente lo que hace cada método. Todas las llamadas a métodos se ejecutan de manera síncrona (es decir, cuando los resultados se devuelven en forma de callbacks), y en los casos en que el resultado se devuelve al llamador, la interfaz acepta una referencia a un callback al cual se invoca con el resultado. Vamos a ver qué necesitamos para implementar este objeto de manera eficiente utilizando una base de datos indexada.
if(window["indexedDB"] === undefined) {
// nope, no IndexedDB!
} else {
// yep, we're good to go!
}
Si no, podemos utilizar también la librería JavaScript llamada Modernizr para comprobar si tenemos soporte para IndexedDB:
if(Modernizr.indexeddb) {
// yep, go indexeddb!
} else {
// bleh! No joy!
}
El aspecto típico de una llamada sería así:
var req = someAsyncCall();
req.onsuccess = function() {
// handle success case
};
req.onerror = function() {
// handle error
};
Cuando empieces a trabajar en serio con el API indexedDB, puede que te resulte algo complicado mantener bajo control todos los callbacks. Para hacerlo todo algo más sencillo, voy a definir y emplear una pequeña rutina de utilidad que abstrae el patrón "request":
var Utils = {
errorHandler: function(cb) {
return function(e) {
if(cb) {
cb(e);
} else {
throw e;
}
};
},
request: function (req, callback, err_callback) {
if (callback) {
req.onsuccess = function (e) {
callback(e);
};
}
req.onerror = errorHandler(err_callback);
}
};
Ahora puedo escribir las llamadas asíncronas de esta forma:
Utils.request(someAsyncCall(), function(e) {
// handle completion of call
});
Esta es una implementación del método init del objeto NoteStore:
var NotesStore = {
name: "notes-db",
db: null,
ver: "1.0",
init: function(callback) {
var self = this;
callback = callback || function () { };
Utils.request(window.indexedDB.open("open", this.name), function(e) {
self.db = e.result;
callback();
});
},
...
El método open abre la base de datos si existe ya. Si no existe, la genera. Podemos considerar que este objeto representa la conexión a la base de datos. Cuando se destruye este objeto, la conexión a la base de datos se termina.
Ahora que ya existe la base de datos, vamos a crear el resto de objetos de la misma, pero antes tenemos que conocer algunos constructos importantes de IndexedDB.
Cada base de datos puede contener múltiples almacenes de objetos, y cada uno de ellos contiene una colección de registros. Cada registro es un simple par clave-valor. Las claves deben identificar de manera exclusiva a un registro particular y se pueden generar automáticamente. Los registros de un almacén de objetos se clasifican de forma automática por sus claves, en sentido ascendente. Y finalmente, los almacenes de objetos se pueden crear y borrar solo dentro del contexto de transacciones de "cambio de versión" (lo veremos más adelante).
Las claves pueden ser claves "en línea" o no. El término "en línea" se refiere a que le indicamos a IndexedDB que la clave de un registro concreto forma parte del propio valor del objeto. En nuestro ejemplo de registro de notas, por ejemplo, cada objeto de nota tiene una propiedad id que contiene el identificador único de una nota concreta. Es un ejemplo de clave "en línea", es decir, que la clave forma parte del valor del objeto.
Siempre que la clave sea "en línea" tenemos que indicar una "ruta de clave", una cadena que indica cómo debe extraerse el valor de la clave desde el valor del objeto.
La ruta de clave para los objetos "notes", por ejemplo, es la cadena "id" puesto que la clave se puede extraer de las instancias de las notas accediendo a la propiedad "id". Pero este esquema nos permite guardar el valor de la clave a cualquier profundidad dentro de la jerarquía de miembros del objeto. Veamos el siguiente ejemplo de instancia de objeto:
var product = {
info: {
name: "Towel",
type: "Indispensable hitchhiker item",
},
identity: {
server: {
value: "T01"
},
client: {
value: "TC01"
},
},
price: "Priceless"
};
En este caso, la ruta a la clave podría ser:
identity.client.value
Es muy útil a la hora de hacer cambios en el modelo de datos de la base de datos y cuando se desea propagar tales cambios a los clientes actuales que tienen una versión anterior del modelo de datos. Basta con cambiar el número de versión en la nueva estructura y comprobar este valor la siguiente vez que el usuario ejecute la aplicación. Si es preciso, actualizaremos la estructura, migraremos los datos y cambiaremos el número de versión.
Los cambios en el número de versión se pueden hacer dentro del contexto de una transacción de "cambio de versión", pero antes de meternos en esto, vamos a ver brevemente en qué consisten las "transacciones".
Vamos a ello, extendiendo el método init del objeto NotesStore para incluir la creación del almacén de objetos. He remarcado con negrita la parte del código que cambia.
var NotesStore = {
name: "notes-db",
store_name: "notes-store",
store_key_path: "id",
db: null,
ver: "1.0",
init: function (callback) {
var self = this;
callback = callback || function () { };
Utils.request(window.indexedDB.open("open", this.name), function (e) {
self.db = e.result;
// if the version of this db is not equal to
// self.version then change the version
if (self.db.version !== self.version) {
Utils.request(self.db.setVersion(self.ver), function (e2) {
var txn = e2.result;
// create object store
self.db.createObjectStore(self.store_name,
self.store_key_path,
true);
txn.commit();
callback();
});
} else {
callback();
}
});
},
...
Los almacenes de objetos se crean por medio de una llamada al método createObjectStore en el objeto de base de datos. El primer parámetro es el nombre del almacén de objetos. Va seguido por la cadena que identifica la ruta de la clave y finalmente un flag booleano que indica si la clave debe generarla automáticamente la base de datos a medida que se añadan nuevos registros.
...
addNote: function (text, tags, callback) {
var self = this;
callback = callback || function () { };
var txn = self.db.transaction(null, TransactionMode.ReadWrite);
var store = txn.objectStore(self.store_name);
Utils.request(store.put({
text: text,
tags: tags
}), function (e) {
txn.commit();
callback();
});
},
...
El método se puede subdividir en los pasos siguientes:
// IndexedDB transaction mode constants
var TransactionMode = {
ReadWrite: 0,
ReadOnly: 1,
VersionChange: 2
};
listNotes: function (callback) {
var self = this,
txn = self.db.transaction(null, TransactionMode.ReadOnly),
notes = [],
store = txn.objectStore(self.store_name);
Utils.request(store.openCursor(), function (e) {
var cursor = e.result,
iterate = function () {
Utils.request(cursor.move(), function (e2) {
// if "result" is true then we have data else
// we have reached end of line
if (e2.result) {
notes.push(cursor.value);
// recursively get next record
iterate();
}
else {
// we are done retrieving rows; invoke callback
txn.commit();
callback(notes);
}
});
};
// set the ball rolling by calling iterate for the first row
iterate();
});
},
Veamos los pasos que hemos seguido en esta implementación:
Y ahora, si tienes ganas de seguir adelante, el documento de especificación del W3C es una buena referencia y ¡es tan escueto que se lee perfectamente! Yo te recomiendo que pruebes esta nueva tecnología, ya que tener acceso a una base de datos funcional en el lado del cliente nos abre todo un mundo de nuevos escenarios para nuestras aplicaciones web.
Otro recurso muy interesante es el Ejemplo de IndexedDB/AppCache en el sitio web IE Test Drive. Este ejemplo describe un escenario donde estas dos especificaciones se complementan mutuamente para conseguir una experiencia de usuario avanzada, ¡aunque no esté conectado a Internet! El ejemplo hace también una demostración de nuevas funcionalidades de IE10, como las transiciones y transformaciones 3D basadas en CSS3.
![]() wambert... | Compatibilidad | 11/7/2012 |