M. Carpeta « js »

Versión para imprimir.

A. js / Bd.js

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 = "sincro"
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
})

B. js / esperaUnPocoYSincroniza.js

1
import { sincroniza } from "./sincroniza.js"
2
3
/**
4
 * Cada 20 segundos (2000 milisegundos) después de la última
5
 * sincronización, los datos se envían al servidor para volver a
6
 * sincronizarse con los datos del servidor.
7
 */
8
const MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR = 20000
9
10
export function esperaUnPocoYSincroniza() {
11
 setTimeout(() => sincroniza(), MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR)
12
}

C. js / 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
 */

D. js / pasatiempoAgrega.js

1
import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
2
import { bdEjecuta } from "./lib/bdEjecuta.js"
3
import { creaIdCliente } from "./lib/creaIdCliente.js"
4
import { recibeTextoObligatorio } from "./lib/recibeTextoObligatorio.js"
5
6
7
/**
8
 * @param {SubmitEvent} event
9
 */
10
export async function pasatiempoAgrega(event) {
11
12
 event.preventDefault()
13
 const target = event.target
14
15
 if (!(target instanceof HTMLFormElement))
16
  throw new Error("target no es de tipo form.")
17
18
 const formData = new FormData(target)
19
20
 const modelo = {
21
  PAS_ID: creaIdCliente(Date.now().toString()), // Genera id único en internet.
22
  PAS_NOMBRE: recibeTextoObligatorio(formData, "nombre"),
23
  PAS_MODIFICACION: Date.now(),
24
  PAS_ELIMINADO: 0,
25
 }
26
27
 await bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => {
28
  const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO)
29
  almacenPasatiempo.add(modelo)
30
 })
31
32
 location.href = "index.html"
33
34
}

E. js / pasatiempoBusca.js

1
import { bdConsulta } from "./lib/bdConsulta.js"
2
import { validaPasatiempo } from "./validaPasatiempo.js"
3
import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
4
5
/**
6
 * @param {string} id
7
 */
8
export async function pasatiempoBusca(id) {
9
10
 return bdConsulta(Bd, [ALMACEN_PASATIEMPO],
11
  /**
12
   * @param {(resultado: import("./PASATIEMPO.js").PASATIEMPO|undefined)
13
   *                                                            => any} resolve 
14
   */
15
  (transaccion, resolve) => {
16
17
   /* Pide el primer objeto de ALMACEN_PASATIEMPO que tenga como llave
18
    * primaria el valor del parámetro id. */
19
   const consulta = transaccion.objectStore(ALMACEN_PASATIEMPO).get(id)
20
21
   // onsuccess se invoca solo una vez, devolviendo el objeto solicitado.
22
   consulta.onsuccess = () => {
23
    /* Se recupera el objeto solicitado usando
24
     *  consulta.result
25
     * Si el objeto no se encuentra se recupera undefined. */
26
    const objeto = consulta.result
27
    if (objeto !== undefined) {
28
     const modelo = validaPasatiempo(objeto)
29
     if (modelo.PAS_ELIMINADO === 0) {
30
      resolve(modelo)
31
      return
32
     }
33
    }
34
    resolve(undefined)
35
36
   }
37
38
  })
39
40
}

F. js / pasatiempoConsultaNoEliminados.js

1
import { ALMACEN_PASATIEMPO, Bd, INDICE_NOMBRE } from "./Bd.js"
2
import { bdConsulta } from "./lib/bdConsulta.js"
3
import { validaPasatiempo } from "./validaPasatiempo.js"
4
5
export async function pasatiempoConsultaNoEliminados() {
6
7
 return bdConsulta(Bd, [ALMACEN_PASATIEMPO],
8
  /**
9
   * @param {(resultado: import("./PASATIEMPO.js").PASATIEMPO[])=>void
10
   *                                                                  } resolve
11
   */
12
  (transaccion, resolve) => {
13
14
   const resultado = []
15
16
   const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO)
17
18
   // Usa el índice INDICE_NOMBRE para recuperar los datos ordenados.
19
   const indiceNombre = almacenPasatiempo.index(INDICE_NOMBRE)
20
21
   // Pide un cursor para recorrer cada objeto que devuelve la consulta.
22
   const consulta = indiceNombre.openCursor()
23
24
   /* onsuccess se invoca por cada uno de los objetos de la consulta y una vez
25
    * cuando se acaban dichos objetos. */
26
   consulta.onsuccess = () => {
27
    /* El cursor correspondiente al objeto se recupera usando
28
     *  consulta.result */
29
    const cursor = consulta.result
30
    if (cursor === null) {
31
     /* Si el cursor vale null, ya no hay más objetos que procesar; por lo
32
      * mismo, se devuelve el resultado con los pasatiempos recuperados, usando
33
      *  resolve(resultado). */
34
     resolve(resultado)
35
    } else {
36
     /* Si el cursor no vale null y hay más objetos, el siguiente se obtiene con
37
      *  cursor.value */
38
     const modelo = validaPasatiempo(cursor.value)
39
     if (modelo.PAS_ELIMINADO === 0) {
40
      resultado.push(modelo)
41
     }
42
     /* Busca el siguiente objeto de la consulta, que se recupera la siguiente
43
      * vez que se invoque la función onsuccess. */
44
     cursor.continue()
45
    }
46
   }
47
48
  })
49
50
}

G. js / pasatiempoConsultaTodos.js

1
import { bdConsulta } from "./lib/bdConsulta.js"
2
import { validaPasatiempo } from "./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("./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
}

H. js / pasatiempoElimina.js

1
import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
2
import { bdEjecuta } from "./lib/bdEjecuta.js"
3
import { pasatiempoBusca } from "./pasatiempoBusca.js"
4
5
/**
6
 * @param { string } id
7
 */
8
export async function pasatiempoElimina(id) {
9
10
 if (confirm('Confirma la eliminación')) {
11
12
  const modelo = await pasatiempoBusca(id)
13
14
  if (modelo !== undefined) {
15
16
   modelo.PAS_MODIFICACION = Date.now()
17
   modelo.PAS_ELIMINADO = 1
18
   await bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => {
19
    const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO)
20
    almacenPasatiempo.put(modelo)
21
   })
22
23
  }
24
25
  location.href = "index.html"
26
27
 }
28
29
}

I. js / pasatiempoModifica.js

1
import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js"
2
import { bdEjecuta } from "./lib/bdEjecuta.js"
3
import { recibeTextoObligatorio } from "./lib/recibeTextoObligatorio.js"
4
import { pasatiempoBusca } from "./pasatiempoBusca.js"
5
6
/**
7
 * @param {SubmitEvent} event
8
 * @param {string} id
9
 */
10
export async function pasatiempoModifica(event, id) {
11
12
 event.preventDefault()
13
 const target = event.target
14
15
 if (!(target instanceof HTMLFormElement))
16
  throw new Error("target no es de tipo form.")
17
18
 const formData = new FormData(target)
19
20
 const nombre = recibeTextoObligatorio(formData, "nombre")
21
22
 const anterior = await pasatiempoBusca(id)
23
24
 if (anterior !== undefined) {
25
26
  anterior.PAS_NOMBRE = nombre
27
  anterior.PAS_MODIFICACION = Date.now()
28
29
  await bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => {
30
   const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO)
31
   almacenPasatiempo.put(anterior)
32
  })
33
34
 location.href = "index.html"
35
36
 }
37
38
}

J. js / pasatiemposReemplaza.js

1
import { bdEjecuta } from "./lib/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("./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
}

K. js / renderiza.js

1
import { htmlentities } from "./lib/htmlentities.js"
2
import { muestraObjeto } from "./lib/muestraObjeto.js"
3
4
/**
5
 * @param {import("./PASATIEMPO.js").PASATIEMPO[]} pasatiempos
6
 */
7
export function renderiza(pasatiempos) {
8
 let render = ""
9
 for (const modelo of pasatiempos) {
10
  const nombre = htmlentities(modelo.PAS_NOMBRE)
11
  const searchParams = new URLSearchParams([["id", modelo.PAS_ID]])
12
  const params = htmlentities(searchParams.toString())
13
  render += /* html */
14
   `<li>
15
     <p><a href="modifica.html?${params}">${nombre}</a></p>
16
    </li>`
17
 }
18
 muestraObjeto(
19
  document,
20
  {
21
   lista: { innerHTML: render }
22
  }
23
 )
24
}
25

L. js / sincroniza.js

1
import { pasatiempoConsultaTodos } from "./pasatiempoConsultaTodos.js"
2
import { pasatiemposReemplaza } from "./pasatiemposReemplaza.js"
3
import { esperaUnPocoYSincroniza } from "./esperaUnPocoYSincroniza.js"
4
import { consume } from "./lib/consume.js"
5
import { enviaJsonRecibeJson } from "./lib/enviaJsonRecibeJson.js"
6
import { muestraError } from "./lib/muestraError.js"
7
import { renderiza } from "./renderiza.js"
8
import { validaPasatiempos } from "./validaPasatiempos.js"
9
10
export async function sincroniza() {
11
12
 try {
13
14
  if (navigator.onLine) {
15
   const todos = await pasatiempoConsultaTodos()
16
   const respuesta =
17
    await consume(enviaJsonRecibeJson("php/sincroniza.php", todos))
18
   const pasatiempos = validaPasatiempos(await respuesta.json())
19
   await pasatiemposReemplaza(pasatiempos)
20
   renderiza(pasatiempos)
21
  }
22
23
 } catch (error) {
24
25
  muestraError(error)
26
27
 }
28
29
 esperaUnPocoYSincroniza()
30
31
}

M. js / validaPasatiempo.js

1
/**
2
 * @param { any } objeto
3
 * @returns {import("./PASATIEMPO.js").PASATIEMPO}
4
 */
5
export function validaPasatiempo(objeto) {
6
7
 if (typeof objeto.PAS_ELIMINADO !== "number" || isNaN(objeto.PAS_ELIMINADO))
8
  throw new Error("El campo eliminado debe ser número.")
9
10
 if (typeof objeto.PAS_ID !== "string" || objeto.PAS_ID === "")
11
  throw new Error("El id debe ser texto que no esté en blanco.")
12
13
 if (
14
  typeof objeto.PAS_MODIFICACION !== "number" || isNaN(objeto.PAS_MODIFICACION)
15
 )
16
  throw new Error("El campo modificacion debe ser número.")
17
18
 if (typeof objeto.PAS_NOMBRE !== "string" || objeto.PAS_ID === "")
19
  throw new Error("El nombre debe ser texto que no esté en blanco.")
20
21
 return objeto
22
23
}

N. js / validaPasatiempos.js

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
}

O. Carpeta « js / lib »

Versión para imprimir.

1. js / lib / bdConsulta.js

1
/**
2
 * @template T
3
 * @param {Promise<IDBDatabase>} bd
4
 * @param {string[]} almacenes
5
 * @param {(transaccion: IDBTransaction, resolve: (resultado:T)=>void) => any
6
 *                                                                   } consulta
7
 * @returns {Promise<T>}
8
 */
9
export async function bdConsulta(bd, almacenes, consulta) {
10
11
 const base = await bd
12
13
 return new Promise((resolve, reject) => {
14
  // Inicia una transacción de solo lectura.
15
  const transaccion = base.transaction(almacenes, "readonly")
16
  // Al terminar con error ejecuta la función reject.
17
  transaccion.onerror = () => reject(transaccion.error)
18
  // Estas son las operaciones para realizar la consulta.
19
  consulta(transaccion, resolve)
20
 })
21
22
}

2. js / lib / bdEjecuta.js

1
/**
2
 * @param {Promise<IDBDatabase>} bd
3
 * @param {string[]} entidades
4
 * @param {(t:IDBTransaction) => void} operaciones
5
 */
6
export async function bdEjecuta(bd, entidades, operaciones) {
7
8
 // Espera que se abra la bd
9
 const base = await bd
10
11
 return new Promise(
12
  (resolve, reject) => {
13
   // Inicia una transacción de lectura y escritura.
14
   const transaccion = base.transaction(entidades, "readwrite")
15
   // Al terminar con éxito, ejecuta la función resolve.
16
   transaccion.oncomplete = resolve
17
   // Al terminar con error, ejecuta la función reject.
18
   transaccion.onerror = () => reject(transaccion.error)
19
   // Estas son las operaciones de la transacción.
20
   operaciones(transaccion)
21
  })
22
23
}

3. js / lib / consume.js

1
import { ProblemDetailsError } from "./ProblemDetailsError.js"
2
3
/**
4
 * Espera a que la promesa de un fetch termine. Si
5
 * hay error, lanza una excepción.
6
 * 
7
 * @param {Promise<Response> } servicio
8
 */
9
export async function consume(servicio) {
10
 const respuesta = await servicio
11
 if (respuesta.ok) {
12
  return respuesta
13
 } else {
14
  const contentType = respuesta.headers.get("Content-Type")
15
  if (
16
   contentType !== null && contentType.startsWith("application/problem+json")
17
  )
18
   throw new ProblemDetailsError(await respuesta.json())
19
  else
20
   throw new Error(respuesta.statusText)
21
 }
22
}

4. js / lib / creaIdCliente.js

1
/**
2
 * Añade caracteres al azar a una raíz, para obtener un clientId único.
3
 * @param {string} raiz 
4
 */
5
export function creaIdCliente(raiz) {
6
 const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
7
 for (var i = 0; i < 15; i++) {
8
  raiz += chars.charAt(Math.floor(Math.random() * chars.length))
9
 }
10
 return raiz
11
}

5. js / lib / enviaJsonRecibeJson.js

1
2
/**
3
 * @param { string } url
4
 * @param { Object } body
5
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
6
 *  | "CONNECT" | "HEAD" } metodoHttp
7
 */
8
export async function enviaJsonRecibeJson(url, body, metodoHttp = "POST") {
9
 return fetch(
10
  url,
11
  {
12
   method: metodoHttp,
13
   headers: {
14
    "Content-Type": "application/json",
15
    "Accept": "application/json, application/problem+json"
16
   },
17
   body: JSON.stringify(body)
18
  }
19
 )
20
}

6. js / lib / htmlentities.js

1
/**
2
 * Codifica un texto para que cambie los caracteres
3
 * especiales y no se pueda interpretar como
4
 * etiiqueta HTML. Esta técnica evita la inyección
5
 * de código.
6
 * @param { string } texto
7
*/
8
export function htmlentities(texto) {
9
 return texto.replace(/[<>"']/g, textoDetectado => {
10
  switch (textoDetectado) {
11
   case "<": return "<"
12
   case ">": return ">"
13
   case '"': return """
14
   case "'": return "'"
15
   default: return textoDetectado
16
  }
17
 })
18
}
19

7. js / lib / manejaErrores.js

1
import { muestraError } from "./muestraError.js"
2
3
/**
4
 * Intercepta Response.prototype.json para capturar errores de parseo
5
 * y asegurar que se reporten correctamente en navegadores Chromium.
6
 */
7
{
8
 const originalJson = Response.prototype.json
9
10
 Response.prototype.json = function () {
11
  // Llamamos al método original usando el contexto (this) de la respuesta
12
  return originalJson.apply(this, arguments)
13
   .catch((/** @type {any} */ error) => {
14
    // Corrige un error de Chrome que evita el manejo correcto de errores.
15
    throw new Error(error)
16
   })
17
 }
18
}
19
20
window.onerror = function (
21
  /** @type {string} */ _message,
22
  /** @type {string} */ _url,
23
  /** @type {number} */ _line,
24
  /** @type {number} */ _column,
25
  /** @type {Error} */ errorObject
26
) {
27
 muestraError(errorObject)
28
 return true
29
}
30
31
window.addEventListener('unhandledrejection', event => {
32
 muestraError(event.reason)
33
 event.preventDefault()
34
})
35

8. js / lib / muestraError.js

1
import { ProblemDetailsError } from "./ProblemDetailsError.js"
2
3
/**
4
 * Muestra los datos de una Error en la consola y en un cuadro de alerta.
5
 * @param { ProblemDetailsError | Error | null } error descripción del error.
6
 */
7
export function muestraError(error) {
8
9
 if (error === null) {
10
11
  console.error("Error")
12
  alert("Error")
13
14
 } else if (error instanceof ProblemDetailsError) {
15
16
  const problemDetails = error.problemDetails
17
18
  let mensaje =
19
   typeof problemDetails["title"] === "string" ? problemDetails["title"] : ""
20
  if (typeof problemDetails["detail"] === "string") {
21
   if (mensaje !== "") {
22
    mensaje += "\n\n"
23
   }
24
   mensaje += problemDetails["detail"]
25
  }
26
  if (mensaje === "") {
27
   mensaje = "Error"
28
  }
29
  console.error(error, problemDetails)
30
  alert(mensaje)
31
32
 } else {
33
34
  console.error(error)
35
  alert(error.message)
36
37
 }
38
39
}

9. js / lib / muestraObjeto.js

1
/**
2
 * @param {Document | HTMLElement | ShadowRoot} raizHtml
3
 * @param { any } objeto
4
 */
5
export function muestraObjeto(raizHtml, objeto) {
6
 for (const [nombre, definiciones] of Object.entries(objeto)) {
7
  if (Array.isArray(definiciones)) {
8
   muestraArray(raizHtml, nombre, definiciones)
9
  } else if (definiciones !== undefined && definiciones !== null) {
10
   muestraElemento(raizHtml, nombre, definiciones)
11
  }
12
 }
13
}
14
15
/**
16
 * @param { string } nombre
17
 */
18
export function selectorDeNombre(nombre) {
19
 return `[id="${nombre}"],[name="${nombre}"],[data-name="${nombre}"]`
20
}
21
22
/**
23
 * @param { Document | HTMLElement | ShadowRoot } raizHtml
24
 * @param { string } propiedad
25
 * @param {any[]} valores
26
 */
27
function muestraArray(raizHtml, propiedad, valores) {
28
 const conjunto = new Set(valores)
29
 const elementos = raizHtml.querySelectorAll(selectorDeNombre(propiedad))
30
 if (elementos.length === 1 && elementos[0] instanceof HTMLSelectElement) {
31
  muestraOptions(elementos[0], conjunto)
32
 } else {
33
  muestraInputs(elementos, conjunto)
34
 }
35
36
}
37
38
/**
39
 * @param {HTMLSelectElement} select
40
 * @param {Set<any>} conjunto
41
 */
42
function muestraOptions(select, conjunto) {
43
 for (let i = 0, options = select.options, len = options.length; i < len; i++) {
44
  const option = options[i]
45
  option.selected = conjunto.has(option.value)
46
 }
47
}
48
49
/**
50
 * @param {NodeListOf<Element>} elementos
51
 * @param {Set<any>} conjunto
52
 */
53
function muestraInputs(elementos, conjunto) {
54
 for (let i = 0, len = elementos.length; i < len; i++) {
55
  const elemento = elementos[i]
56
  if (elemento instanceof HTMLInputElement) {
57
   elemento.checked = conjunto.has(elemento.value)
58
  }
59
 }
60
}
61
62
const data_ = "data-"
63
const data_Length = data_.length
64
65
/**
66
 * @param {Document | HTMLElement | ShadowRoot} raizHtml
67
 * @param {string} nombre
68
 * @param {{ [s: string]: any; } } definiciones
69
 */
70
function muestraElemento(raizHtml, nombre, definiciones) {
71
 const elemento = raizHtml.querySelector(selectorDeNombre(nombre))
72
 if (elemento !== null) {
73
  for (const [propiedad, valor] of Object.entries(definiciones)) {
74
   if (propiedad in elemento) {
75
    elemento[propiedad] = valor
76
   } else if (
77
    propiedad.length > data_Length
78
    && propiedad.startsWith(data_)
79
    && elemento instanceof HTMLElement
80
   ) {
81
    elemento.dataset[propiedad.substring(data_Length)] = valor
82
   }
83
  }
84
 }
85
}

10. js / lib / ProblemDetailsError.js

1
export class ProblemDetailsError extends Error {
2
3
 /**
4
  * Detalle de los errores devueltos por un servicio.
5
  * Crea una instancia de ProblemDetailsError.
6
  * @param {object} problemDetails Objeto con la descripcipon del error.
7
  */
8
 constructor(problemDetails) {
9
10
  super(typeof problemDetails["detail"] === "string"
11
   ? problemDetails["detail"]
12
   : (typeof problemDetails["title"] === "string"
13
    ? problemDetails["title"]
14
    : "Error"))
15
16
  this.problemDetails = problemDetails
17
18
 }
19
20
}

11. js / lib / recibeTexto.js

1
/**
2
 * @param {FormData} formData
3
 * @param {string} parametro
4
 */
5
export function recibeTexto(formData, parametro) {
6
 const valor = formData.get(parametro)
7
 if (valor !== null && typeof valor !== "string")
8
  throw new Error(`El valor de ${parametro} debe ser texto.`)
9
 return valor === null ? undefined : valor
10
}
11

12. js / lib / recibeTextoObligatorio.js

1
import { recibeTexto } from "./recibeTexto.js"
2
3
/**
4
 * @param {FormData} formData
5
 * @param {string} parametro
6
 */
7
export function recibeTextoObligatorio(formData, parametro) {
8
 const texto = recibeTexto(formData, parametro)
9
 if (texto === undefined) throw new Error(`Falta el valor de ${parametro}.`)
10
 const trimTexto = texto.trim()
11
 if (trimTexto === "") throw new Error(`Campo ${parametro} en blanco.`)
12
 return trimTexto
13
}
14

13. js / lib / registraServiceWorker.js

1
const nombreDeServiceWorker = "sw.js"
2
3
try {
4
 navigator.serviceWorker.register(nombreDeServiceWorker)
5
  .then(registro => {
6
   console.log(nombreDeServiceWorker, "registrado.")
7
   console.log(registro)
8
  })
9
  .catch(error => console.log(error))
10
} catch (error) {
11
 console.log(error)
12
}

14. js / lib / validaEntidadObligatoria.js

1
/**
2
 * @template T
3
 * @param {string} nombre
4
 * @param {T | undefined} entidad
5
 */
6
export function validaEntidadObligatoria(nombre, entidad) {
7
8
 if (entidad === undefined)
9
  throw new Error(`Registro de ${nombre} no encontrado.`)
10
11
 return entidad
12
}
13