L. Carpeta « libclienteweb »

Versión para imprimir.

A. libclienteweb / configuraAccionElimina.js

1
import { consume } from "./consume.js"
2
import { submitFormRecibeJson } from "./submitFormRecibeJson.js"
3
4
/**
5
 * @param {HTMLElement} botonEliminar
6
 * @param {string} mensaje
7
 * @param {string} url
8
 * @param {HTMLFormElement} formulario
9
 * @param {string} nuevaVista
10
 */
11
export async function configuraAccionElimina(
12
 botonEliminar, mensaje, url, formulario, nuevaVista
13
) {
14
 botonEliminar.addEventListener(
15
  "click",
16
  async () => {
17
   if (confirm(mensaje)) {
18
    await consume(submitFormRecibeJson(url, formulario))
19
    location.href = nuevaVista
20
   }
21
  }
22
 )
23
}

B. libclienteweb / configuraSubmitAccion.js

1
import { consume } from "./consume.js"
2
import { submitFormRecibeJson } from "./submitFormRecibeJson.js"
3
4
/**
5
 * @param {string} url
6
 * @param {HTMLFormElement} formulario
7
 * @param {string} nuevaVista
8
 */
9
export function configuraSubmitAccion(url, formulario, nuevaVista) {
10
 formulario.addEventListener(
11
  "submit",
12
  async event => {
13
   event.preventDefault()
14
   await consume(submitFormRecibeJson(url, formulario))
15
   location.href = nuevaVista
16
  }
17
 )
18
}

C. libclienteweb / 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
}

D. libclienteweb / descargaVista.js

1
import { consume } from "./consume.js"
2
import { muestraObjeto } from "./muestraObjeto.js"
3
import { recibeJson } from "./recibeJson.js"
4
5
/**
6
 * @param {string} url
7
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
8
 *  | "CONNECT" | "HEAD" } metodoHttp
9
 */
10
export async function descargaVista(url, metodoHttp = "GET") {
11
 const respuesta = await consume(recibeJson(url, metodoHttp))
12
 const json = await respuesta.json()
13
 muestraObjeto(document, json)
14
 return json
15
}

E. libclienteweb / 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.call(this)
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 {Event | string} */ _event,
22
 /** @type {string | undefined} */ _fuente,
23
 /** @type {number | undefined} */ _numeroDeLinea,
24
 /** @type {number | undefined} */ _numeroDeColumna,
25
 /** @type {Error | undefined} */ error
26
) {
27
 muestraError(error)
28
 return true
29
}
30
31
window.addEventListener('unhandledrejection', event => {
32
 muestraError(event.reason)
33
 event.preventDefault()
34
})

F. libclienteweb / 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 { unknown } error descripción del error.
6
 */
7
export function muestraError(error) {
8
9
 if (error instanceof ProblemDetailsError) {
10
11
  const problemDetails = error.problemDetails
12
13
  let mensaje =
14
   typeof problemDetails["title"] === "string" ? problemDetails["title"] : ""
15
  if (typeof problemDetails["detail"] === "string") {
16
   if (mensaje !== "") {
17
    mensaje += "\n"
18
   }
19
   mensaje += problemDetails["detail"]
20
  }
21
  if (mensaje === "") {
22
   mensaje = "Error"
23
  }
24
  console.error(error, problemDetails)
25
  alert(mensaje)
26
27
 } else if (
28
  typeof error === "object" && error !== null && "message" in error
29
 ) {
30
31
  console.error(error)
32
  alert(error.message)
33
34
 } else {
35
36
  console.error("Error", error)
37
  alert("Error")
38
39
 }
40
41
}

G. libclienteweb / 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
   muestraElementos(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 muestraElementos(raizHtml, nombre, definiciones) {
71
 const elementos = raizHtml.querySelectorAll(selectorDeNombre(nombre))
72
 for (let i = 0, len = elementos.length; i < len; i++) {
73
  /**
74
   * @type {any}
75
   */
76
  const elemento = elementos[i]
77
  if (elemento !== null) {
78
   for (const [propiedad, valor] of Object.entries(definiciones)) {
79
    if (propiedad in elemento) {
80
     if (propiedad === "innerHTML") {
81
      // @ts-ignore
82
      elemento["innerHTML"] = DOMPurify.sanitize(valor)
83
     } else {
84
      elemento[propiedad] = valor
85
     }
86
    } else if (
87
     propiedad.length > data_Length
88
     && propiedad.startsWith(data_)
89
     && elemento instanceof HTMLElement
90
    ) {
91
     elemento.dataset[propiedad.substring(data_Length)] = valor
92
    }
93
   }
94
  }
95
 }
96
}

H. libclienteweb / 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 {any} problemDetails Objeto con la descripcipon del error.
7
  */
8
 constructor(problemDetails) {
9
10
  super(
11
   typeof problemDetails["detail"] === "string"
12
    ? problemDetails["detail"]
13
    : (
14
     typeof problemDetails["title"] === "string"
15
      ? problemDetails["title"]
16
      : "Error"
17
    )
18
  )
19
20
  this.problemDetails = problemDetails
21
22
 }
23
24
}

I. libclienteweb / recibeJson.js

1
2
/**
3
 * @param {string} url
4
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
5
 *  | "CONNECT" | "HEAD" } metodoHttp
6
 */
7
export async function recibeJson(url, metodoHttp = "GET") {
8
 return fetch(
9
  url,
10
  {
11
   method: metodoHttp,
12
   headers: { "Accept": "application/json, application/problem+json" }
13
  }
14
 )
15
}

J. libclienteweb / submitFormRecibeJson.js

1
/**
2
 * Envía los datos de un formolario a la url usando la codificación
3
 * multipart/form-data.
4
 * @param {string} url
5
 * @param {HTMLFormElement | FormData} formulario
6
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
7
 *  | "CONNECT" | "HEAD" } metodoHttp
8
 */
9
export function submitFormRecibeJson(url, formulario, metodoHttp = "POST") {
10
11
 const formData =
12
  formulario instanceof FormData ? formulario : new FormData(formulario)
13
14
 if (tieneArchivos(formData)) {
15
16
  return fetch(
17
   url,
18
   {
19
    method: metodoHttp,
20
    headers: { "Accept": "application/json, application/problem+json" },
21
    body: formData
22
   }
23
  )
24
25
 } else {
26
27
  // @ts-ignore
28
  const params = new URLSearchParams(formData)
29
  const queryString = params.toString()
30
31
  return fetch(
32
   url,
33
   {
34
    method: metodoHttp,
35
    headers: {
36
     'Content-Type': 'application/x-www-form-urlencoded',
37
     "Accept": "application/json, application/problem+json"
38
    },
39
    body: queryString
40
   }
41
  )
42
43
 }
44
45
}
46
47
/**
48
 * @param {FormData} formData
49
 */
50
function tieneArchivos(formData) {
51
 for (const value of formData.values()) {
52
  if (value instanceof File) {
53
   return true
54
  }
55
 }
56
 return false
57
}