@massimo-cassandro/minimo 1.33.0 → 1.33.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.
Files changed (84) hide show
  1. package/README.md +2 -1
  2. package/dev-tools/starter-kit/source_files/{CLAUDE-frontend.md → app/CLAUDE.md} +1 -1
  3. package/dev-tools/starter-kit/source_files/app/assets/api_htaccess +20 -0
  4. package/dev-tools/starter-kit/source_files/{layout-critical.css → app/css/layout.critical.css} +1 -1
  5. package/dev-tools/starter-kit/source_files/app/css/layout.css +0 -0
  6. package/dev-tools/starter-kit/source_files/app/index.js +1 -0
  7. package/dev-tools/starter-kit/source_files/root/webpack-config-modules/inline-critical-css.mjs +41 -0
  8. package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/purgecss-setup.mjs +1 -1
  9. package/dev-tools/starter-kit/{webpack.config.mjs → source_files/root/webpack.config.mjs} +50 -24
  10. package/dev-tools/starter-kit/starter-install.sh +141 -83
  11. package/dev-tools/stylelint-config/test/test.module.scss +9 -0
  12. package/package.json +1 -1
  13. package/snippets/php/iviewer.php +488 -0
  14. package/snippets/twig-symfony/ViewerController.php +549 -0
  15. package/src/custom-properties.css +29 -0
  16. package/src/minimo.css +3 -0
  17. package/src/utilities/dom-builder/dom-builder.js +9 -5
  18. package/src/web-components/json-table/json-table-component.js +387 -38
  19. package/src/web-components/json-table/json-table-component.module.css +190 -0
  20. package/src/web-components/json-table/json-table.minimo.tokens.mjs +195 -0
  21. package/src/web-components/json-table/readme.md +603 -82
  22. package/src/web-components/json-table/src/cell-content.js +136 -0
  23. package/src/web-components/json-table/src/content-utils.js +103 -0
  24. package/src/web-components/json-table/src/data-types.js +280 -0
  25. package/src/web-components/json-table/src/defaults.js +377 -58
  26. package/src/web-components/json-table/src/get-data.js +105 -10
  27. package/src/web-components/json-table/src/icons.js +80 -0
  28. package/src/web-components/json-table/src/info-section.js +85 -50
  29. package/src/web-components/json-table/src/main-builder.js +175 -52
  30. package/src/web-components/json-table/src/pagination.js +253 -0
  31. package/src/web-components/json-table/src/parse-cols.js +113 -0
  32. package/src/web-components/json-table/src/parse-rows.js +81 -0
  33. package/src/web-components/json-table/src/resolve-params.js +102 -32
  34. package/src/web-components/json-table/src/search.js +71 -0
  35. package/src/web-components/json-table/src/sorting.js +111 -0
  36. package/src/web-components/json-table/src/table-body.js +109 -0
  37. package/src/web-components/json-table/src/table-builder.js +152 -0
  38. package/src/web-components/json-table/src/table-tfoot.js +56 -0
  39. package/src/web-components/json-table/src/table-thead.js +183 -0
  40. package/src/web-components/json-table/src/update-info.js +43 -24
  41. package/src/web-components/s-datatable-component/{docs.md → readme.md} +5 -3
  42. package/src/web-components/s-datatable-component/s-datatable-component.js +1 -1
  43. package/types/global.d.ts +17 -3
  44. package/dev-tools/starter-kit/source_files/app_index.js +0 -1
  45. package/snippets/config-templates/_htaccess +0 -69
  46. package/snippets/config-templates/robots.txt +0 -5
  47. package/snippets/config-templates/rollup.config.mjs +0 -115
  48. /package/dev-tools/starter-kit/source_files/{_root_robots.txt → app/assets/robots.txt} +0 -0
  49. /package/dev-tools/starter-kit/source_files/{_root_htaccess → app/assets/root_htaccess} +0 -0
  50. /package/{src → dev-tools/starter-kit/source_files/app/css}/fonts.css +0 -0
  51. /package/dev-tools/starter-kit/source_files/{favicons-readme.md → app/favicons/favicons-readme.md} +0 -0
  52. /package/dev-tools/starter-kit/{webpack-template.ejs → source_files/app/tpl/index.ejs} +0 -0
  53. /package/dev-tools/starter-kit/source_files/{__project__.code-workspace → root/__project__.code-workspace} +0 -0
  54. /package/dev-tools/starter-kit/source_files/{_browserslistrc → root/_browserslistrc} +0 -0
  55. /package/dev-tools/starter-kit/source_files/{_editorconfig → root/_editorconfig} +0 -0
  56. /package/dev-tools/starter-kit/source_files/{_gitignore → root/_gitignore} +0 -0
  57. /package/dev-tools/starter-kit/source_files/{_prettierrc → root/_prettierrc} +0 -0
  58. /package/dev-tools/starter-kit/source_files/{eslint.config.mjs → root/eslint.config.mjs} +0 -0
  59. /package/dev-tools/starter-kit/source_files/{jsconfig.json → root/jsconfig.json} +0 -0
  60. /package/dev-tools/starter-kit/{package-tpl.json → source_files/root/package-tpl.json} +0 -0
  61. /package/dev-tools/starter-kit/source_files/{stylelint.config.mjs → root/stylelint.config.mjs} +0 -0
  62. /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/css-rules.mjs +0 -0
  63. /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/get-jsConfig-aliases.mjs +0 -0
  64. /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/mini-svg-data-uri-loader.cjs +0 -0
  65. /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/postcss.config.mjs +0 -0
  66. /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/purgecss-variables-safelist.mjs +0 -0
  67. /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/svg-rules.mjs +0 -0
  68. /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/svgo.config.mjs +0 -0
  69. /package/snippets/{config-templates/_github → _github}/dependabot.yml +0 -0
  70. /package/snippets/{error-pages/error-pages.js → error-pages.js} +0 -0
  71. /package/snippets/{config-templates/react → react}/offcanvas/offcanvas.jsx +0 -0
  72. /package/snippets/{config-templates/react → react}/offcanvas/offcanvas.module.scss +0 -0
  73. /package/snippets/{config-templates → root}/_hintrc.cjs +0 -0
  74. /package/snippets/{config-templates → root}/_npmignore +0 -0
  75. /package/snippets/{config-templates → root}/_npmrc +0 -0
  76. /package/snippets/{config-templates → root}/_stylelintignore +0 -0
  77. /package/snippets/{config-templates → root}/babel.config--react.json +0 -0
  78. /package/snippets/{config-templates → root}/babel.config--solidJS.json +0 -0
  79. /package/snippets/{ImgViewerController.php → twig-symfony/ImgViewerController.php} +0 -0
  80. /package/snippets/{UnsplashController.php → twig-symfony/UnsplashController.php} +0 -0
  81. /package/snippets/{config-templates → twig-symfony}/_env.local +0 -0
  82. /package/snippets/{error-pages → twig-symfony}/bundles/TwigBundle/Exception/error.html.twig +0 -0
  83. /package/snippets/{flash-messages.html.twig → twig-symfony/flash-messages.html.twig} +0 -0
  84. /package/snippets/{minimo-form-layout.html.twig → twig-symfony/minimo-form-layout.html.twig} +0 -0
@@ -1,74 +1,324 @@
1
1
  /*! minimo - json-table: defaults */
2
2
 
3
+ import {
4
+ boolTrueIcon, boolFalseIcon, sortAscArrowIcon, sortDescArrowIcon, sortNoneArrowIcon,
5
+ paginationPrevIcon, paginationNextIcon
6
+ } from './icons.js';
7
+
8
+ /**
9
+ * Content accepted by every cell / heading / info area: plain text, HTML string, number,
10
+ * DOM node or domBuilder array. `null` is rendered as `renderNullAs`, `undefined` lets the
11
+ * default rendering pipeline go on (see `cell-content.js`).
12
+ * @typedef {string|number|Node|DomBuilderItem[]|null|undefined} CellContent
13
+ */
14
+
15
+ /**
16
+ * Icon / markup used for booleans, sort and pagination buttons: an SVG/HTML string (e.g. an
17
+ * `.svg?inline` import), a DOM node, a domBuilder item, or a function returning one of them.
18
+ * @typedef {string|Node|DomBuilderItem|(() => string|Node|DomBuilderItem)} IconDef
19
+ */
20
+
21
+ /**
22
+ * Column cell renderer. Arguments are positional (all optional) to keep the simplest
23
+ * case (`row => row.name`) as short as possible.
24
+ * @callback ColRender
25
+ * @param {Object} row - The data object of the current row
26
+ * @param {HTMLTableRowElement} [tr] - The `<tr>` element of the current row
27
+ * @param {HTMLTableCellElement} [td] - The cell element (`<td>` or `<th scope="row">`)
28
+ * @returns {CellContent|void} Cell content; `undefined` (no `return`) falls back to the data type rendering,
29
+ * `null` to `renderNullAs`
30
+ */
31
+
32
+ /**
33
+ * Footer cell renderer.
34
+ * @callback TfootRender
35
+ * @param {Object[]} rows - The whole filtered data set, or the current page only when `updateFooterOnPageChange` is true
36
+ * @param {HTMLTableCellElement} [td] - The footer cell element
37
+ * @returns {CellContent}
38
+ */
39
+
40
+ /**
41
+ * Column definition (`cols` items).
42
+ *
43
+ * @typedef {Object} ColDefinition
44
+ * @property {string} key - REQUIRED: key of the row object (dot notation allowed for nested values, e.g. `owner.name`)
45
+ * @property {CellContent|(() => CellContent)} [title] - Heading content (text, HTML, Node, domBuilder array or function).
46
+ * When the column is sortable it is the text of the sort button (default: the key itself)
47
+ * @property {string} [dataType] - Data type, one of the `dataTypes` keys. `type` is accepted as an alias (default: 'string')
48
+ * @property {string} [type] - Alias of `dataType`
49
+ * @property {ColRender|string|null} [render] - Cell renderer: a function `(row, tr, td) => content` or,
50
+ * via HTML attribute too, a mustache-like string where `[[key]]` placeholders are replaced with the row
51
+ * values (nested keys allowed). Overrides the data type rendering (default: null)
52
+ * @property {TfootRender|string|null} [tfootRender] - Footer cell content (only when `tfoot` is true): a function
53
+ * `(rows, td) => content`, a static string, or one of the built-in aggregates `'@sum'`, `'@avg'`, `'@min'`,
54
+ * `'@max'`, `'@count'` computed on the column values and formatted by the column data type.
55
+ * `null` renders an empty cell (default: null)
56
+ * @property {boolean} [rowHeading] - When true the cell is a row heading (`<th scope="row">`) (default: false)
57
+ * @property {boolean} [searchable] - Enables the search on this column (default: true)
58
+ * @property {boolean} [sortable] - Enables the sorting on this column (default: true)
59
+ * @property {*|((row: Object) => *)} [sortValue] - Value used for sorting, or function `row => value`.
60
+ * Overrides the data type `sortValue` (default: undefined = data type / raw value)
61
+ * @property {*|((row: Object) => *)} [searchValue] - Same as `sortValue`, for the search (default: undefined)
62
+ * @property {boolean|((params: JsonTableParams) => boolean)} [condition] - When false (or a function returning
63
+ * false) the column is not rendered at all (default: true)
64
+ * @property {string|null} [headerClass] - Class(es) of the `<th>`, replacing the data type ones. When only one of
65
+ * `headerClass`/`cellClass` is set, the other one takes the same value (default: null)
66
+ * @property {string|null} [cellClass] - Class(es) of the body/footer cells, replacing the data type ones (default: null)
67
+ */
68
+
69
+ /**
70
+ * Data type definition (`dataTypes` values). Every function is optional.
71
+ *
72
+ * @typedef {Object} DataTypeDefinition
73
+ * @property {string|null} [headerClass] - Default class(es) of the `<th>` (overridable via `ColDefinition.headerClass`)
74
+ * @property {string|null} [cellClass] - Default class(es) of the cells (overridable via `ColDefinition.cellClass`)
75
+ * @property {((value: *, row: Object, params: JsonTableParams) => string|null)} [internalCellClass] - Class(es) always
76
+ * added to the cells, regardless of `cellClass` (used by the built-in `bool` type for the icon styles)
77
+ * @property {(value: *, row: Object|null, params: JsonTableParams) => CellContent} [render] - Cell renderer; not invoked
78
+ * for `null`/`undefined` values, which are rendered as `renderNullAs`. `row` is null when the function is
79
+ * used to format a footer aggregate (`tfootRender: '@sum'`, ...)
80
+ * @property {(value: *, row: Object, params: JsonTableParams) => *} [sortValue] - Value used for sorting (default: raw value)
81
+ * @property {(value: *, row: Object, params: JsonTableParams) => string} [searchValue] - Value used for searching (default: `String(value)`)
82
+ * @property {Partial<ColDefinition>} [colDefaults] - Column defaults forced by this type (e.g. `{ sortable: false }`),
83
+ * still overridable in the column definition
84
+ * @property {string} [inheritsFrom] - Custom types only: key of the built-in type to extend
85
+ */
86
+
87
+ /**
88
+ * Consumer class names used by the generated structure. Internal layout classes (CSS module)
89
+ * are always applied in addition to these.
90
+ *
91
+ * @typedef {Object} JsonTableClasses
92
+ * @property {string|null} [wrapper] - Main wrapper (`<section>`: info section + table) (default: null)
93
+ * @property {string|null} [infoOuter] - Outer info container (default: null)
94
+ * @property {string|null} [info] - Info container (info text + search) (default: null)
95
+ * @property {string|null} [resultInfo] - Info text container (default: null)
96
+ * @property {string|null} [search] - Search input wrapper (default: null)
97
+ * @property {string|null} [searchInput] - Search input (default: 'form-control form-control-sm')
98
+ * @property {string|null} [tableWrapper] - Div wrapping the table (default: 'table-responsive')
99
+ * @property {string|null} [table] - `<table>` element (default: 'table table-bordered')
100
+ * @property {string|null} [tableFooter] - Bar below the table holding caption and pagination (default: null)
101
+ * @property {string|null} [caption] - Caption container inside the table footer bar (default: null)
102
+ * @property {string|null} [pagination] - Pagination `<nav>` (default: null)
103
+ * @property {string|null} [paginationBtn] - Pagination buttons (default: 'btn-reset')
104
+ * @property {string|null} [sortBtn] - Sort buttons inside the `<th>` (default: 'btn-reset')
105
+ * @property {string|null} [empty] - The single cell shown when there are no rows (default: null)
106
+ * @property {string|null} [textStart] - Inline-start alignment class (default: null, minimo cells are start-aligned by default)
107
+ * @property {string|null} [textCenter] - Center alignment class (default: 'text-center')
108
+ * @property {string|null} [textEnd] - Inline-end alignment class (default: 'text-end')
109
+ * @property {string|null} [nowrap] - No-wrap class (default: 'text-nowrap')
110
+ * @property {string|null} [numeric] - Tabular figures class, used by numeric types (default: 'text-numeric')
111
+ * @property {string|null} [boolCell] - Extra class of every `bool` cell (default: null)
112
+ * @property {string|null} [boolTrue] - Extra class of `bool` cells whose value is `true` (default: null)
113
+ * @property {string|null} [boolFalse] - Extra class of `bool` cells whose value is `false` (default: null)
114
+ */
115
+
116
+ /**
117
+ * Texts used by the component. The `{page}` placeholder of the pagination labels is replaced
118
+ * with the page number.
119
+ *
120
+ * @typedef {Object} JsonTableLabels
121
+ * @property {string} [loading] - Loading placeholder (visually hidden) (default: 'Caricamento dati…')
122
+ * @property {string} [searchPlaceholder] - Search input placeholder (default: 'Cerca...')
123
+ * @property {string} [searchTitle] - Search input `title` (default: 'Cerca nella tabella')
124
+ * @property {string} [searchAriaLabel] - Search input `aria-label` (default: 'Filtra risultati')
125
+ * @property {string} [info] - Info text template, placeholders: `{start}`, `{end}`, `{totRec}`, `{filteredRec}`,
126
+ * `{page}`, `{totPages}` (default: 'Stai visualizzando le righe da {start} a {end}, su un totale di {filteredRec} record trovati')
127
+ * @property {string} [noRows] - Info text and empty cell content when the data set is empty (default: 'Nessun record trovato')
128
+ * @property {string} [noResults] - Same as `noRows`, when a search returns nothing (default: 'Nessun risultato per la ricerca')
129
+ * @property {string} [sortAsc] - `aria-label`/`title` of the sort button when the next click sorts ascending
130
+ * (default: 'Ordina questa colonna in senso ascendente (A → Z)')
131
+ * @property {string} [sortDesc] - Same, descending (default: 'Ordina questa colonna in senso discendente (Z → A)')
132
+ * @property {string} [sortNone] - Same, sorting removal (default: 'Rimuovi l’ordinamento a questa colonna')
133
+ * @property {string} [paginationAriaLabel] - `aria-label` of the pagination `<nav>` (default: 'Navigazione pagine')
134
+ * @property {string} [prevPage] - `aria-label`/`title` of the previous page button (default: 'Pagina precedente')
135
+ * @property {string} [nextPage] - Same, next page button (default: 'Pagina successiva')
136
+ * @property {string} [pageTitle] - `aria-label`/`title` of the page buttons (default: 'Vai a pagina {page}')
137
+ * @property {string} [currentPage] - `aria-label`/`title` of the current page button (default: 'Pagina {page}, corrente')
138
+ */
139
+
3
140
  /**
4
- * Function returning the text of the info area.
141
+ * Info text function.
5
142
  * @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
143
+ * @param {number} start - Index (1-based) of the first displayed row
144
+ * @param {number} end - Index of the last displayed row
145
+ * @param {number} totRec - Total number of records (unfiltered)
146
+ * @param {number} filteredRec - Number of records after filtering
147
+ * @param {number} page - Current page (1-based)
148
+ * @param {number} totPages - Total number of pages
149
+ * @returns {CellContent}
9
150
  */
10
151
 
11
152
  /**
12
- * `<json-table>` parameters.
153
+ * Names of the built-in template parts.
154
+ * @typedef {'infoSection'|'resultInfo'|'search'|'table'|'caption'|'pagination'} TemplateSlot
155
+ */
156
+
157
+ /**
158
+ * Template item: a domBuilder item (whose `children`/`content` may hold other template items)
159
+ * or a slot placeholder `{ slot: 'name' }` replaced with one of the built-in parts.
160
+ * @typedef {(Omit<DomBuilderItem, 'children'|'content'> & {
161
+ * children?: Array<TemplateItem|string|Node>,
162
+ * content?: DomBuilderItem['content']|TemplateItem[]
163
+ * }) | { slot: TemplateSlot }} TemplateItem
164
+ */
165
+
166
+ /**
167
+ * Sort state / initial sort definition.
168
+ * @typedef {Object} SortDef
169
+ * @property {string} key - Column key
170
+ * @property {'asc'|'desc'} dir - Direction
171
+ */
172
+
173
+ /**
174
+ * Names of the query string parameters sent to `jsonUrl` in server-side mode (`serverSide: true`).
175
+ * A `null` value omits the parameter.
176
+ *
177
+ * @typedef {Object} ServerParams
178
+ * @property {string|null} [page] - Requested page, 1-based (default: 'page')
179
+ * @property {string|null} [start] - Index (0-based) of the first requested record, i.e. `(page - 1) * perPage`,
180
+ * handy for `LIMIT start, perPage` queries (default: 'start')
181
+ * @property {string|null} [perPage] - Number of records per page (default: 'perPage')
182
+ * @property {string|null} [sort] - Key of the sorted column; omitted when no sort is active (default: 'sort')
183
+ * @property {string|null} [dir] - Sort direction, `asc`/`desc`; omitted when no sort is active (default: 'dir')
184
+ * @property {string|null} [search] - Search term; omitted when empty (default: 'search')
185
+ */
186
+
187
+ /**
188
+ * `<json-table>` parameters (resolved: every key has a value, see `defaults`).
13
189
  *
14
190
  * Every parameter can be set either as an HTML attribute of the `<json-table>` element
15
191
  * (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()`.
192
+ * or as a property of the object passed to `init()`. Values that are functions can only be
193
+ * set via `init()`. Object parameters (`classes`, `labels`, `dataTypes`, `serverParams`) are
194
+ * merged with the defaults, so only the keys to override need to be passed.
18
195
  *
19
196
  * Precedence: `init()` > HTML attribute > `JsonTable.setDefaults()` > built-in default.
20
197
  *
21
- * TODO parametri `cols`, data-types, locale, icone, ordinamento, paginazione: da aggiungere negli step successivi
22
- *
23
198
  * @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: [...] }`);
199
+ * @property {boolean} debug - Logs resolved params, columns, data and generated elements to the console (default: false)
200
+ * @property {string|null} jsonUrl - URL of the JSON to fetch. Ignored when `data` is set (default: null)
201
+ * @property {string|null} jsonDataField - Key of the fetched JSON holding the rows array (e.g. `{ data: [...] }`);
27
202
  * `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`.
203
+ * @property {string} totRecField - Key of the fetched JSON holding the total number of records (numeric);
204
+ * when missing or not numeric the total is the rows array length (default: 'totRec')
205
+ * @property {string} filteredRecField - Server-side mode only: key of the fetched JSON holding the number of
206
+ * records matching the current search (numeric); when missing the value of `totRecField` is used (default: 'filteredRec')
207
+ * @property {Array<Object>|null} data - Inline rows (array of plain objects); takes precedence over `jsonUrl`.
29
208
  * As an HTML attribute it must be a JSON string, which is always treated as the rows array itself
30
209
  * (`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")
210
+ * @property {ColDefinition[]} cols - REQUIRED: columns definition (default: [])
211
+ * @property {Object<string, DataTypeDefinition>} dataTypes - Custom data types, merged with the built-in ones
212
+ * (`string`, `num`, `id`, `perc`, `percDecimal`, `currency`, `euro`, `date`, `datetime`, `bool`, `email`).
213
+ * A key matching a built-in type overrides only the given properties (default: {})
214
+ * @property {string|Function|null} caption - Table caption, shown below the table (start side of the footer bar)
215
+ * and linked to the table via `aria-labelledby`: a string (plain text or HTML) or, via `init()` only, a function
216
+ * returning a string or a Node (default: null)
217
+ * @property {boolean} search - Whether to render the search input (default: true)
218
+ * @property {number} searchDebounce - Delay (ms) between the last keystroke and the search execution (default: 300)
219
+ * @property {number} perPage - Rows per page; `0` disables the pagination (every row in a single page, no
220
+ * navigation) (default: 25)
221
+ * @property {number} paginationDelta - Number of page buttons shown on each side of the current page (default: 2)
222
+ * @property {boolean} serverSide - Server-side mode: pagination, sorting and search are delegated to the server.
223
+ * Every change triggers a new request to `jsonUrl` (see `serverParams`) and the JSON is expected to hold the
224
+ * rows of the requested page only, plus `totRecField` and `filteredRecField`. Requires `jsonUrl`; `tfoot` is
225
+ * not available in this mode (default: false)
226
+ * @property {ServerParams} serverParams - Server-side mode only: names of the query string parameters (see `ServerParams`)
227
+ * @property {SortDef|null} initialSort - Sort applied on load, e.g. `{ key: 'name', dir: 'asc' }` (default: null)
228
+ * @property {boolean} tfoot - Whether to render the `<tfoot>` (see `ColDefinition.tfootRender`); ignored in
229
+ * server-side mode (default: false)
230
+ * @property {boolean} updateFooterOnPageChange - When true, `tfootRender` receives the rows of the current page
231
+ * only (page subtotals) and the footer is updated on every page change; when false it receives the whole
232
+ * filtered set and is updated on filter changes only (default: false)
233
+ * @property {string|InfoTextFn|null} infoText - Info area content: a mustache-like string (placeholders
234
+ * `{start}`, `{end}`, `{totRec}`, `{filteredRec}`, `{page}`, `{totPages}`) or a function
235
+ * `(start, end, totRec, filteredRec, page, totPages) => content`. `null` uses `labels.info` (default: null)
236
+ * @property {TemplateItem[]|((parts: Object<string, DomBuilderItem>, params: JsonTableParams) => TemplateItem[])} template -
237
+ * Layout of the main wrapper content, as a domBuilder array where `{ slot: 'name' }` items are replaced with the
238
+ * built-in parts `infoSection` (info text + search), `resultInfo`, `search`, `table`, `caption`, `pagination`.
239
+ * `caption` and `pagination` are included in the `table` part (footer bar) unless placed explicitly.
240
+ * A function receiving the parts and returning the array is accepted too (`init()` only)
241
+ * (default: [{ slot: 'infoSection' }, { slot: 'table' }])
242
+ * @property {string} locale - Locale used to format numbers and dates, and to compare strings when sorting (default: 'it-IT')
243
+ * @property {string} currency - ISO 4217 code used by the `currency` data type (default: 'EUR')
244
+ * @property {Intl.DateTimeFormatOptions} datesLocaleOpts - Options for the date part of `date`/`datetime`
245
+ * (default: { year: 'numeric', month: 'short', day: 'numeric' })
246
+ * @property {Intl.DateTimeFormatOptions} timesLocaleOpts - Options for the time part of `datetime`
247
+ * (default: { hour12: false, hour: '2-digit', minute: '2-digit' })
248
+ * @property {Intl.NumberFormatOptions} numbersLocaleOpts - Options for the `num` type (default: { maximumFractionDigits: 2 })
249
+ * @property {Intl.NumberFormatOptions} currPercLocaleOpts - Options for the `currency`/`euro`/`perc`/`percDecimal`
250
+ * types (default: { minimumFractionDigits: 2, maximumFractionDigits: 2 })
251
+ * @property {string|null} renderNullAs - Content shown for `null`/`undefined` values (default: '—')
252
+ * @property {string|null} renderZeroAs - When not null, content shown for numeric values equal to zero (default: null)
253
+ * @property {string|null} renderNaNAs - Content shown by numeric types for non-numeric values (default: '—')
254
+ * @property {IconDef} boolTrueIcon - Icon of `true` values in `bool` columns (default: minimo `check-bold` icon)
255
+ * @property {IconDef} boolFalseIcon - Icon of `false` values in `bool` columns (default: minimo `x-bold` icon)
256
+ * @property {IconDef} sortAscArrowIcon - Sort button icon, ascending sort active (default: minimo `arrow-up` icon)
257
+ * @property {IconDef} sortDescArrowIcon - Sort button icon, descending sort active (default: minimo `arrow-down` icon)
258
+ * @property {IconDef} sortNoneArrowIcon - Sort button icon, no sort active (default: minimo `arrows-down-up` icon)
259
+ * @property {IconDef} paginationPrevIcon - Previous page button icon (default: minimo `caret-left` icon)
260
+ * @property {IconDef} paginationNextIcon - Next page button icon (default: minimo `caret-right` icon)
261
+ * @property {((tr: HTMLTableRowElement, row: Object, params: JsonTableParams) => void)|null} trCallback - Callback
262
+ * invoked after the rendering of every body row (default: null)
263
+ * @property {string|null} tableId - `id` attribute of the `<table>` element (default: null)
264
+ * @property {JsonTableClasses} classes - Consumer class names (see `JsonTableClasses`)
265
+ * @property {JsonTableLabels} labels - Texts (see `JsonTableLabels`)
46
266
  */
47
267
 
268
+ /**
269
+ * Names of the object params whose value is shallow-merged across the sources
270
+ * (built-in default ← `setDefaults()` ← HTML attribute ← `init()`) instead of being replaced.
271
+ * @type {ReadonlyArray<keyof JsonTableParams>}
272
+ */
273
+ export const mergedParams = ['classes', 'labels', 'dataTypes', 'serverParams'];
274
+
48
275
  /**
49
276
  * Built-in defaults.
50
277
  *
51
- * @type {Required<JsonTableParams>}
278
+ * @type {JsonTableParams}
52
279
  *
53
280
  * @example
54
281
  * {
55
282
  * debug: false,
56
283
  * jsonUrl: null,
57
284
  * jsonDataField: 'data',
285
+ * totRecField: 'totRec',
286
+ * filteredRecField: 'filteredRec',
58
287
  * data: null,
288
+ * cols: [], // required
289
+ * dataTypes: {},
59
290
  * caption: null,
60
291
  * 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',
292
+ * searchDebounce: 300,
293
+ * perPage: 25, // 0 = no pagination
294
+ * paginationDelta: 2,
295
+ * serverSide: false,
296
+ * serverParams: { page: 'page', start: 'start', perPage: 'perPage', sort: 'sort', dir: 'dir', search: 'search' },
297
+ * initialSort: null, // e.g. { key: 'name', dir: 'asc' }
298
+ * tfoot: false,
299
+ * updateFooterOnPageChange: false,
300
+ * infoText: null, // → labels.info
301
+ * template: [{ slot: 'infoSection' }, { slot: 'table' }],
302
+ * locale: 'it-IT',
303
+ * currency: 'EUR',
304
+ * datesLocaleOpts: { year: 'numeric', month: 'short', day: 'numeric' },
305
+ * timesLocaleOpts: { hour12: false, hour: '2-digit', minute: '2-digit' },
306
+ * numbersLocaleOpts: { maximumFractionDigits: 2 },
307
+ * currPercLocaleOpts: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
308
+ * renderNullAs: '—',
309
+ * renderZeroAs: null,
310
+ * renderNaNAs: '—',
311
+ * boolTrueIcon: '<svg …>', // minimo check-bold
312
+ * boolFalseIcon: '<svg …>', // minimo x-bold
313
+ * sortAscArrowIcon: '<svg …>', // minimo arrow-up
314
+ * sortDescArrowIcon: '<svg …>', // minimo arrow-down
315
+ * sortNoneArrowIcon: '<svg …>', // minimo arrows-down-up
316
+ * paginationPrevIcon: '<svg …>', // minimo caret-left
317
+ * paginationNextIcon: '<svg …>', // minimo caret-right
318
+ * trCallback: null,
65
319
  * tableId: null,
66
- * tableWrapperClass: 'table-responsive',
67
- * tableClass: 'table table-bordered',
68
- * mainWrapperExtraClass: null,
69
- * outerInfoExtraClass: null,
70
- * infoExtraClass: null,
71
- * infoText: (shown, total) => '…'
320
+ * classes: { … }, // see JsonTableClasses
321
+ * labels: { … } // see JsonTableLabels
72
322
  * }
73
323
  */
74
324
  export const defaults = {
@@ -76,30 +326,99 @@ export const defaults = {
76
326
 
77
327
  jsonUrl: null,
78
328
  jsonDataField: 'data',
329
+ totRecField: 'totRec',
330
+ filteredRecField: 'filteredRec',
79
331
  data: null,
80
332
 
81
- caption: null,
333
+ cols: [],
334
+ dataTypes: {},
82
335
 
336
+ caption: null,
83
337
  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',
338
+ searchDebounce: 300,
339
+
340
+ perPage: 25,
341
+ paginationDelta: 2,
342
+ serverSide: false,
343
+ serverParams: {
344
+ page: 'page',
345
+ start: 'start',
346
+ perPage: 'perPage',
347
+ sort: 'sort',
348
+ dir: 'dir',
349
+ search: 'search'
350
+ },
351
+ initialSort: null,
352
+
353
+ tfoot: false,
354
+ updateFooterOnPageChange: false,
355
+ infoText: null,
356
+
357
+ template: [{ slot: 'infoSection' }, { slot: 'table' }],
358
+
359
+ locale: 'it-IT',
360
+ currency: 'EUR',
361
+ datesLocaleOpts: { year: 'numeric', month: 'short', day: 'numeric' },
362
+ timesLocaleOpts: { hour12: false, hour: '2-digit', minute: '2-digit' },
363
+ numbersLocaleOpts: { maximumFractionDigits: 2 },
364
+ currPercLocaleOpts: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
365
+
366
+ renderNullAs: '—',
367
+ renderZeroAs: null,
368
+ renderNaNAs: '—',
369
+
370
+ boolTrueIcon,
371
+ boolFalseIcon,
372
+ sortAscArrowIcon,
373
+ sortDescArrowIcon,
374
+ sortNoneArrowIcon,
375
+ paginationPrevIcon,
376
+ paginationNextIcon,
377
+
378
+ trCallback: null,
88
379
 
89
380
  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>`;
381
+
382
+ classes: {
383
+ wrapper: null,
384
+ infoOuter: null,
385
+ info: null,
386
+ resultInfo: null,
387
+ search: null,
388
+ searchInput: 'form-control form-control-sm',
389
+ tableWrapper: 'table-responsive',
390
+ table: 'table table-bordered',
391
+ tableFooter: null,
392
+ caption: null,
393
+ pagination: null,
394
+ paginationBtn: 'btn-reset',
395
+ sortBtn: 'btn-reset',
396
+ empty: null,
397
+ textStart: null,
398
+ textCenter: 'text-center',
399
+ textEnd: 'text-end',
400
+ nowrap: 'text-nowrap',
401
+ numeric: 'text-numeric',
402
+ boolCell: null,
403
+ boolTrue: null,
404
+ boolFalse: null
405
+ },
406
+
407
+ labels: {
408
+ loading: 'Caricamento dati…',
409
+ searchPlaceholder: 'Cerca...',
410
+ searchTitle: 'Cerca nella tabella',
411
+ searchAriaLabel: 'Filtra risultati',
412
+ info: 'Stai visualizzando le righe da {start} a {end}, su un totale di {filteredRec} record trovati',
413
+ noRows: 'Nessun record trovato',
414
+ noResults: 'Nessun risultato per la ricerca',
415
+ sortAsc: 'Ordina questa colonna in senso ascendente (A → Z)',
416
+ sortDesc: 'Ordina questa colonna in senso discendente (Z → A)',
417
+ sortNone: 'Rimuovi l’ordinamento a questa colonna',
418
+ paginationAriaLabel: 'Navigazione pagine',
419
+ prevPage: 'Pagina precedente',
420
+ nextPage: 'Pagina successiva',
421
+ pageTitle: 'Vai a pagina {page}',
422
+ currentPage: 'Pagina {page}, corrente'
104
423
  }
105
424
  };
@@ -1,43 +1,129 @@
1
1
  /*! minimo - json-table: data acquisition */
2
2
 
3
3
  /** @typedef {import('./defaults.js').JsonTableParams} JsonTableParams */
4
+ /** @typedef {import('./defaults.js').SortDef} SortDef */
5
+
6
+ /**
7
+ * Result of `getData()`.
8
+ * @typedef {Object} DataResult
9
+ * @property {Array<Object>} rows - The rows array (the requested page only, in server-side mode)
10
+ * @property {number} totRec - Total number of records: the numeric value of `params.totRecField` in the
11
+ * fetched JSON, `rows.length` otherwise
12
+ * @property {number} filteredRec - Number of records matching the current search: the numeric value of
13
+ * `params.filteredRecField` in the fetched JSON (server-side mode), `totRec` otherwise
14
+ */
15
+
16
+ /**
17
+ * State of the request in server-side mode.
18
+ * @typedef {Object} ServerRequest
19
+ * @property {number} page - Requested page (1-based)
20
+ * @property {number} perPage - Rows per page
21
+ * @property {SortDef|null} sort - Active sort, or null
22
+ * @property {string} search - Search term ('' = none)
23
+ */
24
+
25
+ /**
26
+ * Reads a numeric field of the fetched JSON; `undefined` when missing or not numeric.
27
+ * @param {*} json - Fetched JSON
28
+ * @param {string|null|undefined} field - Key to read
29
+ * @returns {number|undefined}
30
+ */
31
+ function numericField(json, field) {
32
+ if (!field || json == null || typeof json !== 'object') {
33
+ return undefined;
34
+ }
35
+ const raw = json[field];
36
+ const num = Number(raw);
37
+ return (raw != null && raw !== '' && Number.isFinite(num)) ? num : undefined;
38
+ }
39
+
40
+
41
+ /**
42
+ * Builds the URL of a server-side request: `params.jsonUrl` plus the query string parameters
43
+ * named in `params.serverParams` (parameters set to `null` are omitted; `sort`/`dir` are sent
44
+ * only when a sort is active, `search` only when not empty). Existing query string parameters
45
+ * of `jsonUrl` are preserved.
46
+ *
47
+ * @param {JsonTableParams} params - Resolved params
48
+ * @param {ServerRequest} request - Request state
49
+ * @returns {string}
50
+ *
51
+ * @example
52
+ * buildServerUrl(
53
+ * { ...params, jsonUrl: '/api/rows.json?year=2025' },
54
+ * { page: 3, perPage: 25, sort: { key: 'name', dir: 'desc' }, search: 'mar' }
55
+ * );
56
+ * // → '/api/rows.json?year=2025&page=3&start=50&perPage=25&sort=name&dir=desc&search=mar'
57
+ */
58
+ export function buildServerUrl(params, request) {
59
+
60
+ const url = new URL(String(params.jsonUrl), document.baseURI);
61
+ const names = params.serverParams ?? {};
62
+
63
+ /** @type {(name: string|null|undefined, value: string|number|null|undefined) => void} */
64
+ const set = (name, value) => {
65
+ if (name && value != null && value !== '') {
66
+ url.searchParams.set(name, String(value));
67
+ }
68
+ };
69
+
70
+ set(names.page, request.page);
71
+ set(names.start, (request.page - 1) * request.perPage);
72
+ set(names.perPage, request.perPage);
73
+ set(names.sort, request.sort?.key);
74
+ set(names.dir, request.sort ? request.sort.dir : null);
75
+ set(names.search, request.search);
76
+
77
+ return url.toString();
78
+ }
79
+
4
80
 
5
81
  /**
6
82
  * Retrieves the rows array from the configured source.
7
83
  *
8
84
  * `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).
85
+ * read from the `jsonDataField` key (`null`/`''` = the JSON root is the array itself); when the
86
+ * JSON also holds a numeric `totRecField` key, it is returned as `totRec`, otherwise `totRec` is
87
+ * the rows array length. In server-side mode (`request` given) the URL carries the pagination /
88
+ * sort / search parameters (see `buildServerUrl`) and `filteredRecField` is read too.
10
89
  * Returns `null` when no source is configured.
11
90
  *
12
91
  * @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
92
+ * @param {ServerRequest|null} [request=null] - Server-side request state; `null` for a plain request (default: null)
93
+ * @returns {Promise<DataResult|null>} Rows and totals, or `null` when neither `data` nor `jsonUrl` is set
14
94
  * @throws {Error} When `data` is not an array, on HTTP/network errors, or when the fetched JSON
15
95
  * does not contain an array at `jsonDataField`
16
96
  *
17
97
  * @example
18
98
  * // inline data (jsonUrl ignored)
19
- * await getData({ ...params, data: [{ id: 1 }], jsonUrl: '/ignored.json' }); // → [{ id: 1 }]
99
+ * await getData({ ...params, data: [{ id: 1 }], jsonUrl: '/ignored.json' });
100
+ * // → { rows: [{ id: 1 }], totRec: 1, filteredRec: 1 }
20
101
  *
21
- * // fetched JSON: { data: [...] } (jsonDataField default: 'data')
102
+ * // fetched JSON: { data: [...], totRec: 1500 } (jsonDataField default: 'data', totRecField default: 'totRec')
22
103
  * await getData({ ...params, jsonUrl: '/api/rows.json' });
23
104
  *
24
105
  * // fetched JSON whose root is the array itself
25
106
  * await getData({ ...params, jsonUrl: '/api/rows.json', jsonDataField: null });
107
+ *
108
+ * // server-side: GET /api/rows.json?page=2&start=25&perPage=25&search=mar
109
+ * // expected JSON: { data: [...25 rows], totRec: 1500, filteredRec: 40 }
110
+ * await getData({ ...params, jsonUrl: '/api/rows.json', serverSide: true }, { page: 2, perPage: 25, sort: null, search: 'mar' });
26
111
  */
27
- export async function getData(params) {
112
+ export async function getData(params, request = null) {
28
113
 
29
114
  if (params.data != null) {
30
115
  if (!Array.isArray(params.data)) {
31
116
  throw new Error('[json-table] `data` deve essere un array di oggetti');
32
117
  }
33
- return params.data;
118
+ return { rows: params.data, totRec: params.data.length, filteredRec: params.data.length };
34
119
  }
35
120
 
36
121
  if (params.jsonUrl) {
37
- const response = await fetch(params.jsonUrl);
122
+ const url = request ? buildServerUrl(params, request) : params.jsonUrl;
123
+ const response = await fetch(url);
38
124
 
39
125
  if (!response.ok) {
40
- throw new Error(`[json-table] ${params.jsonUrl}: risposta HTTP ${response.status}`);
126
+ throw new Error(`[json-table] ${url}: risposta HTTP ${response.status}`);
41
127
  }
42
128
 
43
129
  const json = await response.json();
@@ -46,11 +132,20 @@ export async function getData(params) {
46
132
 
47
133
  if (!Array.isArray(rows)) {
48
134
  throw new Error(
49
- `[json-table] ${params.jsonUrl}: ` +
135
+ `[json-table] ${url}: ` +
50
136
  (field ? `il campo \`${field}\` non contiene un array` : 'la risposta non è un array')
51
137
  );
52
138
  }
53
- return rows;
139
+
140
+ const hasFields = field != null && field !== '';
141
+ const totRec = hasFields ? numericField(json, params.totRecField) : undefined;
142
+ const filteredRec = hasFields ? numericField(json, params.filteredRecField) : undefined;
143
+
144
+ return {
145
+ rows,
146
+ totRec: totRec ?? rows.length,
147
+ filteredRec: filteredRec ?? totRec ?? rows.length
148
+ };
54
149
  }
55
150
 
56
151
  return null;