L. Carpeta « js »

Versión para imprimir.

A. js / esperaUnPocoYSincroniza.js

1import { exportaAHtml } from "../lib/js/exportaAHtml.js"
2import { 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 */
9const MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR = 20000
10
11/**
12 * @param {HTMLUListElement} lista
13 */
14export function esperaUnPocoYSincroniza(lista) {
15 setTimeout(() => sincroniza(lista), MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR)
16}
17
18exportaAHtml(esperaUnPocoYSincroniza)

B. js / registraServiceWorker.js

1"use strict" // usa JavaScript en modo estricto.
2
3const nombreDeServiceWorker = "sw.js"
4
5try {
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}

C. js / renderiza.js

1import { exportaAHtml } from "../lib/js/exportaAHtml.js"
2import { htmlentities } from "../lib/js/htmlentities.js"
3
4/**
5 * @param {HTMLUListElement} lista
6 * @param {import("./modelo/PASATIEMPO.js").PASATIEMPO[]} pasatiempos
7 */
8export 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
24exportaAHtml(renderiza)

D. js / sincroniza.js

1import { enviaJson } from "../lib/js/enviaJson.js"
2import { exportaAHtml } from "../lib/js/exportaAHtml.js"
3import { muestraError } from "../lib/js/muestraError.js"
4import { pasatiempoConsultaTodos } from "./bd/pasatiempoConsultaTodos.js"
5import { pasatiemposReemplaza } from "./bd/pasatiemposReemplaza.js"
6import { esperaUnPocoYSincroniza } from "./esperaUnPocoYSincroniza.js"
7import { validaPasatiempos } from "./modelo/validaPasatiempos.js"
8import { renderiza } from "./renderiza.js"
9
10/**
11 * @param {HTMLUListElement} lista
12 */
13export 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
29exportaAHtml(sincroniza)

E. Carpeta « js / bd »

Versión para imprimir.

1. js / bd / Bd.js

1export const ALMACEN_PASATIEMPO = "PASATIEMPO"
2export const PAS_ID = "PAS_ID"
3export const INDICE_NOMBRE = "INDICE_NOMBRE"
4export const PAS_NOMBRE = "PAS_NOMBRE"
5const BD_NOMBRE = "sincronizacion"
6const BD_VERSION = 1
7
8/** @type { Promise<IDBDatabase> } */
9export 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})

2. js / bd / pasatiempoAgrega.js

1import { bdEjecuta } from "../../lib/js/bdEjecuta.js"
2import { creaIdCliente } from "../../lib/js/creaIdCliente.js"
3import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
4import { validaNombre } from "../modelo/validaNombre.js"
5import { exportaAHtml } from "../../lib/js/exportaAHtml.js"
6
7/**
8 * @param {import("../modelo/PASATIEMPO.js").PASATIEMPO} modelo
9 */
10export 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
22exportaAHtml(pasatiempoAgrega)

3. js / bd / pasatiempoBusca.js

1import { bdConsulta } from "../../lib/js/bdConsulta.js"
2import { exportaAHtml } from "../../lib/js/exportaAHtml.js"
3import { validaPasatiempo } from "../modelo/validaPasatiempo.js"
4import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
5
6/**
7 * @param {string} id
8 */
9export 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
43exportaAHtml(pasatiempoBusca)

4. js / bd / pasatiempoConsultaNoEliminados.js

1import { bdConsulta } from "../../lib/js/bdConsulta.js"
2import { exportaAHtml } from "../../lib/js/exportaAHtml.js"
3import { validaPasatiempo } from "../modelo/validaPasatiempo.js"
4import { ALMACEN_PASATIEMPO, Bd, INDICE_NOMBRE } from "./Bd.js"
5
6export 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
53exportaAHtml(pasatiempoConsultaNoEliminados)

5. js / bd / pasatiempoConsultaTodos.js

1import { bdConsulta } from "../../lib/js/bdConsulta.js"
2import { validaPasatiempo } from "../modelo/validaPasatiempo.js"
3import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
4
5/**
6 * Lista todos los objetos, incluyendo los que tienen borrado lógico.
7 */
8export 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}

6. js / bd / pasatiempoElimina.js

1import { bdEjecuta } from "../../lib/js/bdEjecuta.js"
2import { exportaAHtml } from "../../lib/js/exportaAHtml.js"
3import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
4import { pasatiempoBusca } from "./pasatiempoBusca.js"
5
6/**
7 * @param { string } id
8 */
9export 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
21exportaAHtml(pasatiempoElimina)

7. js / bd / pasatiempoModifica.js

1import { bdEjecuta } from "../../lib/js/bdEjecuta.js"
2import { exportaAHtml } from "../../lib/js/exportaAHtml.js"
3import { validaId } from "../modelo/validaId.js"
4import { validaNombre } from "../modelo/validaNombre.js"
5import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
6import { pasatiempoBusca } from "./pasatiempoBusca.js"
7
8/**
9 * @param { import("../modelo/PASATIEMPO.js").PASATIEMPO } modelo
10 */
11export 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
27exportaAHtml(pasatiempoModifica)

8. js / bd / pasatiemposReemplaza.js

1import { bdEjecuta } from "../../lib/js/bdEjecuta.js"
2import { 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 */
8export 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}

F. Carpeta « js / modelo »

Versión para imprimir.

1. js / modelo / PASATIEMPO.js

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 */

2. js / modelo / validaId.js

1/**
2 * @param {string} id
3 */
4export function validaId(id) {
5 if (id === "")
6 throw new Error("Falta el id.")
7 }

3. js / modelo / validaNombre.js

1/**
2 * @param {string} nombre
3 */
4export function validaNombre(nombre) {
5 if (nombre === "")
6 throw new Error("Falta el nombre.")
7}

4. js / modelo / validaPasatiempo.js

1/**
2 * @param { any } objeto
3 * @returns {import("./PASATIEMPO.js").PASATIEMPO}
4 */
5export 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}

5. js / modelo / validaPasatiempos.js

1import { validaPasatiempo } from "./validaPasatiempo.js"
2
3/**
4 * @param { any } objetos
5 * @returns {import("./PASATIEMPO.js").PASATIEMPO[]}
6 */
7export 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}