IndexedDB: Agregando objetos al almacén

IndexedDB: Tu base de datos local en HTML5

IndexedDB: El camino hasta ahora

Este es el segundo artículo sobre IndexedDB, el sistema de almacenamiento local de HTML5. En el primer artículo «IndexedDB: Tu base de datos local en HTML5» se explicó cómo crear una base de datos y cómo agregarle un almacén de objetos a modo de tabla de nuestra base de datos, con unos índices o llaves: primaria, índice y única.

En este segundo artículo veremos cómo agregar objetos a nuestro almacén. Es recomendable leer el primer artículo puesto que su resultado final es nuestro punto de partido.

Cómo no, aquí dejo la versión en vídeo del artículo:

Primero lo sencillo: Creando el entorno para introducir objetos a nuestro almacén IndexedDB

Lo que queremos lograr en este artículo es incorporar objetos al almacén «people» que existe en la base de datos «object». Bueno, para ello lo primero que tenemos que hacer es crear las etiquetas HTML que permitan al usuario agregar las colecciones. Para ello, lo que vamos a hacer es incorporar tres inputs para los campos que vamos a pedir:

  • Nombre
  • Apellidos
  • DNI

Además, un button que al hacer clic ejecute la función add:

        <input type="text" id="dni" placeholder="Introducir dni" />
        <input type="text" id="name" placeholder="Introducir nombre" /> 
        <input type="text" id="surname" placeholder="Introducir apellidos" />
        <button type="button" onclick="add();">Guardar</button>

Por supuesto, este código va dentro del body. Bueno, ya estamos listos para empezar a insertar objetos en IndexedDB!

IndexedDB y el concepto de transacción

Para interactuar con IndexedDB siempre utilizaremos transacciones. Una transacción es una forma de encapsular una serie de instrucciones que atacan a la base de datos, de forma que o se ejecutan todas o ninguna, pudiendo además cancelar todas las instrucciones una vez se han realizado (rollback).

¿Cómo funciona la transacción en IndexedDB?

En nuestro javascript vamos a crear una función add que será ejecutada cada vez se haga clic en el botón Guardar. En esta función recuperamos la conexión:

            function add() {
                var active = dataBase.result;
            }

Ya tenemos nuestra variable active lista para trabajar. Nuestro siguiente paso es iniciar una transacción, que lo haremos de la siguiente manera:

var data = active.transaction(Array de almacenes, Modo de la transacción);

Los dos parámetros que recibe el método transaction son:

  • Array de almacenes: El primer parámetro es un array con todas las colecciones que van a utilizarse en la transacción. Si sólo se va a utilizar una colección, podemos enviar sólo el nombre del almacén (string) en vez de como array.
  • Modo de la transacción: La transacción se puede iniciar en dos modos: readonly y readwrite. Utilizaremos readonly cuando en la transacción sólo vamos a leer y readwrite cuando vamos a leer y escribir o sólo escribir. Esto significa que si en una transacción en modo readonly queremos agregar un objeto a una colección, se producirá un error.

Como nosotros vamos a querer agregar objetos a nuestro almacén «people», el código es:

var data = active.transaction(["people"], "readwrite");

Ya con nuestra transacción iniciada, podemos empezar a agregar el contenido a nuestras colecciones.

Seleccionando el almacén de IndexedDB dónde agregar el objeto.

Aunque ya tenemos la transacción iniciada, no podemos agregar el objeto sin más. Hay que tener en cuenta que una transacción puede involucrar a varios almacenes de objetos, por lo que antes de nada deberemos indicar sobre qué almacén vamos a trabajar… aunque la transacción sólo se haya iniciado sobre un almacén.

Para ello, tenemos el método objectStore asociado a la transacción:

var object = data.objectStore("people");

Como parámetro, recibe el nombre del almacén a utilizar.

Incorporamos esa línea a nuestro código, teniendo como resultado, por ahora:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>IndexedDB: Almacenamiento local con HTML5</title>
        <script type="text/javascript">
            var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
        
            var dataBase = null;
                
            function startDB() {
            
                dataBase = indexedDB.open("object", 1);
                
                dataBase.onupgradeneeded = function (e) {

                    active = dataBase.result;
                    
                    object = active.createObjectStore("people", { keyPath : 'id', autoIncrement : true });
                    object.createIndex('by_name', 'name', { unique : false });
                    object.createIndex('by_dni', 'dni', { unique : true });
                };

                dataBase.onsuccess = function (e) {
                    alert('Base de datos cargada correctamente');
        
                };
        
                dataBase.onerror = function (e)  {
                    alert('Error cargando la base de datos');
                };
            }
            
            function add() {
                var active = dataBase.result;
                var data = active.transaction(["people"], "readwrite");
                var object = data.objectStore("people");                
            }
            
        </script>
    </head>
    <body onload="startDB();">
        <input type="text" id="dni" placeholder="Introducir dni" />
        <input type="text" id="name" placeholder="Introducir nombre" /> 
        <input type="text" id="surname" placeholder="Introducir apellidos" />
        <button type="button" onclick="add();">Guardar</button>
    </body>
</html>

Agregando el objeto a nuestra colección

Por fin llega el momento… es la hora de agregar un objeto a nuestro almacén en IndexedDB. La sintaxis es extremadamente sencilla:

var request = object.put(objecto)

El parámetro es el objeto a agregar. Hay que tener en cuenta los índices de nuestro almacén, para no equivocarnos con las propiedades mínimas del objeto.

En nuestro caso, las propiedades de nuestro objeto se van a alimentar de lo que el usuario introduce, por lo que el código sería:

                var request = object.put({
                    dni: document.querySelector("#dni").value,
                    name: document.querySelector("#name").value,
                    surname: document.querySelector("#surname").value
                });

Podemos ver cómo no se ha introducido la propiedad «id» puesto que es un valor autoincremental y no tenemos que preocuparnos por él.

Y ya está!! Objeto agregado… ¿o no?

Controlando el resultado

Ya lo hicimos cuando creamos la base de datos y el almacén «people»… y tenemos que volver a hacerlo: Controlar el resultado.

A la hora de agregar un objeto al almacén puede producirse un error… por ejemplo si se intenta almacenar un objeto cuyo valor de dni ya está presente en un objeto de la colección. Por eso mismo, vamos a crear el método onerror sobre request, porque queremos controlar el error en put:

                request.onerror = function (e) {
                    alert(request.error.name + '\n\n' + request.error.message);
                };

Lo que hacemos es mostrar una alerta con el error producido.

De forma pareja al onerror, vamos a declarar un oncomplete que se ejecutará cuando la inserción sea correcta. Pero… este método se declaba en base a data (la transacción) o request (la petición de inserción) ?? Bueno, nosotros vamos a hacerlo sobre data ¿por qué?

Recordemos: Estamos en una transacción. Realmente no nos interesa controlar si una instrucción de la transacción se realiza correctamente, necesitamos controlar que todas se ejecuten correctamente, sólo en ese caso realmente hemos tenido éxito. Lo que vamos a hacer es avisar de que el objeto se agregó correctamente y vaciar los input cubiertos por el usuario:

                data.oncomplete = function (e) {
                    document.querySelector("#dni").value = '';
                    document.querySelector("#name").value = '';
                    document.querySelector("#surname").value = '';
                    alert('Objeto agregado correctamente');
                };

Resultado final

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>IndexedDB: Almacenamiento local con HTML5</title>
        <script type="text/javascript">
            var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
        
            var dataBase = null;
                
            function startDB() {
            
                dataBase = indexedDB.open("object", 1);
                
                dataBase.onupgradeneeded = function (e) {

                    active = dataBase.result;
                    
                    object = active.createObjectStore("people", { keyPath : 'id', autoIncrement : true });
                    object.createIndex('by_name', 'name', { unique : false });
                    object.createIndex('by_dni', 'dni', { unique : true });
                };

                dataBase.onsuccess = function (e) {
                    alert('Base de datos cargada correctamente');
        
                };
        
                dataBase.onerror = function (e)  {
                    alert('Error cargando la base de datos');
                };
            }
            
            function add() {
                var active = dataBase.result;
                var data = active.transaction(["people"], "readwrite");
                var object = data.objectStore("people");

                var request = object.put({
                    dni: document.querySelector("#dni").value,
                    name: document.querySelector("#name").value,
                    surname: document.querySelector("#surname").value
                });

                request.onerror = function (e) {
                    alert(request.error.name + '\n\n' + request.error.message);
                };

                data.oncomplete = function (e) {
                    document.querySelector("#dni").value = '';
                    document.querySelector("#name").value = '';
                    document.querySelector("#surname").value = '';
                    alert('Objeto agregado correctamente');
                };
                
            }
            
        </script>
    </head>
    <body onload="startDB();">
        <input type="text" id="dni" placeholder="Introducir dni" />
        <input type="text" id="name" placeholder="Introducir nombre" /> 
        <input type="text" id="surname" placeholder="Introducir apellidos" />
        <button type="button" onclick="add();">Guardar</button>
    </body>
</html>
Resultado de objeto correctamente agregado al almacén IndexedDB

Resultado de objeto correctamente agregado al almacén IndexedDB

Error al intentar agregar un objeto a un almacén IndexedDB cuando ya existe otro con la misma clave primaria.

Error al intentar agregar un objeto a un almacén IndexedDB cuando ya existe otro con la misma clave primaria.

Próximos pasos

Ya tenemos nuestra base de datos creada con un almacén de objetos (tabla) que contienen los diferentes objetos que conforman los datos almacenados y podemos insertar objetos sin problemas. Los próximos pasos son:

  • Listar los objetos de un almacén.
  • Actualizar objetos de nuestro almacén.
  • Buscar objetos en nuestro almacén.

Recursos

Código en GitHub:
https://github.com/rolando-caldas/IndexedDB/tree/master/src/02-AddObjectIntoStore

Video en mi canal de youtube:
https://youtu.be/ArTj9kRCGwU

16 pensamientos en “IndexedDB: Agregando objetos al almacén

  1. hola de nuevo, intentanto este tuto, me sale el siguiente error «Uncaught NotFoundError: Failed to execute ‘transaction’ on ‘IDBDatabase’: One of the specified object stores was not found.», sinceramente no se k pasa, si es problema del navegador o k , espero k me puedas ayudar, muchas gracias 🙂

  2. Hola, no crea el archivo, donde lo graba para después poder recuperar los datos, utilizo explorer9,no pone tampoco las etiquetas en cada campo, sabes si con este método puedo grabar los datos en una tabla de filemaker, gracias.

  3. Hola rolando, ya hice pruebas con otros navegadores y va bien, se podría ver los datos de una tabla de filemaker con Indexeddb

    1. Buenas nacho, entiendo que no… no conozco filemaker, pero por lo que vi en la web no tiene pinta de ser posible. Otra cosa es que el filemaker te proporcione una api o librería en JS que te permita acceder a los datos de filemaker desde una web… en ese caso podrías hacer un pequeño script JS que utilizase esa api o librería para acceder a las tablas de filemaker y guardar el conenido como objetos en el almacén de indexedDB

      saludos

  4. Hola Rolando,

    Primero de todo agradecerte por este detallado tutorial.

    Queria hacerte una pregunta acerca de request and transaction.

    Volviendo a tu ejemplo del metodo add. Me gustaria saber cual seria la diferencia entre tu codigo y tener:

    request.onsuccess = function (e) {
    document.querySelector(«#dni»).value = »;
    document.querySelector(«#name»).value = »;
    document.querySelector(«#surname»).value = »;
    alert(‘Objeto agregado correctamente’);
    };

    en vez de data.oncomplete. Si de request.onsucess event es trigerred, entinedo que el objeto se ha agregado exitosamente.

    Otra pregunta, en caso de request.onerror, debemos abortar la transacion, o no hace falta hacer nada?

    He visto muchos ejemplos en Internet y se usa mas onsuccess que las transacciones, pero no se si esto es correcto o no se debe hacer de esa forma.

    Te agradezco de antemano cualquier explicacion.

    Saludos,

    Natalia

    1. Buenas Natalia,

      El motivo por el que uso el transaction.oncomplete en lugar del request.onsuccess es por «buenas costumbres». Cuando trabajamos con indexedDB lo hacemos vía transacciones. Esto significa que en una misma transacción podemos realizar varias modificaciones (request) diferentes a nuestra base de datos. El transaction.oncomplete se ejecuta sólo y sólo si todas las request que se realizan finalizan con éxito, en cuanto una falle, se dispararía el evento de error del transaction y, en teoría, la transacción fallaría y se ejecutaría atomáticamente el rollback (siempre se puede forzar con el transaction.abort()).

      Volviendo al ejemplo, debido a que es sencillo, da un poco igual tirar del oncomplete de la transacción o al onsuccess del request… pero piensa en la situación de que en la misma transacción se ejecutan dos request. El primero se hace bien y se dispara el onsucess, momento en el que vacías el formulario… bueno el segundo request falla, la transacción falla etc… pero ya has realizado cambios en la vista, no pudiendo volver a tenerla en el estado anterior. Yo, por buenas costumbres, procuro evitar siempre realizar modificaciones en lo que le mostramos al usaurio hasta que la aplicación puede determinar al 100% qué debe hacer. Si realizo cambios a nivel de transacción en lugar de request, me aseguro siempre que no haga cambios en la vista que no debería hacer. Al mismo tiempo, también me curo en salud… cada SGBD implementa las cosas a su modo y haciendo las modificaciones nivel de transacción en lugar de a nivel de request te aseguras de estar haciéndolo bien independientemente de las particularidades de implementación que pueda tener el SGBD que estés utilizando, el lenguaje etc etc.

      Espero haberme explicado bien 😉

      saludos!

  5. Hola Ronaldo, muchas gracias por tu super rapida respuesta 🙂

    Mira, considerando tu explicacion y hablando claramente, creo que la estoy «cagando» en lo que estoy haciendo. Te comento, tengo 2 stores ui and core. Estos stores estan «relacionados»:

    Cada regsitro en la base de datos esta identificado por un id (nosotros creamos este identificador).

    – El store ui contiene informacion basica que se presenta en pantalla.
    – El store core contiene otra informacion que se presentara dependiendo de la accion del usuario.

    Bueno, lo que quiero es controlar las siguientes operaciones, add, edit, and delete. Quiero asegurarme que cuando el elemento se ha added exitosamente, tengo garantia 100% de que se ha incluido en las 2 stores. Si la inclusion, en alguna de las stores falla, no quiero que se agrege a ninguna de ellas. Lo mismo pasaria con editar y delete. Cual es la mejor forma de controlar esto?

    Opcion a)

    /*
    – db es mi connection abierta a la base de datos.
    – element es el elemento a incluir.
    – callback es la funcion que se ejecutara tras obtener una respuesta.
    */
    function addItem(storeName, element, callback){

    var trans = db.transaction(storeName, «readwrite»);
    var store = trans.objectStore(storeName);

    // add
    var requestAdd = store.add(element);

    requestAdd.onsuccess = function(event) {
    console.log («requestAdd: on success»);
    callback(«Success»);

    };
    requestAdd.onerror = function(event) {
    console.log («requestAdd: on failure»);
    callback(«Error»);
    // failed
    };

    }

    Dada la funcion addItem, lo que hago para agregarlo a las 2 stores es:

    addItem(‘ui’, myUIWorkingObject, function(result){
    if (result ==»Success»)
    {
    console.log(«Element added to ui store»);

    addItem(‘core’, myCoreWorkingObject, function(result){
    if (result ==»Success»)
    {
    console.log(«Element added to core store»);
    }
    else
    {
    console.log(«Element was NOT addeed into the core store»);

    }
    });

    }
    else
    {
    AC.UI.modal.toast(«Element was NOT added to ui store»);
    }
    });

    Pero me da a mi que esto no esta bien… porque si algo fallara al ejecutar addItem(‘core’, myCoreWorkingObject, function(result) (…}) el elemento se habria agregado a la store ui. Y aqui la unica opcion que me quedaria creo que seria intentar borrarlo de la ui store y cruzar los dedos para que nada falle. Asi que no lo veo fiable.

    Opcion b)

    He creado este otro metodo utilizando transaciones en vez de request events. Crees que con este metodo me aseguraria de tener el comportamiento deseado?
    Como puedes ver las variables requestAddUI y requestAddCore nos la estoy usando… asi que no estoy segura de si esto es correcto….

    /*
    – db es mi connection abierta a la base de datos.
    – myUIWorkingObject y myCoreWorkingObject son global variables.
    – callback es la funcion que se ejecutara tras obtener una respuesta.
    */

    function addItem(callback){

    var data = db.transaction([«ui»,»core»], «readwrite»); //transaction initiated.
    var storeUI = data.objectStore(«ui»);
    var storeCore = data.objectStore(«core»);

    var requestAddUI = storeUI.add(myUIWorkingObject);
    var requestAddCore = storeCore.add(myCoreWorkingObject);

    //once the transaction is complete, we can make sure that the element has been successfully added.
    data.oncomplete = function(event){
    console.log («Element succesfully added»);
    callback(«Success»);
    };
    data.onerror = function(event) {

    console.log («Error:» + event.srcElement.error.message);/* handle error */
    data.abort();

    };
    data.onabort = function(event){
    callback(«Error»);
    };

    }

    Muchisimas gracias por tu ayuda. Espero haberme explicado mas o menos 🙂

    Saludos,

    Natalia

  6. RAIMUNDO FLORES
    Responder

    Buenas Rolando, he seguido todos tus pasos y me da el siguietne error, ¿sabes por qué?

    ‘ + elements[key].dni + ‘ ‘ + elements[key].name + ‘

    Estoy desquiciado buscando cual puede ser el error y no lo encuentro. ¿Podrías ayudarme?. Un saludo y gracias por tu trabajo.

  7. Buenos días, Rolando

    Antes que nada, gracias por este super detallado tutorial. Mas claro que el agua! 🙂

    He estado probándolo en Chrome y Firefox y me da el siguiente error: “Uncaught NotFoundError: Failed to execute ‘transaction’ on ‘IDBDatabase’: One of the specified object stores was not found.”

    Agredecere cualquier información que puedas darme al respecto.

    Gracias anticipadas.

  8. Buenas tardes. Excelentes vídeos!
    Estoy incursionando en la programación, y me llama la atención el java script; pero necesito una orientación para adentrarme en el aprendizaje. Tengo un pequeño negocio y quiero hacer un programa dinámico que permita a mis clientes accesar a los servicios y productos. Pero lo quiero hacer yo mismo. Específicamente necesito una pagina que facture, cotice, permita al cliente seleccionar los productos etc. Una página web dinámica.
    He estudiado algunos vídeos de html y css. Pero necesito aprender algo mas sobre base de datos y esas cosas que permitan que el cliente pueda comprar u ordenar vía web mis productos.Ademas que envié las cotizaciones de forma automática; una vez el cliente haya seleccionado sus productos

  9. buenas amigo Caldas he visto su video es full didactico logre abrir la base de datos pero no puedo agregar datos me arroja el error Uncaught DOMException: Failed to execute ‘transaction’ on ‘IDBDatabase’: One of the specified object stores was not found. si puediera ayudarme por favor pienso que el problema con el navegador saqueme de la duda gracias

  10. Hola, primero que nada gracias por tu claro tutorial, valoro mucho las personas que han invertido su tiempo en adquirir conocimientos y luego compartirlos en forma clara para los que recien iniciamos. Tengo un problema con createIndex en el cual utilice otros name y field a los efectos de adaptarlos a mis necesidades. Pero el tema está en que aunque pongo la opcion unique en false no me permite add nuevos name iguales. Por que serà esto. Estuve buscando en foros pero no encontre respuesta. Gracias.

Deja un comentario