14. Mostrar datos en el cliente

Versión para imprimir.

A. Introducción

B. Diagrama de despliegue

Diagrama de despliegue

C. Hazlo funcionar

  1. Prueba el ejemplo en http://srvmuestra.rf.gd/.

  2. Descarga el archivo /src/srvmuestra.zip y descompáctalo.

  3. Crea tu proyecto en GitHub:

    1. Crea una cuenta de email, por ejemplo, pepito@google.com

    2. Crea una cuenta de GitHub usando el email anterior y selecciona el nombre de usuario unsando la parte inicial del correo electrónico, por ejemplo pepito.

    3. Crea un repositorio nuevo. En la página principal de GitHub cliquea 📘 New.

    4. En la página Create a new repository introduce los siguientes datos:

      • Proporciona el nombre de tu repositorio debajo de donde dice Repository name *.

      • Mantén la selección Public para que otros usuarios puedan ver tu proyecto.

      • Verifica la casilla Add a README file. En este archivo se muestra información sobre tu proyecto.

      • Cliquea License: None. y selecciona la licencia que consideres más adecuada para tu proyecto.

      • Cliquea Create repository.

  4. Importa el proyecto en GitHub:

    1. En la página principal de tu proyecto en GitHub, en la pestaña < > Code, cliquea < > Code y en la sección Branches y copia la dirección que está en HTTPS, debajo de Clone.

    2. En Visual Studio Code, usa el botón de la izquierda para Source Control.

      Imagen de Source Control
    3. Cliquea el botón Clone Repository.

    4. Pega la url que copiaste anteriormente hasta arriba, donde dice algo como Provide repository URL y presiona la teclea Intro.

    5. Selecciona la carpeta donde se guardará la carpeta del proyecto.

    6. Abre la carpeta del proyecto importado.

    7. Añade el contenido de la carpeta descompactada que contiene el código del ejemplo.

  5. Edita los archivos que desees.

  6. Haz clic derecho en index.html, selecciona PHP Server: serve project y se abre el navegador para que puedas probar localmente el ejemplo.

  7. Para depurar paso a paso haz lo siguiente:

    1. En el navegador, haz clic derecho en la página que deseas depurar y selecciona inspeccionar.

    2. Recarga la página, de preferencia haciendo clic derecho en el ícono de volver a cargar la página Ïmagen del ícono de recarga y seleccionando vaciar caché y volver a cargar de manera forzada (o algo parecido). Si no aparece un menú emergente, simplemente cliquea volver a cargar la página Ïmagen del ícono de recarga. Revisa que no aparezca ningún error ni en la pestañas Consola, ni en Red.

    3. Selecciona la pestaña Fuentes (o Sources si tu navegador está en Inglés).

    4. Selecciona el archivo donde vas a empezar a depurar.

    5. Haz clic en el número de la línea donde vas a empezar a depurar.

    6. En Visual Studio Code, abre el archivo de PHP donde vas a empezar a depurar.

    7. Haz clic en Run and Debug .

    8. Si no está configurada la depuración, haz clic en create a launch json file.

    9. Haz clic en la flechita RUN AND DEBUG, al lado de la cual debe decir Listen for Xdebug .

    10. Aparece un cuadro con los controles de depuración

    11. Selecciona otra vez el archivo de PHP y haz clic en el número de la línea donde vas a empezar a depurar.

    12. Regresa al navegador, recarga la página y empieza a usarla.

    13. Si se ejecuta alguna de las líneas de código seleccionadas, aparece resaltada en la pestaña de fuentes. Usa los controles de depuración para avanzar, como se muestra en este video.

  8. Sube el proyecto al hosting que elijas. En algunos casos puedes usar filezilla (https://filezilla-project.org/)

  9. Abre un navegador y prueba el proyecto en tu hosting.

  10. En el hosting InfinityFree, la primera ves que corres la página, puede marcar un mensaje de error, pero al recargar funciona correctamente. Puedes evitar este problema usando un dominio propio.

  11. Para subir el código a GitHub, en la sección de SOURCE CONTROL, en Message introduce un mensaje sobre los cambios que hiciste, por ejemplo index.html corregido, selecciona v y luego Commit & Push.

    Imagen de Commit & Push

D. Archivos

Haz clic en los triángulos para expandir las carpetas

E. index.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width">
7 <title>Muestra datos</title>
8</head>
9
10<body onload="descargaDatos()">
11
12 <h1>Muestra datos</h1>
13
14 <!-- Al mostrar un dato, se usan los atributos "id", "name" o "data-name"
15 para buscar la propiedad a desplegar en la página. -->
16
17 <p>
18 <label>
19 Nombre
20 <!-- Muestra la propiedad nombre del objeto descargado, usando la propiedad
21 value del output. -->
22 <output name="nombre"></output>
23 </label>
24 </p>
25
26 <p>
27 <label>
28 Apellido
29 <!-- Muestra la propiedad apellido del objeto descargado, usando la propiedad
30 value del input. -->
31 <input name="apellido" type="text">
32 </label>
33 </p>
34
35 <p>
36 <label>
37 Género
38 <!-- Muestra la propiedad genero del objeto descargado, usando la propiedad
39 value del select. -->
40 <select name="genero">
41 <option value="">Sin selección</option>
42 <option value="pop">Pop</option>
43 <option value="reg">Reguetón</option>
44 </select>
45 </label>
46 </p>
47
48 <p>
49 <label>
50 Géneración
51 <!-- Muestra la propiedad generacion del objeto descargado, usando la
52 propiedad value del select. -->
53 <select name="generacion">
54 <option value="boom">Baby Boom</option>
55 <option value="X">X</option>
56 <option value="">Sin selección</option>
57 <option value="Y">Millenoals</option>
58 <option value="Z">Z</option>
59 <option value="alfa">Alfa</option>
60 </select>
61 </label>
62 </p>
63
64 <p>
65 <label>
66 Edad
67 <!-- Muestra la propiedad edad del objeto descargado, usando la propiedad
68 valueAsNumber del input. -->
69 <input data-name="edad" type="number">
70 </label>
71 </p>
72
73 <p>
74 <label>
75 Número de la suerte
76 <!-- Muestra la propiedad numero del objeto descargado, usando la propiedad
77 value del output. -->
78 <output id="numero"></output>
79 </label>
80 </p>
81
82 <p>
83 <label>
84 Avance
85 <!-- Muestra la propiedad avance del objeto descargado, usando la propiedad
86 value del progress. -->
87 <progress id="avance" max="100"></progress>
88 </label>
89 </p>
90
91 <p>
92 <label>
93 Capacidad
94 <!-- Muestra la propiedad capacidad del objeto descargado, usando la
95 propiedad value del meter. -->
96 <meter id="capacidad" min="50" max="80"></meter>
97 </label>
98 </p>
99
100 <p>
101 <label>
102 Temperatura
103 <!-- Muestra la propiedad temperatura del objeto descargado, usando la
104 propiedad valueAsNumber del meter. -->
105 <input id="temperatura" type="range" min="0" max="50">
106 </label>
107 </p>
108
109 <p>
110 <label>
111 <!-- Muestra la propiedad aprobado del objeto descargado, usando la propiedad
112 checked del input, porque el tipo del dato es boolean y el input tiene
113 type="checkbox". -->
114 <input id="aprobado" type="checkbox">
115 Aprobado
116 </label>
117 </p>
118
119 <p>
120 <label>
121 Gracioso
122 <!-- Muestra la propiedad gracioso del objeto descargado, usando la propiedad
123 value del output. -->
124 <output id="gracioso"></output>
125 </label>
126 </p>
127
128 <p>
129 <label>
130 Emplacado
131 <!-- Muestra la propiedad emplacado del objeto descargado, usando la
132 propiedad value del select. -->
133 <select name="emplacado">
134 <option value="">Sin selección</option>
135 <option value="true">Si</option>
136 <option value="false">No</option>
137 </select>
138 </label>
139 </p>
140
141 <label>Dirección</label>
142 <!-- Muestra la propiedad direccion del objeto descargado, usando la propiedad
143 textContent del pre. -->
144 <pre id="direccion"></pre>
145
146 <p>
147 <label>
148 Encabezado
149 <!-- Muestra la propiedad encabezado del objeto descargado, usando la
150 propiedad innerHTML del span. -->
151 <span id="encabezado"></span>
152 </label>
153 </p>
154
155 <p>
156 <label>
157 Nacimiento
158 <!-- Muestra la propiedad nacimiento del objeto descargado, usando la
159 propiedad value del input. Revisa la especificación de input para
160 los distintos formatos de fecha que se pueden usar. -->
161 <input id="nacimiento" type="date">
162 </label>
163 </p>
164
165 <figure>
166 <!-- Muestra la propiedad imagen1 del objeto descargado, usando la propiedad
167 src del img. -->
168 <img id="imagen1" alt="Imagen 2">
169 <figcaption>Imagen 1</figcaption>
170 </figure>
171
172 <figure>
173 <!-- Muestra la propiedad imagen2 del objeto descargado, usando la propiedad
174 src del img. Como el valor es "", se oculta el img usando hidden = true. -->
175 <img id="imagen2" alt="Imagen 2">
176 <figcaption>Imagen 2</figcaption>
177 </figure>
178
179 <!-- Muestra la propiedad pasatiempos[] del objeto descargado; como es un
180 array, usa los elementos con name="pasatiempos[]" y les pone la propiedad
181 checked en true si su value está en el array; de lo contrario se las pone en
182 false. Los corchetes([]), le indican a PHP que la propiedad es un array
183 que puede llegar a tener 0, 1 o más elementos. -->
184 <fieldset>
185 <legend>Pasatiempos</legend>
186 <label>
187 <input type="checkbox" name="pasatiempos[]" value="fut">Futbol
188 </label>
189 <label>
190 <input type="checkbox" name="pasatiempos[]" value="chess">Ajedrez
191 </label>
192 <label>
193 <input type="checkbox" data-name="pasatiempos[]" value="basket">Basketbol
194 </label>
195 </fieldset>
196
197 <!-- Muestra la propiedad madrugador del objeto descargado; como es un
198 array, usa los elementos con name="madrugador" y les pone la propiedad
199 checked en true si su value está en el array; de lo contrario se las pone en
200 false. Como el name no tiene [], el array solo tiene 0 o un elemento. -->
201 <fieldset>
202 <legend>Madrugador</legend>
203 <label><input type="radio" name="madrugador" value="si">Si</label>
204 <label><input type="radio" name="madrugador" value="no">No</label>
205 </fieldset>
206
207 <p>
208 <label>
209 Patos
210 <!-- Muestra la propiedad patos del objeto descargado; como es un
211 array, usa los las opciones del select con name="patos[]" y les pone la
212 propiedad selected en true si su value está en el array; de lo contrario se
213 las pone en false. Los corchetes([]), le indican a PHP que la propiedad es
214 un array que puede llegar a tener 0, 1 o más elementos. -->
215 <select name="patos[]" multiple size="3">
216 <option value="hugo">Hugo</option>
217 <option value="paco">Paco</option>
218 <option value="luis">Luis</option>
219 </select>
220 </label>
221 </p>
222</body>
223
224<script type="module">
225
226 import { exportaAHtml } from "./lib/js/exportaAHtml.js"
227 import { consumeJson } from "./lib/js/consumeJson.js"
228 import { muestraObjeto } from "./lib/js/muestraObjeto.js"
229 import { muestraError } from "./lib/js/muestraError.js"
230
231 async function descargaDatos() {
232 try {
233 const respuesta = await consumeJson("srv/datos.php")
234 muestraObjeto(document, respuesta.body)
235 } catch (error) {
236 muestraError(error)
237 }
238 }
239 exportaAHtml(descargaDatos)
240
241</script>
242
243</html>

F. Carpeta « srv »

Versión para imprimir.

A. srv / datos.php

1<?php
2
3require_once __DIR__ . "/../lib/php/devuelveJson.php";
4
5devuelveJson([
6 "nombre" => ["value" => "pp"],
7 "apellido" => ["value" => "tkt"],
8 "genero" => ["value" => "pop"],
9 "generacion" => ["value" => ""],
10 "edad" => ["valueAsNumber" => 18],
11 "numero" => ["value" => 5],
12 "avance" => ["value" => 70],
13 "capacidad" => ["value" => 60],
14 "temperatura" => ["valueAsNumber" => 40],
15 "aprobado" => ["checked" => true],
16 "gracioso" => ["value" => false],
17 "emplacado" => ["value" => false],
18 "direccion" => ["textContent" => "Girasoles 23\ncolonia Rosales"],
19 "encabezado" => ["innerHTML" => "<em>Hola, soy <strong>pp</strong>"],
20 "nacimiento" => ["value" => "2000-07-04"],
21 "imagen1" => [
22 "src" => "https://gilpgawoas.github.io/img/icono/maskable_icon_x48.png"
23 ],
24 "imagen2" => ["src" => "", "hidden" => true],
25 "pasatiempos[]" => ["fut", "basket"],
26 "madrugador" => ["no"],
27 "patos[]" => ["paco", "luis"],
28]);
29

G. Carpeta « lib »

Versión para imprimir.

A. Carpeta « lib / js »

1. lib / js / consumeJson.js

1import { exportaAHtml } from "./exportaAHtml.js"
2import { ProblemDetails } from "./ProblemDetails.js"
3
4/**
5 * Espera a que la promesa de un fetch termine. Si
6 * hay error, lanza una excepción. Si no hay error,
7 * interpreta la respuesta del servidor como JSON y
8 * la convierte en una literal de objeto.
9 *
10 * @param { string | Promise<Response> } servicio
11 */
12export async function consumeJson(servicio) {
13
14 if (typeof servicio === "string") {
15 servicio = fetch(servicio, {
16 headers: { "Accept": "application/json, application/problem+json" }
17 })
18 } else if (!(servicio instanceof Promise)) {
19 throw new Error("Servicio de tipo incorrecto.")
20 }
21
22 const respuesta = await servicio
23
24 const headers = respuesta.headers
25
26 if (respuesta.ok) {
27 // Aparentemente el servidor tuvo éxito.
28
29 if (respuesta.status === 204) {
30 // No contiene texto de respuesta.
31
32 return { headers, body: {} }
33
34 } else {
35
36 const texto = await respuesta.text()
37
38 try {
39
40 return { headers, body: JSON.parse(texto) }
41
42 } catch (error) {
43
44 // El contenido no es JSON. Probablemente sea texto de un error.
45 throw new ProblemDetails(respuesta.status, headers, texto,
46 "/error/errorinterno.html")
47
48 }
49
50 }
51
52 } else {
53 // Hay un error.
54
55 const texto = await respuesta.text()
56
57 if (texto === "") {
58
59 // No hay texto. Se usa el texto predeterminado.
60 throw new ProblemDetails(respuesta.status, headers, respuesta.statusText)
61
62 } else {
63 // Debiera se un ProblemDetails en JSON.
64
65 try {
66
67 const { title, type, detail } = JSON.parse(texto)
68
69 throw new ProblemDetails(respuesta.status, headers,
70 typeof title === "string" ? title : respuesta.statusText,
71 typeof type === "string" ? type : undefined,
72 typeof detail === "string" ? detail : undefined)
73
74 } catch (error) {
75
76 if (error instanceof ProblemDetails) {
77 // El error si era un ProblemDetails
78
79 throw error
80
81 } else {
82
83 throw new ProblemDetails(respuesta.status, headers, respuesta.statusText,
84 undefined, texto)
85
86 }
87
88 }
89
90 }
91
92 }
93
94}
95
96exportaAHtml(consumeJson)

2. lib / js / exportaAHtml.js

1/**
2 * Permite que los eventos de html usen la función.
3 * @param {function} functionInstance
4 */
5export function exportaAHtml(functionInstance) {
6 window[nombreDeFuncionParaHtml(functionInstance)] = functionInstance
7}
8
9/**
10 * @param {function} valor
11 */
12export function nombreDeFuncionParaHtml(valor) {
13 const names = valor.name.split(/\s+/g)
14 return names[names.length - 1]
15}

3. lib / js / muestraError.js

1import { exportaAHtml } from "./exportaAHtml.js"
2import { ProblemDetails } from "./ProblemDetails.js"
3
4/**
5 * Muestra un error en la consola y en un cuadro de
6 * alerta el mensaje de una excepción.
7 * @param { ProblemDetails | Error | null } error descripción del error.
8 */
9export function muestraError(error) {
10
11 if (error === null) {
12
13 console.error("Error")
14 alert("Error")
15
16 } else if (error instanceof ProblemDetails) {
17
18 let mensaje = error.title
19 if (error.detail) {
20 mensaje += `\n\n${error.detail}`
21 }
22 mensaje += `\n\nCódigo: ${error.status}`
23 if (error.type) {
24 mensaje += ` ${error.type}`
25 }
26
27 console.error(mensaje)
28 console.error(error)
29 console.error("Headers:")
30 error.headers.forEach((valor, llave) => console.error(llave, "=", valor))
31 alert(mensaje)
32
33 } else {
34
35 console.error(error)
36 alert(error.message)
37
38 }
39
40}
41
42exportaAHtml(muestraError)

4. lib / js / muestraObjeto.js

1import { exportaAHtml } from "./exportaAHtml.js"
2
3/**
4 * @param { Document | HTMLElement } raizHtml
5 * @param { any } objeto
6 */
7export function muestraObjeto(raizHtml, objeto) {
8
9 for (const [nombre, definiciones] of Object.entries(objeto)) {
10
11 if (Array.isArray(definiciones)) {
12
13 muestraArray(raizHtml, nombre, definiciones)
14
15 } else if (definiciones !== undefined && definiciones !== null) {
16
17 const elementoHtml = buscaElementoHtml(raizHtml, nombre)
18
19 if (elementoHtml instanceof HTMLInputElement) {
20
21 muestraInput(raizHtml, elementoHtml, definiciones)
22
23 } else if (elementoHtml !== null) {
24
25 for (const [atributo, valor] of Object.entries(definiciones)) {
26 if (atributo in elementoHtml) {
27 elementoHtml[atributo] = valor
28 }
29 }
30
31 }
32
33 }
34
35 }
36
37}
38exportaAHtml(muestraObjeto)
39
40/**
41 * @param { Document | HTMLElement } raizHtml
42 * @param { string } nombre
43 */
44export function buscaElementoHtml(raizHtml, nombre) {
45 return raizHtml.querySelector(
46 `#${nombre},[name="${nombre}"],[data-name="${nombre}"]`)
47}
48
49/**
50 * @param { Document | HTMLElement } raizHtml
51 * @param { string } propiedad
52 * @param {any[]} valores
53 */
54function muestraArray(raizHtml, propiedad, valores) {
55
56 const conjunto = new Set(valores)
57 const elementos =
58 raizHtml.querySelectorAll(`[name="${propiedad}"],[data-name="${propiedad}"]`)
59
60 if (elementos.length === 1) {
61 const elemento = elementos[0]
62
63 if (elemento instanceof HTMLSelectElement) {
64 const options = elemento.options
65 for (let i = 0, len = options.length; i < len; i++) {
66 const option = options[i]
67 option.selected = conjunto.has(option.value)
68 }
69 return
70 }
71
72 }
73
74 for (let i = 0, len = elementos.length; i < len; i++) {
75 const elemento = elementos[i]
76 if (elemento instanceof HTMLInputElement) {
77 elemento.checked = conjunto.has(elemento.value)
78 }
79 }
80
81}
82
83/**
84 * @param { Document | HTMLElement } raizHtml
85 * @param { HTMLInputElement } input
86 * @param { any } definiciones
87 */
88function muestraInput(raizHtml, input, definiciones) {
89
90 for (const [atributo, valor] of Object.entries(definiciones)) {
91
92 if (atributo == "data-file") {
93
94 const img = getImgParaElementoHtml(raizHtml, input)
95 if (img !== null) {
96 input.dataset.file = valor
97 input.value = ""
98 if (valor === "") {
99 img.src = ""
100 img.hidden = true
101 } else {
102 img.src = valor
103 img.hidden = false
104 }
105 }
106
107 } else if (atributo in input) {
108
109 input[atributo] = valor
110
111 }
112 }
113
114}
115
116/**
117 * @param { Document | HTMLElement } raizHtml
118 * @param { HTMLElement } elementoHtml
119 */
120export function getImgParaElementoHtml(raizHtml, elementoHtml) {
121 const imgId = elementoHtml.getAttribute("data-img")
122 if (imgId === null) {
123 return null
124 } else {
125 const input = buscaElementoHtml(raizHtml, imgId)
126 if (input instanceof HTMLImageElement) {
127 return input
128 } else {
129 return null
130 }
131 }
132}

5. lib / js / ProblemDetails.js

1/**
2 * Detalle de los errores devueltos por un servicio.
3 */
4export class ProblemDetails extends Error {
5
6 /**
7 * @param {number} status
8 * @param {Headers} headers
9 * @param {string} title
10 * @param {string} [type]
11 * @param {string} [detail]
12 */
13 constructor(status, headers, title, type, detail) {
14 super(title)
15 /**
16 * @readonly
17 */
18 this.status = status
19 /**
20 * @readonly
21 */
22 this.headers = headers
23 /**
24 * @readonly
25 */
26 this.type = type
27 /**
28 * @readonly
29 */
30 this.detail = detail
31 /**
32 * @readonly
33 */
34 this.title = title
35 }
36
37}

B. Carpeta « lib / php »

1. lib / php / devuelveJson.php

1<?php
2
3require_once __DIR__ . "/devuelveResultadoNoJson.php";
4
5function devuelveJson($resultado)
6{
7
8 $json = json_encode($resultado);
9
10 if ($json === false) {
11
12 devuelveResultadoNoJson();
13 } else {
14
15 http_response_code(200);
16 header("Content-Type: application/json");
17 echo $json;
18 }
19}
20

2. lib / php / devuelveResultadoNoJson.php

1<?php
2
3require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5function devuelveResultadoNoJson()
6{
7
8 http_response_code(INTERNAL_SERVER_ERROR);
9 header("Content-Type: application/problem+json");
10 echo '{' .
11 '"title": "El resultado no puede representarse como JSON."' .
12 '"type": "/error/resultadonojson.html"' .
13 '}';
14}
15

3. lib / php / INTERNAL_SERVER_ERROR.php

1<?php
2
3const INTERNAL_SERVER_ERROR = 500;

H. Carpeta « error »

Versión para imprimir.

A. error / errorinterno.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Error interno del servidor</title>
10
11</head>
12
13<body>
14
15 <h1>Error interno del servidor</h1>
16
17 <p>Se presentó de forma inesperada un error interno del servidor.</p>
18
19</body>
20
21</html>

B. error / resultadonojson.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>El resultado no puede representarse como JSON</title>
10
11</head>
12
13<body>
14
15 <h1>El resultado no puede representarse como JSON</h1>
16
17 <p>
18 Debido a un error interno del servidor, el resultado generado, no se puede
19 recuperar.
20 </p>
21
22</body>
23
24</html>

I. jsconfig.json

1{
2 "compilerOptions": {
3 "checkJs": true,
4 "strictNullChecks": true,
5 "target": "ES6",
6 "module": "Node16",
7 "moduleResolution": "Node16",
8 "lib": [
9 "ES2017",
10 "WebWorker",
11 "DOM"
12 ]
13 }
14}

J. Resumen