1 | import { exportaAHtml } from "../lib/js/exportaAHtml.js" |
2 | import { sincroniza } from "./sincroniza.js" |
3 | |
4 | /** |
5 | * Cada 20 segundos (2000 milisegundos) después de la última |
6 | * sincronización, los datos se envían al servidor para volver a |
7 | * sincronizarse con los datos del servidor. |
8 | */ |
9 | const MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR = 20000 |
10 | |
11 | /** |
12 | * @param {HTMLUListElement} lista |
13 | */ |
14 | export function esperaUnPocoYSincroniza(lista) { |
15 | setTimeout(() => sincroniza(lista), MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR) |
16 | } |
17 | |
18 | exportaAHtml(esperaUnPocoYSincroniza) |
1 | "use strict" // usa JavaScript en modo estricto. |
2 | |
3 | const nombreDeServiceWorker = "sw.js" |
4 | |
5 | try { |
6 | navigator.serviceWorker.register(nombreDeServiceWorker) |
7 | .then(registro => { |
8 | console.log(nombreDeServiceWorker, "registrado.") |
9 | console.log(registro) |
10 | }) |
11 | .catch(error => console.log(error)) |
12 | } catch (error) { |
13 | console.log(error) |
14 | } |
1 | import { exportaAHtml } from "../lib/js/exportaAHtml.js" |
2 | import { htmlentities } from "../lib/js/htmlentities.js" |
3 | |
4 | /** |
5 | * @param {HTMLUListElement} lista |
6 | * @param {import("./modelo/PASATIEMPO.js").PASATIEMPO[]} pasatiempos |
7 | */ |
8 | export function renderiza(lista, pasatiempos) { |
9 | let render = "" |
10 | for (const modelo of pasatiempos) { |
11 | if (modelo.PAS_ID === undefined) |
12 | throw new Error(`Falta PAS_ID de ${modelo.PAS_NOMBRE}.`) |
13 | const nombre = htmlentities(modelo.PAS_NOMBRE) |
14 | const searchParams = new URLSearchParams([["id", modelo.PAS_ID]]) |
15 | const params = htmlentities(searchParams.toString()) |
16 | render += /* html */ |
17 | `<li> |
18 | <p><a href="modifica.html?${params}">${nombre}</a></p> |
19 | </li>` |
20 | } |
21 | lista.innerHTML = render |
22 | } |
23 | |
24 | exportaAHtml(renderiza) |
1 | import { enviaJson } from "../lib/js/enviaJson.js" |
2 | import { exportaAHtml } from "../lib/js/exportaAHtml.js" |
3 | import { muestraError } from "../lib/js/muestraError.js" |
4 | import { pasatiempoConsultaTodos } from "./bd/pasatiempoConsultaTodos.js" |
5 | import { pasatiemposReemplaza } from "./bd/pasatiemposReemplaza.js" |
6 | import { esperaUnPocoYSincroniza } from "./esperaUnPocoYSincroniza.js" |
7 | import { validaPasatiempos } from "./modelo/validaPasatiempos.js" |
8 | import { renderiza } from "./renderiza.js" |
9 | |
10 | /** |
11 | * @param {HTMLUListElement} lista |
12 | */ |
13 | export async function sincroniza(lista) { |
14 | try { |
15 | if (navigator.onLine) { |
16 | const todos = await pasatiempoConsultaTodos() |
17 | const respuesta = await enviaJson("srv/sincroniza.php", todos) |
18 | const pasatiempos = validaPasatiempos(respuesta.body) |
19 | await pasatiemposReemplaza(pasatiempos) |
20 | renderiza(lista, pasatiempos) |
21 | } |
22 | } catch (error) { |
23 | muestraError(error) |
24 | } |
25 | esperaUnPocoYSincroniza(lista) |
26 | |
27 | } |
28 | |
29 | exportaAHtml(sincroniza) |
1 | export const ALMACEN_PASATIEMPO = "PASATIEMPO" |
2 | export const PAS_ID = "PAS_ID" |
3 | export const INDICE_NOMBRE = "INDICE_NOMBRE" |
4 | export const PAS_NOMBRE = "PAS_NOMBRE" |
5 | const BD_NOMBRE = "sincronizacion" |
6 | const BD_VERSION = 1 |
7 | |
8 | /** @type { Promise<IDBDatabase> } */ |
9 | export const Bd = new Promise((resolve, reject) => { |
10 | |
11 | /* Se solicita abrir la base de datos, indicando nombre y |
12 | * número de versión. */ |
13 | const solicitud = indexedDB.open(BD_NOMBRE, BD_VERSION) |
14 | |
15 | // Si se presenta un error, rechaza la promesa. |
16 | solicitud.onerror = () => reject(solicitud.error) |
17 | |
18 | // Si se abre con éxito, devuelve una conexión a la base de datos. |
19 | solicitud.onsuccess = () => resolve(solicitud.result) |
20 | |
21 | // Si es necesario, se inicia una transacción para cambio de versión. |
22 | solicitud.onupgradeneeded = () => { |
23 | |
24 | const bd = solicitud.result |
25 | |
26 | // Como hay cambio de versión, borra el almacén si es que existe. |
27 | if (bd.objectStoreNames.contains(ALMACEN_PASATIEMPO)) { |
28 | bd.deleteObjectStore(ALMACEN_PASATIEMPO) |
29 | } |
30 | |
31 | // Crea el almacén "PASATIEMPO" con el campo llave "PAS_ID". |
32 | const almacenPasatiempo = |
33 | bd.createObjectStore(ALMACEN_PASATIEMPO, { keyPath: PAS_ID }) |
34 | |
35 | // Crea un índice ordenado por el campo "PAS_NOMBRE" que no acepta duplicados. |
36 | almacenPasatiempo.createIndex(INDICE_NOMBRE, "PAS_NOMBRE") |
37 | } |
38 | |
39 | }) |
1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" |
2 | import { creaIdCliente } from "../../lib/js/creaIdCliente.js" |
3 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" |
4 | import { validaNombre } from "../modelo/validaNombre.js" |
5 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" |
6 | |
7 | /** |
8 | * @param {import("../modelo/PASATIEMPO.js").PASATIEMPO} modelo |
9 | */ |
10 | export async function pasatiempoAgrega(modelo) { |
11 | validaNombre(modelo.PAS_NOMBRE) |
12 | modelo.PAS_MODIFICACION = Date.now() |
13 | modelo.PAS_ELIMINADO = 0 |
14 | // Genera id único en internet. |
15 | modelo.PAS_ID = creaIdCliente(Date.now().toString()) |
16 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { |
17 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) |
18 | almacenPasatiempo.add(modelo) |
19 | }) |
20 | } |
21 | |
22 | exportaAHtml(pasatiempoAgrega) |
1 | import { bdConsulta } from "../../lib/js/bdConsulta.js" |
2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" |
3 | import { validaPasatiempo } from "../modelo/validaPasatiempo.js" |
4 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" |
5 | |
6 | /** |
7 | * @param {string} id |
8 | */ |
9 | export async function pasatiempoBusca(id) { |
10 | |
11 | return bdConsulta(Bd, [ALMACEN_PASATIEMPO], |
12 | /** |
13 | * @param {(resultado: import("../modelo/PASATIEMPO.js").PASATIEMPO|undefined) |
14 | * => any} resolve |
15 | */ |
16 | (transaccion, resolve) => { |
17 | |
18 | /* Pide el primer objeto de ALMACEN_PASATIEMPO que tenga como llave |
19 | * primaria el valor del parámetro id. */ |
20 | const consulta = transaccion.objectStore(ALMACEN_PASATIEMPO).get(id) |
21 | |
22 | // onsuccess se invoca solo una vez, devolviendo el objeto solicitado. |
23 | consulta.onsuccess = () => { |
24 | /* Se recupera el objeto solicitado usando |
25 | * consulta.result |
26 | * Si el objeto no se encuentra se recupera undefined. */ |
27 | const objeto = consulta.result |
28 | if (objeto !== undefined) { |
29 | const modelo = validaPasatiempo(objeto) |
30 | if (modelo.PAS_ELIMINADO === 0) { |
31 | resolve(modelo) |
32 | return |
33 | } |
34 | } |
35 | resolve(undefined) |
36 | |
37 | } |
38 | |
39 | }) |
40 | |
41 | } |
42 | |
43 | exportaAHtml(pasatiempoBusca) |
1 | import { bdConsulta } from "../../lib/js/bdConsulta.js" |
2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" |
3 | import { validaPasatiempo } from "../modelo/validaPasatiempo.js" |
4 | import { ALMACEN_PASATIEMPO, Bd, INDICE_NOMBRE } from "./Bd.js" |
5 | |
6 | export async function pasatiempoConsultaNoEliminados() { |
7 | |
8 | return bdConsulta(Bd, [ALMACEN_PASATIEMPO], |
9 | /** |
10 | * @param {(resultado: import("../modelo/PASATIEMPO.js").PASATIEMPO[])=>void |
11 | * } resolve |
12 | */ |
13 | (transaccion, resolve) => { |
14 | |
15 | const resultado = [] |
16 | |
17 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) |
18 | |
19 | // Usa el índice INDICE_NOMBRE para recuperar los datos ordenados. |
20 | const indiceNombre = almacenPasatiempo.index(INDICE_NOMBRE) |
21 | |
22 | // Pide un cursor para recorrer cada objeto que devuelve la consulta. |
23 | const consulta = indiceNombre.openCursor() |
24 | |
25 | /* onsuccess se invoca por cada uno de los objetos de la consulta y una vez |
26 | * cuando se acaban dichos objetos. */ |
27 | consulta.onsuccess = () => { |
28 | /* El cursor correspondiente al objeto se recupera usando |
29 | * consulta.result */ |
30 | const cursor = consulta.result |
31 | if (cursor === null) { |
32 | /* Si el cursor vale null, ya no hay más objetos que procesar; por lo |
33 | * mismo, se devuelve el resultado con los pasatiempos recuperados, usando |
34 | * resolve(resultado). */ |
35 | resolve(resultado) |
36 | } else { |
37 | /* Si el cursor no vale null y hay más objetos, el siguiente se obtiene con |
38 | * cursor.value */ |
39 | const modelo = validaPasatiempo(cursor.value) |
40 | if (modelo.PAS_ELIMINADO === 0) { |
41 | resultado.push(modelo) |
42 | } |
43 | /* Busca el siguiente objeto de la consulta, que se recupera la siguiente |
44 | * vez que se invoque la función onsuccess. */ |
45 | cursor.continue() |
46 | } |
47 | } |
48 | |
49 | }) |
50 | |
51 | } |
52 | |
53 | exportaAHtml(pasatiempoConsultaNoEliminados) |
1 | import { bdConsulta } from "../../lib/js/bdConsulta.js" |
2 | import { validaPasatiempo } from "../modelo/validaPasatiempo.js" |
3 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" |
4 | |
5 | /** |
6 | * Lista todos los objetos, incluyendo los que tienen borrado lógico. |
7 | */ |
8 | export async function pasatiempoConsultaTodos() { |
9 | |
10 | return bdConsulta(Bd, [ALMACEN_PASATIEMPO], |
11 | /** |
12 | * @param {(resultado: import("../modelo/PASATIEMPO.js").PASATIEMPO[])=>void |
13 | * } resolve |
14 | */ |
15 | (transaccion, resolve) => { |
16 | |
17 | const resultado = [] |
18 | |
19 | // Pide un cursor para recorrer cada objeto que devuelve la consulta. |
20 | const consulta = transaccion.objectStore(ALMACEN_PASATIEMPO).openCursor() |
21 | |
22 | /* onsuccess se invoca por cada uno de los objetos de la consulta y una vez |
23 | * cuando se acaban dichos objetos. */ |
24 | consulta.onsuccess = () => { |
25 | /* El cursor correspondiente al objeto se recupera usando |
26 | * consulta.result */ |
27 | const cursor = consulta.result |
28 | if (cursor === null) { |
29 | /* Si el cursor vale null, ya no hay más objetos que procesar; por lo |
30 | * mismo, se devuelve el resultado con los pasatiempos recuperados, usando |
31 | * resolve(resultado). */ |
32 | resolve(resultado) |
33 | } else { |
34 | /* Si el cursor no vale null y hay más objetos, el siguiente se obtiene con |
35 | * cursor.value*/ |
36 | resultado.push(validaPasatiempo(cursor.value)) |
37 | /* Busca el siguiente objeto de la consulta, que se recupera la siguiente |
38 | * vez que se invoque la función onsuccess. */ |
39 | cursor.continue() |
40 | } |
41 | } |
42 | |
43 | }) |
44 | |
45 | } |
1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" |
2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" |
3 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" |
4 | import { pasatiempoBusca } from "./pasatiempoBusca.js" |
5 | |
6 | /** |
7 | * @param { string } id |
8 | */ |
9 | export async function pasatiempoElimina(id) { |
10 | const modelo = await pasatiempoBusca(id) |
11 | if (modelo !== undefined) { |
12 | modelo.PAS_MODIFICACION = Date.now() |
13 | modelo.PAS_ELIMINADO = 1 |
14 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { |
15 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) |
16 | almacenPasatiempo.put(modelo) |
17 | }) |
18 | } |
19 | } |
20 | |
21 | exportaAHtml(pasatiempoElimina) |
1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" |
2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" |
3 | import { validaId } from "../modelo/validaId.js" |
4 | import { validaNombre } from "../modelo/validaNombre.js" |
5 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" |
6 | import { pasatiempoBusca } from "./pasatiempoBusca.js" |
7 | |
8 | /** |
9 | * @param { import("../modelo/PASATIEMPO.js").PASATIEMPO } modelo |
10 | */ |
11 | export async function pasatiempoModifica(modelo) { |
12 | validaNombre(modelo.PAS_NOMBRE) |
13 | if (modelo.PAS_ID === undefined) |
14 | throw new Error(`Falta PAS_ID de ${modelo.PAS_NOMBRE}.`) |
15 | validaId(modelo.PAS_ID) |
16 | const anterior = await pasatiempoBusca(modelo.PAS_ID) |
17 | if (anterior !== undefined) { |
18 | modelo.PAS_MODIFICACION = Date.now() |
19 | modelo.PAS_ELIMINADO = 0 |
20 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { |
21 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) |
22 | almacenPasatiempo.put(modelo) |
23 | }) |
24 | } |
25 | } |
26 | |
27 | exportaAHtml(pasatiempoModifica) |
1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" |
2 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" |
3 | |
4 | /** |
5 | * Borra el contenido del almacén PASATIEMPO y guarda nuevospasatiempos. |
6 | * @param {import("../modelo/PASATIEMPO.js").PASATIEMPO[]} nuevospasatiempos |
7 | */ |
8 | export async function pasatiemposReemplaza(nuevospasatiempos) { |
9 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { |
10 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) |
11 | almacenPasatiempo.clear() |
12 | for (const objeto of nuevospasatiempos) { |
13 | almacenPasatiempo.add(objeto) |
14 | } |
15 | }) |
16 | } |
1 | /** |
2 | * @typedef {Object} PASATIEMPO |
3 | * @property {string} [PAS_ID] |
4 | * @property {string} PAS_NOMBRE |
5 | * @property {number} [PAS_MODIFICACION] |
6 | * @property {number} [PAS_ELIMINADO] |
7 | */ |
1 | /** |
2 | * @param {string} id |
3 | */ |
4 | export function validaId(id) { |
5 | if (id === "") |
6 | throw new Error("Falta el id.") |
7 | } |
1 | /** |
2 | * @param {string} nombre |
3 | */ |
4 | export function validaNombre(nombre) { |
5 | if (nombre === "") |
6 | throw new Error("Falta el nombre.") |
7 | } |
1 | /** |
2 | * @param { any } objeto |
3 | * @returns {import("./PASATIEMPO.js").PASATIEMPO} |
4 | */ |
5 | export function validaPasatiempo(objeto) { |
6 | |
7 | if (typeof objeto.PAS_ID !== "string") |
8 | throw new Error("El id debe ser texto.") |
9 | |
10 | if (typeof objeto.PAS_NOMBRE !== "string") |
11 | throw new Error("El nombre debe ser texto.") |
12 | |
13 | if (typeof objeto.PAS_MODIFICACION !== "number") |
14 | throw new Error("El campo modificacion debe ser número.") |
15 | |
16 | if (typeof objeto.PAS_ELIMINADO !== "number") |
17 | throw new Error("El campo eliminado debe ser número.") |
18 | |
19 | return objeto |
20 | |
21 | } |
1 | import { validaPasatiempo } from "./validaPasatiempo.js" |
2 | |
3 | /** |
4 | * @param { any } objetos |
5 | * @returns {import("./PASATIEMPO.js").PASATIEMPO[]} |
6 | */ |
7 | export function validaPasatiempos(objetos) { |
8 | if (!Array.isArray(objetos)) |
9 | throw new Error("no se recibió un arreglo.") |
10 | /** |
11 | * @type {import("./PASATIEMPO.js").PASATIEMPO[]} |
12 | */ |
13 | const arreglo = [] |
14 | for (const objeto of objetos) { |
15 | arreglo.push(validaPasatiempo(objeto)) |
16 | } |
17 | return arreglo |
18 | } |