@massimo-cassandro/minimo 0.14.4 → 0.16.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/package.json +1 -1
- package/src/components/snackbar/snackbar.js +10 -9
- package/src/css/utilities.css +3 -0
- package/src/web-components/s-datatable-component/docs.md +130 -0
- package/src/web-components/s-datatable-component/s-datatable-component.js +237 -2
- package/src/web-components/s-datatable-component/src/parse-cols.js +9 -0
package/package.json
CHANGED
|
@@ -31,19 +31,20 @@ export function snackbar(message, options = {}){
|
|
|
31
31
|
...options
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
let _popover, _timeoutID;
|
|
34
|
+
let _popover, _timeoutID, _isRemoving = false;
|
|
35
35
|
|
|
36
36
|
const removePopover = () => {
|
|
37
|
-
if (_popover?.matches(':popover-open'))
|
|
38
|
-
|
|
37
|
+
if (_isRemoving || !_popover?.matches(':popover-open')) return;
|
|
38
|
+
_isRemoving = true; // evita sovrapposizioni nel caso in cui removePopover sia chiamato più volte durante l'animazione di chiusura
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
if (_timeoutID) clearTimeout(_timeoutID);
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
_popover.classList.add(styles.isHiding);
|
|
43
|
+
|
|
44
|
+
_popover.addEventListener('animationend', () => {
|
|
45
|
+
_popover.hidePopover();
|
|
46
|
+
_popover.remove();
|
|
47
|
+
}, { once: true }); // `once` già evita duplicati sul listener, ma il flag protegge prima
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
_popover = domBuilder([
|
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,107 @@ 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
|
+
## Parametro `topSlot`
|
|
358
|
+
|
|
359
|
+
Permette di inserire un elemento DOM o del markup HTML nell'area in alto a sinistra
|
|
360
|
+
del datatable — lo spazio normalmente occupato dal selettore "righe per pagina"
|
|
361
|
+
(qui disabilitato). È utile per aggiungere pulsanti contestuali, filtri aggiuntivi
|
|
362
|
+
o qualsiasi controllo da affiancare alla search box.
|
|
363
|
+
|
|
364
|
+
L'elemento viene avvolto in un `<div class="datatable-top-slot">` e inserito
|
|
365
|
+
come primo figlio dell'area superiore, prima della search box.
|
|
366
|
+
|
|
367
|
+
È configurabile solo via script (non da attributo HTML), perché accetta sia
|
|
368
|
+
stringhe HTML che elementi DOM già costruiti.
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// Stringa HTML
|
|
372
|
+
el.init({
|
|
373
|
+
json: '/api/data.json',
|
|
374
|
+
cols: [...],
|
|
375
|
+
topSlot: '<button class="btn btn-sm btn-primary" onclick="...">Esporta CSV</button>',
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Elemento DOM (utile quando si vuole mantenere un riferimento al nodo)
|
|
379
|
+
const btn = document.createElement('button');
|
|
380
|
+
btn.className = 'btn btn-sm btn-outline-secondary';
|
|
381
|
+
btn.textContent = 'Nuova commessa';
|
|
382
|
+
btn.addEventListener('click', () => { /* … */ });
|
|
383
|
+
|
|
384
|
+
el.init({
|
|
385
|
+
json: '/api/commesse.json',
|
|
386
|
+
cols: [...],
|
|
387
|
+
topSlot: btn,
|
|
388
|
+
});
|
|
389
|
+
```
|
|
@@ -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,23 @@ 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
|
+
* Non è leggibile da attributo HTML (solo via script).
|
|
118
|
+
*
|
|
92
119
|
* @param {string[]} refs Elenco di percorsi URL da cui, se si proviene,
|
|
93
120
|
* la pagina corrente viene ripristinata dal cookie
|
|
94
121
|
* di sessione `sd-pag`. Se il referrer non è tra
|
|
@@ -597,6 +624,10 @@ class SimpleDatatableAdapter extends HTMLElement {
|
|
|
597
624
|
})
|
|
598
625
|
}));
|
|
599
626
|
|
|
627
|
+
// Conserva i dati grezzi e le definizioni di colonna per il tfoot
|
|
628
|
+
this._rawData = rawData;
|
|
629
|
+
this._footerCols = cols;
|
|
630
|
+
|
|
600
631
|
this.columns = cols.map((col_settings, idx) => {
|
|
601
632
|
col_settings = parseCols(col_settings);
|
|
602
633
|
|
|
@@ -775,15 +806,219 @@ class SimpleDatatableAdapter extends HTMLElement {
|
|
|
775
806
|
this._dt.search(ps.term, ps.columns);
|
|
776
807
|
}
|
|
777
808
|
}
|
|
809
|
+
|
|
810
|
+
// Renderizza il tfoot (se almeno una colonna ha _footerRender)
|
|
811
|
+
this._renderTfoot();
|
|
812
|
+
|
|
813
|
+
// Aggiorna il testo info con il totale assoluto
|
|
814
|
+
this._updateInfo();
|
|
815
|
+
|
|
816
|
+
// Inietta l'elemento nello slot top-sinistra (se configurato)
|
|
817
|
+
this._injectTopSlot();
|
|
778
818
|
});
|
|
779
819
|
|
|
780
|
-
// Salva la pagina corrente nel cookie ad ogni cambio di pagina
|
|
820
|
+
// Salva la pagina corrente nel cookie ad ogni cambio di pagina;
|
|
821
|
+
// aggiorna il tfoot solo se updateFooterOnPageChange è attivo.
|
|
781
822
|
this._dt.on('datatable.page', page => {
|
|
782
823
|
this._savePageCookie(page);
|
|
824
|
+
if (this._getParam('updateFooterOnPageChange', false)) this._renderTfoot();
|
|
825
|
+
this._updateInfo();
|
|
783
826
|
});
|
|
784
827
|
|
|
828
|
+
// Aggiorna il tfoot ad ogni cambio di filtro (ricerca / multisearch).
|
|
829
|
+
// Il sort non altera il set filtrato: il footer non deve cambiare.
|
|
830
|
+
this._dt.on('datatable.search', () => { this._renderTfoot(); this._updateInfo(); });
|
|
831
|
+
this._dt.on('datatable.multisearch', () => { this._renderTfoot(); this._updateInfo(); });
|
|
832
|
+
|
|
785
833
|
} // end _render
|
|
786
834
|
|
|
835
|
+
|
|
836
|
+
// ─── Footer (tfoot) ──────────────────────────────────────────────────────────
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Costruisce o aggiorna la riga <tfoot> della tabella.
|
|
840
|
+
*
|
|
841
|
+
* Chiamato dopo il render iniziale, ad ogni cambio di filtro (ricerca /
|
|
842
|
+
* multisearch) e — se `updateFooterOnPageChange` è true — anche ad ogni cambio pagina.
|
|
843
|
+
*
|
|
844
|
+
* Dataset passato alla callback `_footerRender(data)`:
|
|
845
|
+
*
|
|
846
|
+
* updateFooterOnPageChange: false (default)
|
|
847
|
+
* → l'intero set filtrato, indipendente dalla pagina corrente.
|
|
848
|
+
* I totali non cambiano navigando tra le pagine.
|
|
849
|
+
* Se non c'è ricerca attiva, viene passato il dataset completo.
|
|
850
|
+
*
|
|
851
|
+
* updateFooterOnPageChange: true
|
|
852
|
+
* → solo i record della pagina corrente.
|
|
853
|
+
* Utile per subtotali di pagina.
|
|
854
|
+
*
|
|
855
|
+
* `searchData` di simple-datatables è un array di indici nel dataset
|
|
856
|
+
* originale, disponibile (e non vuoto) solo quando `dt.searching === true`.
|
|
857
|
+
* Quando la ricerca è attiva ma non trova nulla, `searchData` è `[]`:
|
|
858
|
+
* in quel caso la callback riceve un array vuoto, non il dataset completo.
|
|
859
|
+
*/
|
|
860
|
+
_renderTfoot() {
|
|
861
|
+
const cols = this._footerCols;
|
|
862
|
+
const rawData = this._rawData;
|
|
863
|
+
|
|
864
|
+
if (!cols?.length || !rawData) return;
|
|
865
|
+
|
|
866
|
+
const hasFooter = cols.some(c => c._footerRender != null);
|
|
867
|
+
if (!hasFooter) return;
|
|
868
|
+
|
|
869
|
+
const table = this.querySelector('table');
|
|
870
|
+
if (!table) return;
|
|
871
|
+
|
|
872
|
+
const dt = this._dt;
|
|
873
|
+
const updateFooterOnPageChange = this._getParam('updateFooterOnPageChange', false);
|
|
874
|
+
|
|
875
|
+
let visibleData;
|
|
876
|
+
|
|
877
|
+
if (updateFooterOnPageChange) {
|
|
878
|
+
// Dati della pagina corrente: ogni "pagina" in dt.pages è un array
|
|
879
|
+
// di oggetti riga; ciascuno ha una proprietà data-index che punta
|
|
880
|
+
// all'indice nel dataset originale.
|
|
881
|
+
const pageRows = dt.pages?.[dt.currentPage - 1] ?? [];
|
|
882
|
+
visibleData = pageRows
|
|
883
|
+
.map(row => rawData[row['data-index']])
|
|
884
|
+
.filter(Boolean);
|
|
885
|
+
} else {
|
|
886
|
+
// Dataset filtrato completo (ignorando la paginazione).
|
|
887
|
+
// dt.searching è true solo quando c'è una ricerca attiva;
|
|
888
|
+
// in quel caso searchData contiene gli indici dei match (può essere []).
|
|
889
|
+
visibleData = dt.searching
|
|
890
|
+
? dt.searchData.map(idx => rawData[idx]).filter(Boolean)
|
|
891
|
+
: rawData;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Riutilizza tfoot esistente o ne crea uno nuovo
|
|
895
|
+
let tfoot = table.querySelector('tfoot');
|
|
896
|
+
if (!tfoot) {
|
|
897
|
+
tfoot = document.createElement('tfoot');
|
|
898
|
+
table.appendChild(tfoot);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const tr = document.createElement('tr');
|
|
902
|
+
|
|
903
|
+
cols.forEach(col => {
|
|
904
|
+
const td = document.createElement('td');
|
|
905
|
+
|
|
906
|
+
if (col._footerRender != null) {
|
|
907
|
+
if (typeof col._footerRender === 'function') {
|
|
908
|
+
td.innerHTML = col._footerRender(visibleData) ?? '';
|
|
909
|
+
} else {
|
|
910
|
+
// Stringa statica (es. etichetta fissa "Totale:")
|
|
911
|
+
td.innerHTML = col._footerRender;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Propaga le classi di allineamento della colonna per coerenza visiva
|
|
916
|
+
const cellClass = col.cellClass ?? col.headerClass ?? '';
|
|
917
|
+
if (cellClass) td.className = cellClass;
|
|
918
|
+
|
|
919
|
+
tr.appendChild(td);
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
tfoot.replaceChildren(tr);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ─── Info label ──────────────────────────────────────────────────────────────
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Sovrascrive il testo dell'area `.info` generato da simple-datatables per
|
|
929
|
+
* includere sempre il totale assoluto dei record (indipendente dal filtro).
|
|
930
|
+
*
|
|
931
|
+
* Comportamento:
|
|
932
|
+
* - Senza ricerca attiva:
|
|
933
|
+
* "Stai visualizzando le righe da {start} a {end}, su un totale di {total}."
|
|
934
|
+
* - Con ricerca attiva:
|
|
935
|
+
* "Stai visualizzando le righe da {start} a {end} dei {filtered} record filtrati,
|
|
936
|
+
* su un totale di {total}."
|
|
937
|
+
* - Nessun risultato (ricerca attiva, 0 match):
|
|
938
|
+
* "Nessun risultato per la ricerca corrente (totale record: {total})."
|
|
939
|
+
*
|
|
940
|
+
* Viene chiamato da datatable.init, datatable.page, datatable.search,
|
|
941
|
+
* datatable.multisearch.
|
|
942
|
+
*/
|
|
943
|
+
_updateInfo() {
|
|
944
|
+
const infoEl = this.querySelector(`.${styles.info}`);
|
|
945
|
+
if (!infoEl) return;
|
|
946
|
+
|
|
947
|
+
const dt = this._dt;
|
|
948
|
+
const total = this._rawData?.length ?? 0;
|
|
949
|
+
|
|
950
|
+
// Numero di record nel set corrente (filtrati o tutti)
|
|
951
|
+
const filtered = dt.searching ? dt.searchData.length : total;
|
|
952
|
+
|
|
953
|
+
if (filtered === 0 && dt.searching) {
|
|
954
|
+
infoEl.textContent = `Nessun risultato per la ricerca corrente (totale record: ${total}).`;
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Calcola start/end della pagina corrente
|
|
959
|
+
const perPage = dt.options.perPage;
|
|
960
|
+
const currentPage = dt.currentPage; // 1-based
|
|
961
|
+
const start = (currentPage - 1) * perPage + 1;
|
|
962
|
+
const end = Math.min(currentPage * perPage, filtered);
|
|
963
|
+
|
|
964
|
+
if (dt.searching) {
|
|
965
|
+
infoEl.textContent =
|
|
966
|
+
`Stai visualizzando le righe da ${start} a ${end} ` +
|
|
967
|
+
`dei ${filtered} record filtrati, su un totale di ${total}.`;
|
|
968
|
+
} else {
|
|
969
|
+
infoEl.textContent =
|
|
970
|
+
`Stai visualizzando le righe da ${start} a ${end}, ` +
|
|
971
|
+
`su un totale di ${total}.`;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
// ─── Top slot ────────────────────────────────────────────────────────────────
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Inietta un elemento DOM o del markup HTML nell'area in alto a sinistra
|
|
980
|
+
* del datatable — lo spazio normalmente occupato dal selettore "righe per
|
|
981
|
+
* pagina" (qui disabilitato con `perPageSelect: false`).
|
|
982
|
+
*
|
|
983
|
+
* Il parametro `topSlot` accetta:
|
|
984
|
+
* - un `Element` (già costruito fuori dal componente)
|
|
985
|
+
* - una stringa HTML (viene parsata con `innerHTML`)
|
|
986
|
+
*
|
|
987
|
+
* L'elemento viene inserito come primo figlio dell'area `.topArea`,
|
|
988
|
+
* prima della search box, avvolto in un `<div class="datatable-top-slot">`.
|
|
989
|
+
* Se `topSlot` non è definito, non viene inserito nulla.
|
|
990
|
+
*
|
|
991
|
+
* Esempio – stringa HTML:
|
|
992
|
+
* el.init({
|
|
993
|
+
* topSlot: '<button class="btn btn-sm btn-primary">Esporta CSV</button>',
|
|
994
|
+
* });
|
|
995
|
+
*
|
|
996
|
+
* Esempio – elemento DOM:
|
|
997
|
+
* const btn = document.createElement('button');
|
|
998
|
+
* btn.textContent = 'Nuova commessa';
|
|
999
|
+
* el.init({ topSlot: btn });
|
|
1000
|
+
*/
|
|
1001
|
+
_injectTopSlot() {
|
|
1002
|
+
const topSlot = this._getParam('topSlot', null);
|
|
1003
|
+
if (topSlot == null) return;
|
|
1004
|
+
|
|
1005
|
+
const topArea = this.querySelector(`.${styles.topArea}`);
|
|
1006
|
+
if (!topArea) return;
|
|
1007
|
+
|
|
1008
|
+
const wrapper = document.createElement('div');
|
|
1009
|
+
wrapper.className = 'datatable-top-slot';
|
|
1010
|
+
|
|
1011
|
+
if (topSlot instanceof Element) {
|
|
1012
|
+
wrapper.appendChild(topSlot);
|
|
1013
|
+
} else {
|
|
1014
|
+
wrapper.innerHTML = topSlot;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Inserisce prima della search box (primo figlio esistente)
|
|
1018
|
+
topArea.insertBefore(wrapper, topArea.firstChild);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
|
|
787
1022
|
} // end component
|
|
788
1023
|
|
|
789
1024
|
|
|
@@ -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
|
|