@massimo-cassandro/minimo 0.15.0 → 0.16.1
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/package.json
CHANGED
package/src/css/utilities.css
CHANGED
|
@@ -222,6 +222,32 @@ cols: [
|
|
|
222
222
|
// cerca su entrambi i campi concatenati; "rossi mario" trova solo
|
|
223
223
|
// le righe che contengono entrambe le parole, in qualsiasi ordine.
|
|
224
224
|
|
|
225
|
+
_footerRender, // Callback o stringa per il contenuto della cella nel <tfoot>.
|
|
226
|
+
// Il <tfoot> (una sola riga) viene aggiunto automaticamente se
|
|
227
|
+
// almeno una colonna definisce questa proprietà; le colonne senza
|
|
228
|
+
// _footerRender producono una <td> vuota.
|
|
229
|
+
//
|
|
230
|
+
// Accetta:
|
|
231
|
+
// - funzione (data) => string
|
|
232
|
+
// `data` è l'array dei record da considerare per il calcolo.
|
|
233
|
+
// Il contenuto di `data` dipende dall'opzione `updateFooterOnPageChange`
|
|
234
|
+
// del componente (default false = dataset filtrato completo,
|
|
235
|
+
// indipendente dalla pagina corrente).
|
|
236
|
+
// - stringa statica (es. etichetta fissa "Totale:")
|
|
237
|
+
//
|
|
238
|
+
// La cella eredita le classi CSS di allineamento della colonna
|
|
239
|
+
// (cellClass / headerClass), in modo da risultare visivamente coerente.
|
|
240
|
+
//
|
|
241
|
+
// Aggiornamenti automatici:
|
|
242
|
+
// - sempre: al cambio di filtro (ricerca / multisearch)
|
|
243
|
+
// - solo se updateFooterOnPageChange: true: al cambio di pagina
|
|
244
|
+
//
|
|
245
|
+
// Esempio – somma di una colonna numerica:
|
|
246
|
+
// _footerRender: (data) => {
|
|
247
|
+
// const tot = data.reduce((s, r) => s + (r.importo ?? 0), 0);
|
|
248
|
+
// return `<strong>${tot.toLocaleString('it-IT')} €</strong>`;
|
|
249
|
+
// }
|
|
250
|
+
|
|
225
251
|
// ── Proprietà native simple-datatables (senza prefisso _) ────────────
|
|
226
252
|
|
|
227
253
|
type, // Tipo di dato: 'string' | 'number' | 'date' | 'boolean' | 'html' | 'other'
|
|
@@ -257,3 +283,122 @@ const cols = [
|
|
|
257
283
|
}
|
|
258
284
|
];
|
|
259
285
|
```
|
|
286
|
+
|
|
287
|
+
### Esempio con tfoot – somma e conteggio
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
// updateFooterOnPageChange: false (default) – il footer mostra il totale del set filtrato,
|
|
291
|
+
// indipendente dalla pagina. Non si aggiorna al cambio pagina.
|
|
292
|
+
const el = document.querySelector('s-datatable');
|
|
293
|
+
el.init({
|
|
294
|
+
json: '/api/commesse.json',
|
|
295
|
+
cols: [
|
|
296
|
+
{
|
|
297
|
+
_field: 'commessa',
|
|
298
|
+
_heading: 'Commessa',
|
|
299
|
+
_footerRender: '<strong>Totale:</strong>', // etichetta fissa
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
_field: 'importo',
|
|
303
|
+
_heading: 'Importo',
|
|
304
|
+
type: 'number',
|
|
305
|
+
cellClass: 'text-end',
|
|
306
|
+
headerClass: 'text-end',
|
|
307
|
+
// data = tutti i record filtrati (intera ricerca, non solo la pagina corrente)
|
|
308
|
+
_footerRender: (data) => {
|
|
309
|
+
const tot = data.reduce((s, r) => s + (r.importo ?? 0), 0);
|
|
310
|
+
return `<strong>${tot.toLocaleString('it-IT', { style: 'currency', currency: 'EUR' })}</strong>`;
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
_field: 'stato',
|
|
315
|
+
_heading: 'Stato',
|
|
316
|
+
_footerRender: (data) => `${data.length} record`,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// updateFooterOnPageChange: true – il footer mostra il subtotale della sola pagina corrente.
|
|
322
|
+
// Si aggiorna sia al cambio filtro sia al cambio pagina.
|
|
323
|
+
el.init({
|
|
324
|
+
json: '/api/commesse.json',
|
|
325
|
+
updateFooterOnPageChange: true,
|
|
326
|
+
cols: [ /* stesse colonne di sopra */ ],
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Il `<tfoot>` viene aggiunto solo se almeno una colonna definisce `_footerRender`.
|
|
331
|
+
|
|
332
|
+
Con `updateFooterOnPageChange: false` (default) la callback riceve l'intero set filtrato: i totali
|
|
333
|
+
non cambiano navigando tra le pagine e si aggiornano solo quando cambia la ricerca.
|
|
334
|
+
|
|
335
|
+
Con `updateFooterOnPageChange: true` la callback riceve i record della pagina corrente: utile per
|
|
336
|
+
subtotali di pagina; il footer si aggiorna sia al cambio filtro sia al cambio pagina.
|
|
337
|
+
|
|
338
|
+
## Testo info
|
|
339
|
+
|
|
340
|
+
Il componente sovrascrive il testo dell'area informativa (in basso a sinistra) per includere
|
|
341
|
+
sempre il numero totale assoluto di record, distinto dal conteggio filtrato.
|
|
342
|
+
|
|
343
|
+
Senza ricerca attiva:
|
|
344
|
+
|
|
345
|
+
> Stai visualizzando le righe da 1 a 25, su un totale di 100.
|
|
346
|
+
|
|
347
|
+
Con ricerca attiva:
|
|
348
|
+
|
|
349
|
+
> Stai visualizzando le righe da 1 a 25 dei 50 record filtrati, su un totale di 100.
|
|
350
|
+
|
|
351
|
+
Ricerca senza risultati:
|
|
352
|
+
|
|
353
|
+
> Nessun risultato per la ricerca corrente (totale record: 100).
|
|
354
|
+
|
|
355
|
+
Il testo si aggiorna automaticamente ad ogni cambio pagina, ricerca e multisearch.
|
|
356
|
+
|
|
357
|
+
## Parametri `topSlot` e `showInfoAtTop`
|
|
358
|
+
|
|
359
|
+
Entrambi occupano l'area in alto a sinistra del datatable — lo spazio normalmente
|
|
360
|
+
riservato al selettore "righe per pagina" (qui disabilitato). I due parametri si
|
|
361
|
+
escludono: se `showInfoAtTop` è `true`, `topSlot` viene ignorato.
|
|
362
|
+
|
|
363
|
+
### `showInfoAtTop`
|
|
364
|
+
|
|
365
|
+
Replica nell'area superiore lo stesso testo dell'area `.info` in basso,
|
|
366
|
+
mantenuto in sincronia ad ogni cambio pagina o filtro. Il nodo viene nascosto
|
|
367
|
+
automaticamente quando non ci sono record o la ricerca non produce risultati
|
|
368
|
+
(evita di mostrare "Nessun risultato…" in due posti).
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
el.init({
|
|
372
|
+
json: '/api/data.json',
|
|
373
|
+
cols: [...],
|
|
374
|
+
showInfoAtTop: true,
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### `topSlot`
|
|
379
|
+
|
|
380
|
+
Inserisce un elemento DOM o markup HTML arbitrario, avvolto in un
|
|
381
|
+
`<div class="datatable-top-slot">`. Accetta sia una stringa HTML che un `Element`
|
|
382
|
+
già costruito (utile per mantenere un riferimento al nodo e aggiungervi listener).
|
|
383
|
+
Non è leggibile da attributo HTML, solo via `init()`.
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
// Stringa HTML
|
|
387
|
+
el.init({
|
|
388
|
+
json: '/api/data.json',
|
|
389
|
+
cols: [...],
|
|
390
|
+
topSlot: '<button class="btn btn-sm btn-primary">Esporta CSV</button>',
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Elemento DOM
|
|
394
|
+
const btn = document.createElement('button');
|
|
395
|
+
btn.className = 'btn btn-sm btn-outline-secondary';
|
|
396
|
+
btn.textContent = 'Nuova commessa';
|
|
397
|
+
btn.addEventListener('click', () => { /* … */ });
|
|
398
|
+
|
|
399
|
+
el.init({
|
|
400
|
+
json: '/api/commesse.json',
|
|
401
|
+
cols: [...],
|
|
402
|
+
topSlot: btn,
|
|
403
|
+
});
|
|
404
|
+
```
|
|
@@ -9,7 +9,7 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Implementazione di simple-datatable (https://fiduswriter.github.io/simple-datatables/documentation/)
|
|
13
13
|
* per generare una tabella da dati json e da un oggetto `cols` di configurazione
|
|
14
14
|
*
|
|
15
15
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -42,6 +42,16 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
|
|
|
42
42
|
* - funzione `(row) => string`
|
|
43
43
|
* - se sono impostate sia `_cellRender` che l'opzione `render` di simple-datatable
|
|
44
44
|
* quest'ultima viene annullata per evitare potenziali conflitti
|
|
45
|
+
* @property {string|Function} [_footerRender] Contenuto della cella <td> nel <tfoot> per questa colonna.
|
|
46
|
+
* Accetta:
|
|
47
|
+
* - funzione `(data) => string` dove `data` è l'array dei
|
|
48
|
+
* record visibili (filtrati dalla ricerca corrente, ma non
|
|
49
|
+
* limitati alla pagina attuale): ideale per somme, medie, conteggi.
|
|
50
|
+
* - stringa statica (es. etichetta fissa "Totale:")
|
|
51
|
+
* Le colonne senza `_footerRender` producono una <td> vuota.
|
|
52
|
+
* Il <tfoot> viene aggiunto solo se almeno una colonna lo definisce.
|
|
53
|
+
* La cella eredita le classi CSS (allineamento) della colonna.
|
|
54
|
+
*
|
|
45
55
|
* @property {string|Function} [_sortValue] Percorso del campo da usare per l'ORDINAMENTO al posto
|
|
46
56
|
* del contenuto visualizzato, oppure una funzione
|
|
47
57
|
* `(row) => string|number` per casi complessi
|
|
@@ -89,6 +99,32 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
|
|
|
89
99
|
* colonna con `_renderNullAs`.
|
|
90
100
|
* Default: `\u2014` (em dash)
|
|
91
101
|
*
|
|
102
|
+
* @param {boolean} updateFooterOnPageChange Se true, la callback `_footerRender` riceve i soli record
|
|
103
|
+
* della pagina corrente (subtotale di pagina). Se false
|
|
104
|
+
* (default), riceve l'intero set filtrato indipendentemente
|
|
105
|
+
* dalla paginazione (totale complessivo).
|
|
106
|
+
* Quando è false il footer si aggiorna solo al cambio di
|
|
107
|
+
* filtro, non al cambio pagina.
|
|
108
|
+
*
|
|
109
|
+
* @param {Element|string} topSlot Elemento DOM o stringa HTML da inserire nell'area in alto
|
|
110
|
+
* a sinistra del datatable (lo spazio del selettore righe/pagina,
|
|
111
|
+
* qui disabilitato). Utile per pulsanti contestuali, filtri
|
|
112
|
+
* aggiuntivi o qualsiasi controllo da affiancare alla search box.
|
|
113
|
+
* Viene avvolto in un `<div class="datatable-top-slot">`.
|
|
114
|
+
* Accetta:
|
|
115
|
+
* - `Element`: un nodo DOM già costruito
|
|
116
|
+
* - stringa HTML: parsata via innerHTML
|
|
117
|
+
* Ignorato se `showInfoAtTop` è true.
|
|
118
|
+
* Non è leggibile da attributo HTML (solo via script).
|
|
119
|
+
*
|
|
120
|
+
* @param {boolean} showInfoAtTop Se true, mostra nell'area superiore (spazio top-sinistra)
|
|
121
|
+
* lo stesso testo dell'area `.info` in basso, mantenuto in
|
|
122
|
+
* sync ad ogni cambio pagina/filtro. Il nodo viene nascosto
|
|
123
|
+
* automaticamente quando non ci sono record o la ricerca
|
|
124
|
+
* non produce risultati. Ha precedenza su `topSlot`:
|
|
125
|
+
* se entrambi sono definiti, `topSlot` viene ignorato.
|
|
126
|
+
* Default: false.
|
|
127
|
+
*
|
|
92
128
|
* @param {string[]} refs Elenco di percorsi URL da cui, se si proviene,
|
|
93
129
|
* la pagina corrente viene ripristinata dal cookie
|
|
94
130
|
* di sessione `sd-pag`. Se il referrer non è tra
|
|
@@ -597,6 +633,10 @@ class SimpleDatatableAdapter extends HTMLElement {
|
|
|
597
633
|
})
|
|
598
634
|
}));
|
|
599
635
|
|
|
636
|
+
// Conserva i dati grezzi e le definizioni di colonna per il tfoot
|
|
637
|
+
this._rawData = rawData;
|
|
638
|
+
this._footerCols = cols;
|
|
639
|
+
|
|
600
640
|
this.columns = cols.map((col_settings, idx) => {
|
|
601
641
|
col_settings = parseCols(col_settings);
|
|
602
642
|
|
|
@@ -729,24 +769,25 @@ class SimpleDatatableAdapter extends HTMLElement {
|
|
|
729
769
|
});
|
|
730
770
|
|
|
731
771
|
this._dt.on('datatable.init', () => {
|
|
772
|
+
// ── Icone paginazione ─────────────────────────────────────────────────
|
|
732
773
|
const pagination = this.querySelector(`.${styles.paginationList}`);
|
|
733
|
-
if (
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
774
|
+
if (pagination) {
|
|
775
|
+
const items = pagination.querySelectorAll(`.${styles.paginationListItem}`);
|
|
776
|
+
if (items.length) {
|
|
777
|
+
pagination.querySelector(
|
|
778
|
+
`.${styles.paginationListItem}:first-child .${styles.paginationListItemBtn}`
|
|
779
|
+
).innerHTML = caretLeftIcon;
|
|
780
|
+
|
|
781
|
+
pagination.querySelector(
|
|
782
|
+
`.${styles.paginationListItem}:last-child .${styles.paginationListItemBtn}`
|
|
783
|
+
).innerHTML = caretRightIcon;
|
|
784
|
+
|
|
785
|
+
pagination.querySelectorAll(`.${styles.paginationListItemBtn}:has(svg)`)
|
|
786
|
+
.forEach(btn => btn.classList.add(styles.hasSvg));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
748
789
|
|
|
749
|
-
// Ripristino pagina dal cookie di sessione (se referrer autorizzato)
|
|
790
|
+
// ── Ripristino pagina dal cookie di sessione (se referrer autorizzato) ─
|
|
750
791
|
// const isRefUrl = this._isReferrer(), refsCallback = this._getParam('refsCallback', null);
|
|
751
792
|
// if (isRefUrl) {
|
|
752
793
|
if (this._isReferrer()) {
|
|
@@ -762,6 +803,7 @@ class SimpleDatatableAdapter extends HTMLElement {
|
|
|
762
803
|
// refsCallback(isRefUrl);
|
|
763
804
|
// }
|
|
764
805
|
|
|
806
|
+
// ── Filtro contestuale post-reload ────────────────────────────────────
|
|
765
807
|
// Applica eventuale filtro contestuale passato via reload({ search: … })
|
|
766
808
|
// Lo facciamo qui, dentro datatable.init, per essere certi che
|
|
767
809
|
// simple-datatables abbia completato il render dei nuovi dati.
|
|
@@ -775,15 +817,254 @@ class SimpleDatatableAdapter extends HTMLElement {
|
|
|
775
817
|
this._dt.search(ps.term, ps.columns);
|
|
776
818
|
}
|
|
777
819
|
}
|
|
820
|
+
|
|
821
|
+
// ── Tfoot, info, top slot ─────────────────────────────────────────────
|
|
822
|
+
// Eseguiti sempre, indipendentemente dalla presenza della paginazione.
|
|
823
|
+
this._renderTfoot();
|
|
824
|
+
this._injectTopSlot(); // va prima di _updateInfo per creare il nodo top-info
|
|
825
|
+
this._updateInfo();
|
|
778
826
|
});
|
|
779
827
|
|
|
780
|
-
// Salva la pagina corrente nel cookie ad ogni cambio di pagina
|
|
828
|
+
// Salva la pagina corrente nel cookie ad ogni cambio di pagina;
|
|
829
|
+
// aggiorna il tfoot solo se updateFooterOnPageChange è attivo.
|
|
781
830
|
this._dt.on('datatable.page', page => {
|
|
782
831
|
this._savePageCookie(page);
|
|
832
|
+
if (this._getParam('updateFooterOnPageChange', false)) this._renderTfoot();
|
|
833
|
+
this._updateInfo();
|
|
783
834
|
});
|
|
784
835
|
|
|
836
|
+
// Aggiorna il tfoot ad ogni cambio di filtro (ricerca / multisearch).
|
|
837
|
+
// Il sort non altera il set filtrato: il footer non deve cambiare.
|
|
838
|
+
this._dt.on('datatable.search', () => { this._renderTfoot(); this._updateInfo(); });
|
|
839
|
+
this._dt.on('datatable.multisearch', () => { this._renderTfoot(); this._updateInfo(); });
|
|
840
|
+
|
|
785
841
|
} // end _render
|
|
786
842
|
|
|
843
|
+
|
|
844
|
+
// ─── Footer (tfoot) ──────────────────────────────────────────────────────────
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Costruisce o aggiorna la riga <tfoot> della tabella.
|
|
848
|
+
*
|
|
849
|
+
* Chiamato dopo il render iniziale, ad ogni cambio di filtro (ricerca /
|
|
850
|
+
* multisearch) e — se `updateFooterOnPageChange` è true — anche ad ogni cambio pagina.
|
|
851
|
+
*
|
|
852
|
+
* Dataset passato alla callback `_footerRender(data)`:
|
|
853
|
+
*
|
|
854
|
+
* updateFooterOnPageChange: false (default)
|
|
855
|
+
* → l'intero set filtrato, indipendente dalla pagina corrente.
|
|
856
|
+
* I totali non cambiano navigando tra le pagine.
|
|
857
|
+
* Se non c'è ricerca attiva, viene passato il dataset completo.
|
|
858
|
+
*
|
|
859
|
+
* updateFooterOnPageChange: true
|
|
860
|
+
* → solo i record della pagina corrente.
|
|
861
|
+
* Utile per subtotali di pagina.
|
|
862
|
+
*
|
|
863
|
+
* `searchData` di simple-datatables è un array di indici nel dataset
|
|
864
|
+
* originale, disponibile (e non vuoto) solo quando `dt.searching === true`.
|
|
865
|
+
* Quando la ricerca è attiva ma non trova nulla, `searchData` è `[]`:
|
|
866
|
+
* in quel caso la callback riceve un array vuoto, non il dataset completo.
|
|
867
|
+
*/
|
|
868
|
+
_renderTfoot() {
|
|
869
|
+
const cols = this._footerCols;
|
|
870
|
+
const rawData = this._rawData;
|
|
871
|
+
|
|
872
|
+
if (!cols?.length || !rawData) return;
|
|
873
|
+
|
|
874
|
+
const hasFooter = cols.some(c => c._footerRender != null);
|
|
875
|
+
if (!hasFooter) return;
|
|
876
|
+
|
|
877
|
+
const table = this.querySelector('table');
|
|
878
|
+
if (!table) return;
|
|
879
|
+
|
|
880
|
+
const dt = this._dt;
|
|
881
|
+
const updateFooterOnPageChange = this._getParam('updateFooterOnPageChange', false);
|
|
882
|
+
|
|
883
|
+
let visibleData;
|
|
884
|
+
|
|
885
|
+
if (updateFooterOnPageChange) {
|
|
886
|
+
// Dati della pagina corrente: ogni "pagina" in dt.pages è un array
|
|
887
|
+
// di oggetti riga; ciascuno ha una proprietà data-index che punta
|
|
888
|
+
// all'indice nel dataset originale.
|
|
889
|
+
const pageRows = dt.pages?.[dt.currentPage - 1] ?? [];
|
|
890
|
+
visibleData = pageRows
|
|
891
|
+
.map(row => rawData[row['data-index']])
|
|
892
|
+
.filter(Boolean);
|
|
893
|
+
} else {
|
|
894
|
+
// Dataset filtrato completo (ignorando la paginazione).
|
|
895
|
+
// dt.searching è true solo quando c'è una ricerca attiva;
|
|
896
|
+
// in quel caso searchData contiene gli indici dei match (può essere []).
|
|
897
|
+
visibleData = dt.searching
|
|
898
|
+
? dt.searchData.map(idx => rawData[idx]).filter(Boolean)
|
|
899
|
+
: rawData;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Riutilizza tfoot esistente o ne crea uno nuovo
|
|
903
|
+
let tfoot = table.querySelector('tfoot');
|
|
904
|
+
if (!tfoot) {
|
|
905
|
+
tfoot = document.createElement('tfoot');
|
|
906
|
+
table.appendChild(tfoot);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const tr = document.createElement('tr');
|
|
910
|
+
|
|
911
|
+
cols.forEach(col => {
|
|
912
|
+
const td = document.createElement('td');
|
|
913
|
+
|
|
914
|
+
if (col._footerRender != null) {
|
|
915
|
+
if (typeof col._footerRender === 'function') {
|
|
916
|
+
td.innerHTML = col._footerRender(visibleData) ?? '';
|
|
917
|
+
} else {
|
|
918
|
+
// Stringa statica (es. etichetta fissa "Totale:")
|
|
919
|
+
td.innerHTML = col._footerRender;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Propaga le classi di allineamento della colonna per coerenza visiva
|
|
924
|
+
const cellClass = col.cellClass ?? col.headerClass ?? '';
|
|
925
|
+
if (cellClass) td.className = cellClass;
|
|
926
|
+
|
|
927
|
+
tr.appendChild(td);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
tfoot.replaceChildren(tr);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// ─── Info label ──────────────────────────────────────────────────────────────
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Sovrascrive il testo dell'area `.info` generato da simple-datatables per
|
|
937
|
+
* includere sempre il totale assoluto dei record (indipendente dal filtro).
|
|
938
|
+
* Se `showInfoAtTop` è attivo, aggiorna in sincronia anche il nodo `.info`
|
|
939
|
+
* nell'area superiore (creato da `_injectTopSlot`).
|
|
940
|
+
*
|
|
941
|
+
* Calcolo di start/end: si conta il numero di righe renderizzate nel tbody
|
|
942
|
+
* (`dt.pages[currentPage - 1]`) invece di fare aritmetica su perPage,
|
|
943
|
+
* perché `dt.currentPage` può essere undefined al primo render.
|
|
944
|
+
*
|
|
945
|
+
* Comportamento:
|
|
946
|
+
* - Senza ricerca attiva:
|
|
947
|
+
* "Stai visualizzando le righe da {start} a {end}, su un totale di {total}."
|
|
948
|
+
* - Con ricerca attiva (risultati > 0):
|
|
949
|
+
* "Stai visualizzando le righe da {start} a {end} dei {filtered} record
|
|
950
|
+
* filtrati, su un totale di {total}."
|
|
951
|
+
* - Nessun risultato (ricerca attiva, 0 match):
|
|
952
|
+
* "Nessun risultato per la ricerca corrente (totale record: {total})."
|
|
953
|
+
*
|
|
954
|
+
* Il nodo top-info viene nascosto quando filtered = 0 o total = 0.
|
|
955
|
+
*/
|
|
956
|
+
_updateInfo() {
|
|
957
|
+
const infoEl = this.querySelector(`.${styles.info}`);
|
|
958
|
+
const topInfoEl = this.querySelector('.datatable-top-info');
|
|
959
|
+
|
|
960
|
+
if (!infoEl && !topInfoEl) return;
|
|
961
|
+
|
|
962
|
+
const dt = this._dt;
|
|
963
|
+
const total = this._rawData?.length ?? 0;
|
|
964
|
+
const filtered = dt.searching ? dt.searchData.length : total;
|
|
965
|
+
|
|
966
|
+
let text;
|
|
967
|
+
|
|
968
|
+
if (total === 0 || (filtered === 0 && dt.searching)) {
|
|
969
|
+
// Nessun risultato — nasconde il nodo top-info se presente
|
|
970
|
+
if (topInfoEl) topInfoEl.hidden = true;
|
|
971
|
+
|
|
972
|
+
text = filtered === 0 && dt.searching
|
|
973
|
+
? `Nessun risultato per la ricerca corrente (totale record: ${total}).`
|
|
974
|
+
: `Nessun record trovato.`;
|
|
975
|
+
|
|
976
|
+
if (infoEl) infoEl.textContent = text;
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Calcola start/end dalla pagina corrente in dt.pages (più affidabile di
|
|
981
|
+
// currentPage * perPage, che può dare NaN se currentPage è undefined
|
|
982
|
+
// al primo render oppure quando non c'è paginazione).
|
|
983
|
+
const currentPage = dt.currentPage ?? 1;
|
|
984
|
+
const pageRows = dt.pages?.[currentPage - 1] ?? [];
|
|
985
|
+
const perPage = dt.options?.perPage ?? filtered;
|
|
986
|
+
const start = pageRows.length > 0
|
|
987
|
+
? (currentPage - 1) * perPage + 1
|
|
988
|
+
: 1;
|
|
989
|
+
const end = pageRows.length > 0
|
|
990
|
+
? start + pageRows.length - 1
|
|
991
|
+
: filtered;
|
|
992
|
+
|
|
993
|
+
if (dt.searching) {
|
|
994
|
+
text =
|
|
995
|
+
`Stai visualizzando le righe da ${start} a ${end} ` +
|
|
996
|
+
`dei ${filtered} record filtrati, su un totale di ${total}.`;
|
|
997
|
+
} else {
|
|
998
|
+
text =
|
|
999
|
+
`Stai visualizzando le righe da ${start} a ${end}, ` +
|
|
1000
|
+
`su un totale di ${total}.`;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (infoEl) infoEl.textContent = text;
|
|
1004
|
+
if (topInfoEl) { topInfoEl.textContent = text; topInfoEl.hidden = false; }
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
// ─── Top slot ────────────────────────────────────────────────────────────────
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Inietta nell'area in alto a sinistra del datatable il contenuto configurato.
|
|
1012
|
+
*
|
|
1013
|
+
* Modalità:
|
|
1014
|
+
*
|
|
1015
|
+
* showInfoAtTop: true (ha precedenza su topSlot)
|
|
1016
|
+
* Inserisce un nodo `.datatable-top-info` con la stessa stringa dell'area
|
|
1017
|
+
* `.info` in basso, mantenuta in sync da `_updateInfo`. Viene nascosto
|
|
1018
|
+
* automaticamente quando filtered = 0 o total = 0.
|
|
1019
|
+
* Il parametro `topSlot`, se presente, viene ignorato.
|
|
1020
|
+
*
|
|
1021
|
+
* topSlot: Element | string (usato solo se showInfoAtTop è false/assente)
|
|
1022
|
+
* Inserisce un elemento DOM o markup HTML arbitrario, avvolto in un
|
|
1023
|
+
* `<div class="datatable-top-slot">`.
|
|
1024
|
+
*
|
|
1025
|
+
* In entrambi i casi l'elemento viene inserito come primo figlio di `.topArea`,
|
|
1026
|
+
* prima della search box.
|
|
1027
|
+
*
|
|
1028
|
+
* Esempi:
|
|
1029
|
+
* el.init({ showInfoAtTop: true });
|
|
1030
|
+
*
|
|
1031
|
+
* el.init({ topSlot: '<button class="btn btn-sm btn-primary">Esporta</button>' });
|
|
1032
|
+
*
|
|
1033
|
+
* const btn = document.createElement('button');
|
|
1034
|
+
* btn.textContent = 'Nuovo';
|
|
1035
|
+
* el.init({ topSlot: btn });
|
|
1036
|
+
*/
|
|
1037
|
+
_injectTopSlot() {
|
|
1038
|
+
const topArea = this.querySelector(`.${styles.topArea}`);
|
|
1039
|
+
if (!topArea) return;
|
|
1040
|
+
|
|
1041
|
+
const showInfoAtTop = this._getParam('showInfoAtTop', false);
|
|
1042
|
+
|
|
1043
|
+
if (showInfoAtTop) {
|
|
1044
|
+
// Crea il nodo info top (il testo verrà scritto da _updateInfo)
|
|
1045
|
+
const infoTop = document.createElement('div');
|
|
1046
|
+
infoTop.className = `datatable-top-info ${styles.info}`;
|
|
1047
|
+
infoTop.hidden = true; // nascosto finché _updateInfo non lo popola
|
|
1048
|
+
topArea.insertBefore(infoTop, topArea.firstChild);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const topSlot = this._getParam('topSlot', null);
|
|
1053
|
+
if (topSlot == null) return;
|
|
1054
|
+
|
|
1055
|
+
const wrapper = document.createElement('div');
|
|
1056
|
+
wrapper.className = 'datatable-top-slot';
|
|
1057
|
+
|
|
1058
|
+
if (topSlot instanceof Element) {
|
|
1059
|
+
wrapper.appendChild(topSlot);
|
|
1060
|
+
} else {
|
|
1061
|
+
wrapper.innerHTML = topSlot;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
topArea.insertBefore(wrapper, topArea.firstChild);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
|
|
787
1068
|
} // end component
|
|
788
1069
|
|
|
789
1070
|
|
|
@@ -110,6 +110,15 @@ export function parseCols(col_obj){
|
|
|
110
110
|
|
|
111
111
|
}
|
|
112
112
|
};
|
|
113
|
+
|
|
114
|
+
// TODO completare, aggiungere formattazione
|
|
115
|
+
} else if(col_obj._renderMode === 'numeric') {
|
|
116
|
+
return {
|
|
117
|
+
|
|
118
|
+
cellClass: 'text-end text-numeric',
|
|
119
|
+
headerClass: 'text-end',
|
|
120
|
+
};
|
|
121
|
+
|
|
113
122
|
}
|
|
114
123
|
} // end if not render
|
|
115
124
|
|