@dogiloki/artha-js 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +818 -1
- package/dist/artha.min.css +1 -1
- package/dist/artha.min.css.map +1 -1
- package/dist/artha.min.js +6 -1
- package/dist/assets/icons/refresh.svg +1 -0
- package/dist/assets/icons/search.svg +1 -0
- package/package.json +1 -1
- package/src/abstract/BaseComponent.js +72 -15
- package/src/components/artha-container.js +101 -20
- package/src/components/artha-form.js +17 -8
- package/src/components/input-search.js +104 -0
- package/src/core/TaskQueue.js +0 -5
- package/src/scss/colors.scss +30 -1
- package/src/scss/icons.scss +20 -0
- package/src/scss/main.scss +1 -0
package/README.md
CHANGED
|
@@ -1 +1,818 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Artha JS
|
|
2
|
+
|
|
3
|
+
Mini librería para construir interfaces HTML reactivas con Web Components, peticiones XHR, cola de tareas y mensajes visuales sin depender de frameworks.
|
|
4
|
+
|
|
5
|
+
`Artha JS` expone un conjunto pequeño de utilidades y componentes personalizados pensados para:
|
|
6
|
+
|
|
7
|
+
- enviar formularios por XMLHttpRequest
|
|
8
|
+
- renderizar listas o bloques desde respuestas JSON
|
|
9
|
+
- mostrar loaders y mensajes de estado
|
|
10
|
+
- buscar con debounce sobre contenedores remotos
|
|
11
|
+
- coordinar eventos globales entre componentes
|
|
12
|
+
|
|
13
|
+
## Contenido
|
|
14
|
+
|
|
15
|
+
- [¿Qué incluye?](#que-incluye)
|
|
16
|
+
- [Instalación](#instalación)
|
|
17
|
+
- [Inicio rápido](#inicio-rápido)
|
|
18
|
+
- [Exportaciones](#exportaciones)
|
|
19
|
+
- [Componentes](#componentes)
|
|
20
|
+
- [Core](#core)
|
|
21
|
+
- [Flujo de respuesta esperado](#flujo-de-respuesta-esperado)
|
|
22
|
+
- [Eventos útiles](#eventos-útiles)
|
|
23
|
+
- [Desarrollo](#desarrollo)
|
|
24
|
+
- [Licencia](#licencia)
|
|
25
|
+
|
|
26
|
+
## ¿Qué incluye?
|
|
27
|
+
|
|
28
|
+
La librería exporta estas piezas:
|
|
29
|
+
|
|
30
|
+
- `Util`: helpers de DOM, formato y utilidades generales
|
|
31
|
+
- `EventBus`: bus global de eventos
|
|
32
|
+
- `TaskQueue`: cola simple para evitar tareas duplicadas y coordinar estados
|
|
33
|
+
- `XHR`: wrapper ligero sobre `XMLHttpRequest`
|
|
34
|
+
- `ArthaMessage`: componente para mostrar mensajes de estado
|
|
35
|
+
- `ArthaLoader`: componente visual de carga
|
|
36
|
+
- `ArthaContainer`: componente para cargar y renderizar datos
|
|
37
|
+
- `ArthaForm`: formulario con envío asíncrono
|
|
38
|
+
- `InputSearch`: componente de búsqueda con debounce
|
|
39
|
+
|
|
40
|
+
Al importar `dist/artha.min.js`, la librería registra automáticamente estos custom elements:
|
|
41
|
+
|
|
42
|
+
- `artha-container`
|
|
43
|
+
- `artha-form`
|
|
44
|
+
- `artha-message`
|
|
45
|
+
- `artha-loader`
|
|
46
|
+
- `input-search`
|
|
47
|
+
|
|
48
|
+
## Instalación
|
|
49
|
+
|
|
50
|
+
### Desde npm
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @dogiloki/artha-js
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Importando el bundle
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import {
|
|
60
|
+
XHR,
|
|
61
|
+
EventBus,
|
|
62
|
+
TaskQueue,
|
|
63
|
+
Util,
|
|
64
|
+
ArthaForm,
|
|
65
|
+
ArthaContainer,
|
|
66
|
+
ArthaMessage,
|
|
67
|
+
ArthaLoader,
|
|
68
|
+
InputSearch
|
|
69
|
+
} from "@dogiloki/artha-js/dist/artha.min.js";
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Importando estilos
|
|
73
|
+
|
|
74
|
+
Si tu bundler soporta CSS desde dependencias:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
import "@dogiloki/artha-js/dist/artha.min.css";
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
O bien desde HTML:
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<link rel="stylesheet" href="./node_modules/@dogiloki/artha-js/dist/artha.min.css">
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Inicio rápido
|
|
87
|
+
|
|
88
|
+
Ejemplo mínimo con formulario, contenedor remoto y búsqueda:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<!DOCTYPE html>
|
|
92
|
+
<html lang="es">
|
|
93
|
+
<head>
|
|
94
|
+
<meta charset="UTF-8">
|
|
95
|
+
<meta name="csrf-token" content="TOKEN_OPCIONAL">
|
|
96
|
+
<link rel="stylesheet" href="./dist/artha.min.css">
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<artha-form
|
|
100
|
+
id="user-form"
|
|
101
|
+
action="/api/user"
|
|
102
|
+
method="POST">
|
|
103
|
+
<input type="text" name="name" placeholder="Nombre">
|
|
104
|
+
<input type="email" name="email" placeholder="Correo">
|
|
105
|
+
<button type="submit">Guardar</button>
|
|
106
|
+
</artha-form>
|
|
107
|
+
|
|
108
|
+
<artha-container
|
|
109
|
+
id="users"
|
|
110
|
+
action="/api/users"
|
|
111
|
+
method="GET"
|
|
112
|
+
template="user-template"
|
|
113
|
+
searcher
|
|
114
|
+
pagination="10">
|
|
115
|
+
</artha-container>
|
|
116
|
+
|
|
117
|
+
<template id="user-template">
|
|
118
|
+
<article>
|
|
119
|
+
<h3 data-wire="name"></h3>
|
|
120
|
+
<p data-wire="email"></p>
|
|
121
|
+
<small data-wire="id"></small>
|
|
122
|
+
</article>
|
|
123
|
+
</template>
|
|
124
|
+
|
|
125
|
+
<script type="module">
|
|
126
|
+
import "./dist/artha.min.js";
|
|
127
|
+
</script>
|
|
128
|
+
</body>
|
|
129
|
+
</html>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Exportaciones
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
import {
|
|
136
|
+
Util,
|
|
137
|
+
EventBus,
|
|
138
|
+
TaskQueue,
|
|
139
|
+
XHR,
|
|
140
|
+
ArthaMessage,
|
|
141
|
+
ArthaLoader,
|
|
142
|
+
ArthaContainer,
|
|
143
|
+
ArthaForm,
|
|
144
|
+
InputSearch
|
|
145
|
+
} from "./dist/artha.min.js";
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Al cargar el módulo también se emiten dos eventos globales:
|
|
149
|
+
|
|
150
|
+
- `artha:before-register`
|
|
151
|
+
- `artha:after-register`
|
|
152
|
+
|
|
153
|
+
Esto sirve, por ejemplo, para personalizar la transformación de respuestas antes de registrar los componentes:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import { EventBus, XHR } from "./dist/artha.min.js";
|
|
157
|
+
|
|
158
|
+
EventBus.on("artha:before-register", () => {
|
|
159
|
+
XHR.defaults.transformResponse = (xhr) => ({
|
|
160
|
+
data: xhr.response,
|
|
161
|
+
message: null,
|
|
162
|
+
errors: null,
|
|
163
|
+
status: "success"
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Importante: si quieres cambiar `XHR.defaults.transformResponse`, hazlo antes de que los componentes se registren. La forma recomendada es usar el evento `artha:before-register`.
|
|
169
|
+
|
|
170
|
+
## Componentes
|
|
171
|
+
|
|
172
|
+
### `artha-message`
|
|
173
|
+
|
|
174
|
+
Componente para mostrar mensajes visuales.
|
|
175
|
+
|
|
176
|
+
#### Tipos soportados
|
|
177
|
+
|
|
178
|
+
- `info`
|
|
179
|
+
- `success`
|
|
180
|
+
- `warning`
|
|
181
|
+
- `error`
|
|
182
|
+
|
|
183
|
+
#### Ejemplo
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<artha-message id="feedback"></artha-message>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
const message = document.getElementById("feedback");
|
|
191
|
+
|
|
192
|
+
message.info("Cargando información...");
|
|
193
|
+
message.success("Guardado correctamente");
|
|
194
|
+
message.warning("Faltan campos por revisar");
|
|
195
|
+
message.error("Ocurrió un error");
|
|
196
|
+
message.hidden();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### API pública
|
|
200
|
+
|
|
201
|
+
- `show(message, type)`
|
|
202
|
+
- `info(message)`
|
|
203
|
+
- `success(message)`
|
|
204
|
+
- `warning(message)`
|
|
205
|
+
- `error(message)`
|
|
206
|
+
- `hidden()`
|
|
207
|
+
|
|
208
|
+
### `artha-loader`
|
|
209
|
+
|
|
210
|
+
Loader visual para estados de carga.
|
|
211
|
+
|
|
212
|
+
#### Atributos
|
|
213
|
+
|
|
214
|
+
- `type`: tipo de loader. Default: `ring`
|
|
215
|
+
- `text`: texto mostrado debajo del loader. Default: `Petición en proceso...`
|
|
216
|
+
|
|
217
|
+
#### Tipos disponibles
|
|
218
|
+
|
|
219
|
+
- `ring`
|
|
220
|
+
- `dots`
|
|
221
|
+
- `bar`
|
|
222
|
+
- `wave`
|
|
223
|
+
|
|
224
|
+
Nota: en la implementación actual `bar` y `wave` reutilizan la misma clase visual que `dots`.
|
|
225
|
+
|
|
226
|
+
#### Ejemplo
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<artha-loader type="ring" text="Cargando usuarios"></artha-loader>
|
|
230
|
+
<artha-loader type="dots" text="Procesando"></artha-loader>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `artha-form`
|
|
234
|
+
|
|
235
|
+
Formulario asíncrono basado en `XMLHttpRequest`.
|
|
236
|
+
|
|
237
|
+
#### Comportamiento
|
|
238
|
+
|
|
239
|
+
- intercepta el evento `submit`
|
|
240
|
+
- valida los campos con `checkValidity()`
|
|
241
|
+
- envía los datos por XHR
|
|
242
|
+
- muestra mensajes con `artha-message`
|
|
243
|
+
- rellena campos automáticamente si la respuesta trae `data`
|
|
244
|
+
|
|
245
|
+
#### Atributos útiles
|
|
246
|
+
|
|
247
|
+
- `action`: endpoint del formulario
|
|
248
|
+
- `method`: método HTTP
|
|
249
|
+
- `response-type`: tipo de respuesta del XHR. Default: `json`
|
|
250
|
+
- `disable-submit`: impide el envío automático al presionar Enter
|
|
251
|
+
- `message-target`: selector interno para localizar el mensaje asociado
|
|
252
|
+
- `id`: usado para identificar la tarea en `TaskQueue`
|
|
253
|
+
|
|
254
|
+
#### API pública
|
|
255
|
+
|
|
256
|
+
- `submit()`
|
|
257
|
+
- `reset(resetMessage = true)`
|
|
258
|
+
- `resetMessage()`
|
|
259
|
+
- `checkValidity()`
|
|
260
|
+
- `loadInputs(selector = "input,select,textarea")`
|
|
261
|
+
- `fillFromJson(json, reset = true)`
|
|
262
|
+
- `getValue(name)`
|
|
263
|
+
- `input(name)`
|
|
264
|
+
|
|
265
|
+
#### Eventos emitidos
|
|
266
|
+
|
|
267
|
+
- `load`
|
|
268
|
+
- `resolve`
|
|
269
|
+
- `component-ready`
|
|
270
|
+
|
|
271
|
+
### `artha-container`
|
|
272
|
+
|
|
273
|
+
Componente para cargar, renderizar y refrescar datos remotos, o actualizar vistas existentes.
|
|
274
|
+
|
|
275
|
+
#### Casos de uso
|
|
276
|
+
|
|
277
|
+
- listados
|
|
278
|
+
- tarjetas
|
|
279
|
+
- tablas simples
|
|
280
|
+
- bloques con plantillas HTML
|
|
281
|
+
- selección simple o múltiple
|
|
282
|
+
- búsqueda con `input-search`
|
|
283
|
+
- paginación simple
|
|
284
|
+
- refresco desde eventos globales
|
|
285
|
+
|
|
286
|
+
#### Atributos útiles
|
|
287
|
+
|
|
288
|
+
- `action`: endpoint a consultar
|
|
289
|
+
- `method`: método HTTP. Default: `GET`
|
|
290
|
+
- `page`: página actual cuando hay paginación. Default: `1`
|
|
291
|
+
- `search`: criterio de búsqueda interno
|
|
292
|
+
- `response-type`: tipo de respuesta del XHR usado por el contenedor. Default: `json`
|
|
293
|
+
- `template`: id de un `<template>` o referencia configurada en el componente
|
|
294
|
+
- `pagination`: cantidad por página enviada en la query. Default: `10`
|
|
295
|
+
- `message`: referencia al mensaje asociado
|
|
296
|
+
- `message-target`: selector interno alternativo para localizar un `artha-message`
|
|
297
|
+
- `searcher`: crea internamente un `input-search` y lo conecta al contenedor
|
|
298
|
+
- `selectable`: permite seleccionar items
|
|
299
|
+
- `multiple`: permite múltiples selecciones
|
|
300
|
+
- `refresh-on`: nombres de eventos del `EventBus`, separados por coma
|
|
301
|
+
- `id`: identificador del contenedor
|
|
302
|
+
|
|
303
|
+
#### API pública
|
|
304
|
+
|
|
305
|
+
- `hasAction()`
|
|
306
|
+
- `hasPagination()`
|
|
307
|
+
- `refresh(search = null)`
|
|
308
|
+
- `refreshWithData(data)`
|
|
309
|
+
- `render(results, refresh = false, refreshChildren = true)`
|
|
310
|
+
- `renderItem(data, refreshChildren = true, update = null)`
|
|
311
|
+
- `nextPage()`
|
|
312
|
+
- `prevPage()`
|
|
313
|
+
- `goToPage(page)`
|
|
314
|
+
- `resetPagination(refresh = false)`
|
|
315
|
+
- `reset()`
|
|
316
|
+
- `selection()`
|
|
317
|
+
- propiedad `value`
|
|
318
|
+
|
|
319
|
+
#### Selección
|
|
320
|
+
|
|
321
|
+
Si `selectable` está activo:
|
|
322
|
+
|
|
323
|
+
- `container.value` devuelve el id seleccionado
|
|
324
|
+
- si también `multiple` está activo, devuelve un arreglo de ids
|
|
325
|
+
- `reset()` limpia la selección actual
|
|
326
|
+
|
|
327
|
+
#### Búsqueda
|
|
328
|
+
|
|
329
|
+
Si `searcher` está activo, `artha-container` crea un `<input-search>` interno y escucha:
|
|
330
|
+
|
|
331
|
+
- `search`: actualiza `search`, ejecuta `refresh()` y reinicia `page` a `1`
|
|
332
|
+
- `cancel-search`: aborta la petición XHR activa si existe
|
|
333
|
+
|
|
334
|
+
#### Paginación
|
|
335
|
+
|
|
336
|
+
Si el contenedor tiene el atributo `pagination`, enviará estos parámetros en la query:
|
|
337
|
+
|
|
338
|
+
- `pagination`
|
|
339
|
+
- `page`
|
|
340
|
+
|
|
341
|
+
Ejemplo:
|
|
342
|
+
|
|
343
|
+
```html
|
|
344
|
+
<artha-container
|
|
345
|
+
id="users"
|
|
346
|
+
action="/api/users"
|
|
347
|
+
template="user-card"
|
|
348
|
+
pagination="10"
|
|
349
|
+
searcher>
|
|
350
|
+
</artha-container>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Y desde JavaScript:
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
const container = document.getElementById("users");
|
|
357
|
+
|
|
358
|
+
container.nextPage();
|
|
359
|
+
container.prevPage();
|
|
360
|
+
container.goToPage(3);
|
|
361
|
+
container.resetPagination(true);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### Eventos emitidos
|
|
365
|
+
|
|
366
|
+
- `load`
|
|
367
|
+
- `resolve`
|
|
368
|
+
- `dynamic-content-loaded`
|
|
369
|
+
- `item-rendered`
|
|
370
|
+
- `item-selected`
|
|
371
|
+
- `item-deselected`
|
|
372
|
+
- `component-ready`
|
|
373
|
+
|
|
374
|
+
#### Sistema `data-wire`
|
|
375
|
+
|
|
376
|
+
El renderizado se basa en atributos `data-wire` dentro de la plantilla.
|
|
377
|
+
|
|
378
|
+
Formato general:
|
|
379
|
+
|
|
380
|
+
```html
|
|
381
|
+
data-wire="ruta"
|
|
382
|
+
data-wire="ruta:atributo"
|
|
383
|
+
data-wire="ruta:atributo:append"
|
|
384
|
+
data-wire="ruta:boolean"
|
|
385
|
+
data-wire="ruta:boolean:chooser"
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Ejemplos de `data-wire`
|
|
389
|
+
|
|
390
|
+
Texto simple:
|
|
391
|
+
|
|
392
|
+
```html
|
|
393
|
+
<span data-wire="name"></span>
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Propiedad anidada:
|
|
397
|
+
|
|
398
|
+
```html
|
|
399
|
+
<span data-wire="user.email"></span>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Append sobre contenido actual:
|
|
403
|
+
|
|
404
|
+
```html
|
|
405
|
+
<span data-wire="price:textContent:append">$ </span>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Booleano como check o cross:
|
|
409
|
+
|
|
410
|
+
```html
|
|
411
|
+
<span data-wire="active:boolean"></span>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Booleano con selección por plantilla:
|
|
415
|
+
|
|
416
|
+
```html
|
|
417
|
+
<div data-wire="status:boolean:chooser">
|
|
418
|
+
<template data-chooser-value="approved">
|
|
419
|
+
<span>Aprobado</span>
|
|
420
|
+
</template>
|
|
421
|
+
<template data-chooser-value="rejected">
|
|
422
|
+
<span>Rechazado</span>
|
|
423
|
+
</template>
|
|
424
|
+
<template data-chooser-default>
|
|
425
|
+
<span>Pendiente</span>
|
|
426
|
+
</template>
|
|
427
|
+
</div>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Renderizado de arreglos
|
|
431
|
+
|
|
432
|
+
El componente soporta rutas terminadas en `[]` para iterar datos. Internamente, busca elementos marcados con `fillable` o `iterable`.
|
|
433
|
+
|
|
434
|
+
Ejemplo conceptual:
|
|
435
|
+
|
|
436
|
+
```html
|
|
437
|
+
<ul data-wire="tags[]">
|
|
438
|
+
<li>
|
|
439
|
+
<span fillable></span>
|
|
440
|
+
</li>
|
|
441
|
+
</ul>
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Nota: el flujo más sólido en la implementación actual es renderizar texto, booleanos y arreglos. Si quieres usar mapeos más avanzados, conviene probarlos primero en tu caso concreto.
|
|
445
|
+
|
|
446
|
+
### `input-search`
|
|
447
|
+
|
|
448
|
+
Componente de búsqueda con debounce pensado para integrarse con `artha-container`.
|
|
449
|
+
|
|
450
|
+
#### Atributos
|
|
451
|
+
|
|
452
|
+
- `delay`: milisegundos de espera antes de emitir la búsqueda. Default: `300`
|
|
453
|
+
- `text`: placeholder del input. Default: `Buscar`
|
|
454
|
+
|
|
455
|
+
#### API pública
|
|
456
|
+
|
|
457
|
+
- `search()`
|
|
458
|
+
|
|
459
|
+
#### Eventos emitidos
|
|
460
|
+
|
|
461
|
+
- `search`: emite `{ query }`
|
|
462
|
+
- `cancel-search`: emite `{ query }` cuando se cancela una búsqueda en cola
|
|
463
|
+
|
|
464
|
+
#### Ejemplo
|
|
465
|
+
|
|
466
|
+
```html
|
|
467
|
+
<input-search delay="300" text="Buscar usuarios"></input-search>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
```js
|
|
471
|
+
const search = document.querySelector("input-search");
|
|
472
|
+
|
|
473
|
+
search.addEventListener("search", (evt) => {
|
|
474
|
+
console.log(evt.detail.query);
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## Core
|
|
479
|
+
|
|
480
|
+
### `BaseComponent`
|
|
481
|
+
|
|
482
|
+
Clase base para los custom elements de la librería. Gestiona atributos, propiedades, defaults, booleanos, referencias a elementos y eventos de cambio.
|
|
483
|
+
|
|
484
|
+
#### Opciones de configuración
|
|
485
|
+
|
|
486
|
+
- `booleans`: lista de props booleanas
|
|
487
|
+
- `element_refs`: props que apuntan a elementos del DOM por atributo `id` o por referencia en memoria
|
|
488
|
+
- `defaults`: valores por defecto
|
|
489
|
+
- `resolvers`: getter/setter personalizado por prop
|
|
490
|
+
- `reflect`: permite indicar props que no deben reflejarse como atributos HTML
|
|
491
|
+
|
|
492
|
+
#### `reflect`
|
|
493
|
+
|
|
494
|
+
Cuando una prop se define con `reflect: false`, `BaseComponent` la guarda en memoria y no usa `setAttribute()` ni `getAttribute()`.
|
|
495
|
+
|
|
496
|
+
Ejemplo conceptual:
|
|
497
|
+
|
|
498
|
+
```js
|
|
499
|
+
super(["search", "page"], {
|
|
500
|
+
reflect: {
|
|
501
|
+
search: false
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Esto es útil para estado interno que no conviene escribir en el DOM.
|
|
507
|
+
|
|
508
|
+
### `XHR`
|
|
509
|
+
|
|
510
|
+
Wrapper de `XMLHttpRequest` con callbacks y opciones centralizadas.
|
|
511
|
+
|
|
512
|
+
#### Uso básico
|
|
513
|
+
|
|
514
|
+
```js
|
|
515
|
+
import { XHR } from "./dist/artha.min.js";
|
|
516
|
+
|
|
517
|
+
XHR.request({
|
|
518
|
+
url: "/api/users",
|
|
519
|
+
method: "GET",
|
|
520
|
+
headers: {
|
|
521
|
+
Accept: "application/json"
|
|
522
|
+
},
|
|
523
|
+
onData: (xhr, data) => {
|
|
524
|
+
console.log("ok", data);
|
|
525
|
+
},
|
|
526
|
+
onError: (error) => {
|
|
527
|
+
console.error("error", error);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Opciones disponibles
|
|
533
|
+
|
|
534
|
+
- `method`: default `GET`
|
|
535
|
+
- `url`: URL final
|
|
536
|
+
- `uri`: alternativa para construir `"/" + uri"`
|
|
537
|
+
- `headers`: headers adicionales
|
|
538
|
+
- `data`: datos del formulario
|
|
539
|
+
- `query`: query params para GET
|
|
540
|
+
- `files`: archivos o listas de archivos
|
|
541
|
+
- `response_type`: default `json`
|
|
542
|
+
- `with_credentials`: default `false`
|
|
543
|
+
- `timeout`: default `0`
|
|
544
|
+
- `retry`: reintenta en `error` o `timeout`
|
|
545
|
+
- `retry_delay`: default `5000`
|
|
546
|
+
- `transformResponse(xhr)`: transforma `xhr.response`
|
|
547
|
+
- `onLoad(xhr)`
|
|
548
|
+
- `onData(xhr, transformed)`
|
|
549
|
+
- `onError(transformed)`
|
|
550
|
+
- `onTimeout(transformed)`
|
|
551
|
+
- `onProgress(event, loaded, total)`
|
|
552
|
+
- `onAbort(transformed)`
|
|
553
|
+
- `onAction(xhr)`
|
|
554
|
+
|
|
555
|
+
#### Notas de comportamiento
|
|
556
|
+
|
|
557
|
+
- si existe `<meta name="csrf-token">` o `<meta name="csrf_token">`, se envía como header `X-CSRF-Token`
|
|
558
|
+
- para métodos distintos de `GET`, la librería envía `FormData`
|
|
559
|
+
- también agrega `_method` dentro del `FormData`
|
|
560
|
+
- si hay token CSRF, también agrega `csrf_token` al cuerpo
|
|
561
|
+
|
|
562
|
+
#### Ejemplo con `transformResponse`
|
|
563
|
+
|
|
564
|
+
Si tu API no responde con la forma esperada por `ArthaForm` o `ArthaContainer`, puedes normalizarla con `XHR.defaults.transformResponse`.
|
|
565
|
+
|
|
566
|
+
Importante: configúralo antes de que se registren los componentes. La forma recomendada es hacerlo dentro de `artha:before-register`.
|
|
567
|
+
|
|
568
|
+
```js
|
|
569
|
+
import { EventBus, XHR } from "./dist/artha.min.js";
|
|
570
|
+
|
|
571
|
+
EventBus.on("artha:before-register", () => {
|
|
572
|
+
XHR.defaults.transformResponse = (xhr) => {
|
|
573
|
+
const raw = xhr.response;
|
|
574
|
+
|
|
575
|
+
// El atributo status se puede omitir si prefieres usar la validación por código HTTP.
|
|
576
|
+
// En este ejemplo se normaliza de forma explícita.
|
|
577
|
+
return {
|
|
578
|
+
status: xhr.status >= 200 && xhr.status < 300 ? "success" : "error",
|
|
579
|
+
message: raw?.message ?? null,
|
|
580
|
+
errors: raw?.errors ?? null,
|
|
581
|
+
data: raw?.result ?? raw?.data ?? raw
|
|
582
|
+
};
|
|
583
|
+
};
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Ejemplo de respuesta original:
|
|
588
|
+
|
|
589
|
+
```json
|
|
590
|
+
{
|
|
591
|
+
"result": [
|
|
592
|
+
{ "id": 1, "name": "Ana" },
|
|
593
|
+
{ "id": 2, "name": "Luis" }
|
|
594
|
+
],
|
|
595
|
+
"message": "OK"
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Respuesta normalizada:
|
|
600
|
+
|
|
601
|
+
```js
|
|
602
|
+
{
|
|
603
|
+
status: "success",
|
|
604
|
+
message: "OK",
|
|
605
|
+
errors: null,
|
|
606
|
+
data: [
|
|
607
|
+
{ id: 1, name: "Ana" },
|
|
608
|
+
{ id: 2, name: "Luis" }
|
|
609
|
+
]
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### `TaskQueue`
|
|
614
|
+
|
|
615
|
+
Evita ejecutar dos tareas con el mismo id al mismo tiempo y centraliza el cierre de estados.
|
|
616
|
+
|
|
617
|
+
#### Uso básico
|
|
618
|
+
|
|
619
|
+
```js
|
|
620
|
+
import { TaskQueue } from "./dist/artha.min.js";
|
|
621
|
+
|
|
622
|
+
const queue = TaskQueue.singleton();
|
|
623
|
+
|
|
624
|
+
queue.loadTask("save-user", "Guardando usuario...", (task) => {
|
|
625
|
+
setTimeout(() => {
|
|
626
|
+
task.resolve({
|
|
627
|
+
status: 200,
|
|
628
|
+
response: JSON.stringify({
|
|
629
|
+
status: "success",
|
|
630
|
+
message: "Usuario guardado",
|
|
631
|
+
data: { id: 1 }
|
|
632
|
+
})
|
|
633
|
+
});
|
|
634
|
+
}, 500);
|
|
635
|
+
}, {
|
|
636
|
+
close: true
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
#### Defaults
|
|
641
|
+
|
|
642
|
+
```js
|
|
643
|
+
TaskQueue.defaults = {
|
|
644
|
+
title: "Petición en proceso...",
|
|
645
|
+
close: false,
|
|
646
|
+
message: null
|
|
647
|
+
};
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
#### Observaciones
|
|
651
|
+
|
|
652
|
+
- cada tarea necesita un id único
|
|
653
|
+
- si se repite el id mientras sigue activa, se cancela la nueva tarea
|
|
654
|
+
- si se pasa un `ArthaMessage`, la cola actualiza sus estados visuales
|
|
655
|
+
- `close: true` intenta cerrar el mensaje automáticamente al finalizar
|
|
656
|
+
|
|
657
|
+
### `EventBus`
|
|
658
|
+
|
|
659
|
+
Bus global basado en `EventTarget`.
|
|
660
|
+
|
|
661
|
+
#### Uso
|
|
662
|
+
|
|
663
|
+
```js
|
|
664
|
+
import { EventBus } from "./dist/artha.min.js";
|
|
665
|
+
|
|
666
|
+
const unsubscribe = EventBus.on("users:reload", (data) => {
|
|
667
|
+
console.log("recargar", data);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
EventBus.emit("users:reload", { source: "manual" });
|
|
671
|
+
EventBus.emitAsync("users:reload", { source: "async" });
|
|
672
|
+
unsubscribe();
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
#### API pública
|
|
676
|
+
|
|
677
|
+
- `EventBus.emit(name, data)`
|
|
678
|
+
- `EventBus.emitAsync(name, data)`
|
|
679
|
+
- `EventBus.on(name, callback)`
|
|
680
|
+
- `EventBus.once(name, callback)`
|
|
681
|
+
- `EventBus.onAny(callback)`
|
|
682
|
+
- `EventBus.off(name, callback)`
|
|
683
|
+
- `EventBus.clean(name)`
|
|
684
|
+
- `EventBus.clearAll()`
|
|
685
|
+
|
|
686
|
+
#### Debug
|
|
687
|
+
|
|
688
|
+
```js
|
|
689
|
+
EventBus.debug = true;
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### `Util`
|
|
693
|
+
|
|
694
|
+
Utilidades generales.
|
|
695
|
+
|
|
696
|
+
#### API pública
|
|
697
|
+
|
|
698
|
+
- `Util.getMeta(name)`
|
|
699
|
+
- `Util.getValueByPath(obj, path, defaultValue = null)`
|
|
700
|
+
- `Util.modal(element, visible = -1)`
|
|
701
|
+
- `Util.modalById(id, visible = -1)`
|
|
702
|
+
- `Util.formatMoney(value, options = {})`
|
|
703
|
+
- `Util.numberRandom(min, max)`
|
|
704
|
+
- `Util.withinRange(value, min, max)`
|
|
705
|
+
- `Util.createElement(type, value = null, options = {})`
|
|
706
|
+
|
|
707
|
+
#### Ejemplos
|
|
708
|
+
|
|
709
|
+
```js
|
|
710
|
+
Util.getMeta("csrf-token");
|
|
711
|
+
Util.getValueByPath({ user: { name: "Ana" } }, "user.name");
|
|
712
|
+
Util.modalById("panel", true);
|
|
713
|
+
Util.formatMoney("1234.5");
|
|
714
|
+
Util.numberRandom(1, 10);
|
|
715
|
+
Util.withinRange(204, 200, 299);
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
## Flujo de respuesta esperado
|
|
719
|
+
|
|
720
|
+
`ArthaForm` y `ArthaContainer` funcionan mejor cuando el backend responde con una estructura parecida a esta:
|
|
721
|
+
|
|
722
|
+
```json
|
|
723
|
+
{
|
|
724
|
+
"status": "success",
|
|
725
|
+
"message": "Operación completada",
|
|
726
|
+
"data": []
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
También soporta errores con este formato:
|
|
731
|
+
|
|
732
|
+
```json
|
|
733
|
+
{
|
|
734
|
+
"status": "error",
|
|
735
|
+
"message": "No se pudo completar la operación",
|
|
736
|
+
"errors": {
|
|
737
|
+
"email": ["El correo ya existe"]
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
Si tu API responde con otro formato, puedes adaptar la salida usando `XHR.defaults.transformResponse`.
|
|
743
|
+
|
|
744
|
+
## Eventos útiles
|
|
745
|
+
|
|
746
|
+
### Eventos de componentes
|
|
747
|
+
|
|
748
|
+
- `component-ready`
|
|
749
|
+
- `load`
|
|
750
|
+
- `resolve`
|
|
751
|
+
- `dynamic-content-loaded`
|
|
752
|
+
- `item-rendered`
|
|
753
|
+
- `item-selected`
|
|
754
|
+
- `item-deselected`
|
|
755
|
+
- `search`
|
|
756
|
+
- `cancel-search`
|
|
757
|
+
|
|
758
|
+
### Eventos globales de Artha
|
|
759
|
+
|
|
760
|
+
- `artha:before-register`
|
|
761
|
+
- `artha:after-register`
|
|
762
|
+
|
|
763
|
+
### Ejemplo de refresco entre componentes
|
|
764
|
+
|
|
765
|
+
```js
|
|
766
|
+
import { EventBus } from "./dist/artha.min.js";
|
|
767
|
+
|
|
768
|
+
EventBus.emit("users:updated", { id: 3, name: "Nuevo nombre" });
|
|
769
|
+
EventBus.emit("users:reload");
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
Y en el contenedor:
|
|
773
|
+
|
|
774
|
+
```html
|
|
775
|
+
<artha-container
|
|
776
|
+
action="/api/users"
|
|
777
|
+
template="user-template"
|
|
778
|
+
refresh-on="users:reload,users:updated">
|
|
779
|
+
</artha-container>
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
## Desarrollo
|
|
783
|
+
|
|
784
|
+
### Instalar dependencias
|
|
785
|
+
|
|
786
|
+
```bash
|
|
787
|
+
npm install
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Desarrollo
|
|
791
|
+
|
|
792
|
+
Compilar CSS en modo watch:
|
|
793
|
+
|
|
794
|
+
```bash
|
|
795
|
+
npm run dev:css
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
Levantar servidor local:
|
|
799
|
+
|
|
800
|
+
```bash
|
|
801
|
+
npm run dev:server
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
Ejecutar ambos:
|
|
805
|
+
|
|
806
|
+
```bash
|
|
807
|
+
npm run dev
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Build
|
|
811
|
+
|
|
812
|
+
```bash
|
|
813
|
+
npm run build
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
## Licencia
|
|
817
|
+
|
|
818
|
+
MIT. Consulta [`LICENSE`](./LICENSE).
|