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 (contentType === "application/problem+json; charset=utf-8")
16
   throw new ProblemDetailsError(await respuesta.json())
17
  else
18
   throw new Error(respuesta.statusText)
19
 }
20
}

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