@composi/idb:

Uso

Cómo Usar IDB

Todos los métodos de @composi/idb methods devuelven promesas. Eso significa que tendrás que usar sentencias on then y catch.

set

Set accepta dos argumentos: una clave y un valor que quardar con ella. El valor puede ser cualquier tipo de o aún un blob (contenido binario), tales como imágenes, archivos de sonido, etc. Almacenar blobs puede usar todo el espacio disponible para almacenaje rápidamente, así que debes de usar algún tipo de compresión y solo usar los que más necesitas.

set devuelve una promesa, así pues, si quieres hacer algo cuando el guardar termina, tendrás que usar then.

idb.set('nombre', 'José')

// O haz algo después de guardar la clave y valor:
idb.set('nombre', 'José')
  .then(() => {
    console.log('Guardamos el nombre.')
  })

Si esta es la primera vez en su código que utiliza un método del objeto idb, @composi/idb creará una instancia de IndexedDB e intentará conectarse a la base de datos y al almacén predeterminados. Si aún no existen, se crearán para que se pueda guardar la clave y valor.

get

El método get accepta un solo argumento: la clave que recuperar. Esta es una clave de texto y devuelve una promesa. Así, para hacer algo con el valor que se retorna, tendrás que usar una función then.

idb.get('productos').then(productoss => {
  // Se espera una matriz de productos:
  if (productoss && productos.length) {
    // Haz algo con los productos...
  }
})

Gestionar el Fracaso

Si get falla en devolver un valor, probablemente porque la clave no existe, devuelve undefined. Debido a que devuelve algo, no se puede usar una función catch para tratar con esto. En su lugar, tendrás que usar una verificación condicional para ver si recibiste algo y, si no, hacer otra cosa. En el siguiente ejemplo, observa cómo usamos primero una sentencia if para ver si obtuvimos algo y luego tratamos con un error en la sentencia else.

idb.get('usuarios').then(usuarios => {
  // Se espera una matriz de usuarios:
  if (usuarios && usuarios.length) {
    render(<ListaUsuarios datos={usuarios} />, 'section')
  } else {
    // No hay usuarios guardados, así proporciona unos por defecto:
    const usuarios = ['Gloria', 'Juan', 'Paula', 'Jorje']
    render(<ListaUsuarios datos={usuarios} />, 'section')
  }
})

Si esta es la primera vez que estás usando un método del objeto idb, @composi/idb creará una instancia de IndexedDB. Si ni existe ni la base de datos ni el almacén (store), los creará también. De otro mode, tratará de conseguir la clave y valor de la base de datos que encontró.

remove

Esta función elimina la base de datos. Úsalo con cuidado.

idb.remove().then(() => {

})

clear

Si no quieres eliminar la base de datos por completo, pero los datos que ella contiene son bastante viejas, puedes vaciar la base de sus contenido con esta función.

idb.clear().then(() => {

})

keys

La función keys() devuelve una matriz de todas las claves en la base de datos. Usarás esta cuando quieres sabers cuáles son las claves en la base de datos.

idb.keys().then(keys => {
  if (keys.includes('Ítemes-guardados')) {
    return idb.get('Ítemes-guardados')
  }
})
  .then(items => {
    // Haz algo con los ítemes guardados...
  })

El examinar las claves guardados te puede dar una buena idea de qué se hizo durante la última sesión.

Conrol de Versiones y Marca de Tiempo

IndexedDB soporte el control de versions y la actualización de las esquemas. @composi/idb no soporta eso. Sin embargo, puedes usar set para guardar una versión o marca de tiempo para que puedas chequear la versión o cuán vieja es la marca de tiempo cuando se accede a la base de datos de nuevo. Si la versión o la marca de tiempo es demasiado viejo, puedas decidir vaciar la base de datos y poblarla con nuevos dataos.

// Guarda una vesión para el store:
idb.set('versión', '3.5')

  // Durante la próxima recarga de la página, chequear la versión:
idb.get('versión')
  .then(version => {
    if (versión && versión < 5) {
      idb.clear()
    }
  })

O puedes guard una marca de tiempo y al recargar la página, chequeas para ver si la marca tiene mas de 30 días::

// Guarda marca de tiempo:
idb.set('tiempo', new Date().getTime())

// Durante el recargar consigue el tiempo para ver si tiene más de 30 días.
// Para eso vamos a usar unas functions auxiliars para convertir el valor UTF a días.
const tiempoActual = new Date().getTime()
// Un día en milisegundos.
const día = 1000 * 60 * 60 * 24
// Consigue el tiempo:
idb.get('tiempo')
  .then(tiempo => {
    // Si el tiempo tiene más de 30 días:
    if (tiempo && (tiempoActual - tiempo) / día > 30) {
      // Vacía todo el contenido de la base de datos:
      idb.clear()
    }
  })

Usando con @composi/core

Here's an example of a todo list using @composi/idb and @composi/core with its runtime.

import { h, render, run, union } from '@composi/core'
import { clone } from '@composi/merge-objects'
import { idb } from '@composi/idb'

// id para los ítemes de lista
function id() {
  return Math.floor(Math.random() * 100000000 + Math.random() * 1000);
}

// Manejar el estado de los botones del pie para mostrar las tareas.
const manejarEstadoBoton = index => {
  const botones = [false, false, false];
  botones[index] = true;
  return botones;
};

// Componente Pie:
function Pie({ estado, manejarEstadoBoton, send }) {
  let cuenta = 0;
  estado.items.forEach(item => {
    if (item.activa === true) {
      cuenta += 1;
    }
  });
  return (
    <footer>
      <div id="vista-de-suma">
        <span>{cuenta} left.</span>
      </div>
      <p>Show: </p>
      <div id="mostrar-estado-tarea">
        <button
          onclick={e => send(Msj.mostrarTodas(e))}
          id="mostrar-todas"
          class={estado.botónSeleccionado[0] ? "selected" : ""}
        >
          All
        </button>
        <button
          onclick={() => send(Msj.mostrarActivas())}
          id="mostrar-activas"
          class={estado.botónSeleccionado[1] ? "selected" : ""}
        >
          Active
        </button>
        <button
          onclick={e => send(Msj.mostrarCompletadas(e))}
          id="mostrar-comletadas"
          class={estado.botónSeleccionado[2] ? "selected" : ""}
        >
          Completed
        </button>
      </div>
    </footer>
  );
}

// Componente ítemes de lista:
function ÍtemesDeLista({ item, animarEntrada, animarSalida, send }) {
  return (
    <li
      key={item.id}
      class={item.activa ? "activa" : ""}
      hidden={item.hidden}
      onmount={animarEntrada}
      onunmount={animarSalida}
    >
      <button
        class="manejar-estado"
        onclick={() => send(Msj.manejarEstadoActivo(item.id))}
      >
        <svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1">
          <g
            id="Página-1"
            stroke="none"
            stroke-width="1"
            fill="none"
            fill-rule="evenodd"
          >
            <g id="indicador-selección">
              <path
                d="M2,13 L9.9294326,16.8406135 L17.1937075,1.90173332"
                id="checkmark"
                stroke="#007AFF"
                stroke-width="2"
              />
            </g>
          </g>
        </svg>
      </button>
      <h3 onclick={() => send(Msj.manejarEstadoActivo(item.id))}>{item.valor}</h3>
      <button onclick={() => send(Msj.eliminarÍtem(item.id))} class="eliminar">
        <svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1">
          <g
            id="Página-2"
            stroke="none"
            stroke-width="1"
            fill="none"
            fill-rule="evenodd"
          >
            <g
              id="Eliminar"
              stroke="#FF0000"
              stroke-width="2"
              stroke-linecap="square"
            >
              <path d="M26.5,3.5 L3.5,26.5" id="Line" />
              <path d="M3.5,3.5 L26.5,26.5" id="Line" />
            </g>
          </g>
        </svg>
      </button>
    </li>
  );
}

// Definir lista de tareas.
function ListaTareas({ estado, send }) {
  console.log(estado)
  let valor;
  function focusInput(input) {
    input.focus();
  }
  function updateValue(e) {
    valor = e.target.value;
    e.target.focus();
  }
  function animarEntrada(el) {
    el.classList.add("añadir-ítem");
  }
  function animarSalida(done, el) {
    el.classList.add("eliminar-ítem");
    setTimeout(() => {
      done();
    }, 1000);
  }
  return (
    <div class="vista-padre">
      <p class="añadir-tarea">
        <input type="text" onchange={updateValue} valor={estado.valorInput} />
        <button class="añadirÍtem" onclick={() => send(Msj.añadirÍtem(valor))}>
          Add Item
        </button>
      </p>
      <ul class="lista-tareas">
        {estado.items.map(item => (
          <ÍtemesDeLista {...{ item, animarEntrada, animarSalida, send }} />
        ))}
      </ul>
      <Pie {...{ estado, manejarEstadoBoton, send }} />
    </div>
  );
}

// El estado por defecto:
let estado = {
  items: [
    { activa: true, valor: "Tomar una siesta", id: id(), hidden: false },
    { activa: false, valor: "Comer algo rico", id: id(), hidden: false },
    { activa: true, valor: "Hablar con me mamá", id: id(), hidden: false }
  ],
  botónSeleccionado: [true, false, false],
  valorInput: ""
};

// Crear una unión etiquetada.
const Msj = union([
  "añadirÍtem",
  "eliminarÍtem",
  "manejarEstadoActivo",
  "mostrarActivas",
  "mostrarCompletadas",
  "mostrarTodas",
  "guardarLocalmente"
]);

// Asemblar el programa.
const programa = {
  init: [estado],
  view(estado, send) {
    render(<ListaTareas {...{ estado, send }} />, "section")
  },
  update(msj, estado) {
    return Msj.match(msj, {
      añadirÍtem: valor => {
        if (valor) {
          // Clonar el estado:
          const estadoPrevio = clone(estado);
          estadoPrevio.items.push({
            activa: true,
            valor,
            id: id(),
            hidden: false
          });
          // Guardar el estado en IndexedDB.
          // Éste es un efecto asincrónico.
          idb.set('tareas', estadoPrevio)
          return [estadoPrevio]
        }
      },
      eliminarÍtem: id => {
        const estadoPrevio = clone(estado);
        estadoPrevio.items = estadoPrevio.items.filter(item => item.id != id);
        // Guardar el estado en IndexedDB.
        // Éste es un efecto asincrónico.
        idb.set('tareas', estadoPrevio)
        return [estadoPrevio]
      },
      manejarEstadoActivo: id => {
        const estadoPrevio = clone(estado);
        const index = estadoPrevio.items.findIndex(item => {
          return item.id == id;
        });
        estadoPrevio.items[index].activa = !estadoPrevio.items[index].activa;
        return [estadoPrevio];
      },
      mostrarActivas: () => {
        const estadoPrevio = clone(estado);
        estadoPrevio.items.map(item => {
          if (!item.activa) item.hidden = true;
          else item.hidden = false;
        });
        estadoPrevio.botónSeleccionado = manejarEstadoBoton(1);
        return [estadoPrevio];
      },
      mostrarCompletadas: () => {
        const estadoPrevio = clone(estado);
        estadoPrevio.items.map(item => {
          if (item.activa) item.hidden = true;
          else item.hidden = false;
        });
        estadoPrevio.botónSeleccionado = manejarEstadoBoton(2);
        return [estadoPrevio];
      },
      mostrarTodas: () => {
        const estadoPrevio = clone(estado);
        estadoPrevio.items.map(item => (item.hidden = false));
        estadoPrevio.botónSeleccionado = manejarEstadoBoton(0);
        return [estadoPrevio];
      },
      guardarLocalmente: estadoPrevio => {
        idb.set('tareas', estadoPrevio)
      }
    });
  }
};

// Antes de ejectuar el programa,
// chequear si hay datos guardos en IndexedDB.
// Si hay, usarlos para el programa.
// Si no, usar los datos por defecto.
idb.get('tareas').then(datos => {
  if (datos) {
    console.log('Hay datos')
    programa.init[0] = datos
    run(programa)
  } else {
    console.log('No hay datos guardos así que vamos a usar otros.')
    // render(<ListaTareas estado={estado} />, "section")
    run(programa)
    idb.set('tareas', estado)
  }
})