@massimo-cassandro/minimo 1.32.4 → 1.33.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.
@@ -0,0 +1,105 @@
1
+ /*! minimo - json-table: defaults */
2
+
3
+ /**
4
+ * Function returning the text of the info area.
5
+ * @callback InfoTextFn
6
+ * @param {number} shown - Number of rows currently displayed
7
+ * @param {number} total - Total number of rows in the data set
8
+ * @returns {string|Node} Plain text, an HTML string or a DOM node
9
+ */
10
+
11
+ /**
12
+ * `<json-table>` parameters.
13
+ *
14
+ * Every parameter can be set either as an HTML attribute of the `<json-table>` element
15
+ * (attribute names are case-insensitive, so `jsonurl="…"` and `jsonUrl="…"` are equivalent)
16
+ * or as a property of the object passed to `init()`. Values that are functions
17
+ * (e.g. `caption`, `infoText`) can only be set via `init()`.
18
+ *
19
+ * Precedence: `init()` > HTML attribute > `JsonTable.setDefaults()` > built-in default.
20
+ *
21
+ * TODO parametri `cols`, data-types, locale, icone, ordinamento, paginazione: da aggiungere negli step successivi
22
+ *
23
+ * @typedef {Object} JsonTableParams
24
+ * @property {boolean} [debug] - Logs resolved params and generated elements to the console (default: false)
25
+ * @property {string|null} [jsonUrl] - URL of the JSON to fetch. Ignored when `data` is set (default: null)
26
+ * @property {string|null} [jsonDataField] - Key of the fetched JSON holding the rows array (e.g. `{ data: [...] }`);
27
+ * `null` or an empty string means the JSON root is the rows array itself (default: 'data')
28
+ * @property {Array<Object>|null} [data] - Inline rows (array of plain objects); takes precedence over `jsonUrl`.
29
+ * As an HTML attribute it must be a JSON string, which is always treated as the rows array itself
30
+ * (`jsonDataField` is ignored) (default: null)
31
+ * @property {string|Function|null} [caption] - Table caption: a string (plain text or HTML) or, via `init()` only,
32
+ * a function returning a string or a Node (default: null)
33
+ * @property {boolean} [search] - Whether to render the search input (default: true)
34
+ * @property {string|null} [searchInputClass] - Class(es) of the search input, replacing the default one (default: 'form-control')
35
+ * @property {string} [searchInputTitle] - `title` attribute of the search input (default: 'Filtra record: inserisci un termine per eseguire la ricerca')
36
+ * @property {string} [searchInputPlaceholder] - Placeholder of the search input (default: 'Inserisci il termine da cercare')
37
+ * @property {string} [searchInputAriaLabel] - `aria-label` of the search input (default: 'Filtra risultati')
38
+ * @property {string|null} [tableId] - `id` attribute of the `<table>` element (default: null)
39
+ * @property {string|null} [tableWrapperClass] - Class(es) of the div wrapping the table, replacing the default one (default: 'table-responsive')
40
+ * @property {string|null} [tableClass] - Class(es) of the `<table>` element, replacing the default ones (default: 'table table-bordered')
41
+ * @property {string|null} [mainWrapperExtraClass] - Extra class(es) added to the main wrapper (info section + table) (default: null)
42
+ * @property {string|null} [outerInfoExtraClass] - Extra class(es) added to the outer info container (default: null)
43
+ * @property {string|null} [infoExtraClass] - Extra class(es) added to the info container (default: null)
44
+ * @property {InfoTextFn} [infoText] - Function returning the info text: plain text, an HTML string or a Node
45
+ * (default: Italian text "Visualizzate N righe su M" / "Nessun record")
46
+ */
47
+
48
+ /**
49
+ * Built-in defaults.
50
+ *
51
+ * @type {Required<JsonTableParams>}
52
+ *
53
+ * @example
54
+ * {
55
+ * debug: false,
56
+ * jsonUrl: null,
57
+ * jsonDataField: 'data',
58
+ * data: null,
59
+ * caption: null,
60
+ * search: true,
61
+ * searchInputClass: 'form-control',
62
+ * searchInputTitle: 'Filtra record: inserisci un termine per eseguire la ricerca',
63
+ * searchInputPlaceholder: 'Inserisci il termine da cercare',
64
+ * searchInputAriaLabel: 'Filtra risultati',
65
+ * tableId: null,
66
+ * tableWrapperClass: 'table-responsive',
67
+ * tableClass: 'table table-bordered',
68
+ * mainWrapperExtraClass: null,
69
+ * outerInfoExtraClass: null,
70
+ * infoExtraClass: null,
71
+ * infoText: (shown, total) => '…'
72
+ * }
73
+ */
74
+ export const defaults = {
75
+ debug: false,
76
+
77
+ jsonUrl: null,
78
+ jsonDataField: 'data',
79
+ data: null,
80
+
81
+ caption: null,
82
+
83
+ search: true,
84
+ searchInputClass: 'form-control',
85
+ searchInputTitle: 'Filtra record: inserisci un termine per eseguire la ricerca',
86
+ searchInputPlaceholder: 'Inserisci il termine da cercare',
87
+ searchInputAriaLabel: 'Filtra risultati',
88
+
89
+ tableId: null,
90
+ tableWrapperClass: 'table-responsive',
91
+ tableClass: 'table table-bordered',
92
+
93
+ mainWrapperExtraClass: null,
94
+ outerInfoExtraClass: null,
95
+ infoExtraClass: null,
96
+
97
+ infoText: (shown, total) => {
98
+ if (total === 0) {
99
+ return 'Nessun record';
100
+ }
101
+ return shown === 1
102
+ ? `Visualizzata <strong>${shown}</strong> riga su <strong>${total}</strong>`
103
+ : `Visualizzate <strong>${shown}</strong> righe su <strong>${total}</strong>`;
104
+ }
105
+ };
@@ -0,0 +1,57 @@
1
+ /*! minimo - json-table: data acquisition */
2
+
3
+ /** @typedef {import('./defaults.js').JsonTableParams} JsonTableParams */
4
+
5
+ /**
6
+ * Retrieves the rows array from the configured source.
7
+ *
8
+ * `data` takes precedence over `jsonUrl`. With `jsonUrl` the JSON is fetched and the rows are
9
+ * read from the `jsonDataField` key (`null`/`''` = the JSON root is the array itself).
10
+ * Returns `null` when no source is configured.
11
+ *
12
+ * @param {JsonTableParams} params - Resolved params (see `resolve-params.js`)
13
+ * @returns {Promise<Array<Object>|null>} The rows array, or `null` when neither `data` nor `jsonUrl` is set
14
+ * @throws {Error} When `data` is not an array, on HTTP/network errors, or when the fetched JSON
15
+ * does not contain an array at `jsonDataField`
16
+ *
17
+ * @example
18
+ * // inline data (jsonUrl ignored)
19
+ * await getData({ ...params, data: [{ id: 1 }], jsonUrl: '/ignored.json' }); // → [{ id: 1 }]
20
+ *
21
+ * // fetched JSON: { data: [...] } (jsonDataField default: 'data')
22
+ * await getData({ ...params, jsonUrl: '/api/rows.json' });
23
+ *
24
+ * // fetched JSON whose root is the array itself
25
+ * await getData({ ...params, jsonUrl: '/api/rows.json', jsonDataField: null });
26
+ */
27
+ export async function getData(params) {
28
+
29
+ if (params.data != null) {
30
+ if (!Array.isArray(params.data)) {
31
+ throw new Error('[json-table] `data` deve essere un array di oggetti');
32
+ }
33
+ return params.data;
34
+ }
35
+
36
+ if (params.jsonUrl) {
37
+ const response = await fetch(params.jsonUrl);
38
+
39
+ if (!response.ok) {
40
+ throw new Error(`[json-table] ${params.jsonUrl}: risposta HTTP ${response.status}`);
41
+ }
42
+
43
+ const json = await response.json();
44
+ const field = params.jsonDataField;
45
+ const rows = (field == null || field === '') ? json : json?.[field];
46
+
47
+ if (!Array.isArray(rows)) {
48
+ throw new Error(
49
+ `[json-table] ${params.jsonUrl}: ` +
50
+ (field ? `il campo \`${field}\` non contiene un array` : 'la risposta non è un array')
51
+ );
52
+ }
53
+ return rows;
54
+ }
55
+
56
+ return null;
57
+ }
@@ -0,0 +1,72 @@
1
+ /*! minimo - json-table: info section */
2
+
3
+ import * as styles from '../json-table-component.module.css';
4
+ import { classnames } from '../../../utilities/classnames.js';
5
+
6
+ /** @typedef {import('./defaults.js').JsonTableParams} JsonTableParams */
7
+ /** @typedef {import('./main-builder.js').JsonTableElements} JsonTableElements */
8
+
9
+ /**
10
+ * Builds the domBuilder configuration of the info section (info text + search input).
11
+ *
12
+ * Generated structure:
13
+ * ```
14
+ * div.infoOuter[outerInfoExtraClass]
15
+ * div.info[infoExtraClass]
16
+ * div.resultInfo ← info text (see `update-info.js`)
17
+ * div.search > input[type=search] ← only if `params.search` is true
18
+ * ```
19
+ *
20
+ * References to the generated elements are stored in `elements`
21
+ * (`infoOuter`, `info`, `resultInfo`, `searchInput`).
22
+ *
23
+ * TODO listener sull'input di ricerca: da impostare quando verrà implementata la ricerca
24
+ *
25
+ * @param {JsonTableParams} params - Resolved params
26
+ * @param {JsonTableElements} elements - Object collecting the generated elements (mutated)
27
+ * @returns {DomBuilderItem[]} domBuilder items to be used as children of the main wrapper
28
+ *
29
+ * @example
30
+ * const elements = {};
31
+ * domBuilder([{ tag: 'section', children: [...infoSection(params, elements)] }], host);
32
+ * elements.searchInput; // → HTMLInputElement (when params.search is true)
33
+ */
34
+ export function infoSection(params, elements) {
35
+
36
+ return [
37
+ {
38
+ className: classnames(styles.infoOuter, params.outerInfoExtraClass),
39
+ callback: el => { elements.infoOuter = /** @type {HTMLElement} */ (el); },
40
+ children: [
41
+ {
42
+ className: classnames(styles.info, params.infoExtraClass),
43
+ callback: el => { elements.info = /** @type {HTMLElement} */ (el); },
44
+ children: [
45
+ {
46
+ className: styles.resultInfo,
47
+ attrs: { 'aria-live': 'polite' },
48
+ callback: el => { elements.resultInfo = /** @type {HTMLElement} */ (el); }
49
+ },
50
+ {
51
+ condition: params.search,
52
+ className: styles.search,
53
+ children: [
54
+ {
55
+ tag: 'input',
56
+ className: classnames(params.searchInputClass),
57
+ attrs: {
58
+ type: 'search',
59
+ title: params.searchInputTitle,
60
+ placeholder: params.searchInputPlaceholder,
61
+ 'aria-label': params.searchInputAriaLabel
62
+ },
63
+ callback: el => { elements.searchInput = /** @type {HTMLInputElement} */ (el); }
64
+ }
65
+ ]
66
+ }
67
+ ]
68
+ }
69
+ ]
70
+ }
71
+ ];
72
+ }
@@ -0,0 +1,95 @@
1
+ /*! minimo - json-table: main structure builder */
2
+
3
+ import * as styles from '../json-table-component.module.css';
4
+ import { domBuilder } from '../../../utilities/dom-builder/dom-builder.js';
5
+ import { classnames } from '../../../utilities/classnames.js';
6
+ import { infoSection } from './info-section.js';
7
+
8
+ /** @typedef {import('./defaults.js').JsonTableParams} JsonTableParams */
9
+
10
+ /**
11
+ * Elements generated by the component, collected during the build.
12
+ * @typedef {Object} JsonTableElements
13
+ * @property {HTMLElement} [wrapper] - Main wrapper (`section`), contains info section and table
14
+ * @property {HTMLElement} [infoOuter] - Outer info container
15
+ * @property {HTMLElement} [info] - Info container (flex row: info text + search)
16
+ * @property {HTMLElement} [resultInfo] - Info text container
17
+ * @property {HTMLInputElement} [searchInput] - Search input (only when `search` is true)
18
+ * @property {HTMLElement} [tableWrapper] - Div wrapping the table (`table-responsive` by default)
19
+ * @property {HTMLTableElement} [table] - The `<table>` element
20
+ */
21
+
22
+ /**
23
+ * Builds the whole component structure inside `host` (emptied first) via domBuilder.
24
+ *
25
+ * Generated structure:
26
+ * ```
27
+ * section.wrapper[mainWrapperExtraClass]
28
+ * div.infoOuter[outerInfoExtraClass]
29
+ * div.info[infoExtraClass]
30
+ * div.resultInfo
31
+ * div.search > input[type=search] (if `search`)
32
+ * div[tableWrapperClass]
33
+ * table#tableId[tableClass]
34
+ * caption (if `caption`)
35
+ * ```
36
+ *
37
+ * TODO thead / tbody / tfoot: step successivo (parametro `cols` e rendering delle righe)
38
+ *
39
+ * @param {HTMLElement} host - The `<json-table>` element
40
+ * @param {JsonTableParams} params - Resolved params
41
+ * @returns {JsonTableElements} References to the generated elements
42
+ *
43
+ * @example
44
+ * const elements = mainBuilder(el, {
45
+ * ...defaults,
46
+ * caption: 'Utenti', // default: null
47
+ * search: true, // default: true
48
+ * tableId: 'users-table', // default: null
49
+ * tableWrapperClass: 'table-responsive', // default: 'table-responsive'
50
+ * tableClass: 'table table-bordered', // default: 'table table-bordered'
51
+ * mainWrapperExtraClass: null, // default: null
52
+ * outerInfoExtraClass: null, // default: null
53
+ * infoExtraClass: null // default: null
54
+ * });
55
+ * elements.table; // → HTMLTableElement
56
+ */
57
+ export function mainBuilder(host, params) {
58
+
59
+ /** @type {JsonTableElements} */
60
+ const elements = {};
61
+
62
+ domBuilder([
63
+ {
64
+ tag: 'section',
65
+ className: classnames(styles.wrapper, params.mainWrapperExtraClass),
66
+ callback: el => { elements.wrapper = /** @type {HTMLElement} */ (el); },
67
+ children: [
68
+
69
+ ...infoSection(params, elements),
70
+
71
+ {
72
+ className: classnames(params.tableWrapperClass),
73
+ callback: el => { elements.tableWrapper = /** @type {HTMLElement} */ (el); },
74
+ children: [
75
+ {
76
+ tag: 'table',
77
+ id: params.tableId,
78
+ className: classnames(styles.table, params.tableClass),
79
+ callback: el => { elements.table = /** @type {HTMLTableElement} */ (el); },
80
+ children: [
81
+ {
82
+ tag: 'caption',
83
+ condition: params.caption != null,
84
+ content: params.caption
85
+ }
86
+ ]
87
+ }
88
+ ]
89
+ }
90
+ ]
91
+ }
92
+ ], host, { emptyParent: true });
93
+
94
+ return elements;
95
+ }
@@ -0,0 +1,96 @@
1
+ /*! minimo - json-table: params resolution */
2
+
3
+ import { defaults } from './defaults.js';
4
+
5
+ /** @typedef {import('./defaults.js').JsonTableParams} JsonTableParams */
6
+
7
+ /**
8
+ * Resolves a single parameter, merging the four possible sources.
9
+ *
10
+ * Precedence (highest first):
11
+ * 1. `config` – value passed via `init()`/`reload()`. Present and `!== undefined` always wins;
12
+ * pass `null` explicitly to skip the HTML attribute and fall back to the defaults.
13
+ * 2. HTML attribute – booleans are auto-parsed when the built-in default is a boolean
14
+ * (`search`, `search="true"`, `search="1"` → true; `search="false"`, `search="0"` → false);
15
+ * values starting with `[` or `{` are parsed as JSON (invalid JSON is reported in the
16
+ * console and ignored, falling back to the defaults).
17
+ * 3. `projectDefaults` – values set via `JsonTable.setDefaults()`.
18
+ * 4. Built-in default (see `defaults.js`).
19
+ *
20
+ * @param {HTMLElement} el - The `<json-table>` element (source of the HTML attributes)
21
+ * @param {Partial<JsonTableParams>|null} config - Config passed via `init()` (default: null)
22
+ * @param {Partial<JsonTableParams>} projectDefaults - Project-wide defaults (default: {})
23
+ * @param {keyof JsonTableParams} name - Parameter name
24
+ * @returns {*} The resolved value
25
+ *
26
+ * @example
27
+ * // <json-table jsonurl="/api/rows.json" search="false"></json-table>
28
+ * getParam(el, { jsonUrl: '/api/other.json' }, {}, 'jsonUrl'); // → '/api/other.json' (config)
29
+ * getParam(el, null, {}, 'search'); // → false (attribute)
30
+ * getParam(el, null, { tableClass: 'table' }, 'tableClass'); // → 'table' (project defaults)
31
+ * getParam(el, null, {}, 'tableWrapperClass'); // → 'table-responsive' (built-in default)
32
+ */
33
+ export function getParam(el, config = null, projectDefaults = {}, name) {
34
+
35
+ // 1. config from script (undefined = key not present → go down one level)
36
+ if (config && config[name] !== undefined) {
37
+ return config[name];
38
+ }
39
+
40
+ // 2. HTML attribute
41
+ const attr = el.getAttribute(name);
42
+ if (attr !== null) {
43
+ const trimmed = attr.trim();
44
+
45
+ if (typeof defaults[name] === 'boolean') {
46
+ const lower = trimmed.toLowerCase();
47
+ // empty string = attribute present without value → true (standard HTML)
48
+ return lower !== 'false' && lower !== '0';
49
+ }
50
+
51
+ if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
52
+ try {
53
+ return JSON.parse(trimmed);
54
+ } catch (err) {
55
+ // eslint-disable-next-line no-console
56
+ console.error(`[json-table] attributo \`${name}\`: JSON non valido, valore ignorato (viene usato il default)`, err);
57
+ // fall through to the defaults
58
+ }
59
+ } else {
60
+ return attr;
61
+ }
62
+ }
63
+
64
+ // 3. project defaults
65
+ if (projectDefaults[name] !== undefined) {
66
+ return projectDefaults[name];
67
+ }
68
+
69
+ // 4. built-in default
70
+ return defaults[name];
71
+ }
72
+
73
+
74
+ /**
75
+ * Resolves every parameter defined in `defaults.js` (see `getParam` for the precedence rules).
76
+ *
77
+ * @param {HTMLElement} el - The `<json-table>` element
78
+ * @param {Partial<JsonTableParams>|null} [config=null] - Config passed via `init()` (default: null)
79
+ * @param {Partial<JsonTableParams>} [projectDefaults={}] - Project-wide defaults (default: {})
80
+ * @returns {JsonTableParams} The complete params object
81
+ *
82
+ * @example
83
+ * const params = resolveParams(el, { jsonUrl: '/api/rows.json', debug: true }, {});
84
+ * // → { debug: true, jsonUrl: '/api/rows.json', jsonDataField: 'data', data: null, ... }
85
+ */
86
+ export function resolveParams(el, config = null, projectDefaults = {}) {
87
+
88
+ const params = Object.fromEntries(
89
+ Object.keys(defaults).map(name => [
90
+ name,
91
+ getParam(el, config, projectDefaults, /** @type {keyof JsonTableParams} */ (name))
92
+ ])
93
+ );
94
+
95
+ return /** @type {JsonTableParams} */ (params);
96
+ }
@@ -0,0 +1,48 @@
1
+ /*! minimo - json-table: info text update */
2
+
3
+ /** @typedef {import('./defaults.js').JsonTableParams} JsonTableParams */
4
+ /** @typedef {import('./main-builder.js').JsonTableElements} JsonTableElements */
5
+
6
+ /**
7
+ * Updates the info text (`elements.resultInfo`) using `params.infoText(shown, total)`.
8
+ *
9
+ * The returned value can be a Node (appended as-is), a plain string (set as `textContent`)
10
+ * or an HTML string (sanitized via `Element.setHTML` where supported, `innerHTML` otherwise,
11
+ * consistently with domBuilder).
12
+ *
13
+ * @param {JsonTableElements} elements - Generated elements (see `main-builder.js`)
14
+ * @param {JsonTableParams} params - Resolved params
15
+ * @param {number} shown - Number of rows currently displayed
16
+ * @param {number} total - Total number of rows in the data set
17
+ * @returns {void}
18
+ *
19
+ * @example
20
+ * updateInfo(elements, params, 25, 100);
21
+ * // with the default `infoText` → "Visualizzate <strong>25</strong> righe su <strong>100</strong>"
22
+ */
23
+ export function updateInfo(elements, params, shown, total) {
24
+
25
+ const target = elements.resultInfo;
26
+ if (!target) {
27
+ return;
28
+ }
29
+
30
+ const content = params.infoText(shown, total);
31
+
32
+ if (content instanceof Node) {
33
+ target.replaceChildren(content);
34
+ return;
35
+ }
36
+
37
+ const text = String(content ?? '');
38
+
39
+ if (!text.includes('<')) {
40
+ target.textContent = text;
41
+
42
+ } else if (typeof target.setHTML === 'function') {
43
+ target.setHTML(text);
44
+
45
+ } else {
46
+ target.innerHTML = text;
47
+ }
48
+ }
@@ -176,6 +176,68 @@ document.dispatchEvent(new CustomEvent('datatable:search', {
176
176
  }));
177
177
  ```
178
178
 
179
+ ## Parametri/attributi
180
+
181
+ Tutti i parametri sono leggibili sia da attributo HTML che da script (`init()`).
182
+ Ordine di precedenza: config (script) > attributo HTML > default interno.
183
+
184
+ ### Parametri del componente
185
+
186
+ ```javascript
187
+ {
188
+ json, // URL da cui recuperare i dati (struttura attesa: { data: [...] }).
189
+ // Ignorato se `data` è presente.
190
+
191
+ data, // Dati inline, array di oggetti plain. Ha precedenza su `json`.
192
+ // Utile in contesti SSR (Twig, Blade, ...) dove il JSON è già
193
+ // disponibile server-side; per grandi dataset preferire `json`.
194
+
195
+ cols, // Definizione delle colonne. Vedi "Parametro cols" più sotto.
196
+ // Default: []
197
+
198
+ tableClass, // Classi CSS del tag <table>, in sostituzione di 'table table-bordered'.
199
+ // La classe interna styles.table (necessaria al layout) viene
200
+ // comunque sempre applicata.
201
+ // Default: 'table table-bordered'
202
+
203
+ renderNullAs, // Stringa globale da mostrare al posto di valori null/undefined,
204
+ // sovrascrivibile per singola colonna con `_renderNullAs`.
205
+ // Default: '—' (em dash)
206
+
207
+ updateFooterOnPageChange, // Se true, `_footerRender` riceve solo i record della
208
+ // pagina corrente (subtotale). Se false, l'intero set filtrato
209
+ // (il footer si aggiorna solo al cambio filtro, non pagina).
210
+ // Default: false
211
+
212
+ refs, // string[] di percorsi URL da cui, se si proviene, la pagina
213
+ // corrente viene ripristinata dal cookie di sessione 'sd-pag'.
214
+ // Confronto su pathname (query string/hash ignorati): es. ['/utenti']
215
+ // intercetta anche '/utenti/1234'.
216
+
217
+ topSlot, // Elemento DOM o markup HTML nell'area in alto a sinistra.
218
+ // Solo via init() (non leggibile da attributo HTML). Ignorato
219
+ // se showInfoAtTop è true. Vedi sezione dedicata più sotto.
220
+
221
+ showInfoAtTop, // Replica il testo dell'area info anche in alto a sinistra.
222
+ // Ha priorità su topSlot se true. Vedi sezione dedicata più sotto.
223
+ // Default: true
224
+ }
225
+ ```
226
+
227
+ ### Parametri nativi di simple-datatables
228
+
229
+ ```javascript
230
+ {
231
+ perPage, // Numero di righe per pagina, passato direttamente come opzione
232
+ // nativa `perPage` del costruttore DataTable.
233
+ // Default: 25
234
+ }
235
+ ```
236
+
237
+ Tutte le altre opzioni native di simple-datatables (`searchable`, `sortable`, `paging`,
238
+ `pagerDelta`, `perPageSelect`, `locale`, `labels`, `classes`, `template`, ...) sono
239
+ impostate internamente dal componente e **non** sono configurabili da attributo/script.
240
+
179
241
  ## Parametro `cols`
180
242
 
181
243
  Array di oggetti di configurazione delle colonne. Le chiavi con prefisso `_` sono