Charger un fichier JSON dans IndexedDB

Un script avec une démonstration pour remplir une base IDB à partir d'un fichier et accéder au contenu.

Une base IndexedDB est constituée d'une liste de clés-valeurs. Donc d'identificateurs associés à des objets qui forment le contenu utile: données ou scripts...

En fait les clés peuvent faire partie des objets contenus, c'est le cas dans notre exemple, quand on stocke des objets JavaScript, qui peuvent venir de fichiers JSON exportés d'un autre base de donnée. On peut aussi stocker des scripts.

Exemple de fichier de données:

{ "fruits": [
  {
	"name": "orange",	
	"color":"orange",
	"origin":"Asia",
	"content":"200"
  },
  {
	"name": "apple",  
	"color":"red, green, yellow",
	"origin":"Asia",
	"content":"1000"
  },
  {
	"name":"strawberry",   
	"color": "red",
	"origin": "France",
	"content":"50"
  },
  {
	"name":"blueberry",
	"color":"purple",
	"origin":"America",
	"content":"300"
  }
 ]
}

1) Charger le fichier JSON

async function loadJSON(fname) {
  var response = await fetch(fname)
  var str = await response.text()
  var data = JSON.parse(str)
  var idb = await importIDB("fruits", "fstore", data["fruits"])
  await displayIDB(idb, "fstore", "storage")
}

Le fichier JSON est chargé sous forme de texte et converti en object avec JSON.parse().

La fonction est déclarée async pour pouvoir utiliser await et donc attendre que chargement soit terminé pour enchaîner avec l'affichage du contenu.

2) Transférer son contenu dans la base

function importIDB(dname, sname, arr) {
  return new Promise(function(resolve) {
    var r = window.indexedDB.open(dname)
    r.onupgradeneeded = function() {
      var idb = r.result
      var store = idb.createObjectStore(sname, {keyPath: "name"})
    }
    r.onsuccess = function() {
      var idb = r.result
        let tactn = idb.transaction(sname, "readwrite")
    	  var store = tactn.objectStore(sname)
        for(var obj of arr) {
          store.put(obj)
        }
        resolve(idb)
    }
    r.onerror = function (e) {
     alert("Enable to access IndexedDB, " + e.target.errorCode)
    }    
  })
}

La fonction importIDB a pour paramètres le nom de la base de donnée, le nom de la table (store), et un tableau contenant la liste des objets à placer dans la table.

La base est ouverte avec la méthode open de indexedDB. Une table est créée avec la méthode createObjectStore dans une fonction associée à l'évènement onupgradeneeded.

L'option keyPath: "name" à pour but de définir la propriété "name", qui fait partie de chaque objet stocké dans la base, comme clé de recherche. Cela équivaut à la colonne de clé primaire en SQL.

Quand la base est créée avec succès, l'évènement success est déclenché et le contenu de la fonction associée exécuté. On ouvre alors une transaction pour accèder à la table et stocker les enregistrements.

3) Montrer le contenu de la base

La fonction suivante à un but de démonstration uniquement, pour vérifier que le remplissage est bien fait. Dans une application, la base est supposée contenir des milliers d'enregistrements, donc son contenu n'est pas affiché en bloc.

Mais il est utile cependant de voir comment cursor peut être utilisé pour parcourir le contenu de la base, si on veut y effectuer une recherche et éventuellement utiliser des requêtes de type GraphQL.

function displayIDB(idb, sname, id) {
  let storage = document.getElementById(id)
  let tactn = idb.transaction(sname, "readonly")
  let osc = tactn.objectStore(sname).openCursor()
  osc.onsuccess = function(e) {
    let cursor = e.target.result
    if (cursor) {
      storage.innerHTML += "Name " + cursor.value["name"] + " : " + cursor.value["origin"] + " " + cursor.value["color"] + "<br>"
      cursor.continue()
    }
  } 
  tactn.oncomplete = function() {
    idb.close();
  }
}

Il faut encore ouvrir une transaction pour accéder à la table des enregistrements, et particularité de indexedDB, utiliser un "curseur", pour parser le contenu.
Quand l'opération est complète, l'évènement oncomplete est déclenché et on ferme la base (c'est une option).

4) Accéder à un enregistrement

async function getIDB(dname, sname, key) {
  return new Promise(function(resolve) {
    var r = indexedDB.open(dname)
      r.onsuccess = function(e) {
        var idb = r.result
        let tactn = idb.transaction(sname, "readonly")
        let store = tactn.objectStore(sname)
        let data = store.get(key)
        data.onsuccess = function() {
          resolve(data.result)
        }
        tactn.oncomplete = function() {
          idb.close()
        }
     }
  })
}

On ouvre de nouveau la base puisqu'elle a été fermée dans la fonction précédente. Une nouvelle transaction est effectuée et la méthode store.get() permet de trouver l'enregistrement dont on donne la clé.

Cette fonction retourne une promise pour que l'on puisse s'assurer que le contenu est disponible avant de l'afficher. Cela dépend de la fonction activée quand on clique sur le bouton, qui est la fonction ci-dessous dans notre démo:

async function search() {
  var key = document.getElementById("searchval").value
  var infos = await getIDB("fruits", "fstore", key)
  document.getElementById("storage").innerHTML = JSON.stringify(infos, null, ' ')
}

L'alternative est d'utiliser un callback.

Le code complet est disponible dans une archive à télécharger.

Pour lancer la démo, chargez localement demoIDB.html dans Firefox ou un autre navigateur qui accepte le chargement de fichiers à partir d"une page locale.

Voir aussi: