wuic-framework-lib 1.0.19 → 1.1.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/fesm2022/{wuic-framework-lib-chart-list.component-C0OYuZoM.mjs → wuic-framework-lib-chart-list.component-C9_EDkGu.mjs} +73 -13
- package/fesm2022/{wuic-framework-lib-code-editor.component-B8OQ3Z0k.mjs → wuic-framework-lib-code-editor.component-D_p7LGSQ.mjs} +6 -6
- package/fesm2022/{wuic-framework-lib-designer.component-DsWgWtFj.mjs → wuic-framework-lib-designer.component-Dx6RraAn.mjs} +83 -50
- package/fesm2022/{wuic-framework-lib-first-run-wizard.component-wR_LrvPK.mjs → wuic-framework-lib-first-run-wizard.component-Cp8IKDb9.mjs} +9 -9
- package/fesm2022/{wuic-framework-lib-framework-docs.component-eyzu9tdV.mjs → wuic-framework-lib-framework-docs.component-CxckBsIX.mjs} +1205 -116
- package/fesm2022/{wuic-framework-lib-pivot-builder.component-BFdZW5Ch.mjs → wuic-framework-lib-pivot-builder.component-C-ceJi48.mjs} +269 -21
- package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-B583WbYe.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-DPZCgxOB.mjs} +6 -6
- package/fesm2022/{wuic-framework-lib-report-designer.component-HJUpslLX.mjs → wuic-framework-lib-report-designer.component-3-hUYft8.mjs} +5 -5
- package/fesm2022/{wuic-framework-lib-report-viewer.component-El32zpzi.mjs → wuic-framework-lib-report-viewer.component-D4QD_wPl.mjs} +5 -5
- package/fesm2022/{wuic-framework-lib-scheduler-list.component-E8s_e533.mjs → wuic-framework-lib-scheduler-list.component-C0nY8Wd6.mjs} +28 -5
- package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-CDn0qi_C.mjs → wuic-framework-lib-spreadsheet-list-sf.component-C9sWgq61.mjs} +449 -10
- package/fesm2022/{wuic-framework-lib-workflow-designer.component-hL7oKqYO.mjs → wuic-framework-lib-workflow-designer.component-8mv_Rbz9.mjs} +10 -10
- package/fesm2022/{wuic-framework-lib-wuic-framework-lib-ABS_G7lO.mjs → wuic-framework-lib-wuic-framework-lib-DJZAYFw8.mjs} +5882 -3545
- package/fesm2022/wuic-framework-lib.mjs +1 -1
- package/package.json +1 -1
- package/types/wuic-framework-lib.d.ts +1905 -998
|
@@ -10,18 +10,18 @@ import * as i13 from 'primeng/inputtext';
|
|
|
10
10
|
import { InputTextModule } from 'primeng/inputtext';
|
|
11
11
|
import * as i10 from 'primeng/tag';
|
|
12
12
|
import { TagModule } from 'primeng/tag';
|
|
13
|
-
import * as
|
|
13
|
+
import * as i6 from 'primeng/tooltip';
|
|
14
14
|
import { TooltipModule } from 'primeng/tooltip';
|
|
15
15
|
import * as i1$3 from 'primeng/image';
|
|
16
16
|
import { ImageModule } from 'primeng/image';
|
|
17
|
-
import * as
|
|
17
|
+
import * as i8 from '@ngx-translate/core';
|
|
18
18
|
import { TranslateService, TranslateModule } from '@ngx-translate/core';
|
|
19
|
-
import { r as getThemeOptions, T as TranslationManagerService, U as UserInfoService } from './wuic-framework-lib-wuic-framework-lib-
|
|
19
|
+
import { r as getThemeOptions, T as TranslationManagerService, U as UserInfoService } from './wuic-framework-lib-wuic-framework-lib-DJZAYFw8.mjs';
|
|
20
20
|
import * as i1 from '@angular/router';
|
|
21
21
|
|
|
22
22
|
const frameworkDocsContent = {
|
|
23
23
|
"version": "1.0.0",
|
|
24
|
-
"generatedAt": "2026-05-
|
|
24
|
+
"generatedAt": "2026-05-13T17:21:00.051Z",
|
|
25
25
|
"groups": [
|
|
26
26
|
{
|
|
27
27
|
"id": "primi-passi",
|
|
@@ -915,6 +915,22 @@ const frameworkDocsContent = {
|
|
|
915
915
|
"integration"
|
|
916
916
|
],
|
|
917
917
|
"tier": "pro"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
"slug": "multi-tenant",
|
|
921
|
+
"title": "Multi-tenant",
|
|
922
|
+
"titles": {
|
|
923
|
+
"en-US": "Multi-tenant",
|
|
924
|
+
"fr-FR": "Multi-tenant",
|
|
925
|
+
"es-ES": "Multi-tenant",
|
|
926
|
+
"de-DE": "Mandantenfaehigkeit"
|
|
927
|
+
},
|
|
928
|
+
"icon": "pi-building",
|
|
929
|
+
"status": "stable",
|
|
930
|
+
"audience": [
|
|
931
|
+
"developer",
|
|
932
|
+
"integration"
|
|
933
|
+
]
|
|
918
934
|
}
|
|
919
935
|
]
|
|
920
936
|
},
|
|
@@ -1791,26 +1807,34 @@ const frameworkDocsContent = {
|
|
|
1791
1807
|
},
|
|
1792
1808
|
{
|
|
1793
1809
|
"id": "sec_6",
|
|
1794
|
-
"title": "md_props_bag:
|
|
1810
|
+
"title": "md_props_bag: toolbar"
|
|
1795
1811
|
},
|
|
1796
1812
|
{
|
|
1797
1813
|
"id": "sec_7",
|
|
1798
|
-
"title": "
|
|
1814
|
+
"title": "md_props_bag: archetypes.list"
|
|
1799
1815
|
},
|
|
1800
1816
|
{
|
|
1801
1817
|
"id": "sec_8",
|
|
1802
|
-
"title": "
|
|
1818
|
+
"title": "Paging: md_pagesize e md_page_size_choice"
|
|
1803
1819
|
},
|
|
1804
1820
|
{
|
|
1805
1821
|
"id": "sec_9",
|
|
1822
|
+
"title": "Forzatura virtualizzazione su page size alto"
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
"id": "sec_10",
|
|
1806
1826
|
"title": "Eventi e subscriptions (host)"
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
"id": "sec_11",
|
|
1830
|
+
"title": "`rowCustomSelect` — signature reale (gotcha)"
|
|
1807
1831
|
}
|
|
1808
1832
|
],
|
|
1809
1833
|
"sections": [
|
|
1810
1834
|
{
|
|
1811
1835
|
"id": "overview",
|
|
1812
1836
|
"title": "Overview",
|
|
1813
|
-
"html": "<h1>List Grid</h1><p>Componente principale per liste tabellari con filtri, sorting, paging server-side e azioni riga.</p><h2>Use cases</h2><ul><li>CRUD tabellari enterprise.</li><li>Report operativi con filtri multi-colonna.</li><li>Dataset molto grandi con <code>cursorMode</code>.</li></ul><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__grid__01.png</code>: modalita <code>inline cell edit</code> in list-grid.</li><li><code>manual__grid__02.png</code>: modalita <code>inline edit</code> (row-level) in list-grid.</li></ul><h2>Metadati inline editing</h2><p>Configurazione nel <code>md_props_bag</code> della tabella metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - abilita l'editing inline della riga in list-grid (celle editabili in contesto riga, senza apertura popup).</p><p> - utile quando vuoi mantenere la UX di tabella con editing rapido per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - abilita l'editing inline "cell-by-cell" (focus sulla singola cella).</p><p> - nota: nelle configurazioni legacy puo comparire come <code>md_inline_cell_editing</code>; il comportamento runtime e lo stesso.</p><p> - <strong>promozione runtime</strong>: quando <code>md_inline_cell_edit</code> e <code>true</code>, il componente forza</p><p> a runtime anche <code>md_inline_edit = true</code> indipendentemente dal valore in DB.</p><p> Entrambe le UX hanno bisogno della action column visibile, quindi sono mutualmente</p><p> non-esclusive a livello di rendering.</p><ul><li><code>md_batch_save</code></li></ul><p> - abilita il salvataggio batch delle modifiche pendenti (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - se usato insieme a inline-cell, le modifiche restano pending fino al salvataggio esplicito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> ha effetto <strong>solo</strong> se <code>md_inline_cell_edit</code> e <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> il flag viene ignorato a runtime e i bottoni</p><p> "Salva modifiche / Annulla modifiche" non vengono renderizzati.</p><h3>Combinazioni valide</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Risultato runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>qualsiasi</td><td>Row-level inline edit con pencil. <code>md_batch_save</code> ignorato.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzato a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Salva / Annulla modifiche</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>qualsiasi</td><td>Nessun inline editing. <code>md_batch_save</code> ignorato.</td></tr></tbody></table><p>Esempio row-level inline edit:</p><p>Snippet 1:</p><p>Esempio cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - mostra dialog di progress con percentuale realtime;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla task</code>, <code>Interrompi e scarica parziale</code>;</p><p> - in background crea una notifica con progress; click riapre il dialog.</p><ul><li>Import XLS/XLSX (se <code>md_importable = true</code>):</li></ul><p> - dialog di progress dopo la conferma;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla import (rollback)</code>, <code>Stop e commit parziale</code>;</p><p> - a fine import crea una notifica di riepilogo che porta alla route.</p><h2>Config metadata</h2><p>Impostazioni chiave in <code>md_props_bag</code> e metadati colonna.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>Il componente legge <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>valori accettati: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>default runtime: <code>true</code> quando assente o vuoto</li><li>effetto: distribuisce le larghezze colonna in percentuale (anziche px) quando non ci sono larghezze utente persistite.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> oppure <code>object</code></li><li>valori accettati:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object con <code>enabled</code> (opzionale) e <code>itemSize</code> (opzionale)</li><li>default runtime: disabilitato (<code>false</code>) quando assente</li><li><code>enabled</code>:</li><li>default: <code>true</code> se il nodo <code>virtualize</code> e object senza <code>enabled</code>, altrimenti parser tollerante</li><li>effetto: abilita <code>virtualScroll</code> su <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effetto: imposta <code>virtualScrollItemSize</code> (altezza riga virtuale in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>default runtime: <code>false</code></li><li>effetto:</li><li>quando <code>true</code>, la griglia nasconde le icone filtro colonna (<code>p-columnFilter</code>) e usa la <code>wuic-filter-bar</code> sopra la tabella (a livello <code>data-repeater</code>/<code>bounded-repeater</code>) per applicare i filtri.</li></ul><p>Nota operativa:</p><ul><li>il suggest <code>md_props_bag</code> nel metadata editor espone anche il nodo checkabile <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize e md_page_size_choice</h2><p>La griglia usa <code>md_pagesize</code> come dimensione pagina di default e <code>md_page_size_choice</code> come elenco valori selezionabili nel paginator.</p><p>Regole runtime:</p><ul><li>se <code>md_pagesize</code> e maggiore del valore massimo presente in <code>md_page_size_choice</code>, il framework aggiunge automaticamente <code>md_pagesize</code> alla lista opzioni;</li><li>la lista viene normalizzata (numeri validi, deduplica, ordinamento crescente).</li></ul><p>Esempio:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>risultato runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzatura virtualizzazione su page size alto</h2><p>Quando l'utente seleziona nel paginator un valore <code>pageSize >= 1000</code>:</p><ul><li>la virtualizzazione viene forzata automaticamente anche se <code>md_props_bag.archetypes.list.virtualize</code> e assente o disabilitato;</li><li><code>virtualScrollItemSize</code> viene forzato al valore predefinito <code>44</code>.</li></ul><p>Questo comportamento protegge la resa della tabella su pagine molto grandi.</p><h2>Eventi e subscriptions (host)</h2><p><code>wuic-list-grid</code> espone eventi runtime utili per intercettare ciclo render e callback p-table lato progetto host.</p><p>Eventi disponibili:</p><ul><li><code>onAfterRender</code>: emesso a fine binding dati della griglia (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emesso prima del rendering logico della singola riga; supporta cancel tramite <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emesso dopo il rendering logico della singola riga.</li><li><code>onPaging</code>: emesso sugli eventi paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emesso sugli eventi sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emesso sugli eventi filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emesso su cambio selezione righe.</li><li><code>onPTableRowExpand</code>: emesso su expand riga.</li><li><code>onPTableRowCollapse</code>: emesso su collapse riga.</li><li><code>onPTableColumnResize</code>: emesso su resize colonna.</li><li><code>onPTableColumnReorder</code>: emesso su reorder colonne.</li></ul><h3>Esempio 1: binding diretto nel template</h3><p>Snippet 5:</p><h3>Esempio 2: subscribe via ViewChild</h3><p>Snippet 6:</p>",
|
|
1837
|
+
"html": "<h1>List Grid</h1><p>Componente principale per liste tabellari con filtri, sorting, paging server-side e azioni riga.</p><h2>Use cases</h2><ul><li>CRUD tabellari enterprise.</li><li>Report operativi con filtri multi-colonna.</li><li>Dataset molto grandi con <code>cursorMode</code>.</li></ul><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__grid__01.png</code>: modalita <code>inline cell edit</code> in list-grid.</li><li><code>manual__grid__02.png</code>: modalita <code>inline edit</code> (row-level) in list-grid.</li></ul><h2>Metadati inline editing</h2><p>Configurazione nel <code>md_props_bag</code> della tabella metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - abilita l'editing inline della riga in list-grid (celle editabili in contesto riga, senza apertura popup).</p><p> - utile quando vuoi mantenere la UX di tabella con editing rapido per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - abilita l'editing inline "cell-by-cell" (focus sulla singola cella).</p><p> - nota: nelle configurazioni legacy puo comparire come <code>md_inline_cell_editing</code>; il comportamento runtime e lo stesso.</p><p> - <strong>promozione runtime</strong>: quando <code>md_inline_cell_edit</code> e <code>true</code>, il componente forza</p><p> a runtime anche <code>md_inline_edit = true</code> indipendentemente dal valore in DB.</p><p> Entrambe le UX hanno bisogno della action column visibile, quindi sono mutualmente</p><p> non-esclusive a livello di rendering.</p><ul><li><code>md_batch_save</code></li></ul><p> - abilita il salvataggio batch delle modifiche pendenti (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - se usato insieme a inline-cell, le modifiche restano pending fino al salvataggio esplicito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> ha effetto <strong>solo</strong> se <code>md_inline_cell_edit</code> e <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> il flag viene ignorato a runtime e i bottoni</p><p> "Salva modifiche / Annulla modifiche" non vengono renderizzati.</p><h3>Combinazioni valide</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Risultato runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>qualsiasi</td><td>Row-level inline edit con pencil. <code>md_batch_save</code> ignorato.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzato a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Salva / Annulla modifiche</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>qualsiasi</td><td>Nessun inline editing. <code>md_batch_save</code> ignorato.</td></tr></tbody></table><p>Esempio row-level inline edit:</p><p>Snippet 1:</p><p>Esempio cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - mostra dialog di progress con percentuale realtime;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla task</code>, <code>Interrompi e scarica parziale</code>;</p><p> - in background crea una notifica con progress; click riapre il dialog.</p><ul><li>Import XLS/XLSX (se <code>md_importable = true</code>):</li></ul><p> - dialog di progress dopo la conferma;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla import (rollback)</code>, <code>Stop e commit parziale</code>;</p><p> - a fine import crea una notifica di riepilogo che porta alla route.</p><h2>Config metadata</h2><p>Impostazioni chiave in <code>md_props_bag</code> e metadati colonna.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Flag opt-in che nascondono blocchi della toolbar della list-grid (<code>caption-bar</code>). Tutti sotto <code>md_props_bag.toolbar.*</code> (parsato in runtime come <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): nasconde nella <strong>caption-right</strong> il bottone "Gestione stato" (icona bookmark) + la <code><select></code> degli stati salvati. Utile per route hardcoded / demo dove il saved-state feature (persistenza per <code>user_id</code> + route via <code>MetaService</code>) non ha senso — es. Pattern 3 puro OData senza route metadata registrata.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): nasconde nella <strong>caption-left</strong> i pulsanti "Salva modifiche" (<code>pi-save</code>) + "Annulla modifiche" (<code>pi-times</code>) + l'indicatore conteggio changes (<code>grid-changes-indicator</code>, badge pencil + count). Generati dal framework quando <code>md_inline_cell_editing</code> + <code>md_batch_save</code> sono attivi. Pensato per <strong>nested grid in parametric-dialog</strong>: il save/cancel del padre persiste master + righe in un colpo via batch save framework, e i pulsanti duplicati sulla nested grid confondono l'UX.</li></ul><p>Esempio nested rows in custom edit-form (runtime patch):</p><p>Snippet 5:</p><p>Tipo TS in <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (proprieta <code>extraProps.toolbar</code>). I flag NON disabilitano la logica <code>md_batch_save</code> sottostante (changes restano tracciate); rimuovono solo l'UI toolbar — il save effettivo passa dal flusso del padre.</p><h2>md_props_bag: archetypes.list</h2><p>Il componente legge <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>valori accettati: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>default runtime: <code>true</code> quando assente o vuoto</li><li>effetto: distribuisce le larghezze colonna in percentuale (anziche px) quando non ci sono larghezze utente persistite.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> oppure <code>object</code></li><li>valori accettati:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object con <code>enabled</code> (opzionale) e <code>itemSize</code> (opzionale)</li><li>default runtime: disabilitato (<code>false</code>) quando assente</li><li><code>enabled</code>:</li><li>default: <code>true</code> se il nodo <code>virtualize</code> e object senza <code>enabled</code>, altrimenti parser tollerante</li><li>effetto: abilita <code>virtualScroll</code> su <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effetto: imposta <code>virtualScrollItemSize</code> (altezza riga virtuale in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>default runtime: <code>false</code></li><li>effetto:</li><li>quando <code>true</code>, la griglia nasconde le icone filtro colonna (<code>p-columnFilter</code>) e usa la <code>wuic-filter-bar</code> sopra la tabella (a livello <code>data-repeater</code>/<code>bounded-repeater</code>) per applicare i filtri.</li></ul><p>Nota operativa:</p><ul><li>il suggest <code>md_props_bag</code> nel metadata editor espone anche il nodo checkabile <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize e md_page_size_choice</h2><p>La griglia usa <code>md_pagesize</code> come dimensione pagina di default e <code>md_page_size_choice</code> come elenco valori selezionabili nel paginator.</p><p>Regole runtime:</p><ul><li>se <code>md_pagesize</code> e maggiore del valore massimo presente in <code>md_page_size_choice</code>, il framework aggiunge automaticamente <code>md_pagesize</code> alla lista opzioni;</li><li>la lista viene normalizzata (numeri validi, deduplica, ordinamento crescente).</li></ul><p>Esempio:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>risultato runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzatura virtualizzazione su page size alto</h2><p>Quando l'utente seleziona nel paginator un valore <code>pageSize >= 1000</code>:</p><ul><li>la virtualizzazione viene forzata automaticamente anche se <code>md_props_bag.archetypes.list.virtualize</code> e assente o disabilitato;</li><li><code>virtualScrollItemSize</code> viene forzato al valore predefinito <code>44</code>.</li></ul><p>Questo comportamento protegge la resa della tabella su pagine molto grandi.</p><h2>Eventi e subscriptions (host)</h2><p><code>wuic-list-grid</code> espone eventi runtime utili per intercettare ciclo render e callback p-table lato progetto host.</p><p>Eventi disponibili:</p><ul><li><code>onAfterRender</code>: emesso a fine binding dati della griglia (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emesso prima del rendering logico della singola riga; supporta cancel tramite <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emesso dopo il rendering logico della singola riga.</li><li><code>onPaging</code>: emesso sugli eventi paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emesso sugli eventi sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emesso sugli eventi filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emesso su cambio selezione righe.</li><li><code>onPTableRowExpand</code>: emesso su expand riga.</li><li><code>onPTableRowCollapse</code>: emesso su collapse riga.</li><li><code>onPTableColumnResize</code>: emesso su resize colonna.</li><li><code>onPTableColumnReorder</code>: emesso su reorder colonne.</li></ul><h3>Esempio 1: binding diretto nel template</h3><p>Snippet 7:</p><h3>Esempio 2: subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — signature reale (gotcha)</h2><p><code><wuic-list-grid></code> accetta un input <code>[rowCustomSelect]</code> per intercettare la selezione di una riga (apertura dialog "scegli documento", master-detail, ecc.). La firma TypeScript dichiarata in <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> è:</p><p>Snippet 9:</p><p><strong>Pero' a runtime la callback viene invocata con argomenti invertiti</strong> — il framework chiama <code>rowCustomSelect($event, rowData, dt)</code> (vedi <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> e <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Sintomo se sbagli ordine: <code>rowData?.id</code> e' undefined → guard precoce nel handler → la callback ritorna senza side-effect e la dialog/azione non parte. Nessun errore in console.</p><p>Forma corretta (allineata all'invocazione runtime):</p><p>Snippet 10:</p><p>Esempio integrato nel template:</p><p>Snippet 11:</p><p>> Note: i test in <code>designer.component.spec.ts</code> (es. riga 512) chiamano la callback con <code>({currentTarget: rowCell}, {id: 42}, null)</code> confermando l'ordine <code>($event, rowData, dt)</code>. Se in futuro il framework dovesse uniformare la signature dichiarata, si aggiornera' anche questa pagina.</p>",
|
|
1814
1838
|
"codeSamples": [
|
|
1815
1839
|
{
|
|
1816
1840
|
"id": "code_1",
|
|
@@ -1834,19 +1858,49 @@ const frameworkDocsContent = {
|
|
|
1834
1858
|
"id": "code_4",
|
|
1835
1859
|
"title": "Snippet 4",
|
|
1836
1860
|
"language": "json",
|
|
1837
|
-
"code": "{\n \"
|
|
1861
|
+
"code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
|
|
1838
1862
|
},
|
|
1839
1863
|
{
|
|
1840
1864
|
"id": "code_5",
|
|
1841
1865
|
"title": "Snippet 5",
|
|
1842
|
-
"language": "
|
|
1843
|
-
"code": "
|
|
1866
|
+
"language": "ts",
|
|
1867
|
+
"code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
|
|
1844
1868
|
},
|
|
1845
1869
|
{
|
|
1846
1870
|
"id": "code_6",
|
|
1847
1871
|
"title": "Snippet 6",
|
|
1872
|
+
"language": "json",
|
|
1873
|
+
"code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
"id": "code_7",
|
|
1877
|
+
"title": "Snippet 7",
|
|
1878
|
+
"language": "html",
|
|
1879
|
+
"code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
"id": "code_8",
|
|
1883
|
+
"title": "Snippet 8",
|
|
1848
1884
|
"language": "ts",
|
|
1849
1885
|
"code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // esempio cancel riga\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
|
|
1886
|
+
},
|
|
1887
|
+
{
|
|
1888
|
+
"id": "code_9",
|
|
1889
|
+
"title": "Snippet 9",
|
|
1890
|
+
"language": "ts",
|
|
1891
|
+
"code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
"id": "code_10",
|
|
1895
|
+
"title": "Snippet 10",
|
|
1896
|
+
"language": "ts",
|
|
1897
|
+
"code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData e' il record dato dalla list-grid\n this.handleSelection(rowData);\n};"
|
|
1898
|
+
},
|
|
1899
|
+
{
|
|
1900
|
+
"id": "code_11",
|
|
1901
|
+
"title": "Snippet 11",
|
|
1902
|
+
"language": "html",
|
|
1903
|
+
"code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
|
|
1850
1904
|
}
|
|
1851
1905
|
]
|
|
1852
1906
|
}
|
|
@@ -2005,6 +2059,14 @@ const frameworkDocsContent = {
|
|
|
2005
2059
|
},
|
|
2006
2060
|
{
|
|
2007
2061
|
"id": "sec_6",
|
|
2062
|
+
"title": "Personalizzazione marker per-record (colore + immagine/SVG)"
|
|
2063
|
+
},
|
|
2064
|
+
{
|
|
2065
|
+
"id": "sec_7",
|
|
2066
|
+
"title": "Polyline overlay (tracking percorsi GPS)"
|
|
2067
|
+
},
|
|
2068
|
+
{
|
|
2069
|
+
"id": "sec_8",
|
|
2008
2070
|
"title": "Eventi e subscriptions (host)"
|
|
2009
2071
|
}
|
|
2010
2072
|
],
|
|
@@ -2012,29 +2074,59 @@ const frameworkDocsContent = {
|
|
|
2012
2074
|
{
|
|
2013
2075
|
"id": "overview",
|
|
2014
2076
|
"title": "Overview",
|
|
2015
|
-
"html": "<h1>Map List</h1><p>Visualizzazione geografica con marker, cluster, filtri su boundaries e template marker custom.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__map__01.png</code>: overview mappa route con toolbar, marker e clustering.</li><li><code>manual__map__02.png</code>: configurazione avanzata archetype map (center/zoom/campi/info/template).</li></ul><h2>Use cases</h2><ul><li>Geo-localizzazione asset.</li><li>Tracking eventi geolocalizzati.</li><li>Heatmap o aggregazioni territoriali.</li></ul><h2>Differenza comportamento: <code>point</code> vs <code>polygon</code></h2><p>La mappa adatta automaticamente rendering e interazioni in base ai campi metadata con <code>mc_ui_column_type</code>.</p><ul><li>Se e presente almeno una colonna <code>point</code>:</li></ul><p> - ogni record viene renderizzato come marker (<code>map-advanced-marker</code>);</p><p> - supporta clustering (<code>useClusterer</code>);</p><p> - supporta drag marker (quando abilitato dalla toolbar);</p><p> - click su marker apre <code>infoWindow</code> con <code>titleField</code>/<code>infoField</code> e template.</p><ul><li>Se e presente una colonna <code>polygon</code>:</li></ul><p> - ogni record viene renderizzato come poligono (<code>map-polygon</code>);</p><p> - non usa marker clusterer;</p><p> - il filtro boundaries puo usare anche la geometria poligonale;</p><p> - click su poligono apre la stessa <code>infoWindow</code> dei marker (posizionata sul punto cliccato o, in fallback, sul centro geometrico).</p><ul><li>Se sono presenti entrambi (<code>point</code> e <code>polygon</code>):</li></ul><p> - il componente puo mostrare entrambi i layer;</p><p> - la toolbar privilegia le azioni coerenti col tipo principale (drag marker su scenari marker, edit polygons su scenari polygon-only);</p><p> - il filtro <code>maparea</code> usa il primo campo geografico disponibile tra <code>point</code>/<code>polygon</code>.</p><h2>Note operative</h2><p>Assicurarsi che i campi coordinate siano correttamente mappati nei metadata colonna.</p><h2>md_props_bag: archetypes.map</h2><p>Snippet 1:</p><ul><li><code>advancedFilter</code>: quando <code>true</code> mostra la <code>wuic-filter-bar</code> per l'archetype <code>map</code> (a livello data-repeater).</li><li><code>zoom</code>: zoom iniziale.</li><li><code>center.lat</code>/<code>center.lng</code>: centro iniziale mappa; se mancanti viene usato il comportamento default del componente.</li><li><code>minZoom</code>/<code>maxZoom</code>: limiti zoom.</li><li><code>useCurrentLocation</code>: usa geolocalizzazione browser come centro.</li><li><code>useClusterer</code>: abilita clustering marker.</li><li><code>filterByBoundaries</code>: filtra i record visibili in base al bounding box corrente.</li><li><code>titleField</code>: campo metadata usato come titolo marker.</li><li><code>infoField</code>: campo metadata usato come info base marker.</li><li><code>customMarkerImageSrc</code>: URL marker fisso.</li><li><code>customMarkerImageSrcField</code>: campo metadata con URL marker per-record.</li><li><code>markerContentCallback</code>: callback JS per contenuto marker.</li><li><code>infoFunction</code>: callback JS per render info window. (Alternativo a itemTemplateString)</li><li><code>itemTemplateString</code>: template custom del contenuto. (Alternativo a infoFunction)</li></ul><h3>Contesto Template (<code>itemTemplateString</code>)</h3><p>Nel template custom sono disponibili questi input:</p><ul><li><code>record</code>: record corrente (in map archetype coincide con <code>rowData.record</code> quando presente).</li><li><code>rowData</code>: payload evento marker/poligono corrente.</li><li><code>metaInfo</code>: metadati tabella/colonne della datasource.</li><li><code>datasource</code>: riferimento al componente datasource associato.</li></ul><p>Contesto runtime usato dal framework:</p><p>Snippet 2:</p><h2>Eventi e subscriptions (host)</h2><p>Eventi disponibili su <code>wuic-map-list</code>:</p><ul><li><code>onMapClick</code>: click su mappa (<code>google-map mapClick</code>).</li><li><code>onMarkerClick</code>: click su marker.</li><li><code>onMarkerDragStart</code>: inizio drag marker.</li><li><code>onMarkerDragEnd</code>: fine drag marker (con posizione finale).</li><li><code>onPolygonClick</code>: click su poligono.</li><li><code>onPolygonShapeChangedEvent</code>: shape changed su poligono in edit mode.</li><li><code>onMapDataBound</code>: emesso quando il wrapper completa il binding dati dal datasource.</li></ul><p>Esempio template:</p><p>Snippet 3:</p><p>Esempio subscribe (ViewChild):</p><p>Snippet 4:</p>",
|
|
2077
|
+
"html": "<h1>Map List</h1><p>Visualizzazione geografica con marker, cluster, filtri su boundaries e template marker custom.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__map__01.png</code>: overview mappa route con toolbar, marker e clustering.</li><li><code>manual__map__02.png</code>: configurazione avanzata archetype map (center/zoom/campi/info/template).</li></ul><h2>Use cases</h2><ul><li>Geo-localizzazione asset.</li><li>Tracking eventi geolocalizzati.</li><li>Heatmap o aggregazioni territoriali.</li></ul><h2>Differenza comportamento: <code>point</code> vs <code>polygon</code></h2><p>La mappa adatta automaticamente rendering e interazioni in base ai campi metadata con <code>mc_ui_column_type</code>.</p><ul><li>Se e presente almeno una colonna <code>point</code>:</li></ul><p> - ogni record viene renderizzato come marker (<code>map-advanced-marker</code>);</p><p> - supporta clustering (<code>useClusterer</code>);</p><p> - supporta drag marker (quando abilitato dalla toolbar);</p><p> - click su marker apre <code>infoWindow</code> con <code>titleField</code>/<code>infoField</code> e template.</p><ul><li>Se e presente una colonna <code>polygon</code>:</li></ul><p> - ogni record viene renderizzato come poligono (<code>map-polygon</code>);</p><p> - non usa marker clusterer;</p><p> - il filtro boundaries puo usare anche la geometria poligonale;</p><p> - click su poligono apre la stessa <code>infoWindow</code> dei marker (posizionata sul punto cliccato o, in fallback, sul centro geometrico).</p><ul><li>Se sono presenti entrambi (<code>point</code> e <code>polygon</code>):</li></ul><p> - il componente puo mostrare entrambi i layer;</p><p> - la toolbar privilegia le azioni coerenti col tipo principale (drag marker su scenari marker, edit polygons su scenari polygon-only);</p><p> - il filtro <code>maparea</code> usa il primo campo geografico disponibile tra <code>point</code>/<code>polygon</code>.</p><h2>Note operative</h2><p>Assicurarsi che i campi coordinate siano correttamente mappati nei metadata colonna.</p><h2>md_props_bag: archetypes.map</h2><p>Snippet 1:</p><ul><li><code>advancedFilter</code>: quando <code>true</code> mostra la <code>wuic-filter-bar</code> per l'archetype <code>map</code> (a livello data-repeater).</li><li><code>zoom</code>: zoom iniziale.</li><li><code>center.lat</code>/<code>center.lng</code>: centro iniziale mappa; se mancanti viene usato il comportamento default del componente.</li><li><code>minZoom</code>/<code>maxZoom</code>: limiti zoom.</li><li><code>useCurrentLocation</code>: usa geolocalizzazione browser come centro.</li><li><code>useClusterer</code>: abilita clustering marker.</li><li><code>filterByBoundaries</code>: filtra i record visibili in base al bounding box corrente.</li><li><code>titleField</code>: campo metadata usato come titolo marker.</li><li><code>infoField</code>: campo metadata usato come info base marker.</li><li><code>customMarkerImageSrc</code>: URL marker fisso (immagine o SVG inline applicata a tutti i marker).</li><li><code>customMarkerImageSrcField</code>: nome del campo record che contiene URL immagine <strong>o SVG inline</strong> (<code><svg ...>...</svg></code>); auto-detect del formato. Vince su <code>markerColorField</code> e <code>markerContentCallback</code>.</li><li><code>markerColorField</code>: nome del campo record che contiene un colore CSS (<code>#rrggbb</code>); il marker viene reso come <code>PinElement</code> colorato. Ignorato quando <code>customMarkerImageSrcField</code> e' valorizzato per quel record (immagine ha precedenza).</li><li><code>markerContentCallback</code>: callback JS per contenuto marker.</li><li><code>infoFunction</code>: callback JS per render info window. (Alternativo a itemTemplateString)</li><li><code>itemTemplateString</code>: template custom del contenuto. (Alternativo a infoFunction)</li><li><code>polyline</code>: overlay polyline per tracking percorsi GPS — vedi sezione dedicata sotto.</li></ul><h2>Personalizzazione marker per-record (colore + immagine/SVG)</h2><p>L'archetype <code>map</code> permette di **personalizzare ogni marker in base ai dati del</p><p>singolo record** senza scrivere callback JS, configurando i campi del record</p><p>direttamente dal <code>mdpropsbag</code>.</p><h3>Priorità di rendering del marker</h3><p>Il framework risolve il contenuto del marker in quest'ordine (prima che matcha vince):</p><p>1. <strong>`customMarkerImageSrcField`</strong> — campo record con URL immagine o SVG inline.</p><p> Auto-detect: se la stringa inizia con <code><svg</code>, viene wrappata in <code><div></code> come</p><p> AdvancedMarker content; altrimenti viene messa in <code><img src></code>.</p><p>2. <strong>`customMarkerImageSrc`</strong> — immagine/SVG fisso applicato a tutti.</p><p>3. <strong>`markerColorField`</strong> — colore CSS per-record, reso come <code>PinElement</code></p><p> (pin Google Maps standard colorato).</p><p>4. <strong>`markerContentCallback`</strong> — callback JS user-supplied (riceve <code>record</code>).</p><h3>Esempio: campo <code>colore_marker</code> + <code>svg_marker</code> su tabella <code>mezzi</code></h3><p>Schema DB Dati (estratto):</p><p>Snippet 2:</p><p>Metadata colonna:</p><table><thead><tr><th>Colonna</th><th><code>mc_ui_column_type</code></th><th><code>voa_class</code></th><th>Note</th></tr></thead><tbody><tr><td><code>colore_marker</code></td><td><code>color</code></td><td>1</td><td>color picker nativo (<code>#rrggbb</code>)</td></tr><tr><td><code>svg_marker</code></td><td><code>txt_area</code></td><td>1</td><td>SVG inline o URL — opzionale, sovrascrive il colore</td></tr></tbody></table><p>Propsbag dell'archetype map sulla route che renderizza i mezzi:</p><p>Snippet 3:</p><p>Comportamento runtime:</p><ul><li>record con <code>svg_marker = '<svg ...>...</svg>'</code> → marker = SVG inline (override del colore).</li><li>record con <code>svg_marker = 'https://.../truck.png'</code> → marker = <code><img></code> con quella URL.</li><li>record con <code>svg_marker</code> vuoto e <code>colore_marker = '#16a34a'</code> → marker = <code>PinElement</code> verde.</li><li>record con entrambi vuoti → marker default Google Maps.</li></ul><h2>Polyline overlay (tracking percorsi GPS)</h2><p>L'archetype <code>map</code> puo' renderizzare <strong>una polyline per gruppo di record</strong></p><p>(es. tracciato GPS storicizzato di un mezzo per una giornata) leggendo le</p><p>coordinate dalla colonna <code>point</code> esistente sulla route, raggruppando i record</p><p>per un campo (<code>groupByField</code>) e ordinandoli per un campo temporale</p><p>(<code>orderByField</code>).</p><p>Quando <code>polyline.enabled=true</code> la mappa NON renderizza un marker per ogni</p><p>record: solo <strong>un marker per gruppo</strong>, posizionato sull'**ultimo punto</p><p>temporale** (il piu' recente). Tutti gli altri record sono rappresentati come</p><p>nodi della polyline.</p><h3>Configurazione minima</h3><p>Snippet 4:</p><h3>Tutte le opzioni <code>polyline</code></h3><ul><li><code>enabled</code>: abilita l'overlay polyline (default <code>false</code>).</li><li><code>groupByField</code>: campo record per raggruppare i record in polyline distinte</li></ul><p> (es. <code>mezzo_id</code>, <code>tracking_session_id</code>). Se omesso, tutti i record finiscono</p><p> in un'unica polyline globale.</p><ul><li><code>orderByField</code>: campo record per ordinare i punti dentro ogni gruppo</li></ul><p> (es. <code>timestamp_pos</code>). Se omesso, viene usato l'ordine di arrivo.</p><ul><li><code>pointField</code>: campo record da cui leggere <code>{lat,lng}</code>. <strong>Default</strong>:</li></ul><p> auto-detect della prima colonna con <code>mc_ui_column_type='point'</code> nel</p><p> metaInfo (la stessa che il map-list usa per i marker normali).</p><ul><li><code>colorField</code>: campo record con colore polyline per-gruppo (CSS <code>#rrggbb</code>).</li></ul><p> Il colore viene letto dal <strong>primo record del gruppo</strong> (assume omogeneita').</p><p> Sovrascrive <code>strokeColorByGroup</code> e <code>strokeColor</code>. Tipico use-case: portare</p><p> in view il <code>colore_marker</code> configurato sull'entita' lookup (es. <code>mezzi</code>).</p><ul><li><code>strokeColor</code>: colore polyline fisso (default <code>#1d4ed8</code>). Usato solo se</li></ul><p> <code>colorField</code> non risolve e <code>strokeColorByGroup=false</code>.</p><ul><li><code>strokeColorByGroup</code>: se <code>true</code> e <code>colorField</code> non e' settato, ogni gruppo</li></ul><p> riceve un colore distinto da una palette interna (8 colori).</p><ul><li><code>strokeWeight</code>: spessore tratto in px (default 4).</li><li><code>strokeOpacity</code>: opacita 0..1 (default 0.85).</li><li><code>snapToRoads</code>: se <code>true</code>, la polyline viene snappata alle strade reali via</li></ul><p> Google Maps Routes API (<code>Route.computeRoutes</code>, fallback automatico al</p><p> legacy <code>DirectionsService</code> se la libreria <code>routes</code> non e' caricata).</p><p> <strong>Richiede</strong> "Routes API" abilitata sulla key in Google Cloud Console.</p><p> La chiamata viene chunkata a 25 waypoint per request (limite Routes API).</p><ul><li><code>showMarkers</code>: se <code>false</code>, nasconde anche il marker last-in-group (default</li></ul><p> <code>true</code> — un marker per gruppo).</p><ul><li><code>showWaypointDots</code>: se <code>true</code>, disegna un <code><map-circle></code> (cerchio</li></ul><p> geografico) su ogni waypoint originale del record. Utile per distinguere</p><p> visivamente i punti GPS storicizzati nel DB dal path</p><p> interpolato/snappato.</p><ul><li><code>waypointDotRadius</code>: raggio in metri del dot (default <code>8</code>).</li><li><code>travelMode</code>: modalita di viaggio per <code>snapToRoads</code> (<code>DRIVING</code>/<code>WALKING</code>/</li></ul><p> <code>BICYCLING</code>). Per giri cittadini con waypoint in ZTL/zone pedonali,</p><p> preferire <code>WALKING</code> (<code>DRIVING</code> da spesso <code>no results</code> in centro storico</p><p> italiano).</p><h3>Combinare colore polyline + colore marker dalla stessa colonna</h3><p>Se la tabella entita' (es. <code>mezzi</code>) ha gia' un campo <code>colore_marker</code>,</p><p>configura <strong>lo stesso campo</strong> sia per il marker che per la polyline portando</p><p>quel campo nella view sorgente:</p><p>Snippet 5:</p><p>Snippet 6:</p><p>Ogni mezzo avra' marker, polyline e dot dello stesso colore configurato sul</p><p>record <code>mezzi</code> (l'utente lo modifica dall'anagrafica e si propaga ovunque).</p><p>Per il mezzo con <code>svg_marker</code> valorizzato, il marker sara' l'icona custom</p><p>(SVG inline o immagine URL) e il colore continuera' ad essere usato per</p><p>polyline + dot.</p><h3>Contesto Template (<code>itemTemplateString</code>)</h3><p>Nel template custom sono disponibili questi input:</p><ul><li><code>record</code>: record corrente (in map archetype coincide con <code>rowData.record</code> quando presente).</li><li><code>rowData</code>: payload evento marker/poligono corrente.</li><li><code>metaInfo</code>: metadati tabella/colonne della datasource.</li><li><code>datasource</code>: riferimento al componente datasource associato.</li></ul><p>Contesto runtime usato dal framework:</p><p>Snippet 7:</p><h2>Eventi e subscriptions (host)</h2><p>Eventi disponibili su <code>wuic-map-list</code>:</p><ul><li><code>onMapClick</code>: click su mappa (<code>google-map mapClick</code>).</li><li><code>onMarkerClick</code>: click su marker.</li><li><code>onMarkerDragStart</code>: inizio drag marker.</li><li><code>onMarkerDragEnd</code>: fine drag marker (con posizione finale).</li><li><code>onPolygonClick</code>: click su poligono.</li><li><code>onPolygonShapeChangedEvent</code>: shape changed su poligono in edit mode.</li><li><code>onMapDataBound</code>: emesso quando il wrapper completa il binding dati dal datasource.</li></ul><p>Esempio template:</p><p>Snippet 8:</p><p>Esempio subscribe (ViewChild):</p><p>Snippet 9:</p>",
|
|
2016
2078
|
"codeSamples": [
|
|
2017
2079
|
{
|
|
2018
2080
|
"id": "code_1",
|
|
2019
2081
|
"title": "Snippet 1",
|
|
2020
2082
|
"language": "json",
|
|
2021
|
-
"code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyphText: glyph, scale: 1.2 }); return pin;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
|
|
2083
|
+
"code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerColorField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyphText: glyph, scale: 1.2 }); return pin;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\",\n \"polyline\": {\n \"enabled\": false,\n \"groupByField\": \"\",\n \"orderByField\": \"\",\n \"pointField\": \"\",\n \"colorField\": \"\",\n \"strokeColor\": \"#1d4ed8\",\n \"strokeColorByGroup\": false,\n \"strokeWeight\": 4,\n \"strokeOpacity\": 0.85,\n \"snapToRoads\": false,\n \"showMarkers\": true,\n \"showWaypointDots\": false,\n \"waypointDotRadius\": 8,\n \"travelMode\": \"DRIVING\"\n }\n }\n }\n}"
|
|
2022
2084
|
},
|
|
2023
2085
|
{
|
|
2024
2086
|
"id": "code_2",
|
|
2025
2087
|
"title": "Snippet 2",
|
|
2026
|
-
"language": "
|
|
2027
|
-
"code": "
|
|
2088
|
+
"language": "sql",
|
|
2089
|
+
"code": "ALTER TABLE dbo.mezzi ADD colore_marker NVARCHAR(7) NULL; -- es. '#dc2626'\nALTER TABLE dbo.mezzi ADD svg_marker NVARCHAR(MAX) NULL; -- SVG inline o URL"
|
|
2028
2090
|
},
|
|
2029
2091
|
{
|
|
2030
2092
|
"id": "code_3",
|
|
2031
2093
|
"title": "Snippet 3",
|
|
2032
|
-
"language": "
|
|
2033
|
-
"code": "
|
|
2094
|
+
"language": "json",
|
|
2095
|
+
"code": "{\n \"archetypes\": {\n \"map\": {\n \"markerColorField\": \"colore_marker\",\n \"customMarkerImageSrcField\": \"svg_marker\"\n }\n }\n}"
|
|
2034
2096
|
},
|
|
2035
2097
|
{
|
|
2036
2098
|
"id": "code_4",
|
|
2037
2099
|
"title": "Snippet 4",
|
|
2100
|
+
"language": "json",
|
|
2101
|
+
"code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": true,\n \"polyline\": {\n \"enabled\": true,\n \"groupByField\": \"mezzo_id\",\n \"orderByField\": \"timestamp_pos\",\n \"snapToRoads\": true,\n \"travelMode\": \"WALKING\"\n }\n }\n }\n}"
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
"id": "code_5",
|
|
2105
|
+
"title": "Snippet 5",
|
|
2106
|
+
"language": "sql",
|
|
2107
|
+
"code": "CREATE VIEW vw_mezzi_posizioni_giorno AS\nSELECT p.*, m.colore_marker, m.svg_marker, ...\nFROM dbo.mezzi_posizioni p JOIN dbo.mezzi m ON m.id = p.mezzo_id;"
|
|
2108
|
+
},
|
|
2109
|
+
{
|
|
2110
|
+
"id": "code_6",
|
|
2111
|
+
"title": "Snippet 6",
|
|
2112
|
+
"language": "json",
|
|
2113
|
+
"code": "{\n \"archetypes\": {\n \"map\": {\n \"markerColorField\": \"colore_marker\",\n \"customMarkerImageSrcField\": \"svg_marker\",\n \"polyline\": {\n \"enabled\": true,\n \"groupByField\": \"mezzo_id\",\n \"orderByField\": \"timestamp_pos\",\n \"colorField\": \"colore_marker\",\n \"showWaypointDots\": true,\n \"snapToRoads\": true,\n \"travelMode\": \"WALKING\"\n }\n }\n }\n}"
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
"id": "code_7",
|
|
2117
|
+
"title": "Snippet 7",
|
|
2118
|
+
"language": "ts",
|
|
2119
|
+
"code": "inputs: { record: item, rowData: item, metaInfo: metaInfo, datasource: datasource }"
|
|
2120
|
+
},
|
|
2121
|
+
{
|
|
2122
|
+
"id": "code_8",
|
|
2123
|
+
"title": "Snippet 8",
|
|
2124
|
+
"language": "html",
|
|
2125
|
+
"code": "<wuic-map-list\n [hardcodedDatasource]=\"citiesDs\"\n (onMapClick)=\"handleMapClick($event)\"\n (onMarkerClick)=\"handleMarkerClick($event)\"\n (onMarkerDragStart)=\"handleMarkerDragStart($event)\"\n (onMarkerDragEnd)=\"handleMarkerDragEnd($event)\"\n (onPolygonClick)=\"handlePolygonClick($event)\"\n (onPolygonShapeChangedEvent)=\"handlePolygonShapeChanged($event)\"\n (onMapDataBound)=\"handleMapDataBound($event)\">\n</wuic-map-list>"
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
"id": "code_9",
|
|
2129
|
+
"title": "Snippet 9",
|
|
2038
2130
|
"language": "ts",
|
|
2039
2131
|
"code": "@ViewChild(MapListComponent) mapList?: MapListComponent;\nprivate readonly subs = new Subscription();\n\nngAfterViewInit(): void {\n if (!this.mapList) return;\n this.subs.add(this.mapList.onMapClick.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMarkerClick.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMarkerDragStart.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMarkerDragEnd.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onPolygonClick.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onPolygonShapeChangedEvent.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMapDataBound.subscribe((e) => console.log(e)));\n}"
|
|
2040
2132
|
}
|
|
@@ -2103,14 +2195,47 @@ const frameworkDocsContent = {
|
|
|
2103
2195
|
{
|
|
2104
2196
|
"id": "sec_3",
|
|
2105
2197
|
"title": "Riferimento al designer"
|
|
2198
|
+
},
|
|
2199
|
+
{
|
|
2200
|
+
"id": "sec_4",
|
|
2201
|
+
"title": "Parametri report (variabili SQL filtrate)"
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
"id": "sec_5",
|
|
2205
|
+
"title": "Setup backend obbligatorio"
|
|
2106
2206
|
}
|
|
2107
2207
|
],
|
|
2108
2208
|
"sections": [
|
|
2109
2209
|
{
|
|
2110
2210
|
"id": "overview",
|
|
2111
2211
|
"title": "Overview",
|
|
2112
|
-
"html": "<h1>Reporting</h1><p>Reportistica operativa su route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__reporting__01.png</code>: apertura report viewer con rendering report e toolbar operativa.</li><li><code>manual__reporting__02.png</code>: esempio runtime con parametri/filtro applicati al report.</li></ul><h2>Report viewer</h2><p>Il <code>report-viewer</code> esegue e mostra report Stimulsoft associati alla route metadata corrente.</p><p>Feature principali:</p><ul><li>apertura report da list/grid con nome report (<code>reportName</code>) e parametri opzionali.</li><li>rendering dati lato runtime con toolbar viewer (zoom, export, stampa).</li><li>supporto filtro/parametri propagati dalla pagina chiamante.</li></ul><h2>Riferimento al designer</h2><p>Per creazione o modifica del report <code>.mrt</code>, vedi la sottosezione</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer & Workflow</code>.</p>",
|
|
2113
|
-
"codeSamples": [
|
|
2212
|
+
"html": "<h1>Reporting</h1><p>Reportistica operativa su route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__reporting__01.png</code>: apertura report viewer con rendering report e toolbar operativa.</li><li><code>manual__reporting__02.png</code>: esempio runtime con parametri/filtro applicati al report.</li></ul><h2>Report viewer</h2><p>Il <code>report-viewer</code> esegue e mostra report Stimulsoft associati alla route metadata corrente.</p><p>Feature principali:</p><ul><li>apertura report da list/grid con nome report (<code>reportName</code>) e parametri opzionali.</li><li>rendering dati lato runtime con toolbar viewer (zoom, export, stampa).</li><li>supporto filtro/parametri propagati dalla pagina chiamante.</li></ul><h2>Riferimento al designer</h2><p>Per creazione o modifica del report <code>.mrt</code>, vedi la sottosezione</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer & Workflow</code>.</p><h2>Parametri report (variabili SQL filtrate)</h2><p>Per filtrare un report su una specifica entita (es. stampa di una singola fattura), il pattern canonico e':</p><p>1. <strong>Nel `.mrt`</strong>: dichiara una <strong>Variable</strong> nel Dictionary con <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Usa la variabile come <code>@nomeParam</code> nelle SqlCommand:</p><p>Snippet 1:</p><p> > <strong>Trappola</strong>: NON usare il pattern legacy <code><StiDataParameter></code> block nel DataSource — non e' piu' supportato dalla versione corrente di Stimulsoft e produce <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter 'value')</code> durante <code>report.Load()</code>.</p><p>2. <strong>Nell'URL del viewer</strong>: passa il valore via query param <code>parameters</code> (NON come query param diretto):</p><p>Snippet 2:</p><p> Sintassi <code>parameters</code> URL-encoded: <code>nome||eq||valore</code> (separati da <code>@</code> per piu' parametri).</p><p>3. <strong>Da row action o link UI</strong>: usa il callback canonico per costruire l'URL:</p><p>Snippet 3:</p><h2>Setup backend obbligatorio</h2><p>In <code>appsettings.json</code> dell'app, deve esistere <code>AppSettings.reportQueryTimeout</code> (es. <code>"120"</code>). Senza, <code>ReportViewerController.prepareDataSql</code> lancia <code>int.Parse(null)</code> → 500. Esempio:</p><p>Snippet 4:</p>",
|
|
2213
|
+
"codeSamples": [
|
|
2214
|
+
{
|
|
2215
|
+
"id": "code_1",
|
|
2216
|
+
"title": "Snippet 1",
|
|
2217
|
+
"language": "sql",
|
|
2218
|
+
"code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
"id": "code_2",
|
|
2222
|
+
"title": "Snippet 2",
|
|
2223
|
+
"language": "text",
|
|
2224
|
+
"code": " ✅ #/<route>/report-viewer?reportName=Report.mrt¶meters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignorato"
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
"id": "code_3",
|
|
2228
|
+
"title": "Snippet 3",
|
|
2229
|
+
"language": "js",
|
|
2230
|
+
"code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt¶meters='\n + encodeURIComponent(params);"
|
|
2231
|
+
},
|
|
2232
|
+
{
|
|
2233
|
+
"id": "code_4",
|
|
2234
|
+
"title": "Snippet 4",
|
|
2235
|
+
"language": "json",
|
|
2236
|
+
"code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
|
|
2237
|
+
}
|
|
2238
|
+
]
|
|
2114
2239
|
}
|
|
2115
2240
|
],
|
|
2116
2241
|
"images": [
|
|
@@ -2843,28 +2968,54 @@ const frameworkDocsContent = {
|
|
|
2843
2968
|
},
|
|
2844
2969
|
{
|
|
2845
2970
|
"id": "sec_5",
|
|
2846
|
-
"title": "
|
|
2971
|
+
"title": "Combo slim — campi extra via `mc_props_bag.slimCombo`"
|
|
2847
2972
|
},
|
|
2848
2973
|
{
|
|
2849
2974
|
"id": "sec_6",
|
|
2850
|
-
"title": "
|
|
2975
|
+
"title": "Workflow Assistito: Clone Lookup + Filtro Default + Lookup Hierarchy"
|
|
2851
2976
|
},
|
|
2852
2977
|
{
|
|
2853
2978
|
"id": "sec_7",
|
|
2979
|
+
"title": "Effetti Client/Server"
|
|
2980
|
+
},
|
|
2981
|
+
{
|
|
2982
|
+
"id": "sec_8",
|
|
2854
2983
|
"title": "Note operative"
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
2986
|
+
"id": "sec_9",
|
|
2987
|
+
"title": "Auto-fetch su FK programmatici"
|
|
2855
2988
|
}
|
|
2856
2989
|
],
|
|
2857
2990
|
"sections": [
|
|
2858
2991
|
{
|
|
2859
2992
|
"id": "overview",
|
|
2860
2993
|
"title": "Overview",
|
|
2861
|
-
"html": "<h1>Widget Lookup</h1><p>Widget colonna per relazioni (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicato <strong>Lookup</strong> nel metadata-editor colonne.</p><h2>Esempi</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup puro-OData (State/Province da `/odata/Stateprovinces`)</a> — dimostra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> e rendering inline via <code>$expand</code>.</li></ul><h2>Quando usarlo</h2><ul><li>selezione record correlati da tabella metadata esterna.</li><li>scenari con ricerca lato server e paging lookup.</li><li>supporto singola selezione (<code>lookupByID</code>) e multi selezione (<code>multiple_check</code>).</li></ul><h2>Proprieta metadata colonna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata sorgente lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID restituito.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descrizione mostrata.</li><li><code>mc_ui_lookup_filter</code>: filtro base lookup.</li><li><code>mc_serverside_operations</code>: ricerca/paging server-side.</li><li><code>mc_ui_pagesize</code>: dimensione pagina risultati lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: abilita edit record lookup da popup.</li><li><code>mc_ui_lookup_insert_allow</code>: abilita inserimento record lookup da popup.</li><li><code>mc_ui_lookup_search_grid</code>: abilita griglia di ricerca nel popup.</li><li><code>mc_logic_allow_navigation</code>: navigazione su route collegata.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: vincolo aggiuntivo lato client/server nella query lookup.</li><li><code>lookup.virtualize</code>: abilita virtual scroll su <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tollerante (<code>true/false</code>, <code>1/0</code>, stringhe equivalenti); default <code>false</code> quando assente.</li><li><code>lookup.virtualize.itemSize</code>: altezza riga virtuale in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override provider dati del dropdown. Quando settato, il nested datasource del lookup-editor non passa piu' dal combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) ma direttamente dal provider dichiarato in <code>type</code> con la URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (dispatcher instrada a <code>DataProviderOdataService.selectCombo</code> che emette GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> e <code>$filter=contains(textField,'<query>')</code> per la server-side search). Altri valori seguono la stessa logica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base dell'entity set (es. <code>/odata/<EntitySet></code>), con o senza origine esplicita. Query params esistenti vengono ignorati — il provider ricostruisce i params ad ogni fetch.</li><li><code>form.columns</code>: larghezza campo nel form edit.</li><li><code>style.editCss</code>: stile custom editor.</li></ul><h2>Workflow Assistito: Clone Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flusso operativo inline: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline campi principali: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtro rapido: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Effetto client: setup lookup accelerato nel metadata-editor e filtro coerente gia pronto nel widget.</li><li>Effetto server: query lookup e select clause restano allineate agli endpoint <code>MetaService.*</code> senza mapping manuale.</li></ul><p>Checklist operativa rapida:</p><p>1. clonare configurazione base con <code>suggestLookup2</code>;</p><p>2. generare filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. rifinire gerarchia/select clause dal tree lookup;</p><p>4. validare in runtime che ricerca e value/text mapping siano coerenti.</p><h2>Effetti Client/Server</h2><ul><li>Client: rendering widget lookup, search, etichetta/value binding.</li><li>Client: con <code>lookup.virtualize</code> attivo, l'autocomplete renderizza solo il subset visuale delle opzioni.</li><li>Server: query lookup paginata/filtrata tramite <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Note operative</h2><ul><li>in <code>multiple_check</code> la parte lookup resta la stessa, cambia il renderer multi valore.</li><li>se manca <code>mc_ui_lookup_entity_name</code>, il widget non puo risolvere la sorgente.</li></ul>",
|
|
2994
|
+
"html": "<h1>Widget Lookup</h1><p>Widget colonna per relazioni (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicato <strong>Lookup</strong> nel metadata-editor colonne.</p><h2>Esempi</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup puro-OData (State/Province da `/odata/Stateprovinces`)</a> — dimostra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> e rendering inline via <code>$expand</code>.</li></ul><h2>Quando usarlo</h2><ul><li>selezione record correlati da tabella metadata esterna.</li><li>scenari con ricerca lato server e paging lookup.</li><li>supporto singola selezione (<code>lookupByID</code>) e multi selezione (<code>multiple_check</code>).</li></ul><h2>Proprieta metadata colonna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata sorgente lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID restituito.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descrizione mostrata.</li><li><code>mc_ui_lookup_filter</code>: filtro base lookup.</li><li><code>mc_serverside_operations</code>: ricerca/paging server-side.</li><li><code>mc_ui_pagesize</code>: dimensione pagina risultati lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: abilita edit record lookup da popup.</li><li><code>mc_ui_lookup_insert_allow</code>: abilita inserimento record lookup da popup.</li><li><code>mc_ui_lookup_search_grid</code>: abilita griglia di ricerca nel popup.</li><li><code>mc_logic_allow_navigation</code>: navigazione su route collegata.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: vincolo aggiuntivo lato client/server nella query lookup.</li><li><code>lookup.virtualize</code>: abilita virtual scroll su <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tollerante (<code>true/false</code>, <code>1/0</code>, stringhe equivalenti); default <code>false</code> quando assente.</li><li><code>lookup.virtualize.itemSize</code>: altezza riga virtuale in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override provider dati del dropdown. Quando settato, il nested datasource del lookup-editor non passa piu' dal combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) ma direttamente dal provider dichiarato in <code>type</code> con la URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (dispatcher instrada a <code>DataProviderOdataService.selectCombo</code> che emette GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> e <code>$filter=contains(textField,'<query>')</code> per la server-side search). Altri valori seguono la stessa logica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base dell'entity set (es. <code>/odata/<EntitySet></code>), con o senza origine esplicita. Query params esistenti vengono ignorati — il provider ricostruisce i params ad ogni fetch.</li><li><code>form.columns</code>: larghezza campo nel form edit.</li><li><code>style.editCss</code>: stile custom editor.</li></ul><h2>Combo slim — campi extra via <code>mc_props_bag.slimCombo</code></h2><p>Il dropdown lookup di default fa un SELECT minimale sulla tabella correlata: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Riduce il payload del combo. Se il client ha bisogno di altri campi della tabella correlata — tipicamente per pre-popolare colonne dipendenti via <code>mc_selection_changed_custom_function__fn</code> — si usa <code>mc_props_bag.slimCombo</code> come <strong>array di nomi colonna extra</strong> da includere nel SELECT del combo:</p><p>Snippet 2:</p><ul><li>Array: i nomi vengono aggiunti al <code>columnRestrictionList</code> del combo response (logica in <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: disabilita la restrizione → il combo ritorna <strong>tutte</strong> le colonne. Utile in debug, payload piu' grande.</li><li>Se la colonna ha <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> valorizzato (formula display computata), <code>slimCombo</code> viene ignorato e il combo ritorna tutte le colonne (la formula puo' referenziare campi arbitrari).</li></ul><p>I campi extra finiscono in <code>record['<col>__lookup_obj'].value</code> (record pieno della tabella correlata, gia' completo dei campi dichiarati in <code>slimCombo</code>) e sono accessibili dal callback <code>mc_selection_changed_custom_function__fn</code>:</p><p>Snippet 3:</p><p>Tradeoff: payload combo leggermente piu' grande ma niente HTTP roundtrip aggiuntivo (<code>getFlatRecordData</code> per recuperare il record pieno per id) ad ogni selezione.</p><h2>Workflow Assistito: Clone Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flusso operativo inline: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline campi principali: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtro rapido: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Effetto client: setup lookup accelerato nel metadata-editor e filtro coerente gia pronto nel widget.</li><li>Effetto server: query lookup e select clause restano allineate agli endpoint <code>MetaService.*</code> senza mapping manuale.</li></ul><p>Checklist operativa rapida:</p><p>1. clonare configurazione base con <code>suggestLookup2</code>;</p><p>2. generare filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. rifinire gerarchia/select clause dal tree lookup;</p><p>4. validare in runtime che ricerca e value/text mapping siano coerenti.</p><h2>Effetti Client/Server</h2><ul><li>Client: rendering widget lookup, search, etichetta/value binding.</li><li>Client: con <code>lookup.virtualize</code> attivo, l'autocomplete renderizza solo il subset visuale delle opzioni.</li><li>Server: query lookup paginata/filtrata tramite <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Note operative</h2><ul><li>in <code>multiple_check</code> la parte lookup resta la stessa, cambia il renderer multi valore.</li><li>se manca <code>mc_ui_lookup_entity_name</code>, il widget non puo risolvere la sorgente.</li></ul><h2>Auto-fetch su FK programmatici</h2><p>Il <code>wuic-lookup-editor</code> sottoscrive automaticamente al <code>BehaviorSubject</code> di <code>record[mc_nome_colonna]</code> e gestisce i cambi di FK programmatici (es. import documento sorgente, callback custom che setta <code>record.cliente_id.next(99)</code>):</p><ul><li>Se il nuovo valore e' <strong>gia' presente in `items`</strong> (la pagina caricata della lookup): popola solo <code>__lookup_obj</code> per coerenza, niente HTTP.</li><li>Se il nuovo valore <strong>non e' negli items</strong> (es. valore fuori dalla pagina corrente del combo, o non ancora fetchato): scatta una <strong>second-stage fetch</strong> filtrata <code>WHERE <valueField> eqor <newValue></code> per recuperare il record correlato e popolare automaticamente il dropdown.</li></ul><p>Skip cases (no-op):</p><ul><li>Lookup multi-value (<code>isFilter</code> con <code>eqor</code>, <code>multiple_check</code>)</li><li>Prima emit del BS (gia' coperto dal flow init)</li><li>Valore null/undefined (utente ha clearato)</li><li><code>hasActiveLookupQuery=true</code> (utente sta typing una query manuale)</li></ul><p><strong>Implicazione per i custom form</strong>: settare solo il FK basta — non servono piu' workaround manuali per popolare <code>__lookup_obj</code> o l'alias joined. Il display del dropdown si aggiorna da solo.</p><p>Snippet 4:</p><p>> <strong>Nota</strong>: l'auto-fetch fired solo quando il <code><wuic-lookup-editor></code> e' <strong>montato</strong> (cella in edit mode). Per il display in <strong>lista</strong> (cella read-only), <code>formatGridViewValue</code> legge l'alias joined <code><route>___<textField>__<col></code> direttamente dal record — quello va comunque popolato manualmente se non viene dal backend.</p>",
|
|
2862
2995
|
"codeSamples": [
|
|
2863
2996
|
{
|
|
2864
2997
|
"id": "code_1",
|
|
2865
2998
|
"title": "Snippet 1",
|
|
2866
2999
|
"language": "json",
|
|
2867
3000
|
"code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
|
|
3001
|
+
},
|
|
3002
|
+
{
|
|
3003
|
+
"id": "code_2",
|
|
3004
|
+
"title": "Snippet 2",
|
|
3005
|
+
"language": "json",
|
|
3006
|
+
"code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
"id": "code_3",
|
|
3010
|
+
"title": "Snippet 3",
|
|
3011
|
+
"language": "ts",
|
|
3012
|
+
"code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
|
|
3013
|
+
},
|
|
3014
|
+
{
|
|
3015
|
+
"id": "code_4",
|
|
3016
|
+
"title": "Snippet 4",
|
|
3017
|
+
"language": "ts",
|
|
3018
|
+
"code": "// PRIMA (workaround manuale richiesto)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// ORA (auto-fetch del lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor monta? Subscribe rileva il change → fetch filtrata → __lookup_obj popolato → dropdown mostra \"Acme S.r.l.\""
|
|
2868
3019
|
}
|
|
2869
3020
|
]
|
|
2870
3021
|
}
|
|
@@ -3127,10 +3278,14 @@ const frameworkDocsContent = {
|
|
|
3127
3278
|
},
|
|
3128
3279
|
{
|
|
3129
3280
|
"id": "sec_4",
|
|
3130
|
-
"title": "
|
|
3281
|
+
"title": "Context menu sugli elementi"
|
|
3131
3282
|
},
|
|
3132
3283
|
{
|
|
3133
3284
|
"id": "sec_5",
|
|
3285
|
+
"title": "Flusso operativo consigliato"
|
|
3286
|
+
},
|
|
3287
|
+
{
|
|
3288
|
+
"id": "sec_6",
|
|
3134
3289
|
"title": "Note operative"
|
|
3135
3290
|
}
|
|
3136
3291
|
],
|
|
@@ -3138,7 +3293,7 @@ const frameworkDocsContent = {
|
|
|
3138
3293
|
{
|
|
3139
3294
|
"id": "overview",
|
|
3140
3295
|
"title": "Overview",
|
|
3141
|
-
"html": "<h1>Designer</h1><p>Editor visuale per dashboard metadata-driven su route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composizione board via drag-and-drop di nodi e blocchi UI.</li><li>Layout e styling pilotati da <code>boardcontent</code> e metadata tabella/colonna.</li><li>Editing property panel senza hardcode nei componenti runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: sorgente dati metadata (<code>route</code>, filtri, sort, paging).</li><li><code>DataRepeater</code>: visualizzazione lista/card collegata a datasource.</li><li><code>Dynamic Template</code>: rendering template dinamico con input metadata-driven.</li><li><code>Action/Button</code>: azioni utente agganciate a record e callback.</li><li><code>Container/Layout</code>: blocchi strutturali per colonne, righe e wrapper.</li></ul><h2>Feature principali</h2><ul><li>Drag-and-drop nodi sulla canvas con persistenza nel <code>boardcontent</code>.</li><li>Property panel contestuale con input coerenti ai componenti selezionati.</li><li>Binding tra nodi (<code>datasource -> repeater/template</code>) con validazione minima.</li><li>Preview runtime integrata e riallineamento layout.</li><li>Gestione pending changes con guard prima dell'uscita.</li></ul><h2>Flusso operativo consigliato</h2><ul><li>Aprire route dashboard target.</li><li>Inserire/collegare nodi dalla palette.</li><li>Configurare <code>inputs</code> essenziali (es. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Salvare e validare il risultato in runtime.</li></ul><h2>Note operative</h2><ul><li>Per fix specifici dashboard, privilegiare patch del <code>boardcontent</code> invece di modifiche globali.</li><li>Evitare proprietà duplicate tra <code>inputs</code> e stile inline non coerente.</li><li>Verificare sempre che i datasource abbiano <code>inputs.route</code> valorizzata.</li></ul>",
|
|
3296
|
+
"html": "<h1>Designer</h1><p>Editor visuale per dashboard metadata-driven su route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composizione board via drag-and-drop di nodi e blocchi UI.</li><li>Layout e styling pilotati da <code>boardcontent</code> e metadata tabella/colonna.</li><li>Editing property panel senza hardcode nei componenti runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: sorgente dati metadata (<code>route</code>, filtri, sort, paging).</li><li><code>DataRepeater</code>: visualizzazione lista/card collegata a datasource.</li><li><code>Dynamic Template</code>: rendering template dinamico con input metadata-driven.</li><li><code>Action/Button</code>: azioni utente agganciate a record e callback.</li><li><code>Container/Layout</code>: blocchi strutturali per colonne, righe e wrapper.</li></ul><h2>Feature principali</h2><ul><li>Drag-and-drop nodi sulla canvas con persistenza nel <code>boardcontent</code>.</li><li>Property panel contestuale con input coerenti ai componenti selezionati.</li><li>Binding tra nodi (<code>datasource -> repeater/template</code>) con validazione minima.</li><li>Preview runtime integrata e riallineamento layout.</li><li>Gestione pending changes con guard prima dell'uscita.</li></ul><h2>Context menu sugli elementi</h2><p>Right-click su un elemento del canvas, una voce del breadcrumb in basso o un nodo dell'albero gerarchia mostra un context menu con tre voci:</p><ul><li><strong>Modifica proprietà</strong> (pi-cog) — seleziona l'elemento come "current tool" e ne mostra il property panel a destra; equivalente al click nell'albero. Utile per editare proprietà di elementi annidati senza dover cercare la voce nella gerarchia.</li><li><strong>Rinomina</strong> (solo breadcrumb in basso) — apre dialog per rinominare il <code>displayName</code>.</li><li><strong>Rimuovi</strong> — elimina l'elemento dalla board.</li></ul><h2>Flusso operativo consigliato</h2><ul><li>Aprire route dashboard target.</li><li>Inserire/collegare nodi dalla palette.</li><li>Configurare <code>inputs</code> essenziali (es. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Salvare e validare il risultato in runtime.</li></ul><h2>Note operative</h2><ul><li>Per fix specifici dashboard, privilegiare patch del <code>boardcontent</code> invece di modifiche globali.</li><li>Evitare proprietà duplicate tra <code>inputs</code> e stile inline non coerente.</li><li>Verificare sempre che i datasource abbiano <code>inputs.route</code> valorizzata.</li></ul>",
|
|
3142
3297
|
"codeSamples": []
|
|
3143
3298
|
}
|
|
3144
3299
|
],
|
|
@@ -3202,14 +3357,18 @@ const frameworkDocsContent = {
|
|
|
3202
3357
|
},
|
|
3203
3358
|
{
|
|
3204
3359
|
"id": "sec_3",
|
|
3205
|
-
"title": "
|
|
3360
|
+
"title": "View Builder Tab — Aggregazioni e GROUP BY (Tier 1, 2026-05-08)"
|
|
3206
3361
|
},
|
|
3207
3362
|
{
|
|
3208
3363
|
"id": "sec_4",
|
|
3209
|
-
"title": "
|
|
3364
|
+
"title": "Flusso operativo consigliato"
|
|
3210
3365
|
},
|
|
3211
3366
|
{
|
|
3212
3367
|
"id": "sec_5",
|
|
3368
|
+
"title": "Pulsanti azione"
|
|
3369
|
+
},
|
|
3370
|
+
{
|
|
3371
|
+
"id": "sec_6",
|
|
3213
3372
|
"title": "Note di utilizzo"
|
|
3214
3373
|
}
|
|
3215
3374
|
],
|
|
@@ -3217,7 +3376,7 @@ const frameworkDocsContent = {
|
|
|
3217
3376
|
{
|
|
3218
3377
|
"id": "overview",
|
|
3219
3378
|
"title": "Overview",
|
|
3220
|
-
"html": "<h1>View/Pivot builder</h1><p>Il Pivot Builder consente di costruire viste analitiche dinamiche partendo dalle route metadata, senza scrivere query manuali lato UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configurazione e risultato del builder in runtime.</li><li><code>manual__pivot_builder__02.png</code>: dettaglio dialog di creazione tabella materializzata e opzioni operative.</li></ul><h2>Funzionalità principali</h2><ul><li>Selezione campi in area <code>rows</code>, <code>columns</code> e <code>values</code>.</li><li>Aggregazioni disponibili sui valori (somma, conteggio, media, minimo, massimo).</li><li>Raggruppamento multidimensionale con ordinamento per dimensione.</li><li>Filtri interattivi per ridurre il dataset analizzato.</li><li>Aggiornamento vista pivot in tempo reale dopo cambio layout o metrica.</li><li>Esportazione/riuso della configurazione pivot nel contesto operativo.</li></ul><h2>Flusso operativo consigliato</h2><p>1. Apri la route pivot builder.</p><p>2. Seleziona datasource/route metadata di partenza.</p><p>3. Trascina i campi nelle zone <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura l'aggregazione per ogni metrica.</p><p>5. Applica filtri e verifica i totali.</p><p>6. Salva o riusa la configurazione secondo il flusso applicativo.</p><h2>Pulsanti azione</h2><p><code>Crea vista</code></p><ul><li>Genera una vista SQL a partire dalla configurazione pivot corrente.</li><li>Utile quando vuoi riusare il risultato come sorgente dati stabile in altre route/report.</li><li>Mantiene la logica centralizzata nel DB, evitando di ricostruire ogni volta il layout manualmente.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Crea una tabella fisica con i dati aggregati della configurazione pivot.</li><li>Utile per dataset grandi o dashboard dove servono tempi di lettura rapidi.</li><li>Scaffolda automaticamente la route associata alla tabella fisica creata e genera una schedulazione per refresh/allineamento dati.</li><li>Premendo Aggiorna sulla grid nella route associata forza un run della schedulazione.</li></ul><h2>Note di utilizzo</h2><ul><li>Per analisi coerenti, usare colonne con tipologia dati corretta (numeriche per metriche, testuali/date per dimensioni).</li><li>In presenza di grandi volumi, preferire filtri e aggregazioni mirate prima di espandere il dettaglio.</li></ul>",
|
|
3379
|
+
"html": "<h1>View/Pivot builder</h1><p>Il Pivot Builder consente di costruire viste analitiche dinamiche partendo dalle route metadata, senza scrivere query manuali lato UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configurazione e risultato del builder in runtime.</li><li><code>manual__pivot_builder__02.png</code>: dettaglio dialog di creazione tabella materializzata e opzioni operative.</li></ul><h2>Funzionalità principali</h2><ul><li>Selezione campi in area <code>rows</code>, <code>columns</code> e <code>values</code>.</li><li>Aggregazioni disponibili sui valori (somma, conteggio, media, minimo, massimo).</li><li>Raggruppamento multidimensionale con ordinamento per dimensione.</li><li>Filtri interattivi per ridurre il dataset analizzato.</li><li>Aggiornamento vista pivot in tempo reale dopo cambio layout o metrica.</li><li>Esportazione/riuso della configurazione pivot nel contesto operativo.</li></ul><h2>View Builder Tab — Aggregazioni e GROUP BY (Tier 1, 2026-05-08)</h2><p>Il Tab 1 "View Builder" oltre a tabelle/JOIN ora permette di emettere <code>GROUP BY</code> con aggregazioni <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> direttamente dalla <code>ViewDefinition</code>, senza passare in modalità manualSql.</p><p>Pannello sotto il canvas:</p><ul><li><strong>Aggregazioni</strong>: form per aggiungere <code>funzione(colonna) AS alias</code>. La colonna source può essere qualsiasi colonna del canvas o <code>*</code> (per <code>COUNT(*)</code>); l'alias output è auto-generato se vuoto.</li><li><strong>GROUP BY</strong>: checkbox per ogni colonna selezionata sui nodi del canvas. Se nessuna è marcata esplicitamente, il backend deduce le dimensioni dalle colonne <code>selected=true</code> non aggregate.</li><li><strong>CASE WHEN per pivoting</strong>: per pattern come <code>SUM(CASE WHEN bucket='X' THEN importo ELSE 0 END)</code> usa una colonna formula (right-click → Edit formula) come source dell'aggregazione. La formula viene emessa come argomento dell'aggregato.</li></ul><p>Output backend (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → emit <code><FN>(<col|formula>) AS <alias></code> come colonne SELECT aggiuntive</li><li><code>viewDefinition.groupByColumns[]</code> → emit <code>GROUP BY</code> clause</li><li>Auto-inferenza GROUP BY se <code>aggregations</code> presenti senza <code>groupByColumns</code> esplicito</li></ul><h3>UNION blocks (Tier 2, 2026-05-08)</h3><p>Un'estensione successiva permette di concatenare più SELECT con <code>UNION</code> o <code>UNION ALL</code> direttamente da una singola <code>ViewDefinition</code>:</p><ul><li><strong>Block 0</strong> = top-level (le tabelle/joins/aggregations sul canvas) → definisce il <strong>vocabolario canonico</strong> di colonne SELECT</li><li><strong>Block N (N≥1)</strong> = ogni <code>unionBlocks[]</code> aggiuntivo è una sub-definition single-table con un <code>columnMapping</code> obbligatorio: per ogni canonical alias del block 0, il block N specifica <code>sourceQualified</code> (colonna del block) <strong>o</strong> <code>constantValue</code> (literal SQL come <code>'CLIENTE'</code>) <strong>o</strong> <code>formula</code> (espressione SQL per cast cross-block)</li><li><strong>Toggle UNION ALL / UNION</strong>: default <code>unionAll: true</code> (no dedup, performance migliore); <code>false</code> per <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: applicato all'intera UNION, wrappata in subquery <code>SELECT TOP N * FROM (<union>) AS u</code></li></ul><p>UI sotto il canvas: select route + bottone "Add UNION block" → ogni block compare come accordion con header (caption + route) + matrix column-mapping (per canonical alias: dropdown source col del block, oppure input constant SQL).</p><p>Limiti v0: ogni block UNION è single-table (no JOIN within block). Per cross-block JOIN (es. categoria 'ENTRAMBI' di <code>vw_anagrafica_unificata</code> che richiede <code>clienti INNER JOIN fornitori ON piva</code>) serve Tier 2.5.</p><h2>Flusso operativo consigliato</h2><p>1. Apri la route pivot builder.</p><p>2. Seleziona datasource/route metadata di partenza.</p><p>3. Trascina i campi nelle zone <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura l'aggregazione per ogni metrica.</p><p>5. Applica filtri e verifica i totali.</p><p>6. Salva o riusa la configurazione secondo il flusso applicativo.</p><h2>Pulsanti azione</h2><p><code>Crea vista</code></p><ul><li>Genera una vista SQL a partire dalla configurazione pivot corrente.</li><li>Utile quando vuoi riusare il risultato come sorgente dati stabile in altre route/report.</li><li>Mantiene la logica centralizzata nel DB, evitando di ricostruire ogni volta il layout manualmente.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Crea una tabella fisica con i dati aggregati della configurazione pivot.</li><li>Utile per dataset grandi o dashboard dove servono tempi di lettura rapidi.</li><li>Scaffolda automaticamente la route associata alla tabella fisica creata e genera una schedulazione per refresh/allineamento dati.</li><li>Premendo Aggiorna sulla grid nella route associata forza un run della schedulazione.</li></ul><h2>Note di utilizzo</h2><ul><li>Per analisi coerenti, usare colonne con tipologia dati corretta (numeriche per metriche, testuali/date per dimensioni).</li><li>In presenza di grandi volumi, preferire filtri e aggregazioni mirate prima di espandere il dettaglio.</li></ul>",
|
|
3221
3380
|
"codeSamples": []
|
|
3222
3381
|
}
|
|
3223
3382
|
],
|
|
@@ -4172,7 +4331,7 @@ const frameworkDocsContent = {
|
|
|
4172
4331
|
{
|
|
4173
4332
|
"id": "overview",
|
|
4174
4333
|
"title": "Overview",
|
|
4175
|
-
"html": "<h1>Catalogo Componenti</h1><p>Totale cartelle componenti rilevate:
|
|
4334
|
+
"html": "<h1>Catalogo Componenti</h1><p>Totale cartelle componenti rilevate: 50.</p><h2>Componenti disponibili</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>azienda-switcher</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Export da public-api</h2><ul><li>data-source/data-source.component</li><li>azienda-switcher/azienda-switcher.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
|
|
4176
4335
|
"codeSamples": []
|
|
4177
4336
|
}
|
|
4178
4337
|
],
|
|
@@ -4293,13 +4452,21 @@ const frameworkDocsContent = {
|
|
|
4293
4452
|
{
|
|
4294
4453
|
"id": "sec_3",
|
|
4295
4454
|
"title": "Esempi"
|
|
4455
|
+
},
|
|
4456
|
+
{
|
|
4457
|
+
"id": "sec_4",
|
|
4458
|
+
"title": "Patch runtime di `metaInfo` via `fetchInfo$.subscribe`"
|
|
4459
|
+
},
|
|
4460
|
+
{
|
|
4461
|
+
"id": "sec_5",
|
|
4462
|
+
"title": "Multipli `<wuic-data-source>` nello stesso componente"
|
|
4296
4463
|
}
|
|
4297
4464
|
],
|
|
4298
4465
|
"sections": [
|
|
4299
4466
|
{
|
|
4300
4467
|
"id": "overview",
|
|
4301
4468
|
"title": "Overview",
|
|
4302
|
-
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> e il componente che gestisce metadata, fetch dati, stato corrente, tracking modifiche e sync CRUD.</p><h2>Descrizione</h2><ul><li>Risolve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carica metadata tabella/colonne e pubblica stato su <code>fetchInfo$</code>.</li><li>Gestisce <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) e cursor paging.</li><li>Espone metodi runtime per insert/update/delete, batch save, rollback e export.</li><li>E il punto centrale a cui si agganciano <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> e <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Input principali:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Stato/runtime principale:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (cancellabile)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Metodi runtime piu usati:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventi runtime:</p><ul><li><code>fetchInfo$</code>: stream principale stato dati/metadati.</li><li><code>datasourceReady$</code>: emesso quando l'istanza datasource e pronta.</li><li><code>afterFirstLoad$</code>: emesso una sola volta al primo payload utile.</li><li><code>beforeSync$</code>: emesso prima di <code>insert/update/delete/clone/batch</code>; puoi annullare con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emesso dopo sync riuscita.</li></ul><p>Nota compatibilita:</p><ul><li>e disponibile anche alias <code>fetchInfo</code> (getter/setter), ma il naming consigliato e <code>fetchInfo$</code>.</li></ul><h2>Esempi</h2><h3>Esempio base (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Esempio master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Esempio operativo (fetch manuale + sync)</h3><p>Snippet 3:</p><h3>Esempio subscriptions (host)</h3><p>Snippet 4:</p>",
|
|
4469
|
+
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> e il componente che gestisce metadata, fetch dati, stato corrente, tracking modifiche e sync CRUD.</p><h2>Descrizione</h2><ul><li>Risolve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carica metadata tabella/colonne e pubblica stato su <code>fetchInfo$</code>.</li><li>Gestisce <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) e cursor paging.</li><li>Espone metodi runtime per insert/update/delete, batch save, rollback e export.</li><li>E il punto centrale a cui si agganciano <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> e <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Input principali:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Stato/runtime principale:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (cancellabile)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Metodi runtime piu usati:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventi runtime:</p><ul><li><code>fetchInfo$</code>: stream principale stato dati/metadati.</li><li><code>datasourceReady$</code>: emesso quando l'istanza datasource e pronta.</li><li><code>afterFirstLoad$</code>: emesso una sola volta al primo payload utile.</li><li><code>beforeSync$</code>: emesso prima di <code>insert/update/delete/clone/batch</code>; puoi annullare con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emesso dopo sync riuscita.</li></ul><p>Nota compatibilita:</p><ul><li>e disponibile anche alias <code>fetchInfo</code> (getter/setter), ma il naming consigliato e <code>fetchInfo$</code>.</li></ul><h2>Esempi</h2><h3>Esempio base (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Esempio master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Esempio operativo (fetch manuale + sync)</h3><p>Snippet 3:</p><h3>Esempio subscriptions (host)</h3><p>Snippet 4:</p><h2>Patch runtime di <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Per modificare runtime i metadati di un nested datasource (es. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>senza toccare il DB</strong> — patch locale al riferimento cached dal <code>MetadataProviderService</code>, latest-wins, single-dialog scenario — sottoscrivi a <code>fetchInfo$</code> del data-source:</p><p>Snippet 5:</p><p><strong>Scope</strong>:</p><ul><li><strong>session-only + istanza-only</strong>: la patch sopravvive solo finche' il <code>MetadataProviderService</code> cache non viene invalidata o il componente non e' distrutto.</li><li><strong>utile</strong> quando un custom-form vuole una rappresentazione diversa di una nested grid rispetto al rendering standalone (es. nascondere colonne ridondanti in dialog).</li></ul><p><strong>Quando NON usare patch runtime</strong>:</p><ul><li>Per modifiche destinate a tutte le istanze della route (anche standalone <code><route>/list</code>) → patch SQL diretta sui metadati (vedi skill <code>metadata-tables-columns</code>).</li><li>Per modifiche persistenti tra restart → SQL diretto + <code>MetaService.invalidateMetadataRuntime</code>.</li></ul><h2>Multipli <code><wuic-data-source></code> nello stesso componente</h2><p>Quando un componente custom contiene piu' di un <code><wuic-data-source></code> (es. master + nested righe + nested scadenze), il pattern <strong>`@ViewChild(DataSourceComponent)`</strong> by-type matcha <strong>solo il primo</strong> data-source nell'ordine top-down del template. Per i successivi serve <strong>template reference variable</strong> + <code>@ViewChild('refName')</code>:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong>: <code>document.querySelectorAll('wuic-data-source')</code> + <code>window.ng.getComponent(el)</code> per filtrare per <code>hardcodedRoute</code>. Funziona ma e' fragile, dipende dalla debug API <code>window.ng</code> (non garantita in produzione). Usa sempre template ref.</p>",
|
|
4303
4470
|
"codeSamples": [
|
|
4304
4471
|
{
|
|
4305
4472
|
"id": "code_1",
|
|
@@ -4324,6 +4491,24 @@ const frameworkDocsContent = {
|
|
|
4324
4491
|
"title": "Snippet 4",
|
|
4325
4492
|
"language": "ts",
|
|
4326
4493
|
"code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Delete non consentito dal contesto host');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
|
|
4494
|
+
},
|
|
4495
|
+
{
|
|
4496
|
+
"id": "code_5",
|
|
4497
|
+
"title": "Snippet 5",
|
|
4498
|
+
"language": "ts",
|
|
4499
|
+
"code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n // Patch tableMetadata, mc_logic_editable, ecc. allo stesso modo\n });\n this.subs.add(sub); // cleanup in ngOnDestroy\n}"
|
|
4500
|
+
},
|
|
4501
|
+
{
|
|
4502
|
+
"id": "code_6",
|
|
4503
|
+
"title": "Snippet 6",
|
|
4504
|
+
"language": "html",
|
|
4505
|
+
"code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
|
|
4506
|
+
},
|
|
4507
|
+
{
|
|
4508
|
+
"id": "code_7",
|
|
4509
|
+
"title": "Snippet 7",
|
|
4510
|
+
"language": "ts",
|
|
4511
|
+
"code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Primo data-source (matcha by type) -> niente template ref necessario\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Secondo data-source -> serve `#scadenzeDs` nel template + by-name ViewChild\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
|
|
4327
4512
|
}
|
|
4328
4513
|
]
|
|
4329
4514
|
}
|
|
@@ -5909,6 +6094,82 @@ const frameworkDocsContent = {
|
|
|
5909
6094
|
],
|
|
5910
6095
|
"images": []
|
|
5911
6096
|
},
|
|
6097
|
+
{
|
|
6098
|
+
"slug": "multi-tenant",
|
|
6099
|
+
"lang": "it-IT",
|
|
6100
|
+
"title": "Multi-tenant",
|
|
6101
|
+
"description": "Multi-tenant - documentazione operativa e tecnica",
|
|
6102
|
+
"audience": [
|
|
6103
|
+
"developer",
|
|
6104
|
+
"integration"
|
|
6105
|
+
],
|
|
6106
|
+
"status": "stable",
|
|
6107
|
+
"tags": [
|
|
6108
|
+
"advanced"
|
|
6109
|
+
],
|
|
6110
|
+
"keywords": [
|
|
6111
|
+
"Multi-tenant"
|
|
6112
|
+
],
|
|
6113
|
+
"toc": [
|
|
6114
|
+
{
|
|
6115
|
+
"id": "sec_1",
|
|
6116
|
+
"title": "Scopo"
|
|
6117
|
+
},
|
|
6118
|
+
{
|
|
6119
|
+
"id": "sec_2",
|
|
6120
|
+
"title": "Abilitazione"
|
|
6121
|
+
},
|
|
6122
|
+
{
|
|
6123
|
+
"id": "sec_3",
|
|
6124
|
+
"title": "Modello dati"
|
|
6125
|
+
},
|
|
6126
|
+
{
|
|
6127
|
+
"id": "sec_4",
|
|
6128
|
+
"title": "Login fallback"
|
|
6129
|
+
},
|
|
6130
|
+
{
|
|
6131
|
+
"id": "sec_5",
|
|
6132
|
+
"title": "Switch azienda (superadmin)"
|
|
6133
|
+
},
|
|
6134
|
+
{
|
|
6135
|
+
"id": "sec_6",
|
|
6136
|
+
"title": "Cache tenant-scoped"
|
|
6137
|
+
},
|
|
6138
|
+
{
|
|
6139
|
+
"id": "sec_7",
|
|
6140
|
+
"title": "Anti-hijack"
|
|
6141
|
+
},
|
|
6142
|
+
{
|
|
6143
|
+
"id": "sec_8",
|
|
6144
|
+
"title": "Scheduler tenant-aware"
|
|
6145
|
+
},
|
|
6146
|
+
{
|
|
6147
|
+
"id": "sec_9",
|
|
6148
|
+
"title": "Propagazione scaffolding cross-tenant"
|
|
6149
|
+
},
|
|
6150
|
+
{
|
|
6151
|
+
"id": "sec_10",
|
|
6152
|
+
"title": "Account utente non-superadmin attraverso più tenant"
|
|
6153
|
+
},
|
|
6154
|
+
{
|
|
6155
|
+
"id": "sec_11",
|
|
6156
|
+
"title": "Limiti noti"
|
|
6157
|
+
},
|
|
6158
|
+
{
|
|
6159
|
+
"id": "sec_12",
|
|
6160
|
+
"title": "AppSettings rilevanti"
|
|
6161
|
+
}
|
|
6162
|
+
],
|
|
6163
|
+
"sections": [
|
|
6164
|
+
{
|
|
6165
|
+
"id": "overview",
|
|
6166
|
+
"title": "Overview",
|
|
6167
|
+
"html": "<h1>Multi-tenant</h1><p>Routing connessioni per-utente: ogni azienda ha le proprie connection string verso DB metadati e dati separati, mantenendo retro-compatibilità totale per le installazioni single-tenant esistenti.</p><h2>Scopo</h2><p>Il modulo multi-tenant permette a un singolo backend di servire più aziende — ciascuna su DB metadati e dati distinti — senza duplicare codice o avviare istanze separate. Quando il flag è OFF il comportamento è identico al pre-multi-tenant (zero overhead, cache invariate). Quando il flag è ON il framework:</p><ul><li>risolve a runtime la connection string giusta in base all'<code>id_azienda</code> dell'utente loggato.</li><li>segrega le cache server (<code>Application[]</code>) e client (Dexie + localStorage) per tenant.</li><li>propaga il <code>TenantScope.Current</code> su tutte le query, scheduler e job background.</li></ul><h2>Abilitazione</h2><p>In <code>appsettings.json</code> (sezione <code>AppSettings</code>):</p><ul><li><code>"multiConnectionEnabled": "true"</code> — attiva il routing tenant-aware in <code>MultiTenantHelpers</code> + middleware <code>TenantScope</code> in <code>Startup.cs</code>.</li><li><code>"enableCookieAuthentication": "true"</code> — richiesto dal flusso multi-tenant: il cookie <code>k-user</code> v:2 contiene <code>azienda_id</code> + <code>azienda_id_user</code> per anti-hijack.</li></ul><p>Sono disponibili i template stand-alone <code>appsettings.multi-tenant.mssql.json</code> e <code>appsettings.multi-tenant.mysql.json</code> (6 connection string: 2 default + 4 tenant <code>Tenant1_Meta</code>/<code>Tenant1_Data</code>/<code>Tenant2_Meta</code>/<code>Tenant2_Data</code>).</p><h2>Modello dati</h2><p>Il mapping <code>id_azienda → (data, meta)</code> vive su due colonne della tabella <code>Aziende</code> del DB metadati primario:</p><ul><li><code>Aziende.Connessione_DB_Dati</code>: NOME di una entry in <code>appsettings.ConnectionStrings</code> per il DB dati del tenant (NON la connection string letterale).</li><li><code>Aziende.CONNESSIONE_DB_Meta</code>: NOME di una entry in <code>appsettings.ConnectionStrings</code> per il DB metadati del tenant.</li></ul><p>Esempio popolazione:</p><ul><li><code>Aziende.ID_Azienda=1</code>, <code>Connessione_DB_Dati='Tenant1_Data'</code>, <code>CONNESSIONE_DB_Meta='Tenant1_Meta'</code>.</li><li><code>Aziende.ID_Azienda=100</code> (Sede principale), <code>Connessione_DB_Dati='DataSQLConnection'</code>, <code>CONNESSIONE_DB_Meta='MetaDataSQLConnection'</code> (alias dei default → coperto dallo scope 0).</li></ul><p>Indirezione via nome entry:</p><ul><li>niente password in chiaro nel DB.</li><li>credenziali portabili per ambiente (stesso DB, diversi <code>appsettings.<env>.json</code>).</li><li>coerenza con <code>_metadati__tabelle.md_conn_name</code>.</li></ul><h2>Login fallback</h2><p>Quando un utente NON esiste sul DB metadati primario il login fa fallback via <code>_login_index</code>:</p><p>1. lookup utente su <code>MetaDataSQLConnection</code> (primario) → trovato? login.</p><p>2. miss → query <code>_login_index</code> (<code>SHA-256(LOWER(username))</code> → <code>id_azienda</code>).</p><p>3. per ogni <code>id_azienda</code> candidato: push <code>TenantScope</code>, retry <code>metaQuery.login</code> sul DB metadati del tenant.</p><p>4. primo match wins → cookie emesso con <code>azienda_id</code> del tenant.</p><p>Lo SP <code>sp__login_index_upsert(@username, @idAzienda)</code> popola l'indice. Hash SHA-256 calcolato server-side (allineato col client C# <code>MultiTenantHelpers.ComputeUsernameHash</code> su UTF-8).</p><h2>Switch azienda (superadmin)</h2><p>I superadmin possono cambiare l'azienda attiva runtime via <code>MetaService.switchAzienda(idAzienda)</code>:</p><ul><li>401 se non superadmin.</li><li>400 se l'<code>idAzienda</code> non ha entrambe le colonne <code>Aziende.Connessione_DB_Dati</code> / <code>CONNESSIONE_DB_Meta</code> valorizzate.</li><li>riemette cookie con <code>azienda_id</code> aggiornato, preservando <code>azienda_id_user</code> (snapshot al login).</li><li>componente Angular <code><wuic-azienda-switcher></code> espone il dropdown UI. Si auto-nasconde via <code>@HostBinding('attr.hidden')</code> per utenti non-super o flag OFF.</li></ul><h2>Cache tenant-scoped</h2><p>Server-side (<code>Application[]</code> keys):</p><ul><li><code>MultiTenantHelpers.TenantKey(baseKey)</code> aggiunge suffisso <code>__a<id></code> quando flag ON e <code>TenantScope.Current.AziendaId > 0</code>.</li><li>chiavi affette: <code>userList</code>, <code>roleList</code>, <code>userRoleList</code>, <code>companyList</code>, <code>storedMeta</code>, <code>storedTableMeta</code>, <code>storedTableActionMeta</code>, <code>storedConditionGroupMeta</code>, <code>storedTableStyleMeta</code>, <code>SysInfo</code>.</li></ul><p>Client-side (Angular framework lib):</p><ul><li>Dexie name → <code>MetaDB__a<id></code> e <code>WuicClientSideCrudDB__a<id></code>.</li><li>localStorage keys → <code>menu_<userId>__a<id></code>, <code>wuic_custom_settings_<userId>__a<id></code>, ecc.</li><li>orphan cleanup all'init di <code>MetadataProviderService</code>: enumera <code>indexedDB.databases()</code> e droppa DB di tenant non più correnti.</li></ul><p>Quando flag OFF: TenantKey ritorna la baseKey invariata → cache identica al pre-multi-tenant.</p><h2>Anti-hijack</h2><p><code>RawHelpers.authenticate()</code> ad ogni request:</p><ul><li>richiede cookie v:2 con <code>azienda_id</code> + <code>azienda_id_user</code> (cookie legacy → relogin forzato).</li><li>per utenti non-super, <code>cookie.azienda_id</code> DEVE coincidere con <code>cookie.azienda_id_user</code> (manomissione → relogin + security log).</li><li>i superadmin sono esenti (la loro <code>azienda_id</code> cambia legittimamente via <code>switchAzienda</code>).</li><li>popola <code>TenantScope.Current</code> con <code>(azienda_id, isSuperAdmin)</code> per il resto della request.</li></ul><p>Il middleware <code>HydrateLegacyPrincipalFromKUserCookie</code> in <code>Startup.cs</code> popola <code>TenantScope</code> anche per endpoint AsmxProxy che non chiamano <code>authenticate()</code> esplicitamente (es. <code>readCustomSettings</code>, <code>getMenuByUserID</code>).</p><h2>Scheduler tenant-aware</h2><p><code>SchedulerHostedService</code> enumera le aziende a ogni ciclo (<code>Scheduler:PollSeconds</code>, default 15s):</p><ul><li>scope 0 (primario / no-tenant) — sempre processato.</li><li>più uno scope per ciascuna azienda con mapping conn ≠ default.</li><li>aziende mappate alle entry default (<code>MetaDataSQLConnection</code>) sono skippate (coperte dallo scope 0).</li></ul><p>Tutti e 4 i tipi action sono supportati per-tenant:</p><ul><li><code>1 | sql</code>: connection dati via routing tenant-aware.</li><li><code>2 | webservice</code>: utente <code>__scheduler</code> creato in ogni DB tenant; cookie session per-tenant via <code>switchAzienda</code> HTTP call.</li><li><code>3 | assembly method</code>: <code>TenantScope.Current</code> propagato via AsyncLocal al metodo invocato.</li><li><code>4 | mailing</code>: <code>_mailing_lists</code> / <code>_mail_recipients</code> letti dal DB del tenant corrente; SMTP settings override-able per-tenant via naming convention <code><key>__a<id></code> (es. <code>email-host__a1</code>).</li></ul><p>Lo schema scheduler (<code>scheduler</code>, <code>scheduler_execution</code>) viene validato per-tenant: tenant senza le tabelle vengono skippati silenziosamente fino al restart.</p><h2>Propagazione scaffolding cross-tenant</h2><p>Quando un superadmin scaffolda una tabella o vista su un tenant, di default i metadata (<code>_metadati__tabelle</code> + <code>_metadati__colonne</code>) vengono scritti <strong>solo sul DB metadati del tenant corrente</strong>. Per evitare di dover ripetere manualmente lo scaffolding su ogni tenant, l'endpoint <code>scaffolding.scaffoldTable</code> / <code>scaffolding.scaffoldView</code> espone il parametro booleano <code>propagateToTenants</code>:</p><ul><li><code>false</code> (default): scaffold solo sul tenant corrente (back-compat).</li><li><code>true</code>: dopo aver scaffoldato il tenant corrente, il backend itera su tutti gli altri tenant attivi (<code>MultiTenantHelpers.GetAziendePrimary()</code>) e replica lo scaffolding sul DB metadati di ciascuno. Lo schema viene letto dal DB DATI del tenant target risolto via <code>Aziende.Connessione_DB_Dati</code>. Tenant senza la tabella fisica vengono skippati con esito <code>SKIPPED:not-found</code> → operazione best-effort, non transazionale cross-tenant.</li></ul><p>Sicurezza:</p><ul><li>gated a superadmin con flag <code>multiConnectionEnabled=true</code> — per gli altri il parametro viene silenziosamente ignorato.</li><li>aziende mappate alle entry default (<code>MetaDataSQLConnection</code>) sono skippate (coperte dallo scope 0 del tenant corrente se il superadmin è già lì).</li></ul><p>Il risultato del scaffold include chiavi <code>tenant_<id>:</code> con valore <code>OK</code> / <code>SKIPPED:<reason></code> / <code>FAILED:<message></code> per diagnosi per-tenant.</p><p>UI: il dialog di scaffolding (<code>md_id=1556</code>) espone una checkbox <strong>Propagate to all tenants</strong> sul form; l'action <code>Scaffold Table</code> invia il flag al backend. In single-tenant (<code>multiConnectionEnabled=false</code>) il backend filtra la colonna dalla risposta di <code>getTableMetadata</code> → la checkbox non compare nel form (niente UI inutile da gestire).</p><h2>Account utente non-superadmin attraverso più tenant</h2><p>Il selettore <code><wuic-azienda-switcher></code> è visibile <strong>solo ai superadmin</strong>. Per gli utenti non-superadmin il check anti-hijack (<code>cookie.azienda_id != user.azienda_id_user → reject</code>) impedisce di cambiare tenant runtime: il cookie ha <code>azienda_id_user</code> snapshottato al login e immutabile.</p><p>Convenzione operativa per scenari in cui la stessa persona deve accedere a più tenant come utente standard:</p><ul><li><strong>creare un'utenza separata per ogni tenant</strong>, con <strong>username distinto</strong> (es. <code>mario.rossi@T1</code>, <code>mario.rossi@T2</code>, oppure <code>mario.rossi.t1</code> / <code>mario.rossi.t2</code>).</li><li>ogni utenza vive nel <code>utenti</code> del proprio DB tenant, con <code>id_azienda</code> corrispondente.</li><li><code>_login_index</code> mappa ciascun username al proprio <code>id_azienda</code>: niente ambiguità nel fallback chain.</li><li>l'utente sceglie a quale tenant accedere cambiando username al login.</li></ul><p>Anti-pattern da evitare:</p><ul><li><strong>stesso username su due tenant</strong>: il login fallback prova entrambi i candidati <code>_login_index</code> in ordine di <code>id_azienda</code> ascendente; il primo che valida la password vince. L'utente entra sempre sul tenant con <code>id_azienda</code> più basso e non può raggiungere l'altro senza widget switcher. La password resta condivisa cross-tenant, esposta a brute-force su entrambi i DB.</li></ul><p>Se la persona è un superadmin, basta un'unica utenza sul DB primario (<code>id_azienda</code> = id azienda madre): il widget switcher copre runtime il cambio tenant.</p><h2>Limiti noti</h2><ul><li><strong>DBMS uniforme cross-tenant</strong>: tutti i tenant DEVONO condividere lo stesso DBMS del primario (<code>AppSettings.dbms</code> / <code>meta-dbms</code>). Il routing tenant-aware instrada solo il <em>catalog</em> (quale DB), non <em>quale provider</em>. Es: backend MSSQL + un <code>Tenant2_Meta</code> con connection string MySQL → <code>SqlException</code> al primo accesso al tenant. Una validazione fail-soft (WARN log) viene eseguita all'avvio (<code>MultiTenantHelpers.ValidateTenantConnectionStrings</code>): per ogni cs referenziata da <code>Aziende.{Connessione_DB_Dati,CONNESSIONE_DB_Meta}</code> rileva tramite heuristic (keyword come <code>Port=3306</code>, <code>Initial Catalog=</code>, <code>(HOST=...)</code>, ecc.) il DBMS target e logga warning se differente da quello configurato. Mismatch → boot prosegue ma il tenant disallineato sara' inutilizzabile a runtime.</li><li>l'utente <code>__scheduler</code> creato sui DB tenant ha <code>id_azienda=NULL</code> (non valorizzato): adeguato per il flow webservice (login parte sempre dal primario), ma non risolvibile via <code>_login_index</code> per casi inversi.</li><li>ogni nuovo tenant richiede una entry in <code>appsettings.ConnectionStrings</code> + redeploy/reload config.</li><li>job scheduler in tenant senza tabella <code>scheduler</code> vengono skippati silenziosamente (info-log al primo poll).</li></ul><h2>AppSettings rilevanti</h2><p>Snippet (chiavi multi-tenant):</p><ul><li><code>"AppSettings.multiConnectionEnabled": "true"</code></li><li><code>"AppSettings.enableCookieAuthentication": "true"</code></li><li><code>"ConnectionStrings.MetaDataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.DataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Data": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Data": "..."</code></li><li>(opzionale) <code>"AppSettings.email-host__a1": "smtp.tenant1.example"</code> — override SMTP per tenant 1.</li></ul>",
|
|
6168
|
+
"codeSamples": []
|
|
6169
|
+
}
|
|
6170
|
+
],
|
|
6171
|
+
"images": []
|
|
6172
|
+
},
|
|
5912
6173
|
{
|
|
5913
6174
|
"slug": "troubleshooting",
|
|
5914
6175
|
"lang": "it-IT",
|
|
@@ -6800,26 +7061,34 @@ const frameworkDocsContent = {
|
|
|
6800
7061
|
},
|
|
6801
7062
|
{
|
|
6802
7063
|
"id": "sec_6",
|
|
6803
|
-
"title": "md_props_bag:
|
|
7064
|
+
"title": "md_props_bag: toolbar"
|
|
6804
7065
|
},
|
|
6805
7066
|
{
|
|
6806
7067
|
"id": "sec_7",
|
|
6807
|
-
"title": "
|
|
7068
|
+
"title": "md_props_bag: archetypes.list"
|
|
6808
7069
|
},
|
|
6809
7070
|
{
|
|
6810
7071
|
"id": "sec_8",
|
|
6811
|
-
"title": "
|
|
7072
|
+
"title": "Paging: md_pagesize and md_page_size_choice"
|
|
6812
7073
|
},
|
|
6813
7074
|
{
|
|
6814
7075
|
"id": "sec_9",
|
|
7076
|
+
"title": "Forced Virtualization on High Page Size"
|
|
7077
|
+
},
|
|
7078
|
+
{
|
|
7079
|
+
"id": "sec_10",
|
|
6815
7080
|
"title": "Events and Subscriptions (Host)"
|
|
7081
|
+
},
|
|
7082
|
+
{
|
|
7083
|
+
"id": "sec_11",
|
|
7084
|
+
"title": "`rowCustomSelect` — actual signature (gotcha)"
|
|
6816
7085
|
}
|
|
6817
7086
|
],
|
|
6818
7087
|
"sections": [
|
|
6819
7088
|
{
|
|
6820
7089
|
"id": "overview",
|
|
6821
7090
|
"title": "Overview",
|
|
6822
|
-
"html": "<h1>List Grid</h1><p>Main component for tabular lists with filters, sorting, server-side paging, and row actions.</p><h2>Use Cases</h2><ul><li>Enterprise tabular CRUD.</li><li>Operational reports with multi-column filters.</li><li>Very large datasets with <code>cursorMode</code>.</li></ul><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__grid__01.png</code>: <code>inline cell edit</code> mode in list-grid.</li><li><code>manual__grid__02.png</code>: <code>inline edit</code> (row-level) mode in list-grid.</li></ul><h2>Inline Editing Metadata</h2><p>Configuration in the table metadata <code>md_props_bag</code>:</p><ul><li><code>md_inline_edit</code></li></ul><p> - Enables inline row editing in list-grid (editable cells in row context, without opening a popup).</p><p> - Useful when you want to maintain the table UX with quick editing per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - Enables "cell-by-cell" inline editing (focus on the individual cell).</p><p> - Note: in legacy configurations it may appear as <code>md_inline_cell_editing</code>; the runtime behavior is the same.</p><p> - <strong>Runtime promotion</strong>: when <code>md_inline_cell_edit</code> is <code>true</code>, the component forces</p><p> <code>md_inline_edit = true</code> at runtime regardless of the DB value.</p><p> Both UX modes need the action column visible, so they are mutually</p><p> non-exclusive at the rendering level.</p><ul><li><code>md_batch_save</code></li></ul><p> - Enables batch saving of pending changes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - When used with inline-cell, changes remain pending until explicit save.</p><p> - <strong>Prerequisite</strong>: <code>md_batch_save</code> takes effect <strong>only</strong> if <code>md_inline_cell_edit</code> is <code>true</code>.</p><p> With <code>md_inline_cell_edit:false</code> the flag is ignored at runtime and the</p><p> "Save changes / Cancel changes" buttons are not rendered.</p><h3>Valid Combinations</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime Result</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>any</td><td>Row-level inline edit with pencil. <code>md_batch_save</code> ignored.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forced to <code>true</code> at runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell with pending buffer + <code>Save / Cancel changes</code> toolbar.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>any</td><td>No inline editing. <code>md_batch_save</code> ignored.</td></tr></tbody></table><p>Row-level inline edit example:</p><p>Snippet 1:</p><p>Cell-by-cell with batch save example:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - Shows a progress dialog with real-time percentage;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel task</code>, <code>Stop and download partial</code>;</p><p> - In background creates a notification with progress; click reopens the dialog.</p><ul><li>Import XLS/XLSX (if <code>md_importable = true</code>):</li></ul><p> - Progress dialog after confirmation;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel import (rollback)</code>, <code>Stop and partial commit</code>;</p><p> - At the end of import creates a summary notification that navigates to the route.</p><h2>Metadata Config</h2><p>Key settings in <code>md_props_bag</code> and column metadata.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>The component reads <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>type: boolean (with tolerant parser)</li><li>accepted values: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>runtime default: <code>true</code> when absent or empty</li><li>effect: distributes column widths proportionally (instead of px) when no user-persisted widths exist.</li></ul><ul><li><code>virtualize</code>:</li><li>type: <code>boolean</code> or <code>object</code></li><li>accepted values:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object with <code>enabled</code> (optional) and <code>itemSize</code> (optional)</li><li>runtime default: disabled (<code>false</code>) when absent</li><li><code>enabled</code>:</li><li>default: <code>true</code> if the <code>virtualize</code> node is an object without <code>enabled</code>, otherwise tolerant parser</li><li>effect: enables <code>virtualScroll</code> on <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effect: sets <code>virtualScrollItemSize</code> (virtual row height in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>type: boolean (with tolerant parser)</li><li>runtime default: <code>false</code></li><li>effect:</li><li>when <code>true</code>, the grid hides column filter icons (<code>p-columnFilter</code>) and uses the <code>wuic-filter-bar</code> above the table (at <code>data-repeater</code>/<code>bounded-repeater</code> level) to apply filters.</li></ul><p>Operational note:</p><ul><li>The <code>md_props_bag</code> suggest in the metadata editor also exposes the checkable <code>archetypes.list.advancedFilter</code> node.</li></ul><h2>Paging: md_pagesize and md_page_size_choice</h2><p>The grid uses <code>md_pagesize</code> as the default page size and <code>md_page_size_choice</code> as the list of selectable values in the paginator.</p><p>Runtime rules:</p><ul><li>If <code>md_pagesize</code> is greater than the maximum value present in <code>md_page_size_choice</code>, the framework automatically adds <code>md_pagesize</code> to the options list;</li><li>The list is normalized (valid numbers, deduplication, ascending sort).</li></ul><p>Example:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>runtime result: <code>10,25,50,100,200</code></li></ul><h2>Forced Virtualization on High Page Size</h2><p>When the user selects in the paginator a <code>pageSize >= 1000</code>:</p><ul><li>virtualization is automatically forced even if <code>md_props_bag.archetypes.list.virtualize</code> is absent or disabled;</li><li><code>virtualScrollItemSize</code> is forced to the default value <code>44</code>.</li></ul><p>This behavior protects the table rendering on very large pages.</p><h2>Events and Subscriptions (Host)</h2><p><code>wuic-list-grid</code> exposes runtime events useful for intercepting the render cycle and p-table callbacks on the host project side.</p><p>Available events:</p><ul><li><code>onAfterRender</code>: emitted at the end of grid data binding (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitted before the logical rendering of a single row; supports cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitted after the logical rendering of a single row.</li><li><code>onPaging</code>: emitted on paging events (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitted on sorting events (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitted on filtering events (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitted on row selection change.</li><li><code>onPTableRowExpand</code>: emitted on row expand.</li><li><code>onPTableRowCollapse</code>: emitted on row collapse.</li><li><code>onPTableColumnResize</code>: emitted on column resize.</li><li><code>onPTableColumnReorder</code>: emitted on column reorder.</li></ul><h3>Example 1: Direct Binding in Template</h3><p>Snippet 5:</p><h3>Example 2: Subscribe via ViewChild</h3><p>Snippet 6:</p>",
|
|
7091
|
+
"html": "<h1>List Grid</h1><p>Main component for tabular lists with filters, sorting, server-side paging, and row actions.</p><h2>Use Cases</h2><ul><li>Enterprise tabular CRUD.</li><li>Operational reports with multi-column filters.</li><li>Very large datasets with <code>cursorMode</code>.</li></ul><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__grid__01.png</code>: <code>inline cell edit</code> mode in list-grid.</li><li><code>manual__grid__02.png</code>: <code>inline edit</code> (row-level) mode in list-grid.</li></ul><h2>Inline Editing Metadata</h2><p>Configuration in the table metadata <code>md_props_bag</code>:</p><ul><li><code>md_inline_edit</code></li></ul><p> - Enables inline row editing in list-grid (editable cells in row context, without opening a popup).</p><p> - Useful when you want to maintain the table UX with quick editing per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - Enables "cell-by-cell" inline editing (focus on the individual cell).</p><p> - Note: in legacy configurations it may appear as <code>md_inline_cell_editing</code>; the runtime behavior is the same.</p><p> - <strong>Runtime promotion</strong>: when <code>md_inline_cell_edit</code> is <code>true</code>, the component forces</p><p> <code>md_inline_edit = true</code> at runtime regardless of the DB value.</p><p> Both UX modes need the action column visible, so they are mutually</p><p> non-exclusive at the rendering level.</p><ul><li><code>md_batch_save</code></li></ul><p> - Enables batch saving of pending changes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - When used with inline-cell, changes remain pending until explicit save.</p><p> - <strong>Prerequisite</strong>: <code>md_batch_save</code> takes effect <strong>only</strong> if <code>md_inline_cell_edit</code> is <code>true</code>.</p><p> With <code>md_inline_cell_edit:false</code> the flag is ignored at runtime and the</p><p> "Save changes / Cancel changes" buttons are not rendered.</p><h3>Valid Combinations</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime Result</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>any</td><td>Row-level inline edit with pencil. <code>md_batch_save</code> ignored.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forced to <code>true</code> at runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell with pending buffer + <code>Save / Cancel changes</code> toolbar.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>any</td><td>No inline editing. <code>md_batch_save</code> ignored.</td></tr></tbody></table><p>Row-level inline edit example:</p><p>Snippet 1:</p><p>Cell-by-cell with batch save example:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - Shows a progress dialog with real-time percentage;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel task</code>, <code>Stop and download partial</code>;</p><p> - In background creates a notification with progress; click reopens the dialog.</p><ul><li>Import XLS/XLSX (if <code>md_importable = true</code>):</li></ul><p> - Progress dialog after confirmation;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel import (rollback)</code>, <code>Stop and partial commit</code>;</p><p> - At the end of import creates a summary notification that navigates to the route.</p><h2>Metadata Config</h2><p>Key settings in <code>md_props_bag</code> and column metadata.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Opt-in flags that hide blocks of the list-grid toolbar (<code>caption-bar</code>). All under <code>md_props_bag.toolbar.*</code> (parsed at runtime as <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): hides in the <strong>caption-right</strong> the "Manage state" button (bookmark icon) + the saved-states <code><select></code>. Useful for hardcoded / demo routes where the saved-state feature (persistence per <code>user_id</code> + route via <code>MetaService</code>) doesn't make sense — e.g. Pattern 3 pure OData without registered route metadata.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): hides in the <strong>caption-left</strong> the "Save changes" (<code>pi-save</code>) + "Discard changes" (<code>pi-times</code>) buttons + the changes count indicator (<code>grid-changes-indicator</code>, pencil badge + count). Generated by the framework when <code>md_inline_cell_editing</code> + <code>md_batch_save</code> are active. Designed for <strong>nested grids inside a parametric-dialog</strong>: the parent's save/cancel persists master + rows in one shot via the framework batch save, and the duplicate buttons on the nested grid confuse the UX.</li></ul><p>Example nested rows in custom edit-form (runtime patch):</p><p>Snippet 5:</p><p>TS type in <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (<code>extraProps.toolbar</code> property). The flags do NOT disable the underlying <code>md_batch_save</code> logic (changes are still tracked); they only remove the toolbar UI — the actual save flows through the parent.</p><h2>md_props_bag: archetypes.list</h2><p>The component reads <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>type: boolean (with tolerant parser)</li><li>accepted values: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>runtime default: <code>true</code> when absent or empty</li><li>effect: distributes column widths proportionally (instead of px) when no user-persisted widths exist.</li></ul><ul><li><code>virtualize</code>:</li><li>type: <code>boolean</code> or <code>object</code></li><li>accepted values:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object with <code>enabled</code> (optional) and <code>itemSize</code> (optional)</li><li>runtime default: disabled (<code>false</code>) when absent</li><li><code>enabled</code>:</li><li>default: <code>true</code> if the <code>virtualize</code> node is an object without <code>enabled</code>, otherwise tolerant parser</li><li>effect: enables <code>virtualScroll</code> on <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effect: sets <code>virtualScrollItemSize</code> (virtual row height in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>type: boolean (with tolerant parser)</li><li>runtime default: <code>false</code></li><li>effect:</li><li>when <code>true</code>, the grid hides column filter icons (<code>p-columnFilter</code>) and uses the <code>wuic-filter-bar</code> above the table (at <code>data-repeater</code>/<code>bounded-repeater</code> level) to apply filters.</li></ul><p>Operational note:</p><ul><li>The <code>md_props_bag</code> suggest in the metadata editor also exposes the checkable <code>archetypes.list.advancedFilter</code> node.</li></ul><h2>Paging: md_pagesize and md_page_size_choice</h2><p>The grid uses <code>md_pagesize</code> as the default page size and <code>md_page_size_choice</code> as the list of selectable values in the paginator.</p><p>Runtime rules:</p><ul><li>If <code>md_pagesize</code> is greater than the maximum value present in <code>md_page_size_choice</code>, the framework automatically adds <code>md_pagesize</code> to the options list;</li><li>The list is normalized (valid numbers, deduplication, ascending sort).</li></ul><p>Example:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>runtime result: <code>10,25,50,100,200</code></li></ul><h2>Forced Virtualization on High Page Size</h2><p>When the user selects in the paginator a <code>pageSize >= 1000</code>:</p><ul><li>virtualization is automatically forced even if <code>md_props_bag.archetypes.list.virtualize</code> is absent or disabled;</li><li><code>virtualScrollItemSize</code> is forced to the default value <code>44</code>.</li></ul><p>This behavior protects the table rendering on very large pages.</p><h2>Events and Subscriptions (Host)</h2><p><code>wuic-list-grid</code> exposes runtime events useful for intercepting the render cycle and p-table callbacks on the host project side.</p><p>Available events:</p><ul><li><code>onAfterRender</code>: emitted at the end of grid data binding (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitted before the logical rendering of a single row; supports cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitted after the logical rendering of a single row.</li><li><code>onPaging</code>: emitted on paging events (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitted on sorting events (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitted on filtering events (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitted on row selection change.</li><li><code>onPTableRowExpand</code>: emitted on row expand.</li><li><code>onPTableRowCollapse</code>: emitted on row collapse.</li><li><code>onPTableColumnResize</code>: emitted on column resize.</li><li><code>onPTableColumnReorder</code>: emitted on column reorder.</li></ul><h3>Example 1: Direct Binding in Template</h3><p>Snippet 7:</p><h3>Example 2: Subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — actual signature (gotcha)</h2><p><code><wuic-list-grid></code> accepts a <code>[rowCustomSelect]</code> input to hook into row selection (opening a "pick a document" dialog, master-detail flow, etc.). The TypeScript signature declared in <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> reads:</p><p>Snippet 9:</p><p><strong>However at runtime the callback is invoked with arguments reversed</strong> — the framework calls <code>rowCustomSelect($event, rowData, dt)</code> (see <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> and <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Symptom of wrong arg order: <code>rowData?.id</code> is undefined → early-return guard in your handler → the callback returns silently and your dialog/action never fires. No console error.</p><p>Correct form (matches runtime invocation):</p><p>Snippet 10:</p><p>Template usage:</p><p>Snippet 11:</p><p>> Note: tests in <code>designer.component.spec.ts</code> (e.g. line 512) invoke the callback with <code>({currentTarget: rowCell}, {id: 42}, null)</code>, confirming the <code>($event, rowData, dt)</code> order. If the framework eventually aligns the declared signature, this page will be updated as well.</p>",
|
|
6823
7092
|
"codeSamples": [
|
|
6824
7093
|
{
|
|
6825
7094
|
"id": "code_1",
|
|
@@ -6843,19 +7112,49 @@ const frameworkDocsContent = {
|
|
|
6843
7112
|
"id": "code_4",
|
|
6844
7113
|
"title": "Snippet 4",
|
|
6845
7114
|
"language": "json",
|
|
6846
|
-
"code": "{\n \"
|
|
7115
|
+
"code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
|
|
6847
7116
|
},
|
|
6848
7117
|
{
|
|
6849
7118
|
"id": "code_5",
|
|
6850
7119
|
"title": "Snippet 5",
|
|
6851
|
-
"language": "
|
|
6852
|
-
"code": "
|
|
7120
|
+
"language": "ts",
|
|
7121
|
+
"code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
|
|
6853
7122
|
},
|
|
6854
7123
|
{
|
|
6855
7124
|
"id": "code_6",
|
|
6856
7125
|
"title": "Snippet 6",
|
|
7126
|
+
"language": "json",
|
|
7127
|
+
"code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
|
|
7128
|
+
},
|
|
7129
|
+
{
|
|
7130
|
+
"id": "code_7",
|
|
7131
|
+
"title": "Snippet 7",
|
|
7132
|
+
"language": "html",
|
|
7133
|
+
"code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
|
|
7134
|
+
},
|
|
7135
|
+
{
|
|
7136
|
+
"id": "code_8",
|
|
7137
|
+
"title": "Snippet 8",
|
|
6857
7138
|
"language": "ts",
|
|
6858
7139
|
"code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // example: cancel row\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
|
|
7140
|
+
},
|
|
7141
|
+
{
|
|
7142
|
+
"id": "code_9",
|
|
7143
|
+
"title": "Snippet 9",
|
|
7144
|
+
"language": "ts",
|
|
7145
|
+
"code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
|
|
7146
|
+
},
|
|
7147
|
+
{
|
|
7148
|
+
"id": "code_10",
|
|
7149
|
+
"title": "Snippet 10",
|
|
7150
|
+
"language": "ts",
|
|
7151
|
+
"code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData is the record provided by list-grid\n this.handleSelection(rowData);\n};"
|
|
7152
|
+
},
|
|
7153
|
+
{
|
|
7154
|
+
"id": "code_11",
|
|
7155
|
+
"title": "Snippet 11",
|
|
7156
|
+
"language": "html",
|
|
7157
|
+
"code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
|
|
6859
7158
|
}
|
|
6860
7159
|
]
|
|
6861
7160
|
}
|
|
@@ -7112,14 +7411,47 @@ const frameworkDocsContent = {
|
|
|
7112
7411
|
{
|
|
7113
7412
|
"id": "sec_3",
|
|
7114
7413
|
"title": "Designer Reference"
|
|
7414
|
+
},
|
|
7415
|
+
{
|
|
7416
|
+
"id": "sec_4",
|
|
7417
|
+
"title": "Report parameters (filtered SQL variables)"
|
|
7418
|
+
},
|
|
7419
|
+
{
|
|
7420
|
+
"id": "sec_5",
|
|
7421
|
+
"title": "Required backend setup"
|
|
7115
7422
|
}
|
|
7116
7423
|
],
|
|
7117
7424
|
"sections": [
|
|
7118
7425
|
{
|
|
7119
7426
|
"id": "overview",
|
|
7120
7427
|
"title": "Overview",
|
|
7121
|
-
"html": "<h1>Reporting</h1><p>Operational reporting on route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__reporting__01.png</code>: report viewer opening with report rendering and operational toolbar.</li><li><code>manual__reporting__02.png</code>: runtime example with parameters/filter applied to the report.</li></ul><h2>Report Viewer</h2><p>The <code>report-viewer</code> executes and displays Stimulsoft reports associated with the current metadata route.</p><p>Main features:</p><ul><li>report opening from list/grid with report name (<code>reportName</code>) and optional parameters.</li><li>runtime data rendering with viewer toolbar (zoom, export, print).</li><li>filter/parameter support propagated from the calling page.</li></ul><h2>Designer Reference</h2><p>For report <code>.mrt</code> creation or modification, see the subsection</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer & Workflow</code>.</p>",
|
|
7122
|
-
"codeSamples": [
|
|
7428
|
+
"html": "<h1>Reporting</h1><p>Operational reporting on route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__reporting__01.png</code>: report viewer opening with report rendering and operational toolbar.</li><li><code>manual__reporting__02.png</code>: runtime example with parameters/filter applied to the report.</li></ul><h2>Report Viewer</h2><p>The <code>report-viewer</code> executes and displays Stimulsoft reports associated with the current metadata route.</p><p>Main features:</p><ul><li>report opening from list/grid with report name (<code>reportName</code>) and optional parameters.</li><li>runtime data rendering with viewer toolbar (zoom, export, print).</li><li>filter/parameter support propagated from the calling page.</li></ul><h2>Designer Reference</h2><p>For report <code>.mrt</code> creation or modification, see the subsection</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer & Workflow</code>.</p><h2>Report parameters (filtered SQL variables)</h2><p>To filter a report on a specific entity (e.g. printing a single invoice), the canonical pattern is:</p><p>1. <strong>In the `.mrt`</strong>: declare a <strong>Variable</strong> in the Dictionary with <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Use the variable as <code>@paramName</code> in SqlCommands:</p><p>Snippet 1:</p><p> > <strong>Pitfall</strong>: do NOT use the legacy <code><StiDataParameter></code> block in the DataSource — it is no longer supported by the current Stimulsoft version and produces <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter 'value')</code> during <code>report.Load()</code>.</p><p>2. <strong>In the viewer URL</strong>: pass the value via the <code>parameters</code> query param (NOT as a direct query param):</p><p>Snippet 2:</p><p> <code>parameters</code> URL-encoded syntax: <code>name||eq||value</code> (separated by <code>@</code> for multiple parameters).</p><p>3. <strong>From a row action or UI link</strong>: use the canonical callback to build the URL:</p><p>Snippet 3:</p><h2>Required backend setup</h2><p>In the app's <code>appsettings.json</code>, the <code>AppSettings.reportQueryTimeout</code> setting must exist (e.g. <code>"120"</code>). Without it, <code>ReportViewerController.prepareDataSql</code> throws <code>int.Parse(null)</code> → 500. Example:</p><p>Snippet 4:</p>",
|
|
7429
|
+
"codeSamples": [
|
|
7430
|
+
{
|
|
7431
|
+
"id": "code_1",
|
|
7432
|
+
"title": "Snippet 1",
|
|
7433
|
+
"language": "sql",
|
|
7434
|
+
"code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
|
|
7435
|
+
},
|
|
7436
|
+
{
|
|
7437
|
+
"id": "code_2",
|
|
7438
|
+
"title": "Snippet 2",
|
|
7439
|
+
"language": "text",
|
|
7440
|
+
"code": " ✅ #/<route>/report-viewer?reportName=Report.mrt¶meters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignored"
|
|
7441
|
+
},
|
|
7442
|
+
{
|
|
7443
|
+
"id": "code_3",
|
|
7444
|
+
"title": "Snippet 3",
|
|
7445
|
+
"language": "js",
|
|
7446
|
+
"code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt¶meters='\n + encodeURIComponent(params);"
|
|
7447
|
+
},
|
|
7448
|
+
{
|
|
7449
|
+
"id": "code_4",
|
|
7450
|
+
"title": "Snippet 4",
|
|
7451
|
+
"language": "json",
|
|
7452
|
+
"code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
|
|
7453
|
+
}
|
|
7454
|
+
]
|
|
7123
7455
|
}
|
|
7124
7456
|
],
|
|
7125
7457
|
"images": [
|
|
@@ -7852,28 +8184,54 @@ const frameworkDocsContent = {
|
|
|
7852
8184
|
},
|
|
7853
8185
|
{
|
|
7854
8186
|
"id": "sec_5",
|
|
7855
|
-
"title": "
|
|
8187
|
+
"title": "Slim combo — extra fields via `mc_props_bag.slimCombo`"
|
|
7856
8188
|
},
|
|
7857
8189
|
{
|
|
7858
8190
|
"id": "sec_6",
|
|
7859
|
-
"title": "
|
|
8191
|
+
"title": "Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy"
|
|
7860
8192
|
},
|
|
7861
8193
|
{
|
|
7862
8194
|
"id": "sec_7",
|
|
8195
|
+
"title": "Client/Server Effects"
|
|
8196
|
+
},
|
|
8197
|
+
{
|
|
8198
|
+
"id": "sec_8",
|
|
7863
8199
|
"title": "Operational Notes"
|
|
8200
|
+
},
|
|
8201
|
+
{
|
|
8202
|
+
"id": "sec_9",
|
|
8203
|
+
"title": "Auto-fetch on programmatic FK changes"
|
|
7864
8204
|
}
|
|
7865
8205
|
],
|
|
7866
8206
|
"sections": [
|
|
7867
8207
|
{
|
|
7868
8208
|
"id": "overview",
|
|
7869
8209
|
"title": "Overview",
|
|
7870
|
-
"html": "<h1>Widget Lookup</h1><p>Column widget for relationships (<code>lookupByID</code>, <code>multiple_check</code>) with a dedicated <strong>Lookup</strong> tab in the column metadata-editor.</p><h2>Examples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — pure-OData lookup (State/Province from `/odata/Stateprovinces`)</a> — demonstrates <code>mc_props_bag.lookup.endpoint</code> with the <code>odata</code> provider and inline rendering via <code>$expand</code>.</li></ul><h2>When to Use It</h2><ul><li>Selecting related records from an external metadata table.</li><li>Scenarios with server-side lookup search and paging.</li><li>Support for single selection (<code>lookupByID</code>) and multi-selection (<code>multiple_check</code>).</li></ul><h2>Column Metadata Properties (Lookup tab)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: lookup data source metadata route.</li><li><code>mc_ui_lookup_dataValueField</code>: returned ID field.</li><li><code>mc_ui_lookup_dataTextField</code>: displayed description field.</li><li><code>mc_ui_lookup_filter</code>: base lookup filter.</li><li><code>mc_serverside_operations</code>: server-side search/paging.</li><li><code>mc_ui_pagesize</code>: lookup result page size.</li><li><code>mc_ui_lookup_edit_allow</code>: enables lookup record edit from popup.</li><li><code>mc_ui_lookup_insert_allow</code>: enables lookup record insertion from popup.</li><li><code>mc_ui_lookup_search_grid</code>: enables search grid in the popup.</li><li><code>mc_logic_allow_navigation</code>: navigation to linked route.</li></ul><h2>Useful mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: additional constraint on the client/server side in the lookup query.</li><li><code>lookup.virtualize</code>: enables virtual scroll on the lookup widget's <code>p-autocomplete</code>.</li><li><code>lookup.virtualize.enabled</code>: tolerant parser (<code>true/false</code>, <code>1/0</code>, equivalent strings); default <code>false</code> when absent.</li><li><code>lookup.virtualize.itemSize</code>: virtual row height in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override of the dropdown's data provider. When set, the lookup-editor's nested datasource no longer goes through the metadata combo endpoint (<code>MetaService.getFlatRecordComboData</code>) but directly through the provider declared in <code>type</code> with the URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (dispatcher routes to <code>DataProviderOdataService.selectCombo</code> which issues a GET with <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> and <code>$filter=contains(textField,'<query>')</code> for server-side search). Other values follow the same logic as table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: base URL of the entity set (e.g., <code>/odata/<EntitySet></code>), with or without explicit origin. Existing query params are ignored — the provider rebuilds the params on each fetch.</li><li><code>form.columns</code>: field width in the edit form.</li><li><code>style.editCss</code>: custom editor style.</li></ul><h2>Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy</h2><ul><li>Inline operational flow: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Inline snippet main fields: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline snippet quick filter: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Client effect: accelerated lookup setup in the metadata-editor and consistent filter already ready in the widget.</li><li>Server effect: lookup query and select clause remain aligned with <code>MetaService.*</code> endpoints without manual mapping.</li></ul><p>Quick operational checklist:</p><p>1. Clone base configuration with <code>suggestLookup2</code>;</p><p>2. Generate default filter with <code>suggestLookupDefaultFilter</code>;</p><p>3. Refine hierarchy/select clause from the lookup tree;</p><p>4. Validate at runtime that search and value/text mapping are consistent.</p><h2>Client/Server Effects</h2><ul><li>Client: lookup widget rendering, search, label/value binding.</li><li>Client: with <code>lookup.virtualize</code> active, the autocomplete renders only the visual subset of options.</li><li>Server: paginated/filtered lookup query via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Operational Notes</h2><ul><li>In <code>multiple_check</code>, the lookup part remains the same; the multi-value renderer changes.</li><li>If <code>mc_ui_lookup_entity_name</code> is missing, the widget cannot resolve the source.</li></ul>",
|
|
8210
|
+
"html": "<h1>Widget Lookup</h1><p>Column widget for relationships (<code>lookupByID</code>, <code>multiple_check</code>) with a dedicated <strong>Lookup</strong> tab in the column metadata-editor.</p><h2>Examples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — pure-OData lookup (State/Province from `/odata/Stateprovinces`)</a> — demonstrates <code>mc_props_bag.lookup.endpoint</code> with the <code>odata</code> provider and inline rendering via <code>$expand</code>.</li></ul><h2>When to Use It</h2><ul><li>Selecting related records from an external metadata table.</li><li>Scenarios with server-side lookup search and paging.</li><li>Support for single selection (<code>lookupByID</code>) and multi-selection (<code>multiple_check</code>).</li></ul><h2>Column Metadata Properties (Lookup tab)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: lookup data source metadata route.</li><li><code>mc_ui_lookup_dataValueField</code>: returned ID field.</li><li><code>mc_ui_lookup_dataTextField</code>: displayed description field.</li><li><code>mc_ui_lookup_filter</code>: base lookup filter.</li><li><code>mc_serverside_operations</code>: server-side search/paging.</li><li><code>mc_ui_pagesize</code>: lookup result page size.</li><li><code>mc_ui_lookup_edit_allow</code>: enables lookup record edit from popup.</li><li><code>mc_ui_lookup_insert_allow</code>: enables lookup record insertion from popup.</li><li><code>mc_ui_lookup_search_grid</code>: enables search grid in the popup.</li><li><code>mc_logic_allow_navigation</code>: navigation to linked route.</li></ul><h2>Useful mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: additional constraint on the client/server side in the lookup query.</li><li><code>lookup.virtualize</code>: enables virtual scroll on the lookup widget's <code>p-autocomplete</code>.</li><li><code>lookup.virtualize.enabled</code>: tolerant parser (<code>true/false</code>, <code>1/0</code>, equivalent strings); default <code>false</code> when absent.</li><li><code>lookup.virtualize.itemSize</code>: virtual row height in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override of the dropdown's data provider. When set, the lookup-editor's nested datasource no longer goes through the metadata combo endpoint (<code>MetaService.getFlatRecordComboData</code>) but directly through the provider declared in <code>type</code> with the URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (dispatcher routes to <code>DataProviderOdataService.selectCombo</code> which issues a GET with <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> and <code>$filter=contains(textField,'<query>')</code> for server-side search). Other values follow the same logic as table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: base URL of the entity set (e.g., <code>/odata/<EntitySet></code>), with or without explicit origin. Existing query params are ignored — the provider rebuilds the params on each fetch.</li><li><code>form.columns</code>: field width in the edit form.</li><li><code>style.editCss</code>: custom editor style.</li></ul><h2>Slim combo — extra fields via <code>mc_props_bag.slimCombo</code></h2><p>The lookup dropdown by default issues a minimal SELECT on the related table: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. This shrinks the combo payload. When the client needs additional columns from the related table — typically to pre-fill dependent fields via <code>mc_selection_changed_custom_function__fn</code> — use <code>mc_props_bag.slimCombo</code> as an <strong>array of extra column names</strong> to include in the combo SELECT:</p><p>Snippet 2:</p><ul><li>Array: the names are added to the <code>columnRestrictionList</code> of the combo response (logic in <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: disables the restriction → the combo returns <strong>all</strong> columns. Useful for debugging, larger payload.</li><li>If the column has <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> set (computed display formula), <code>slimCombo</code> is ignored and the combo returns all columns (the formula may reference arbitrary fields).</li></ul><p>The extra fields land in <code>record['<col>__lookup_obj'].value</code> (full record from the related table, already including the fields declared in <code>slimCombo</code>) and are accessible from the <code>mc_selection_changed_custom_function__fn</code> callback:</p><p>Snippet 3:</p><p>Tradeoff: slightly larger combo payload but no extra HTTP roundtrip (<code>getFlatRecordData</code> to fetch the full record by id) on every selection.</p><h2>Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy</h2><ul><li>Inline operational flow: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Inline snippet main fields: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline snippet quick filter: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Client effect: accelerated lookup setup in the metadata-editor and consistent filter already ready in the widget.</li><li>Server effect: lookup query and select clause remain aligned with <code>MetaService.*</code> endpoints without manual mapping.</li></ul><p>Quick operational checklist:</p><p>1. Clone base configuration with <code>suggestLookup2</code>;</p><p>2. Generate default filter with <code>suggestLookupDefaultFilter</code>;</p><p>3. Refine hierarchy/select clause from the lookup tree;</p><p>4. Validate at runtime that search and value/text mapping are consistent.</p><h2>Client/Server Effects</h2><ul><li>Client: lookup widget rendering, search, label/value binding.</li><li>Client: with <code>lookup.virtualize</code> active, the autocomplete renders only the visual subset of options.</li><li>Server: paginated/filtered lookup query via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Operational Notes</h2><ul><li>In <code>multiple_check</code>, the lookup part remains the same; the multi-value renderer changes.</li><li>If <code>mc_ui_lookup_entity_name</code> is missing, the widget cannot resolve the source.</li></ul><h2>Auto-fetch on programmatic FK changes</h2><p>The <code>wuic-lookup-editor</code> automatically subscribes to <code>record[mc_nome_colonna]</code> BehaviorSubject and handles programmatic FK changes (e.g. import from source document, custom callback that does <code>record.cliente_id.next(99)</code>):</p><ul><li>If the new value is <strong>already in `items`</strong> (the loaded lookup page): only <code>__lookup_obj</code> is updated for consistency, no HTTP call.</li><li>If the new value is <strong>not in items</strong> (e.g. value outside current combo page, or not yet fetched): a <strong>second-stage fetch</strong> is triggered, filtered <code>WHERE <valueField> eqor <newValue></code>, to retrieve the related record and automatically populate the dropdown.</li></ul><p>Skip cases (no-op):</p><ul><li>Multi-value lookup (<code>isFilter</code> with <code>eqor</code>, <code>multiple_check</code>)</li><li>First emit of the BS (already covered by the init flow)</li><li>null/undefined value (user cleared)</li><li><code>hasActiveLookupQuery=true</code> (user is typing a manual query)</li></ul><p><strong>Implication for custom forms</strong>: setting just the FK is enough — manual workarounds to populate <code>__lookup_obj</code> or the joined alias are no longer required. The dropdown display updates by itself.</p><p>Snippet 4:</p><p>> <strong>Note</strong>: auto-fetch fires only when the <code><wuic-lookup-editor></code> is <strong>mounted</strong> (cell in edit mode). For <strong>list display</strong> (read-only cell), <code>formatGridViewValue</code> reads the joined alias <code><route>___<textField>__<col></code> directly from the record — that still must be populated manually if it doesn't come from the backend.</p>",
|
|
7871
8211
|
"codeSamples": [
|
|
7872
8212
|
{
|
|
7873
8213
|
"id": "code_1",
|
|
7874
8214
|
"title": "Snippet 1",
|
|
7875
8215
|
"language": "json",
|
|
7876
8216
|
"code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
|
|
8217
|
+
},
|
|
8218
|
+
{
|
|
8219
|
+
"id": "code_2",
|
|
8220
|
+
"title": "Snippet 2",
|
|
8221
|
+
"language": "json",
|
|
8222
|
+
"code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
|
|
8223
|
+
},
|
|
8224
|
+
{
|
|
8225
|
+
"id": "code_3",
|
|
8226
|
+
"title": "Snippet 3",
|
|
8227
|
+
"language": "ts",
|
|
8228
|
+
"code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
|
|
8229
|
+
},
|
|
8230
|
+
{
|
|
8231
|
+
"id": "code_4",
|
|
8232
|
+
"title": "Snippet 4",
|
|
8233
|
+
"language": "ts",
|
|
8234
|
+
"code": "// BEFORE (manual workaround required)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// NOW (auto-fetch by lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor mounted? Subscribe detects change → filtered fetch → __lookup_obj populated → dropdown shows \"Acme S.r.l.\""
|
|
7877
8235
|
}
|
|
7878
8236
|
]
|
|
7879
8237
|
}
|
|
@@ -8136,10 +8494,14 @@ const frameworkDocsContent = {
|
|
|
8136
8494
|
},
|
|
8137
8495
|
{
|
|
8138
8496
|
"id": "sec_4",
|
|
8139
|
-
"title": "
|
|
8497
|
+
"title": "Element context menu"
|
|
8140
8498
|
},
|
|
8141
8499
|
{
|
|
8142
8500
|
"id": "sec_5",
|
|
8501
|
+
"title": "Recommended Operational Flow"
|
|
8502
|
+
},
|
|
8503
|
+
{
|
|
8504
|
+
"id": "sec_6",
|
|
8143
8505
|
"title": "Operational Notes"
|
|
8144
8506
|
}
|
|
8145
8507
|
],
|
|
@@ -8147,7 +8509,7 @@ const frameworkDocsContent = {
|
|
|
8147
8509
|
{
|
|
8148
8510
|
"id": "overview",
|
|
8149
8511
|
"title": "Overview",
|
|
8150
|
-
"html": "<h1>Designer</h1><p>Visual editor for metadata-driven dashboards on route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Board composition via drag-and-drop of nodes and UI blocks.</li><li>Layout and styling driven by <code>boardcontent</code> and table/column metadata.</li><li>Property panel editing without hardcoding in runtime components.</li></ul><h2>Designer Palette</h2><ul><li><code>Datasource</code>: metadata data source (<code>route</code>, filters, sort, paging).</li><li><code>DataRepeater</code>: list/card view connected to a datasource.</li><li><code>Dynamic Template</code>: dynamic template rendering with metadata-driven inputs.</li><li><code>Action/Button</code>: user actions attached to records and callbacks.</li><li><code>Container/Layout</code>: structural blocks for columns, rows, and wrappers.</li></ul><h2>Main Features</h2><ul><li>Drag-and-drop nodes onto the canvas with persistence in <code>boardcontent</code>.</li><li>Contextual property panel with inputs consistent with the selected components.</li><li>Binding between nodes (<code>datasource -> repeater/template</code>) with minimal validation.</li><li>Integrated runtime preview and layout realignment.</li><li>Pending changes management with guard before exit.</li></ul><h2>Recommended Operational Flow</h2><ul><li>Open the target dashboard route.</li><li>Insert/connect nodes from the palette.</li><li>Configure essential <code>inputs</code> (e.g., <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Save and validate the result at runtime.</li></ul><h2>Operational Notes</h2><ul><li>For specific dashboard fixes, prefer <code>boardcontent</code> patches instead of global changes.</li><li>Avoid duplicate properties between <code>inputs</code> and inconsistent inline styles.</li><li>Always verify that datasources have a populated <code>inputs.route</code>.</li></ul>",
|
|
8512
|
+
"html": "<h1>Designer</h1><p>Visual editor for metadata-driven dashboards on route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Board composition via drag-and-drop of nodes and UI blocks.</li><li>Layout and styling driven by <code>boardcontent</code> and table/column metadata.</li><li>Property panel editing without hardcoding in runtime components.</li></ul><h2>Designer Palette</h2><ul><li><code>Datasource</code>: metadata data source (<code>route</code>, filters, sort, paging).</li><li><code>DataRepeater</code>: list/card view connected to a datasource.</li><li><code>Dynamic Template</code>: dynamic template rendering with metadata-driven inputs.</li><li><code>Action/Button</code>: user actions attached to records and callbacks.</li><li><code>Container/Layout</code>: structural blocks for columns, rows, and wrappers.</li></ul><h2>Main Features</h2><ul><li>Drag-and-drop nodes onto the canvas with persistence in <code>boardcontent</code>.</li><li>Contextual property panel with inputs consistent with the selected components.</li><li>Binding between nodes (<code>datasource -> repeater/template</code>) with minimal validation.</li><li>Integrated runtime preview and layout realignment.</li><li>Pending changes management with guard before exit.</li></ul><h2>Element context menu</h2><p>Right-click on a canvas element, a breadcrumb item at the bottom or a node in the hierarchy tree shows a context menu with three items:</p><ul><li><strong>Edit properties</strong> (pi-cog) — selects the element as the "current tool" and shows its property panel on the right; equivalent to clicking in the tree. Useful to edit properties of nested elements without hunting for them in the hierarchy.</li><li><strong>Rename</strong> (breadcrumb only) — opens a dialog to rename the <code>displayName</code>.</li><li><strong>Remove</strong> — deletes the element from the board.</li></ul><h2>Recommended Operational Flow</h2><ul><li>Open the target dashboard route.</li><li>Insert/connect nodes from the palette.</li><li>Configure essential <code>inputs</code> (e.g., <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Save and validate the result at runtime.</li></ul><h2>Operational Notes</h2><ul><li>For specific dashboard fixes, prefer <code>boardcontent</code> patches instead of global changes.</li><li>Avoid duplicate properties between <code>inputs</code> and inconsistent inline styles.</li><li>Always verify that datasources have a populated <code>inputs.route</code>.</li></ul>",
|
|
8151
8513
|
"codeSamples": []
|
|
8152
8514
|
}
|
|
8153
8515
|
],
|
|
@@ -8211,14 +8573,18 @@ const frameworkDocsContent = {
|
|
|
8211
8573
|
},
|
|
8212
8574
|
{
|
|
8213
8575
|
"id": "sec_3",
|
|
8214
|
-
"title": "
|
|
8576
|
+
"title": "View Builder Tab — Aggregations and GROUP BY (Tier 1, 2026-05-08)"
|
|
8215
8577
|
},
|
|
8216
8578
|
{
|
|
8217
8579
|
"id": "sec_4",
|
|
8218
|
-
"title": "
|
|
8580
|
+
"title": "Recommended Operational Flow"
|
|
8219
8581
|
},
|
|
8220
8582
|
{
|
|
8221
8583
|
"id": "sec_5",
|
|
8584
|
+
"title": "Action Buttons"
|
|
8585
|
+
},
|
|
8586
|
+
{
|
|
8587
|
+
"id": "sec_6",
|
|
8222
8588
|
"title": "Usage Notes"
|
|
8223
8589
|
}
|
|
8224
8590
|
],
|
|
@@ -8226,7 +8592,7 @@ const frameworkDocsContent = {
|
|
|
8226
8592
|
{
|
|
8227
8593
|
"id": "overview",
|
|
8228
8594
|
"title": "Overview",
|
|
8229
|
-
"html": "<h1>View/Pivot Builder</h1><p>The Pivot Builder allows you to build dynamic analytical views starting from metadata routes, without writing manual queries on the UI side.</p><h2>Screenshot Reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: builder configuration and runtime result.</li><li><code>manual__pivot_builder__02.png</code>: materialized table creation dialog detail and operational options.</li></ul><h2>Main Features</h2><ul><li>Field selection in <code>rows</code>, <code>columns</code>, and <code>values</code> areas.</li><li>Available aggregations on values (sum, count, average, minimum, maximum).</li><li>Multidimensional grouping with sorting by dimension.</li><li>Interactive filters to reduce the analyzed dataset.</li><li>Real-time pivot view update after layout or metric change.</li><li>Export/reuse of the pivot configuration in the operational context.</li></ul><h2>Recommended Operational Flow</h2><p>1. Open the pivot builder route.</p><p>2. Select the starting datasource/metadata route.</p><p>3. Drag fields into the <code>rows</code>, <code>columns</code>, <code>values</code> zones.</p><p>4. Configure the aggregation for each metric.</p><p>5. Apply filters and verify the totals.</p><p>6. Save or reuse the configuration according to the application flow.</p><h2>Action Buttons</h2><p><code>Create view</code></p><ul><li>Generates a SQL view from the current pivot configuration.</li><li>Useful when you want to reuse the result as a stable data source in other routes/reports.</li><li>Keeps the logic centralized in the DB, avoiding manual layout reconstruction each time.</li></ul><p><code>Create materialized table</code></p><ul><li>Creates a physical table with the aggregated data from the pivot configuration.</li><li>Useful for large datasets or dashboards where fast read times are needed.</li><li>Automatically scaffolds the route associated with the created physical table and generates a schedule for data refresh/alignment.</li><li>Pressing Refresh on the grid in the associated route forces a schedule run.</li></ul><h2>Usage Notes</h2><ul><li>For consistent analysis, use columns with the correct data type (numeric for metrics, text/dates for dimensions).</li><li>When dealing with large volumes, prefer targeted filters and aggregations before expanding the detail.</li></ul>",
|
|
8595
|
+
"html": "<h1>View/Pivot Builder</h1><p>The Pivot Builder allows you to build dynamic analytical views starting from metadata routes, without writing manual queries on the UI side.</p><h2>Screenshot Reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: builder configuration and runtime result.</li><li><code>manual__pivot_builder__02.png</code>: materialized table creation dialog detail and operational options.</li></ul><h2>Main Features</h2><ul><li>Field selection in <code>rows</code>, <code>columns</code>, and <code>values</code> areas.</li><li>Available aggregations on values (sum, count, average, minimum, maximum).</li><li>Multidimensional grouping with sorting by dimension.</li><li>Interactive filters to reduce the analyzed dataset.</li><li>Real-time pivot view update after layout or metric change.</li><li>Export/reuse of the pivot configuration in the operational context.</li></ul><h2>View Builder Tab — Aggregations and GROUP BY (Tier 1, 2026-05-08)</h2><p>Tab 1 "View Builder" now lets you emit <code>GROUP BY</code> with <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> aggregations directly from the <code>ViewDefinition</code>, without falling back to manualSql mode.</p><p>Panel below the canvas:</p><ul><li><strong>Aggregations</strong>: form to add <code>function(column) AS alias</code>. The source column can be any column on the canvas or <code>*</code> (for <code>COUNT(*)</code>); the output alias is auto-generated when blank.</li><li><strong>GROUP BY</strong>: checkbox per selected column on canvas nodes. If none is explicitly checked, the backend infers dimensions from the <code>selected=true</code> non-aggregated columns.</li><li><strong>CASE WHEN for pivoting</strong>: for patterns like <code>SUM(CASE WHEN bucket='X' THEN amount ELSE 0 END)</code> use a formula column (right-click → Edit formula) as aggregation source. The formula is emitted as the aggregate argument.</li></ul><p>Backend output (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → emits <code><FN>(<col|formula>) AS <alias></code> as additional SELECT columns</li><li><code>viewDefinition.groupByColumns[]</code> → emits <code>GROUP BY</code> clause</li><li>Auto-infers GROUP BY when <code>aggregations</code> are present without an explicit <code>groupByColumns</code></li></ul><h3>UNION blocks (Tier 2, 2026-05-08)</h3><p>A subsequent extension lets you concatenate multiple SELECTs with <code>UNION</code> or <code>UNION ALL</code> directly from a single <code>ViewDefinition</code>:</p><ul><li><strong>Block 0</strong> = top-level (canvas tables/joins/aggregations) → defines the <strong>canonical vocabulary</strong> of SELECT columns</li><li><strong>Block N (N≥1)</strong> = each additional <code>unionBlocks[]</code> is a single-table sub-definition with a required <code>columnMapping</code>: for every canonical alias of block 0, block N specifies <code>sourceQualified</code> (column from the block) <strong>or</strong> <code>constantValue</code> (SQL literal like <code>'CLIENT'</code>) <strong>or</strong> <code>formula</code> (SQL expression for cross-block cast)</li><li><strong>UNION ALL / UNION toggle</strong>: default <code>unionAll: true</code> (no dedup, better performance); <code>false</code> for <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: applied to the whole UNION, wrapped in subquery <code>SELECT TOP N * FROM (<union>) AS u</code></li></ul><p>UI below the canvas: route picker + "Add UNION block" button → each block shows as an accordion with header (caption + route) + column-mapping matrix (per canonical alias: dropdown of block source col, or constant SQL input).</p><p>v0 limits: each UNION block is single-table (no JOIN within block). For cross-block JOIN (e.g. 'BOTH' category in <code>vw_anagrafica_unificata</code> that requires <code>clients INNER JOIN suppliers ON vat</code>) Tier 2.5 is needed.</p><h2>Recommended Operational Flow</h2><p>1. Open the pivot builder route.</p><p>2. Select the starting datasource/metadata route.</p><p>3. Drag fields into the <code>rows</code>, <code>columns</code>, <code>values</code> zones.</p><p>4. Configure the aggregation for each metric.</p><p>5. Apply filters and verify the totals.</p><p>6. Save or reuse the configuration according to the application flow.</p><h2>Action Buttons</h2><p><code>Create view</code></p><ul><li>Generates a SQL view from the current pivot configuration.</li><li>Useful when you want to reuse the result as a stable data source in other routes/reports.</li><li>Keeps the logic centralized in the DB, avoiding manual layout reconstruction each time.</li></ul><p><code>Create materialized table</code></p><ul><li>Creates a physical table with the aggregated data from the pivot configuration.</li><li>Useful for large datasets or dashboards where fast read times are needed.</li><li>Automatically scaffolds the route associated with the created physical table and generates a schedule for data refresh/alignment.</li><li>Pressing Refresh on the grid in the associated route forces a schedule run.</li></ul><h2>Usage Notes</h2><ul><li>For consistent analysis, use columns with the correct data type (numeric for metrics, text/dates for dimensions).</li><li>When dealing with large volumes, prefer targeted filters and aggregations before expanding the detail.</li></ul>",
|
|
8230
8596
|
"codeSamples": []
|
|
8231
8597
|
}
|
|
8232
8598
|
],
|
|
@@ -9181,7 +9547,7 @@ const frameworkDocsContent = {
|
|
|
9181
9547
|
{
|
|
9182
9548
|
"id": "overview",
|
|
9183
9549
|
"title": "Overview",
|
|
9184
|
-
"html": "<h1>Component Catalog</h1><p>Total component folders detected:
|
|
9550
|
+
"html": "<h1>Component Catalog</h1><p>Total component folders detected: 50.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>azienda-switcher</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>azienda-switcher/azienda-switcher.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
|
|
9185
9551
|
"codeSamples": []
|
|
9186
9552
|
}
|
|
9187
9553
|
],
|
|
@@ -9302,13 +9668,21 @@ const frameworkDocsContent = {
|
|
|
9302
9668
|
{
|
|
9303
9669
|
"id": "sec_3",
|
|
9304
9670
|
"title": "Examples"
|
|
9671
|
+
},
|
|
9672
|
+
{
|
|
9673
|
+
"id": "sec_4",
|
|
9674
|
+
"title": "Runtime `metaInfo` patches via `fetchInfo$.subscribe`"
|
|
9675
|
+
},
|
|
9676
|
+
{
|
|
9677
|
+
"id": "sec_5",
|
|
9678
|
+
"title": "Multiple `<wuic-data-source>` in the same component"
|
|
9305
9679
|
}
|
|
9306
9680
|
],
|
|
9307
9681
|
"sections": [
|
|
9308
9682
|
{
|
|
9309
9683
|
"id": "overview",
|
|
9310
9684
|
"title": "Overview",
|
|
9311
|
-
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> is the component that manages metadata, data fetching, current state, change tracking, and CRUD sync.</p><h2>Description</h2><ul><li>Resolves the route (<code>route</code>/<code>hardcodedRoute</code> or URL routing).</li><li>Loads table/column metadata and publishes state on <code>fetchInfo$</code>.</li><li>Manages <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) and cursor paging.</li><li>Exposes runtime methods for insert/update/delete, batch save, rollback, and export.</li><li>It is the central point to which <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code>, and <code>wuic-pager</code> connect.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Main inputs:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Main state/runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (cancellable)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Most used runtime methods:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Runtime subscriptions/events:</p><ul><li><code>fetchInfo$</code>: main data/metadata state stream.</li><li><code>datasourceReady$</code>: emitted when the datasource instance is ready.</li><li><code>afterFirstLoad$</code>: emitted once on the first useful payload.</li><li><code>beforeSync$</code>: emitted before <code>insert/update/delete/clone/batch</code>; you can cancel with <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitted after successful sync.</li></ul><p>Compatibility note:</p><ul><li>The <code>fetchInfo</code> alias (getter/setter) is also available, but the recommended naming is <code>fetchInfo$</code>.</li></ul><h2>Examples</h2><h3>Basic Example (Hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail Example (Nested Datasource)</h3><p>Snippet 2:</p><h3>Operational Example (Manual Fetch + Sync)</h3><p>Snippet 3:</p><h3>Subscriptions Example (Host)</h3><p>Snippet 4:</p>",
|
|
9685
|
+
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> is the component that manages metadata, data fetching, current state, change tracking, and CRUD sync.</p><h2>Description</h2><ul><li>Resolves the route (<code>route</code>/<code>hardcodedRoute</code> or URL routing).</li><li>Loads table/column metadata and publishes state on <code>fetchInfo$</code>.</li><li>Manages <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) and cursor paging.</li><li>Exposes runtime methods for insert/update/delete, batch save, rollback, and export.</li><li>It is the central point to which <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code>, and <code>wuic-pager</code> connect.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Main inputs:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Main state/runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (cancellable)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Most used runtime methods:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Runtime subscriptions/events:</p><ul><li><code>fetchInfo$</code>: main data/metadata state stream.</li><li><code>datasourceReady$</code>: emitted when the datasource instance is ready.</li><li><code>afterFirstLoad$</code>: emitted once on the first useful payload.</li><li><code>beforeSync$</code>: emitted before <code>insert/update/delete/clone/batch</code>; you can cancel with <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitted after successful sync.</li></ul><p>Compatibility note:</p><ul><li>The <code>fetchInfo</code> alias (getter/setter) is also available, but the recommended naming is <code>fetchInfo$</code>.</li></ul><h2>Examples</h2><h3>Basic Example (Hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail Example (Nested Datasource)</h3><p>Snippet 2:</p><h3>Operational Example (Manual Fetch + Sync)</h3><p>Snippet 3:</p><h3>Subscriptions Example (Host)</h3><p>Snippet 4:</p><h2>Runtime <code>metaInfo</code> patches via <code>fetchInfo$.subscribe</code></h2><p>To modify a nested datasource's metadata at runtime (e.g. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>without touching the DB</strong> — local patch on the reference cached by <code>MetadataProviderService</code>, latest-wins, single-dialog scenario — subscribe to the data-source's <code>fetchInfo$</code>:</p><p>Snippet 5:</p><p><strong>Scope</strong>: session-only + instance-only. The patch survives only until the <code>MetadataProviderService</code> cache is invalidated or the component is destroyed. Useful when a custom form needs a different representation of a nested grid compared to the standalone rendering.</p><p><strong>When NOT to use runtime patches</strong>: for changes targeted at all instances of the route (including standalone <code><route>/list</code>) → direct SQL patch on metadata; for changes persisting across restarts → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Multiple <code><wuic-data-source></code> in the same component</h2><p>When a custom component contains more than one <code><wuic-data-source></code> (e.g. master + nested rows + nested due-dates), the <code>@ViewChild(DataSourceComponent)</code> by-type pattern matches <strong>only the first</strong> data-source in template top-down order. For the others you need a <strong>template reference variable</strong> + <code>@ViewChild('refName')</code>:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong>: <code>document.querySelectorAll('wuic-data-source')</code> + <code>window.ng.getComponent(el)</code> to filter by <code>hardcodedRoute</code>. Works but is fragile, depends on the <code>window.ng</code> debug API (not guaranteed in production). Always use template refs.</p>",
|
|
9312
9686
|
"codeSamples": [
|
|
9313
9687
|
{
|
|
9314
9688
|
"id": "code_1",
|
|
@@ -9333,6 +9707,24 @@ const frameworkDocsContent = {
|
|
|
9333
9707
|
"title": "Snippet 4",
|
|
9334
9708
|
"language": "ts",
|
|
9335
9709
|
"code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Delete not allowed by host context');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
|
|
9710
|
+
},
|
|
9711
|
+
{
|
|
9712
|
+
"id": "code_5",
|
|
9713
|
+
"title": "Snippet 5",
|
|
9714
|
+
"language": "ts",
|
|
9715
|
+
"code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup in ngOnDestroy\n}"
|
|
9716
|
+
},
|
|
9717
|
+
{
|
|
9718
|
+
"id": "code_6",
|
|
9719
|
+
"title": "Snippet 6",
|
|
9720
|
+
"language": "html",
|
|
9721
|
+
"code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
|
|
9722
|
+
},
|
|
9723
|
+
{
|
|
9724
|
+
"id": "code_7",
|
|
9725
|
+
"title": "Snippet 7",
|
|
9726
|
+
"language": "ts",
|
|
9727
|
+
"code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// First data-source (matched by type) -> no template ref needed\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Second data-source -> needs `#scadenzeDs` in the template + by-name ViewChild\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
|
|
9336
9728
|
}
|
|
9337
9729
|
]
|
|
9338
9730
|
}
|
|
@@ -10891,6 +11283,82 @@ const frameworkDocsContent = {
|
|
|
10891
11283
|
],
|
|
10892
11284
|
"images": []
|
|
10893
11285
|
},
|
|
11286
|
+
{
|
|
11287
|
+
"slug": "multi-tenant",
|
|
11288
|
+
"lang": "en-US",
|
|
11289
|
+
"title": "Multi-tenant",
|
|
11290
|
+
"description": "Multi-tenant - documentazione operativa e tecnica",
|
|
11291
|
+
"audience": [
|
|
11292
|
+
"developer",
|
|
11293
|
+
"integration"
|
|
11294
|
+
],
|
|
11295
|
+
"status": "stable",
|
|
11296
|
+
"tags": [
|
|
11297
|
+
"advanced"
|
|
11298
|
+
],
|
|
11299
|
+
"keywords": [
|
|
11300
|
+
"Multi-tenant"
|
|
11301
|
+
],
|
|
11302
|
+
"toc": [
|
|
11303
|
+
{
|
|
11304
|
+
"id": "sec_1",
|
|
11305
|
+
"title": "Scope"
|
|
11306
|
+
},
|
|
11307
|
+
{
|
|
11308
|
+
"id": "sec_2",
|
|
11309
|
+
"title": "Enablement"
|
|
11310
|
+
},
|
|
11311
|
+
{
|
|
11312
|
+
"id": "sec_3",
|
|
11313
|
+
"title": "Data model"
|
|
11314
|
+
},
|
|
11315
|
+
{
|
|
11316
|
+
"id": "sec_4",
|
|
11317
|
+
"title": "Login fallback"
|
|
11318
|
+
},
|
|
11319
|
+
{
|
|
11320
|
+
"id": "sec_5",
|
|
11321
|
+
"title": "Tenant switch (superadmin)"
|
|
11322
|
+
},
|
|
11323
|
+
{
|
|
11324
|
+
"id": "sec_6",
|
|
11325
|
+
"title": "Tenant-scoped caches"
|
|
11326
|
+
},
|
|
11327
|
+
{
|
|
11328
|
+
"id": "sec_7",
|
|
11329
|
+
"title": "Anti-hijack"
|
|
11330
|
+
},
|
|
11331
|
+
{
|
|
11332
|
+
"id": "sec_8",
|
|
11333
|
+
"title": "Tenant-aware scheduler"
|
|
11334
|
+
},
|
|
11335
|
+
{
|
|
11336
|
+
"id": "sec_9",
|
|
11337
|
+
"title": "Cross-tenant scaffolding propagation"
|
|
11338
|
+
},
|
|
11339
|
+
{
|
|
11340
|
+
"id": "sec_10",
|
|
11341
|
+
"title": "Non-superadmin user accounts across multiple tenants"
|
|
11342
|
+
},
|
|
11343
|
+
{
|
|
11344
|
+
"id": "sec_11",
|
|
11345
|
+
"title": "Known limits"
|
|
11346
|
+
},
|
|
11347
|
+
{
|
|
11348
|
+
"id": "sec_12",
|
|
11349
|
+
"title": "Relevant AppSettings"
|
|
11350
|
+
}
|
|
11351
|
+
],
|
|
11352
|
+
"sections": [
|
|
11353
|
+
{
|
|
11354
|
+
"id": "overview",
|
|
11355
|
+
"title": "Overview",
|
|
11356
|
+
"html": "<h1>Multi-tenant</h1><p>Per-user connection routing: each tenant has its own connection strings to separate metadata and data DBs, while preserving full backward compatibility for existing single-tenant installations.</p><h2>Scope</h2><p>The multi-tenant module allows a single backend to serve multiple tenants — each on distinct metadata and data DBs — without duplicating code or starting separate instances. When the flag is OFF behavior is identical to pre-multi-tenant (zero overhead, caches unchanged). When ON the framework:</p><ul><li>resolves the right connection string at runtime based on the logged-in user's <code>id_azienda</code>.</li><li>segregates server caches (<code>Application[]</code>) and client caches (Dexie + localStorage) per tenant.</li><li>propagates <code>TenantScope.Current</code> across all queries, scheduler and background jobs.</li></ul><h2>Enablement</h2><p>In <code>appsettings.json</code> (<code>AppSettings</code> section):</p><ul><li><code>"multiConnectionEnabled": "true"</code> — activates tenant-aware routing in <code>MultiTenantHelpers</code> + <code>TenantScope</code> middleware in <code>Startup.cs</code>.</li><li><code>"enableCookieAuthentication": "true"</code> — required by the multi-tenant flow: the <code>k-user</code> v:2 cookie carries <code>azienda_id</code> + <code>azienda_id_user</code> for anti-hijack.</li></ul><p>Stand-alone templates <code>appsettings.multi-tenant.mssql.json</code> and <code>appsettings.multi-tenant.mysql.json</code> are available (6 connection strings: 2 default + 4 tenant <code>Tenant1_Meta</code>/<code>Tenant1_Data</code>/<code>Tenant2_Meta</code>/<code>Tenant2_Data</code>).</p><h2>Data model</h2><p>The <code>id_azienda → (data, meta)</code> mapping lives on two columns of the <code>Aziende</code> table on the primary metadata DB:</p><ul><li><code>Aziende.Connessione_DB_Dati</code>: NAME of an entry in <code>appsettings.ConnectionStrings</code> for the tenant data DB (NOT the literal connection string).</li><li><code>Aziende.CONNESSIONE_DB_Meta</code>: NAME of an entry in <code>appsettings.ConnectionStrings</code> for the tenant metadata DB.</li></ul><p>Example population:</p><ul><li><code>Aziende.ID_Azienda=1</code>, <code>Connessione_DB_Dati='Tenant1_Data'</code>, <code>CONNESSIONE_DB_Meta='Tenant1_Meta'</code>.</li><li><code>Aziende.ID_Azienda=100</code> (Sede principale), <code>Connessione_DB_Dati='DataSQLConnection'</code>, <code>CONNESSIONE_DB_Meta='MetaDataSQLConnection'</code> (default aliases → covered by scope 0).</li></ul><p>Entry-name indirection:</p><ul><li>no clear-text passwords in the DB.</li><li>credentials portable per environment (same DB, different <code>appsettings.<env>.json</code>).</li><li>consistent with <code>_metadati__tabelle.md_conn_name</code>.</li></ul><h2>Login fallback</h2><p>When a user does NOT exist on the primary metadata DB, login falls back via <code>_login_index</code>:</p><p>1. user lookup on <code>MetaDataSQLConnection</code> (primary) → found? login.</p><p>2. miss → query <code>_login_index</code> (<code>SHA-256(LOWER(username))</code> → <code>id_azienda</code>).</p><p>3. for each candidate <code>id_azienda</code>: push <code>TenantScope</code>, retry <code>metaQuery.login</code> on the tenant metadata DB.</p><p>4. first match wins → cookie issued with the tenant's <code>azienda_id</code>.</p><p>The <code>sp__login_index_upsert(@username, @idAzienda)</code> stored procedure populates the index. SHA-256 hash computed server-side (aligned with C# client <code>MultiTenantHelpers.ComputeUsernameHash</code> on UTF-8).</p><h2>Tenant switch (superadmin)</h2><p>Superadmins can change the active tenant at runtime via <code>MetaService.switchAzienda(idAzienda)</code>:</p><ul><li>401 if not superadmin.</li><li>400 if <code>idAzienda</code> doesn't have both <code>Aziende.Connessione_DB_Dati</code> / <code>CONNESSIONE_DB_Meta</code> columns populated.</li><li>re-emits cookie with updated <code>azienda_id</code>, preserving <code>azienda_id_user</code> (snapshot at login).</li><li>Angular component <code><wuic-azienda-switcher></code> exposes the dropdown UI. Self-hides via <code>@HostBinding('attr.hidden')</code> for non-super users or flag OFF.</li></ul><h2>Tenant-scoped caches</h2><p>Server-side (<code>Application[]</code> keys):</p><ul><li><code>MultiTenantHelpers.TenantKey(baseKey)</code> appends <code>__a<id></code> suffix when flag ON and <code>TenantScope.Current.AziendaId > 0</code>.</li><li>affected keys: <code>userList</code>, <code>roleList</code>, <code>userRoleList</code>, <code>companyList</code>, <code>storedMeta</code>, <code>storedTableMeta</code>, <code>storedTableActionMeta</code>, <code>storedConditionGroupMeta</code>, <code>storedTableStyleMeta</code>, <code>SysInfo</code>.</li></ul><p>Client-side (Angular framework lib):</p><ul><li>Dexie name → <code>MetaDB__a<id></code> and <code>WuicClientSideCrudDB__a<id></code>.</li><li>localStorage keys → <code>menu_<userId>__a<id></code>, <code>wuic_custom_settings_<userId>__a<id></code>, etc.</li><li>orphan cleanup at <code>MetadataProviderService</code> init: enumerates <code>indexedDB.databases()</code> and drops DBs of tenants no longer current.</li></ul><p>When flag OFF: TenantKey returns the baseKey unchanged → caches identical to pre-multi-tenant.</p><h2>Anti-hijack</h2><p><code>RawHelpers.authenticate()</code> on every request:</p><ul><li>requires cookie v:2 with <code>azienda_id</code> + <code>azienda_id_user</code> (legacy cookie → forced relogin).</li><li>for non-super users, <code>cookie.azienda_id</code> MUST match <code>cookie.azienda_id_user</code> (tampering → relogin + security log).</li><li>superadmins are exempt (their <code>azienda_id</code> legitimately changes via <code>switchAzienda</code>).</li><li>populates <code>TenantScope.Current</code> with <code>(azienda_id, isSuperAdmin)</code> for the rest of the request.</li></ul><p>The <code>HydrateLegacyPrincipalFromKUserCookie</code> middleware in <code>Startup.cs</code> also populates <code>TenantScope</code> for AsmxProxy endpoints that don't call <code>authenticate()</code> explicitly (e.g. <code>readCustomSettings</code>, <code>getMenuByUserID</code>).</p><h2>Tenant-aware scheduler</h2><p><code>SchedulerHostedService</code> enumerates tenants on every cycle (<code>Scheduler:PollSeconds</code>, default 15s):</p><ul><li>scope 0 (primary / no-tenant) — always processed.</li><li>plus one scope per tenant with conn mapping ≠ default.</li><li>tenants mapped to default entries (<code>MetaDataSQLConnection</code>) are skipped (covered by scope 0).</li></ul><p>All 4 action types are supported per-tenant:</p><ul><li><code>1 | sql</code>: data connection via tenant-aware routing.</li><li><code>2 | webservice</code>: <code>__scheduler</code> user created in each tenant DB; per-tenant session cookie via <code>switchAzienda</code> HTTP call.</li><li><code>3 | assembly method</code>: <code>TenantScope.Current</code> propagated via AsyncLocal to the invoked method.</li><li><code>4 | mailing</code>: <code>_mailing_lists</code> / <code>_mail_recipients</code> read from the current tenant DB; SMTP settings overridable per-tenant via naming convention <code><key>__a<id></code> (e.g. <code>email-host__a1</code>).</li></ul><p>The scheduler schema (<code>scheduler</code>, <code>scheduler_execution</code>) is validated per-tenant: tenants missing the tables are silently skipped until restart.</p><h2>Cross-tenant scaffolding propagation</h2><p>When a superadmin scaffolds a table or view on one tenant, by default the metadata (<code>_metadati__tabelle</code> + <code>_metadati__colonne</code>) is written <strong>only to the current tenant's metadata DB</strong>. To avoid manually repeating the scaffolding on every tenant, the <code>scaffolding.scaffoldTable</code> / <code>scaffolding.scaffoldView</code> endpoint exposes the boolean parameter <code>propagateToTenants</code>:</p><ul><li><code>false</code> (default): scaffold only on the current tenant (back-compat).</li><li><code>true</code>: after scaffolding the current tenant, the backend iterates over all other active tenants (<code>MultiTenantHelpers.GetAziendePrimary()</code>) and replicates the scaffolding on each one's metadata DB. The schema is read from the target tenant's DATA DB resolved via <code>Aziende.Connessione_DB_Dati</code>. Tenants without the physical table are skipped with outcome <code>SKIPPED:not-found</code> → best-effort operation, not cross-tenant transactional.</li></ul><p>Security:</p><ul><li>gated to superadmin with <code>multiConnectionEnabled=true</code> flag — for others the parameter is silently ignored.</li><li>companies mapped to default entries (<code>MetaDataSQLConnection</code>) are skipped (covered by scope 0 of the current tenant if the superadmin is already there).</li></ul><p>The scaffold result includes <code>tenant_<id>:</code> keys with value <code>OK</code> / <code>SKIPPED:<reason></code> / <code>FAILED:<message></code> for per-tenant diagnosis.</p><p>UI: the scaffolding dialog (<code>md_id=1556</code>) exposes a <strong>Propagate to all tenants</strong> checkbox on the form; the <code>Scaffold Table</code> action sends the flag to the backend. In single-tenant mode (<code>multiConnectionEnabled=false</code>) the backend filters the column out of the <code>getTableMetadata</code> response → the checkbox does not appear in the form (no useless UI to handle).</p><h2>Non-superadmin user accounts across multiple tenants</h2><p>The <code><wuic-azienda-switcher></code> selector is visible <strong>only to superadmins</strong>. For non-superadmin users the anti-hijack check (<code>cookie.azienda_id != user.azienda_id_user → reject</code>) prevents changing tenant at runtime: the cookie has <code>azienda_id_user</code> snapshotted at login and immutable.</p><p>Operational convention for scenarios where the same person needs to access multiple tenants as a standard user:</p><ul><li><strong>create a separate user per tenant</strong>, with a <strong>distinct username</strong> (e.g. <code>mario.rossi@T1</code>, <code>mario.rossi@T2</code>, or <code>mario.rossi.t1</code> / <code>mario.rossi.t2</code>).</li><li>each user lives in <code>utenti</code> of its own tenant DB, with matching <code>id_azienda</code>.</li><li><code>_login_index</code> maps each username to its <code>id_azienda</code>: no ambiguity in the fallback chain.</li><li>the user picks which tenant to access by changing username at login.</li></ul><p>Anti-pattern to avoid:</p><ul><li><strong>same username on two tenants</strong>: the fallback login tries both <code>_login_index</code> candidates in ascending <code>id_azienda</code> order; the first one that validates the password wins. The user always lands on the tenant with the lowest <code>id_azienda</code> and cannot reach the other without the switcher widget. The password remains shared cross-tenant, exposed to brute-force on both DBs.</li></ul><p>If the person is a superadmin, a single user on the primary DB (<code>id_azienda</code> = parent company id) is enough: the switcher widget covers runtime tenant changes.</p><h2>Known limits</h2><ul><li><strong>Uniform DBMS cross-tenant</strong>: all tenants MUST share the same DBMS as the primary (<code>AppSettings.dbms</code> / <code>meta-dbms</code>). The tenant-aware routing only routes the <em>catalog</em> (which DB), not <em>which provider</em>. Example: MSSQL backend + a <code>Tenant2_Meta</code> with a MySQL connection string → <code>SqlException</code> on first tenant access. A fail-soft validation (WARN log) runs at startup (<code>MultiTenantHelpers.ValidateTenantConnectionStrings</code>): for each cs referenced by <code>Aziende.{Connessione_DB_Dati,CONNESSIONE_DB_Meta}</code> it heuristically detects the target DBMS (keywords like <code>Port=3306</code>, <code>Initial Catalog=</code>, <code>(HOST=...)</code>, etc.) and logs a warning if it differs from the configured one. Mismatch → boot proceeds but the mismatched tenant is unusable at runtime.</li><li>the <code>__scheduler</code> user created on tenant DBs has <code>id_azienda=NULL</code> (not populated): adequate for the webservice flow (login always starts on primary) but not resolvable via <code>_login_index</code> for reverse cases.</li><li>each new tenant requires an entry in <code>appsettings.ConnectionStrings</code> + redeploy/reload config.</li><li>scheduler jobs in tenants without the <code>scheduler</code> table are silently skipped (info-log at first poll).</li></ul><h2>Relevant AppSettings</h2><p>Snippet (multi-tenant keys):</p><ul><li><code>"AppSettings.multiConnectionEnabled": "true"</code></li><li><code>"AppSettings.enableCookieAuthentication": "true"</code></li><li><code>"ConnectionStrings.MetaDataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.DataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Data": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Data": "..."</code></li><li>(optional) <code>"AppSettings.email-host__a1": "smtp.tenant1.example"</code> — SMTP override for tenant 1.</li></ul>",
|
|
11357
|
+
"codeSamples": []
|
|
11358
|
+
}
|
|
11359
|
+
],
|
|
11360
|
+
"images": []
|
|
11361
|
+
},
|
|
10894
11362
|
{
|
|
10895
11363
|
"slug": "troubleshooting",
|
|
10896
11364
|
"lang": "en-US",
|
|
@@ -11782,26 +12250,34 @@ const frameworkDocsContent = {
|
|
|
11782
12250
|
},
|
|
11783
12251
|
{
|
|
11784
12252
|
"id": "sec_6",
|
|
11785
|
-
"title": "md_props_bag:
|
|
12253
|
+
"title": "md_props_bag: toolbar"
|
|
11786
12254
|
},
|
|
11787
12255
|
{
|
|
11788
12256
|
"id": "sec_7",
|
|
11789
|
-
"title": "
|
|
12257
|
+
"title": "md_props_bag: archetypes.list"
|
|
11790
12258
|
},
|
|
11791
12259
|
{
|
|
11792
12260
|
"id": "sec_8",
|
|
11793
|
-
"title": "
|
|
12261
|
+
"title": "Pagination : md_pagesize et md_page_size_choice"
|
|
11794
12262
|
},
|
|
11795
12263
|
{
|
|
11796
12264
|
"id": "sec_9",
|
|
12265
|
+
"title": "Activation forcee de la virtualisation sur une taille de page elevee"
|
|
12266
|
+
},
|
|
12267
|
+
{
|
|
12268
|
+
"id": "sec_10",
|
|
11797
12269
|
"title": "Evenements et subscriptions (host)"
|
|
12270
|
+
},
|
|
12271
|
+
{
|
|
12272
|
+
"id": "sec_11",
|
|
12273
|
+
"title": "`rowCustomSelect` — signature reelle (piege)"
|
|
11798
12274
|
}
|
|
11799
12275
|
],
|
|
11800
12276
|
"sections": [
|
|
11801
12277
|
{
|
|
11802
12278
|
"id": "overview",
|
|
11803
12279
|
"title": "Overview",
|
|
11804
|
-
"html": "<h1>List Grid</h1><p>Composant principal pour les listes tabulaires avec filtres, tri, pagination cote serveur et actions sur les lignes.</p><h2>Cas d'utilisation</h2><ul><li>CRUD tabulaires enterprise.</li><li>Rapports operationnels avec filtres multi-colonnes.</li><li>Jeux de donnees tres volumineux avec <code>cursorMode</code>.</li></ul><h2>Reference de capture d'ecran (manuel utilisateur)</h2><ul><li><code>manual__grid__01.png</code> : mode <code>inline cell edit</code> dans list-grid.</li><li><code>manual__grid__02.png</code> : mode <code>inline edit</code> (au niveau ligne) dans list-grid.</li></ul><h2>Metadonnees inline editing</h2><p>Configuration dans le <code>md_props_bag</code> de la table metadata :</p><ul><li><code>md_inline_edit</code></li></ul><p> - active l'edition inline de la ligne dans list-grid (cellules editables dans le contexte de la ligne, sans ouverture de popup).</p><p> - utile lorsque vous souhaitez conserver l'UX tabulaire avec une edition rapide par enregistrement.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - active l'edition inline "cellule par cellule" (focus sur la cellule individuelle).</p><p> - note : dans les configurations legacy, il peut apparaitre sous le nom <code>md_inline_cell_editing</code> ; le comportement runtime est le meme.</p><p> - <strong>promotion runtime</strong> : lorsque <code>md_inline_cell_edit</code> est <code>true</code>, le composant force</p><p> a runtime egalement <code>md_inline_edit = true</code> independamment de la valeur en DB.</p><p> Les deux UX necessitent que la colonne d'actions soit visible, elles sont donc mutuellement</p><p> non exclusives au niveau du rendu.</p><ul><li><code>md_batch_save</code></li></ul><p> - active la sauvegarde batch des modifications en attente (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si utilise conjointement avec inline-cell, les modifications restent en attente jusqu'a la sauvegarde explicite.</p><p> - <strong>prerequis</strong> : <code>md_batch_save</code> n'a d'effet <strong>que</strong> si <code>md_inline_cell_edit</code> est <code>true</code>.</p><p> Avec <code>md_inline_cell_edit:false</code>, le flag est ignore a runtime et les boutons</p><p> "Sauvegarder les modifications / Annuler les modifications" ne sont pas affiches.</p><h3>Combinaisons valides</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultat runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>quelconque</td><td>Edition inline au niveau ligne avec crayon. <code>md_batch_save</code> ignore.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Autosave cellule par cellule au blur. <code>md_inline_edit</code> force a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cellule par cellule avec buffer en attente + barre d'outils <code>Sauvegarder / Annuler les modifications</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>quelconque</td><td>Aucune edition inline. <code>md_batch_save</code> ignore.</td></tr></tbody></table><p>Exemple edition inline au niveau ligne :</p><p>Snippet 1:</p><p>Exemple cellule par cellule avec sauvegarde batch :</p><p>Snippet 2:</p><h2>Barre d'outils export/import</h2><ul><li>Export XLS :</li></ul><p> - affiche une boite de dialogue de progression avec pourcentage en temps reel ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler la tache</code>, <code>Interrompre et telecharger le partiel</code> ;</p><p> - en arriere-plan, cree une notification avec progression ; un clic rouvre la boite de dialogue.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>) :</li></ul><p> - boite de dialogue de progression apres la confirmation ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler l'import (rollback)</code>, <code>Arreter et commit partiel</code> ;</p><p> - a la fin de l'import, cree une notification recapitulative qui redirige vers la route.</p><h2>Config metadata</h2><p>Parametres cles dans <code>md_props_bag</code> et metadonnees de colonnes.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>Le composant lit <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeurs acceptees : <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>valeur par defaut runtime : <code>true</code> lorsqu'absent ou vide</li><li>effet : distribue les largeurs de colonnes en pourcentage (au lieu de px) lorsqu'il n'y a pas de largeurs utilisateur persistees.</li></ul><ul><li><code>virtualize</code> :</li><li>type : <code>boolean</code> ou <code>object</code></li><li>valeurs acceptees :</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object avec <code>enabled</code> (optionnel) et <code>itemSize</code> (optionnel)</li><li>valeur par defaut runtime : desactive (<code>false</code>) lorsqu'absent</li><li><code>enabled</code> :</li><li>par defaut : <code>true</code> si le noeud <code>virtualize</code> est un objet sans <code>enabled</code>, sinon parser tolerant</li><li>effet : active <code>virtualScroll</code> sur <code>p-table</code></li><li><code>itemSize</code> :</li><li>par defaut : <code>44</code></li><li>effet : definit <code>virtualScrollItemSize</code> (hauteur de la ligne virtuelle en px)</li></ul><ul><li><code>advancedFilter</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeur par defaut runtime : <code>false</code></li><li>effet :</li><li>lorsque <code>true</code>, la grille masque les icones de filtre de colonne (<code>p-columnFilter</code>) et utilise la <code>wuic-filter-bar</code> au-dessus de la table (au niveau <code>data-repeater</code>/<code>bounded-repeater</code>) pour appliquer les filtres.</li></ul><p>Note operationnelle :</p><ul><li>le suggest <code>md_props_bag</code> dans le metadata editor expose egalement le noeud cochable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Pagination : md_pagesize et md_page_size_choice</h2><p>La grille utilise <code>md_pagesize</code> comme taille de page par defaut et <code>md_page_size_choice</code> comme liste de valeurs selectionnables dans le paginateur.</p><p>Regles runtime :</p><ul><li>si <code>md_pagesize</code> est superieur a la valeur maximale presente dans <code>md_page_size_choice</code>, le framework ajoute automatiquement <code>md_pagesize</code> a la liste d'options ;</li><li>la liste est normalisee (nombres valides, deduplication, tri croissant).</li></ul><p>Exemple :</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>resultat runtime : <code>10,25,50,100,200</code></li></ul><h2>Activation forcee de la virtualisation sur une taille de page elevee</h2><p>Lorsque l'utilisateur selectionne dans le paginateur une valeur <code>pageSize >= 1000</code> :</p><ul><li>la virtualisation est forcee automatiquement meme si <code>md_props_bag.archetypes.list.virtualize</code> est absent ou desactive ;</li><li><code>virtualScrollItemSize</code> est force a la valeur par defaut <code>44</code>.</li></ul><p>Ce comportement protege le rendu de la table sur des pages tres volumineuses.</p><h2>Evenements et subscriptions (host)</h2><p><code>wuic-list-grid</code> expose des evenements runtime utiles pour intercepter le cycle de rendu et les callbacks p-table cote projet host.</p><p>Evenements disponibles :</p><ul><li><code>onAfterRender</code> : emis a la fin du binding des donnees de la grille (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code> : emis avant le rendu logique de la ligne individuelle ; supporte l'annulation via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code> : emis apres le rendu logique de la ligne individuelle.</li><li><code>onPaging</code> : emis sur les evenements de pagination (<code>p-table onPage</code>).</li><li><code>onSorting</code> : emis sur les evenements de tri (<code>p-table onSort</code>).</li><li><code>onFiltering</code> : emis sur les evenements de filtrage (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code> : emis lors du changement de selection des lignes.</li><li><code>onPTableRowExpand</code> : emis lors de l'expansion d'une ligne.</li><li><code>onPTableRowCollapse</code> : emis lors de la reduction d'une ligne.</li><li><code>onPTableColumnResize</code> : emis lors du redimensionnement d'une colonne.</li><li><code>onPTableColumnReorder</code> : emis lors de la reorganisation des colonnes.</li></ul><h3>Exemple 1 : binding direct dans le template</h3><p>Snippet 5:</p><h3>Exemple 2 : subscribe via ViewChild</h3><p>Snippet 6:</p>",
|
|
12280
|
+
"html": "<h1>List Grid</h1><p>Composant principal pour les listes tabulaires avec filtres, tri, pagination cote serveur et actions sur les lignes.</p><h2>Cas d'utilisation</h2><ul><li>CRUD tabulaires enterprise.</li><li>Rapports operationnels avec filtres multi-colonnes.</li><li>Jeux de donnees tres volumineux avec <code>cursorMode</code>.</li></ul><h2>Reference de capture d'ecran (manuel utilisateur)</h2><ul><li><code>manual__grid__01.png</code> : mode <code>inline cell edit</code> dans list-grid.</li><li><code>manual__grid__02.png</code> : mode <code>inline edit</code> (au niveau ligne) dans list-grid.</li></ul><h2>Metadonnees inline editing</h2><p>Configuration dans le <code>md_props_bag</code> de la table metadata :</p><ul><li><code>md_inline_edit</code></li></ul><p> - active l'edition inline de la ligne dans list-grid (cellules editables dans le contexte de la ligne, sans ouverture de popup).</p><p> - utile lorsque vous souhaitez conserver l'UX tabulaire avec une edition rapide par enregistrement.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - active l'edition inline "cellule par cellule" (focus sur la cellule individuelle).</p><p> - note : dans les configurations legacy, il peut apparaitre sous le nom <code>md_inline_cell_editing</code> ; le comportement runtime est le meme.</p><p> - <strong>promotion runtime</strong> : lorsque <code>md_inline_cell_edit</code> est <code>true</code>, le composant force</p><p> a runtime egalement <code>md_inline_edit = true</code> independamment de la valeur en DB.</p><p> Les deux UX necessitent que la colonne d'actions soit visible, elles sont donc mutuellement</p><p> non exclusives au niveau du rendu.</p><ul><li><code>md_batch_save</code></li></ul><p> - active la sauvegarde batch des modifications en attente (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si utilise conjointement avec inline-cell, les modifications restent en attente jusqu'a la sauvegarde explicite.</p><p> - <strong>prerequis</strong> : <code>md_batch_save</code> n'a d'effet <strong>que</strong> si <code>md_inline_cell_edit</code> est <code>true</code>.</p><p> Avec <code>md_inline_cell_edit:false</code>, le flag est ignore a runtime et les boutons</p><p> "Sauvegarder les modifications / Annuler les modifications" ne sont pas affiches.</p><h3>Combinaisons valides</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultat runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>quelconque</td><td>Edition inline au niveau ligne avec crayon. <code>md_batch_save</code> ignore.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Autosave cellule par cellule au blur. <code>md_inline_edit</code> force a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cellule par cellule avec buffer en attente + barre d'outils <code>Sauvegarder / Annuler les modifications</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>quelconque</td><td>Aucune edition inline. <code>md_batch_save</code> ignore.</td></tr></tbody></table><p>Exemple edition inline au niveau ligne :</p><p>Snippet 1:</p><p>Exemple cellule par cellule avec sauvegarde batch :</p><p>Snippet 2:</p><h2>Barre d'outils export/import</h2><ul><li>Export XLS :</li></ul><p> - affiche une boite de dialogue de progression avec pourcentage en temps reel ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler la tache</code>, <code>Interrompre et telecharger le partiel</code> ;</p><p> - en arriere-plan, cree une notification avec progression ; un clic rouvre la boite de dialogue.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>) :</li></ul><p> - boite de dialogue de progression apres la confirmation ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler l'import (rollback)</code>, <code>Arreter et commit partiel</code> ;</p><p> - a la fin de l'import, cree une notification recapitulative qui redirige vers la route.</p><h2>Config metadata</h2><p>Parametres cles dans <code>md_props_bag</code> et metadonnees de colonnes.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Flags opt-in qui masquent des blocs de la barre d'outils de la list-grid (<code>caption-bar</code>). Tous sous <code>md_props_bag.toolbar.*</code> (parse au runtime comme <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, defaut <code>false</code>) : masque dans la <strong>caption-right</strong> le bouton "Gestion etat" (icone bookmark) + le <code><select></code> des etats enregistres. Utile pour les routes hardcoded / demo ou la fonctionnalite saved-state (persistance par <code>user_id</code> + route via <code>MetaService</code>) n'a pas de sens — par ex. Pattern 3 pur OData sans route metadata enregistree.</li><li><code>hideBatchActions</code> (boolean, defaut <code>false</code>) : masque dans la <strong>caption-left</strong> les boutons "Enregistrer les modifications" (<code>pi-save</code>) + "Annuler les modifications" (<code>pi-times</code>) + l'indicateur de comptage des changements (<code>grid-changes-indicator</code>, badge pencil + count). Generes par le framework quand <code>md_inline_cell_editing</code> + <code>md_batch_save</code> sont actifs. Concu pour les <strong>grids imbriquees dans un parametric-dialog</strong> : le save/cancel du parent persiste le master + les lignes en un seul coup via le batch save framework, et les boutons en double sur la grid imbriquee perturbent l'UX.</li></ul><p>Exemple de lignes imbriquees dans un edit-form custom (patch runtime) :</p><p>Snippet 5:</p><p>Type TS dans <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (propriete <code>extraProps.toolbar</code>). Les flags ne desactivent PAS la logique <code>md_batch_save</code> sous-jacente (les changements restent traces) ; ils suppriment uniquement l'UI de la barre d'outils — le save effectif passe par le flux du parent.</p><h2>md_props_bag: archetypes.list</h2><p>Le composant lit <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeurs acceptees : <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>valeur par defaut runtime : <code>true</code> lorsqu'absent ou vide</li><li>effet : distribue les largeurs de colonnes en pourcentage (au lieu de px) lorsqu'il n'y a pas de largeurs utilisateur persistees.</li></ul><ul><li><code>virtualize</code> :</li><li>type : <code>boolean</code> ou <code>object</code></li><li>valeurs acceptees :</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object avec <code>enabled</code> (optionnel) et <code>itemSize</code> (optionnel)</li><li>valeur par defaut runtime : desactive (<code>false</code>) lorsqu'absent</li><li><code>enabled</code> :</li><li>par defaut : <code>true</code> si le noeud <code>virtualize</code> est un objet sans <code>enabled</code>, sinon parser tolerant</li><li>effet : active <code>virtualScroll</code> sur <code>p-table</code></li><li><code>itemSize</code> :</li><li>par defaut : <code>44</code></li><li>effet : definit <code>virtualScrollItemSize</code> (hauteur de la ligne virtuelle en px)</li></ul><ul><li><code>advancedFilter</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeur par defaut runtime : <code>false</code></li><li>effet :</li><li>lorsque <code>true</code>, la grille masque les icones de filtre de colonne (<code>p-columnFilter</code>) et utilise la <code>wuic-filter-bar</code> au-dessus de la table (au niveau <code>data-repeater</code>/<code>bounded-repeater</code>) pour appliquer les filtres.</li></ul><p>Note operationnelle :</p><ul><li>le suggest <code>md_props_bag</code> dans le metadata editor expose egalement le noeud cochable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Pagination : md_pagesize et md_page_size_choice</h2><p>La grille utilise <code>md_pagesize</code> comme taille de page par defaut et <code>md_page_size_choice</code> comme liste de valeurs selectionnables dans le paginateur.</p><p>Regles runtime :</p><ul><li>si <code>md_pagesize</code> est superieur a la valeur maximale presente dans <code>md_page_size_choice</code>, le framework ajoute automatiquement <code>md_pagesize</code> a la liste d'options ;</li><li>la liste est normalisee (nombres valides, deduplication, tri croissant).</li></ul><p>Exemple :</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>resultat runtime : <code>10,25,50,100,200</code></li></ul><h2>Activation forcee de la virtualisation sur une taille de page elevee</h2><p>Lorsque l'utilisateur selectionne dans le paginateur une valeur <code>pageSize >= 1000</code> :</p><ul><li>la virtualisation est forcee automatiquement meme si <code>md_props_bag.archetypes.list.virtualize</code> est absent ou desactive ;</li><li><code>virtualScrollItemSize</code> est force a la valeur par defaut <code>44</code>.</li></ul><p>Ce comportement protege le rendu de la table sur des pages tres volumineuses.</p><h2>Evenements et subscriptions (host)</h2><p><code>wuic-list-grid</code> expose des evenements runtime utiles pour intercepter le cycle de rendu et les callbacks p-table cote projet host.</p><p>Evenements disponibles :</p><ul><li><code>onAfterRender</code> : emis a la fin du binding des donnees de la grille (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code> : emis avant le rendu logique de la ligne individuelle ; supporte l'annulation via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code> : emis apres le rendu logique de la ligne individuelle.</li><li><code>onPaging</code> : emis sur les evenements de pagination (<code>p-table onPage</code>).</li><li><code>onSorting</code> : emis sur les evenements de tri (<code>p-table onSort</code>).</li><li><code>onFiltering</code> : emis sur les evenements de filtrage (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code> : emis lors du changement de selection des lignes.</li><li><code>onPTableRowExpand</code> : emis lors de l'expansion d'une ligne.</li><li><code>onPTableRowCollapse</code> : emis lors de la reduction d'une ligne.</li><li><code>onPTableColumnResize</code> : emis lors du redimensionnement d'une colonne.</li><li><code>onPTableColumnReorder</code> : emis lors de la reorganisation des colonnes.</li></ul><h3>Exemple 1 : binding direct dans le template</h3><p>Snippet 7:</p><h3>Exemple 2 : subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — signature reelle (piege)</h2><p><code><wuic-list-grid></code> accepte une entree <code>[rowCustomSelect]</code> pour intercepter la selection d'une ligne (ouverture d'une dialog "choisir un document", flux master-detail, etc.). La signature TypeScript declaree dans <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> est :</p><p>Snippet 9:</p><p><strong>Mais au runtime la callback est invoquee avec les arguments inverses</strong> — le framework appelle <code>rowCustomSelect($event, rowData, dt)</code> (voir <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> et <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Symptome en cas d'ordre incorrect : <code>rowData?.id</code> est undefined → guard precoce dans votre handler → la callback retourne silencieusement et votre dialog/action ne demarre pas. Aucune erreur en console.</p><p>Forme correcte (alignee a l'invocation runtime) :</p><p>Snippet 10:</p><p>Utilisation dans le template :</p><p>Snippet 11:</p><p>> Note : les tests dans <code>designer.component.spec.ts</code> (par ex. ligne 512) invoquent la callback avec <code>({currentTarget: rowCell}, {id: 42}, null)</code>, confirmant l'ordre <code>($event, rowData, dt)</code>. Si le framework finit par aligner la signature declaree, cette page sera mise a jour.</p>",
|
|
11805
12281
|
"codeSamples": [
|
|
11806
12282
|
{
|
|
11807
12283
|
"id": "code_1",
|
|
@@ -11825,19 +12301,49 @@ const frameworkDocsContent = {
|
|
|
11825
12301
|
"id": "code_4",
|
|
11826
12302
|
"title": "Snippet 4",
|
|
11827
12303
|
"language": "json",
|
|
11828
|
-
"code": "{\n \"
|
|
12304
|
+
"code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
|
|
11829
12305
|
},
|
|
11830
12306
|
{
|
|
11831
12307
|
"id": "code_5",
|
|
11832
12308
|
"title": "Snippet 5",
|
|
11833
|
-
"language": "
|
|
11834
|
-
"code": "
|
|
12309
|
+
"language": "ts",
|
|
12310
|
+
"code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
|
|
11835
12311
|
},
|
|
11836
12312
|
{
|
|
11837
12313
|
"id": "code_6",
|
|
11838
12314
|
"title": "Snippet 6",
|
|
12315
|
+
"language": "json",
|
|
12316
|
+
"code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
|
|
12317
|
+
},
|
|
12318
|
+
{
|
|
12319
|
+
"id": "code_7",
|
|
12320
|
+
"title": "Snippet 7",
|
|
12321
|
+
"language": "html",
|
|
12322
|
+
"code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
|
|
12323
|
+
},
|
|
12324
|
+
{
|
|
12325
|
+
"id": "code_8",
|
|
12326
|
+
"title": "Snippet 8",
|
|
11839
12327
|
"language": "ts",
|
|
11840
12328
|
"code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // exemple d'annulation de ligne\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
|
|
12329
|
+
},
|
|
12330
|
+
{
|
|
12331
|
+
"id": "code_9",
|
|
12332
|
+
"title": "Snippet 9",
|
|
12333
|
+
"language": "ts",
|
|
12334
|
+
"code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
|
|
12335
|
+
},
|
|
12336
|
+
{
|
|
12337
|
+
"id": "code_10",
|
|
12338
|
+
"title": "Snippet 10",
|
|
12339
|
+
"language": "ts",
|
|
12340
|
+
"code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData est l'enregistrement fourni par list-grid\n this.handleSelection(rowData);\n};"
|
|
12341
|
+
},
|
|
12342
|
+
{
|
|
12343
|
+
"id": "code_11",
|
|
12344
|
+
"title": "Snippet 11",
|
|
12345
|
+
"language": "html",
|
|
12346
|
+
"code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
|
|
11841
12347
|
}
|
|
11842
12348
|
]
|
|
11843
12349
|
}
|
|
@@ -12094,14 +12600,47 @@ const frameworkDocsContent = {
|
|
|
12094
12600
|
{
|
|
12095
12601
|
"id": "sec_3",
|
|
12096
12602
|
"title": "Reference au designer"
|
|
12603
|
+
},
|
|
12604
|
+
{
|
|
12605
|
+
"id": "sec_4",
|
|
12606
|
+
"title": "Parametres de rapport (variables SQL filtrees)"
|
|
12607
|
+
},
|
|
12608
|
+
{
|
|
12609
|
+
"id": "sec_5",
|
|
12610
|
+
"title": "Configuration backend requise"
|
|
12097
12611
|
}
|
|
12098
12612
|
],
|
|
12099
12613
|
"sections": [
|
|
12100
12614
|
{
|
|
12101
12615
|
"id": "overview",
|
|
12102
12616
|
"title": "Overview",
|
|
12103
|
-
"html": "<h1>Reporting</h1><p>Rapports operationnels sur la route <code>#/<route>/report-viewer</code>.</p><h2>Reference captures d'ecran (manuel utilisateur)</h2><ul><li><code>manual__reporting__01.png</code> : ouverture du report viewer avec rendu du rapport et barre d'outils operationnelle.</li><li><code>manual__reporting__02.png</code> : exemple runtime avec parametres/filtre appliques au rapport.</li></ul><h2>Report viewer</h2><p>Le <code>report-viewer</code> execute et affiche les rapports Stimulsoft associes a la route metadata courante.</p><p>Fonctionnalites principales :</p><ul><li>ouverture du rapport depuis list/grid avec le nom du rapport (<code>reportName</code>) et les parametres optionnels.</li><li>rendu des donnees a l'execution avec la barre d'outils du viewer (zoom, export, impression).</li><li>support des filtres/parametres propages depuis la page appelante.</li></ul><h2>Reference au designer</h2><p>Pour la creation ou la modification du rapport <code>.mrt</code>, voir la sous-section</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> dans <code>Designer & Workflow</code>.</p>",
|
|
12104
|
-
"codeSamples": [
|
|
12617
|
+
"html": "<h1>Reporting</h1><p>Rapports operationnels sur la route <code>#/<route>/report-viewer</code>.</p><h2>Reference captures d'ecran (manuel utilisateur)</h2><ul><li><code>manual__reporting__01.png</code> : ouverture du report viewer avec rendu du rapport et barre d'outils operationnelle.</li><li><code>manual__reporting__02.png</code> : exemple runtime avec parametres/filtre appliques au rapport.</li></ul><h2>Report viewer</h2><p>Le <code>report-viewer</code> execute et affiche les rapports Stimulsoft associes a la route metadata courante.</p><p>Fonctionnalites principales :</p><ul><li>ouverture du rapport depuis list/grid avec le nom du rapport (<code>reportName</code>) et les parametres optionnels.</li><li>rendu des donnees a l'execution avec la barre d'outils du viewer (zoom, export, impression).</li><li>support des filtres/parametres propages depuis la page appelante.</li></ul><h2>Reference au designer</h2><p>Pour la creation ou la modification du rapport <code>.mrt</code>, voir la sous-section</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> dans <code>Designer & Workflow</code>.</p><h2>Parametres de rapport (variables SQL filtrees)</h2><p>Pour filtrer un rapport sur une entite specifique (par ex. impression d'une seule facture), le pattern canonique est :</p><p>1. <strong>Dans le `.mrt`</strong> : declarer une <strong>Variable</strong> dans le Dictionary avec <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Utiliser la variable comme <code>@nomParam</code> dans les SqlCommand :</p><p>Snippet 1:</p><p> > <strong>Piege</strong> : NE PAS utiliser le bloc legacy <code><StiDataParameter></code> dans le DataSource — il n'est plus supporte par la version actuelle de Stimulsoft et produit <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter 'value')</code> pendant <code>report.Load()</code>.</p><p>2. <strong>Dans l'URL du viewer</strong> : passer la valeur via le query param <code>parameters</code> (PAS comme query param direct) :</p><p>Snippet 2:</p><p> Syntaxe <code>parameters</code> URL-encoded : <code>nom||eq||valeur</code> (separes par <code>@</code> pour plusieurs parametres).</p><p>3. <strong>Depuis une row action ou un lien UI</strong> : utiliser le callback canonique pour construire l'URL :</p><p>Snippet 3:</p><h2>Configuration backend requise</h2><p>Dans le fichier <code>appsettings.json</code> de l'application, le parametre <code>AppSettings.reportQueryTimeout</code> doit exister (par ex. <code>"120"</code>). Sans lui, <code>ReportViewerController.prepareDataSql</code> leve <code>int.Parse(null)</code> → 500. Exemple :</p><p>Snippet 4:</p>",
|
|
12618
|
+
"codeSamples": [
|
|
12619
|
+
{
|
|
12620
|
+
"id": "code_1",
|
|
12621
|
+
"title": "Snippet 1",
|
|
12622
|
+
"language": "sql",
|
|
12623
|
+
"code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
|
|
12624
|
+
},
|
|
12625
|
+
{
|
|
12626
|
+
"id": "code_2",
|
|
12627
|
+
"title": "Snippet 2",
|
|
12628
|
+
"language": "text",
|
|
12629
|
+
"code": " ✅ #/<route>/report-viewer?reportName=Report.mrt¶meters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignore"
|
|
12630
|
+
},
|
|
12631
|
+
{
|
|
12632
|
+
"id": "code_3",
|
|
12633
|
+
"title": "Snippet 3",
|
|
12634
|
+
"language": "js",
|
|
12635
|
+
"code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt¶meters='\n + encodeURIComponent(params);"
|
|
12636
|
+
},
|
|
12637
|
+
{
|
|
12638
|
+
"id": "code_4",
|
|
12639
|
+
"title": "Snippet 4",
|
|
12640
|
+
"language": "json",
|
|
12641
|
+
"code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
|
|
12642
|
+
}
|
|
12643
|
+
]
|
|
12105
12644
|
}
|
|
12106
12645
|
],
|
|
12107
12646
|
"images": [
|
|
@@ -12834,28 +13373,54 @@ const frameworkDocsContent = {
|
|
|
12834
13373
|
},
|
|
12835
13374
|
{
|
|
12836
13375
|
"id": "sec_5",
|
|
12837
|
-
"title": "
|
|
13376
|
+
"title": "Combo slim — champs supplementaires via `mc_props_bag.slimCombo`"
|
|
12838
13377
|
},
|
|
12839
13378
|
{
|
|
12840
13379
|
"id": "sec_6",
|
|
12841
|
-
"title": "
|
|
13380
|
+
"title": "Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup"
|
|
12842
13381
|
},
|
|
12843
13382
|
{
|
|
12844
13383
|
"id": "sec_7",
|
|
13384
|
+
"title": "Effets Client/Serveur"
|
|
13385
|
+
},
|
|
13386
|
+
{
|
|
13387
|
+
"id": "sec_8",
|
|
12845
13388
|
"title": "Notes operationnelles"
|
|
13389
|
+
},
|
|
13390
|
+
{
|
|
13391
|
+
"id": "sec_9",
|
|
13392
|
+
"title": "Auto-fetch sur changement programmatique de FK"
|
|
12846
13393
|
}
|
|
12847
13394
|
],
|
|
12848
13395
|
"sections": [
|
|
12849
13396
|
{
|
|
12850
13397
|
"id": "overview",
|
|
12851
13398
|
"title": "Overview",
|
|
12852
|
-
"html": "<h1>Widget Lookup</h1><p>Widget colonne pour les relations (<code>lookupByID</code>, <code>multiple_check</code>) avec onglet dedie <strong>Lookup</strong> dans le metadata-editor colonnes.</p><h2>Exemples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup pur OData (State/Province depuis `/odata/Stateprovinces`)</a> — demontre <code>mc_props_bag.lookup.endpoint</code> avec provider <code>odata</code> et rendu inline via <code>$expand</code>.</li></ul><h2>Quand l'utiliser</h2><ul><li>selection d'enregistrements correles depuis une table metadata externe.</li><li>scenarios avec recherche cote serveur et pagination lookup.</li><li>support de la selection simple (<code>lookupByID</code>) et de la selection multiple (<code>multiple_check</code>).</li></ul><h2>Proprietes metadata colonne (onglet Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code> : route metadata source du lookup.</li><li><code>mc_ui_lookup_dataValueField</code> : champ ID retourne.</li><li><code>mc_ui_lookup_dataTextField</code> : champ description affiche.</li><li><code>mc_ui_lookup_filter</code> : filtre de base du lookup.</li><li><code>mc_serverside_operations</code> : recherche/pagination cote serveur.</li><li><code>mc_ui_pagesize</code> : taille de page des resultats lookup.</li><li><code>mc_ui_lookup_edit_allow</code> : autorise l'edition d'enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_insert_allow</code> : autorise l'insertion d'enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_search_grid</code> : active la grille de recherche dans le popup.</li><li><code>mc_logic_allow_navigation</code> : navigation vers la route liee.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code> : contrainte additionnelle cote client/serveur dans la requete lookup.</li><li><code>lookup.virtualize</code> : active le defilement virtuel sur <code>p-autocomplete</code> du widget lookup.</li><li><code>lookup.virtualize.enabled</code> : parseur tolerant (<code>true/false</code>, <code>1/0</code>, chaines equivalentes) ; defaut <code>false</code> lorsqu'absent.</li><li><code>lookup.virtualize.itemSize</code> : hauteur de ligne virtuelle en px ; defaut <code>44</code>.</li><li><code>lookup.endpoint</code> : override du provider de donnees du dropdown. Lorsqu'il est defini, la datasource imbriquee du lookup-editor ne passe plus par le combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) mais directement par le provider declare dans <code>type</code> avec l'URL dans <code>uri</code>.</li><li><code>lookup.endpoint.type</code> : <code>'odata'</code> (le dispatcher route vers <code>DataProviderOdataService.selectCombo</code> qui emet un GET avec <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> et <code>$filter=contains(textField,'<query>')</code> pour la recherche server-side). Les autres valeurs suivent la meme logique que le table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code> : URL de base de l'entity set (ex. <code>/odata/<EntitySet></code>), avec ou sans origine explicite. Les query params existants sont ignores — le provider reconstruit les params a chaque fetch.</li><li><code>form.columns</code> : largeur du champ dans le formulaire d'edition.</li><li><code>style.editCss</code> : style personnalise de l'editeur.</li></ul><h2>Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup</h2><ul><li>Flux operationnel inline : <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline champs principaux : <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtre rapide : <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Effet client : configuration du lookup acceleree dans le metadata-editor et filtre coherent deja pret dans le widget.</li><li>Effet serveur : la requete lookup et la clause select restent alignees avec les endpoints <code>MetaService.*</code> sans mapping manuel.</li></ul><p>Checklist operationnelle rapide :</p><p>1. cloner la configuration de base avec <code>suggestLookup2</code> ;</p><p>2. generer le filtre par defaut avec <code>suggestLookupDefaultFilter</code> ;</p><p>3. affiner la hierarchie/clause select depuis l'arbre lookup ;</p><p>4. valider en runtime que la recherche et le mapping value/text sont coherents.</p><h2>Effets Client/Serveur</h2><ul><li>Client : rendu du widget lookup, recherche, binding libelle/valeur.</li><li>Client : avec <code>lookup.virtualize</code> actif, l'autocompletion rend uniquement le sous-ensemble visuel des options.</li><li>Serveur : requete lookup paginee/filtree via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notes operationnelles</h2><ul><li>en <code>multiple_check</code>, la partie lookup reste la meme, seul le rendu multi-valeurs change.</li><li>si <code>mc_ui_lookup_entity_name</code> est absent, le widget ne peut pas resoudre la source.</li></ul>",
|
|
13399
|
+
"html": "<h1>Widget Lookup</h1><p>Widget colonne pour les relations (<code>lookupByID</code>, <code>multiple_check</code>) avec onglet dedie <strong>Lookup</strong> dans le metadata-editor colonnes.</p><h2>Exemples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup pur OData (State/Province depuis `/odata/Stateprovinces`)</a> — demontre <code>mc_props_bag.lookup.endpoint</code> avec provider <code>odata</code> et rendu inline via <code>$expand</code>.</li></ul><h2>Quand l'utiliser</h2><ul><li>selection d'enregistrements correles depuis une table metadata externe.</li><li>scenarios avec recherche cote serveur et pagination lookup.</li><li>support de la selection simple (<code>lookupByID</code>) et de la selection multiple (<code>multiple_check</code>).</li></ul><h2>Proprietes metadata colonne (onglet Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code> : route metadata source du lookup.</li><li><code>mc_ui_lookup_dataValueField</code> : champ ID retourne.</li><li><code>mc_ui_lookup_dataTextField</code> : champ description affiche.</li><li><code>mc_ui_lookup_filter</code> : filtre de base du lookup.</li><li><code>mc_serverside_operations</code> : recherche/pagination cote serveur.</li><li><code>mc_ui_pagesize</code> : taille de page des resultats lookup.</li><li><code>mc_ui_lookup_edit_allow</code> : autorise l'edition d'enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_insert_allow</code> : autorise l'insertion d'enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_search_grid</code> : active la grille de recherche dans le popup.</li><li><code>mc_logic_allow_navigation</code> : navigation vers la route liee.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code> : contrainte additionnelle cote client/serveur dans la requete lookup.</li><li><code>lookup.virtualize</code> : active le defilement virtuel sur <code>p-autocomplete</code> du widget lookup.</li><li><code>lookup.virtualize.enabled</code> : parseur tolerant (<code>true/false</code>, <code>1/0</code>, chaines equivalentes) ; defaut <code>false</code> lorsqu'absent.</li><li><code>lookup.virtualize.itemSize</code> : hauteur de ligne virtuelle en px ; defaut <code>44</code>.</li><li><code>lookup.endpoint</code> : override du provider de donnees du dropdown. Lorsqu'il est defini, la datasource imbriquee du lookup-editor ne passe plus par le combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) mais directement par le provider declare dans <code>type</code> avec l'URL dans <code>uri</code>.</li><li><code>lookup.endpoint.type</code> : <code>'odata'</code> (le dispatcher route vers <code>DataProviderOdataService.selectCombo</code> qui emet un GET avec <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> et <code>$filter=contains(textField,'<query>')</code> pour la recherche server-side). Les autres valeurs suivent la meme logique que le table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code> : URL de base de l'entity set (ex. <code>/odata/<EntitySet></code>), avec ou sans origine explicite. Les query params existants sont ignores — le provider reconstruit les params a chaque fetch.</li><li><code>form.columns</code> : largeur du champ dans le formulaire d'edition.</li><li><code>style.editCss</code> : style personnalise de l'editeur.</li></ul><h2>Combo slim — champs supplementaires via <code>mc_props_bag.slimCombo</code></h2><p>Le dropdown lookup emet par defaut un SELECT minimal sur la table liee : PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Cela reduit le payload du combo. Quand le client a besoin d'autres colonnes de la table liee — typiquement pour pre-remplir des champs dependants via <code>mc_selection_changed_custom_function__fn</code> — utiliser <code>mc_props_bag.slimCombo</code> comme <strong>tableau de noms de colonnes supplementaires</strong> a inclure dans le SELECT du combo :</p><p>Snippet 2:</p><ul><li>Tableau : les noms sont ajoutes au <code>columnRestrictionList</code> de la reponse combo (logique dans <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code> : desactive la restriction → le combo retourne <strong>toutes</strong> les colonnes. Utile en debug, payload plus gros.</li><li>Si la colonne a <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> valorise (formule d'affichage calculee), <code>slimCombo</code> est ignore et le combo retourne toutes les colonnes (la formule peut referencer des champs arbitraires).</li></ul><p>Les champs supplementaires arrivent dans <code>record['<col>__lookup_obj'].value</code> (record complet de la table liee, deja avec les champs declares dans <code>slimCombo</code>) et sont accessibles depuis le callback <code>mc_selection_changed_custom_function__fn</code> :</p><p>Snippet 3:</p><p>Compromis : payload combo legerement plus gros mais aucun roundtrip HTTP supplementaire (<code>getFlatRecordData</code> pour recuperer le record complet par id) a chaque selection.</p><h2>Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup</h2><ul><li>Flux operationnel inline : <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline champs principaux : <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtre rapide : <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Effet client : configuration du lookup acceleree dans le metadata-editor et filtre coherent deja pret dans le widget.</li><li>Effet serveur : la requete lookup et la clause select restent alignees avec les endpoints <code>MetaService.*</code> sans mapping manuel.</li></ul><p>Checklist operationnelle rapide :</p><p>1. cloner la configuration de base avec <code>suggestLookup2</code> ;</p><p>2. generer le filtre par defaut avec <code>suggestLookupDefaultFilter</code> ;</p><p>3. affiner la hierarchie/clause select depuis l'arbre lookup ;</p><p>4. valider en runtime que la recherche et le mapping value/text sont coherents.</p><h2>Effets Client/Serveur</h2><ul><li>Client : rendu du widget lookup, recherche, binding libelle/valeur.</li><li>Client : avec <code>lookup.virtualize</code> actif, l'autocompletion rend uniquement le sous-ensemble visuel des options.</li><li>Serveur : requete lookup paginee/filtree via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notes operationnelles</h2><ul><li>en <code>multiple_check</code>, la partie lookup reste la meme, seul le rendu multi-valeurs change.</li><li>si <code>mc_ui_lookup_entity_name</code> est absent, le widget ne peut pas resoudre la source.</li></ul><h2>Auto-fetch sur changement programmatique de FK</h2><p>Le <code>wuic-lookup-editor</code> souscrit automatiquement au <code>BehaviorSubject</code> de <code>record[mc_nome_colonna]</code> et gere les changements de FK programmatiques (par ex. import depuis un document source, callback custom qui fait <code>record.cliente_id.next(99)</code>) :</p><ul><li>Si la nouvelle valeur est <strong>deja presente dans `items`</strong> (la page chargee du lookup) : seul <code>__lookup_obj</code> est mis a jour pour la coherence, aucun HTTP.</li><li>Si la nouvelle valeur <strong>n'est pas dans items</strong> (par ex. valeur hors page courante du combo, ou pas encore fetchee) : un <strong>fetch second-stage</strong> filtre <code>WHERE <valueField> eqor <newValue></code> est declenche pour recuperer l'enregistrement lie et remplir automatiquement le dropdown.</li></ul><p>Skip cases (no-op) :</p><ul><li>Lookup multi-valeur (<code>isFilter</code> avec <code>eqor</code>, <code>multiple_check</code>)</li><li>Premier emit du BS (deja couvert par le flux d'init)</li><li>Valeur null/undefined (utilisateur a clear)</li><li><code>hasActiveLookupQuery=true</code> (l'utilisateur tape une requete manuelle)</li></ul><p><strong>Implication pour les custom forms</strong> : il suffit de setter le FK — plus besoin de workaround manuel pour remplir <code>__lookup_obj</code> ou l'alias jointure. Le dropdown se met a jour tout seul.</p><p>Snippet 4:</p><p>> <strong>Note</strong> : l'auto-fetch ne se declenche que lorsque le <code><wuic-lookup-editor></code> est <strong>monte</strong> (cellule en mode edit). Pour l'affichage en <strong>liste</strong> (cellule read-only), <code>formatGridViewValue</code> lit l'alias jointure <code><route>___<textField>__<col></code> directement depuis le record — celui-ci doit toujours etre rempli manuellement s'il ne vient pas du backend.</p>",
|
|
12853
13400
|
"codeSamples": [
|
|
12854
13401
|
{
|
|
12855
13402
|
"id": "code_1",
|
|
12856
13403
|
"title": "Snippet 1",
|
|
12857
13404
|
"language": "json",
|
|
12858
13405
|
"code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
|
|
13406
|
+
},
|
|
13407
|
+
{
|
|
13408
|
+
"id": "code_2",
|
|
13409
|
+
"title": "Snippet 2",
|
|
13410
|
+
"language": "json",
|
|
13411
|
+
"code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
|
|
13412
|
+
},
|
|
13413
|
+
{
|
|
13414
|
+
"id": "code_3",
|
|
13415
|
+
"title": "Snippet 3",
|
|
13416
|
+
"language": "ts",
|
|
13417
|
+
"code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
|
|
13418
|
+
},
|
|
13419
|
+
{
|
|
13420
|
+
"id": "code_4",
|
|
13421
|
+
"title": "Snippet 4",
|
|
13422
|
+
"language": "ts",
|
|
13423
|
+
"code": "// AVANT (workaround manuel requis)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// MAINTENANT (auto-fetch du lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor monte ? Subscribe detecte le change → fetch filtre → __lookup_obj rempli → dropdown affiche \"Acme S.r.l.\""
|
|
12859
13424
|
}
|
|
12860
13425
|
]
|
|
12861
13426
|
}
|
|
@@ -13118,10 +13683,14 @@ const frameworkDocsContent = {
|
|
|
13118
13683
|
},
|
|
13119
13684
|
{
|
|
13120
13685
|
"id": "sec_4",
|
|
13121
|
-
"title": "
|
|
13686
|
+
"title": "Menu contextuel des elements"
|
|
13122
13687
|
},
|
|
13123
13688
|
{
|
|
13124
13689
|
"id": "sec_5",
|
|
13690
|
+
"title": "Flux operationnel conseille"
|
|
13691
|
+
},
|
|
13692
|
+
{
|
|
13693
|
+
"id": "sec_6",
|
|
13125
13694
|
"title": "Notes operationnelles"
|
|
13126
13695
|
}
|
|
13127
13696
|
],
|
|
@@ -13129,7 +13698,7 @@ const frameworkDocsContent = {
|
|
|
13129
13698
|
{
|
|
13130
13699
|
"id": "overview",
|
|
13131
13700
|
"title": "Overview",
|
|
13132
|
-
"html": "<h1>Designer</h1><p>Editeur visuel pour les dashboards pilotes par metadata sur la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composition de board par glisser-deposer de noeuds et blocs UI.</li><li>Mise en page et styles pilotes par <code>boardcontent</code> et metadata table/colonne.</li><li>Edition via le panneau de proprietes sans codage en dur dans les composants runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code> : source de donnees metadata (<code>route</code>, filtres, tri, pagination).</li><li><code>DataRepeater</code> : visualisation liste/carte liee a une datasource.</li><li><code>Dynamic Template</code> : rendu de template dynamique avec inputs pilotes par metadata.</li><li><code>Action/Button</code> : actions utilisateur liees aux enregistrements et callbacks.</li><li><code>Container/Layout</code> : blocs structurels pour colonnes, lignes et wrappers.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Glisser-deposer de noeuds sur le canvas avec persistance dans le <code>boardcontent</code>.</li><li>Panneau de proprietes contextuel avec des inputs coherents aux composants selectionnes.</li><li>Liaison entre noeuds (<code>datasource -> repeater/template</code>) avec validation minimale.</li><li>Apercu runtime integre et realignement de la mise en page.</li><li>Gestion des modifications en attente avec guard avant la sortie.</li></ul><h2>Flux operationnel conseille</h2><ul><li>Ouvrir la route dashboard cible.</li><li>Inserer/relier les noeuds depuis la palette.</li><li>Configurer les <code>inputs</code> essentiels (ex. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Sauvegarder et valider le resultat en runtime.</li></ul><h2>Notes operationnelles</h2><ul><li>Pour les correctifs specifiques a un dashboard, privilegier la patch du <code>boardcontent</code> plutot que des modifications globales.</li><li>Eviter les proprietes dupliquees entre <code>inputs</code> et le style inline incoherent.</li><li>Toujours verifier que les datasources ont <code>inputs.route</code> renseignee.</li></ul>",
|
|
13701
|
+
"html": "<h1>Designer</h1><p>Editeur visuel pour les dashboards pilotes par metadata sur la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composition de board par glisser-deposer de noeuds et blocs UI.</li><li>Mise en page et styles pilotes par <code>boardcontent</code> et metadata table/colonne.</li><li>Edition via le panneau de proprietes sans codage en dur dans les composants runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code> : source de donnees metadata (<code>route</code>, filtres, tri, pagination).</li><li><code>DataRepeater</code> : visualisation liste/carte liee a une datasource.</li><li><code>Dynamic Template</code> : rendu de template dynamique avec inputs pilotes par metadata.</li><li><code>Action/Button</code> : actions utilisateur liees aux enregistrements et callbacks.</li><li><code>Container/Layout</code> : blocs structurels pour colonnes, lignes et wrappers.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Glisser-deposer de noeuds sur le canvas avec persistance dans le <code>boardcontent</code>.</li><li>Panneau de proprietes contextuel avec des inputs coherents aux composants selectionnes.</li><li>Liaison entre noeuds (<code>datasource -> repeater/template</code>) avec validation minimale.</li><li>Apercu runtime integre et realignement de la mise en page.</li><li>Gestion des modifications en attente avec guard avant la sortie.</li></ul><h2>Menu contextuel des elements</h2><p>Le clic droit sur un element du canvas, sur un element du fil d'Ariane en bas ou sur un noeud de l'arbre de hierarchie affiche un menu contextuel avec trois entrees :</p><ul><li><strong>Modifier les proprietes</strong> (pi-cog) - selectionne l'element comme "current tool" et affiche son panneau de proprietes a droite ; equivalent au clic dans l'arbre. Utile pour editer les proprietes d'elements imbriques sans devoir les chercher dans la hierarchie.</li><li><strong>Renommer</strong> (uniquement fil d'Ariane) - ouvre une boite de dialogue pour renommer le <code>displayName</code>.</li><li><strong>Supprimer</strong> - retire l'element du board.</li></ul><h2>Flux operationnel conseille</h2><ul><li>Ouvrir la route dashboard cible.</li><li>Inserer/relier les noeuds depuis la palette.</li><li>Configurer les <code>inputs</code> essentiels (ex. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Sauvegarder et valider le resultat en runtime.</li></ul><h2>Notes operationnelles</h2><ul><li>Pour les correctifs specifiques a un dashboard, privilegier la patch du <code>boardcontent</code> plutot que des modifications globales.</li><li>Eviter les proprietes dupliquees entre <code>inputs</code> et le style inline incoherent.</li><li>Toujours verifier que les datasources ont <code>inputs.route</code> renseignee.</li></ul>",
|
|
13133
13702
|
"codeSamples": []
|
|
13134
13703
|
}
|
|
13135
13704
|
],
|
|
@@ -13193,14 +13762,18 @@ const frameworkDocsContent = {
|
|
|
13193
13762
|
},
|
|
13194
13763
|
{
|
|
13195
13764
|
"id": "sec_3",
|
|
13196
|
-
"title": "
|
|
13765
|
+
"title": "Onglet View Builder — Agregations et GROUP BY (Tier 1, 2026-05-08)"
|
|
13197
13766
|
},
|
|
13198
13767
|
{
|
|
13199
13768
|
"id": "sec_4",
|
|
13200
|
-
"title": "
|
|
13769
|
+
"title": "Flux operationnel conseille"
|
|
13201
13770
|
},
|
|
13202
13771
|
{
|
|
13203
13772
|
"id": "sec_5",
|
|
13773
|
+
"title": "Boutons d'action"
|
|
13774
|
+
},
|
|
13775
|
+
{
|
|
13776
|
+
"id": "sec_6",
|
|
13204
13777
|
"title": "Notes d'utilisation"
|
|
13205
13778
|
}
|
|
13206
13779
|
],
|
|
@@ -13208,7 +13781,7 @@ const frameworkDocsContent = {
|
|
|
13208
13781
|
{
|
|
13209
13782
|
"id": "overview",
|
|
13210
13783
|
"title": "Overview",
|
|
13211
|
-
"html": "<h1>Constructeur Vue/Pivot</h1><p>Le Pivot Builder permet de construire des vues analytiques dynamiques a partir des routes metadata, sans ecrire de requetes manuelles cote interface.</p><h2>Reference captures d'ecran</h2><ul><li><code>manual__pivot_builder__01.png</code> : configuration et resultat du builder a l'execution.</li><li><code>manual__pivot_builder__02.png</code> : detail du dialogue de creation de table materialisee et options operationnelles.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Selection des champs dans les zones <code>rows</code>, <code>columns</code> et <code>values</code>.</li><li>Agregations disponibles sur les valeurs (somme, comptage, moyenne, minimum, maximum).</li><li>Regroupement multidimensionnel avec tri par dimension.</li><li>Filtres interactifs pour reduire le jeu de donnees analyse.</li><li>Mise a jour de la vue pivot en temps reel apres modification de la mise en page ou de la metrique.</li><li>Exportation/reutilisation de la configuration pivot dans le contexte operationnel.</li></ul><h2>Flux operationnel conseille</h2><p>1. Ouvrez la route pivot builder.</p><p>2. Selectionnez la datasource/route metadata de depart.</p><p>3. Faites glisser les champs dans les zones <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configurez l'agregation pour chaque metrique.</p><p>5. Appliquez les filtres et verifiez les totaux.</p><p>6. Sauvegardez ou reutilisez la configuration selon le flux applicatif.</p><h2>Boutons d'action</h2><p><code>Crea vista</code></p><ul><li>Genere une vue SQL a partir de la configuration pivot courante.</li><li>Utile lorsque vous souhaitez reutiliser le resultat comme source de donnees stable dans d'autres routes/rapports.</li><li>Maintient la logique centralisee dans la base de donnees, evitant de reconstruire la mise en page manuellement a chaque fois.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Cree une table physique avec les donnees agregees de la configuration pivot.</li><li>Utile pour les gros jeux de donnees ou les dashboards necessitant des temps de lecture rapides.</li><li>Scaffolde automatiquement la route associee a la table physique creee et genere une planification pour le rafraichissement/alignement des donnees.</li><li>En appuyant sur Actualiser dans la grille de la route associee, une execution forcee de la planification est declenchee.</li></ul><h2>Notes d'utilisation</h2><ul><li>Pour des analyses coherentes, utiliser des colonnes avec un type de donnees correct (numeriques pour les metriques, textuelles/dates pour les dimensions).</li><li>En presence de gros volumes, preferer les filtres et agregations ciblees avant de developper le detail.</li></ul>",
|
|
13784
|
+
"html": "<h1>Constructeur Vue/Pivot</h1><p>Le Pivot Builder permet de construire des vues analytiques dynamiques a partir des routes metadata, sans ecrire de requetes manuelles cote interface.</p><h2>Reference captures d'ecran</h2><ul><li><code>manual__pivot_builder__01.png</code> : configuration et resultat du builder a l'execution.</li><li><code>manual__pivot_builder__02.png</code> : detail du dialogue de creation de table materialisee et options operationnelles.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Selection des champs dans les zones <code>rows</code>, <code>columns</code> et <code>values</code>.</li><li>Agregations disponibles sur les valeurs (somme, comptage, moyenne, minimum, maximum).</li><li>Regroupement multidimensionnel avec tri par dimension.</li><li>Filtres interactifs pour reduire le jeu de donnees analyse.</li><li>Mise a jour de la vue pivot en temps reel apres modification de la mise en page ou de la metrique.</li><li>Exportation/reutilisation de la configuration pivot dans le contexte operationnel.</li></ul><h2>Onglet View Builder — Agregations et GROUP BY (Tier 1, 2026-05-08)</h2><p>L'onglet 1 "View Builder" permet desormais d'emettre <code>GROUP BY</code> avec agregations <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> directement depuis la <code>ViewDefinition</code>, sans passer en mode manualSql.</p><p>Panneau sous le canvas :</p><ul><li><strong>Agregations</strong> : formulaire pour ajouter <code>fonction(colonne) AS alias</code>. La colonne source peut etre n'importe quelle colonne du canvas ou <code>*</code> (pour <code>COUNT(*)</code>) ; l'alias de sortie est genere automatiquement si vide.</li><li><strong>GROUP BY</strong> : case a cocher par colonne selectionnee sur les noeuds du canvas. Si aucune n'est cochee explicitement, le backend deduit les dimensions des colonnes <code>selected=true</code> non agregees.</li><li><strong>CASE WHEN pour pivot</strong> : pour des motifs comme <code>SUM(CASE WHEN bucket='X' THEN montant ELSE 0 END)</code>, utilisez une colonne formule (clic droit → Modifier la formule) comme source d'agregation. La formule est emise comme argument de l'agregat.</li></ul><p>Sortie backend (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>) :</p><ul><li><code>viewDefinition.aggregations[]</code> → emet <code><FN>(<col|formule>) AS <alias></code> comme colonnes SELECT supplementaires</li><li><code>viewDefinition.groupByColumns[]</code> → emet la clause <code>GROUP BY</code></li><li>Deduction automatique du GROUP BY si <code>aggregations</code> sont presentes sans <code>groupByColumns</code> explicite</li></ul><h3>Blocs UNION (Tier 2, 2026-05-08)</h3><p>Une extension ulterieure permet de concatener plusieurs SELECT avec <code>UNION</code> ou <code>UNION ALL</code> directement depuis une seule <code>ViewDefinition</code> :</p><ul><li><strong>Bloc 0</strong> = top-level (tables/joins/agregations sur le canvas) -> definit le <strong>vocabulaire canonique</strong> des colonnes SELECT</li><li><strong>Bloc N (N>=1)</strong> = chaque <code>unionBlocks[]</code> supplementaire est une sub-definition mono-table avec un <code>columnMapping</code> obligatoire : pour chaque alias canonique du bloc 0, le bloc N specifie <code>sourceQualified</code> (colonne du bloc) <strong>ou</strong> <code>constantValue</code> (litteral SQL comme <code>'CLIENT'</code>) <strong>ou</strong> <code>formula</code> (expression SQL pour cast cross-block)</li><li><strong>Bascule UNION ALL / UNION</strong> : par defaut <code>unionAll: true</code> (sans dedup, meilleure performance) ; <code>false</code> pour <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong> : applique a toute l'UNION, encapsule dans la sous-requete <code>SELECT TOP N * FROM (<union>) AS u</code></li></ul><p>UI sous le canvas : selecteur de route + bouton "Ajouter un bloc UNION" -> chaque bloc apparait comme un accordeon avec en-tete (caption + route) + matrice column-mapping (par alias canonique : liste deroulante de colonne source du bloc, ou saisie de constante SQL).</p><p>Limites v0 : chaque bloc UNION est mono-table (pas de JOIN dans le bloc). Pour des JOIN cross-block (par ex. categorie 'LES DEUX' de <code>vw_anagrafica_unificata</code> qui necessite <code>clients INNER JOIN fournisseurs ON tva</code>) le Tier 2.5 est necessaire.</p><h2>Flux operationnel conseille</h2><p>1. Ouvrez la route pivot builder.</p><p>2. Selectionnez la datasource/route metadata de depart.</p><p>3. Faites glisser les champs dans les zones <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configurez l'agregation pour chaque metrique.</p><p>5. Appliquez les filtres et verifiez les totaux.</p><p>6. Sauvegardez ou reutilisez la configuration selon le flux applicatif.</p><h2>Boutons d'action</h2><p><code>Crea vista</code></p><ul><li>Genere une vue SQL a partir de la configuration pivot courante.</li><li>Utile lorsque vous souhaitez reutiliser le resultat comme source de donnees stable dans d'autres routes/rapports.</li><li>Maintient la logique centralisee dans la base de donnees, evitant de reconstruire la mise en page manuellement a chaque fois.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Cree une table physique avec les donnees agregees de la configuration pivot.</li><li>Utile pour les gros jeux de donnees ou les dashboards necessitant des temps de lecture rapides.</li><li>Scaffolde automatiquement la route associee a la table physique creee et genere une planification pour le rafraichissement/alignement des donnees.</li><li>En appuyant sur Actualiser dans la grille de la route associee, une execution forcee de la planification est declenchee.</li></ul><h2>Notes d'utilisation</h2><ul><li>Pour des analyses coherentes, utiliser des colonnes avec un type de donnees correct (numeriques pour les metriques, textuelles/dates pour les dimensions).</li><li>En presence de gros volumes, preferer les filtres et agregations ciblees avant de developper le detail.</li></ul>",
|
|
13212
13785
|
"codeSamples": []
|
|
13213
13786
|
}
|
|
13214
13787
|
],
|
|
@@ -14163,7 +14736,7 @@ const frameworkDocsContent = {
|
|
|
14163
14736
|
{
|
|
14164
14737
|
"id": "overview",
|
|
14165
14738
|
"title": "Overview",
|
|
14166
|
-
"html": "<h1>Component Catalog</h1><p>Total component folders detected:
|
|
14739
|
+
"html": "<h1>Component Catalog</h1><p>Total component folders detected: 50.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>azienda-switcher</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>azienda-switcher/azienda-switcher.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
|
|
14167
14740
|
"codeSamples": []
|
|
14168
14741
|
}
|
|
14169
14742
|
],
|
|
@@ -14284,13 +14857,21 @@ const frameworkDocsContent = {
|
|
|
14284
14857
|
{
|
|
14285
14858
|
"id": "sec_3",
|
|
14286
14859
|
"title": "Exemples"
|
|
14860
|
+
},
|
|
14861
|
+
{
|
|
14862
|
+
"id": "sec_4",
|
|
14863
|
+
"title": "Patch runtime de `metaInfo` via `fetchInfo$.subscribe`"
|
|
14864
|
+
},
|
|
14865
|
+
{
|
|
14866
|
+
"id": "sec_5",
|
|
14867
|
+
"title": "Plusieurs `<wuic-data-source>` dans le meme composant"
|
|
14287
14868
|
}
|
|
14288
14869
|
],
|
|
14289
14870
|
"sections": [
|
|
14290
14871
|
{
|
|
14291
14872
|
"id": "overview",
|
|
14292
14873
|
"title": "Overview",
|
|
14293
|
-
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> est le composant qui gere les metadata, le chargement de donnees, l'etat courant, le suivi des modifications et la synchronisation CRUD.</p><h2>Description</h2><ul><li>Resout la route (<code>route</code>/<code>hardcodedRoute</code> ou routage URL).</li><li>Charge les metadata table/colonnes et publie l'etat sur <code>fetchInfo$</code>.</li><li>Gere <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, pagination (<code>pageSize</code>, <code>currentPage</code>) et cursor paging.</li><li>Expose les methodes runtime pour insert/update/delete, sauvegarde batch, rollback et export.</li><li>C'est le point central auquel se raccordent <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> et <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selecteur :</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principaux :</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Etat/runtime principal :</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (annulable)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Methodes runtime les plus utilisees :</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Souscriptions/evenements runtime :</p><ul><li><code>fetchInfo$</code> : flux principal de l'etat donnees/metadonnees.</li><li><code>datasourceReady$</code> : emis lorsque l'instance de la datasource est prete.</li><li><code>afterFirstLoad$</code> : emis une seule fois au premier payload utile.</li><li><code>beforeSync$</code> : emis avant <code>insert/update/delete/clone/batch</code> ; vous pouvez annuler avec <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code> : emis apres une synchronisation reussie.</li></ul><p>Note de compatibilite :</p><ul><li>un alias <code>fetchInfo</code> (getter/setter) est egalement disponible, mais le nommage conseille est <code>fetchInfo$</code>.</li></ul><h2>Exemples</h2><h3>Exemple de base (route codee en dur + chargement automatique)</h3><p>Snippet 1:</p><h3>Exemple master-detail (datasource imbriquee)</h3><p>Snippet 2:</p><h3>Exemple operationnel (chargement manuel + synchronisation)</h3><p>Snippet 3:</p><h3>Exemple de souscriptions (hote)</h3><p>Snippet 4:</p>",
|
|
14874
|
+
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> est le composant qui gere les metadata, le chargement de donnees, l'etat courant, le suivi des modifications et la synchronisation CRUD.</p><h2>Description</h2><ul><li>Resout la route (<code>route</code>/<code>hardcodedRoute</code> ou routage URL).</li><li>Charge les metadata table/colonnes et publie l'etat sur <code>fetchInfo$</code>.</li><li>Gere <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, pagination (<code>pageSize</code>, <code>currentPage</code>) et cursor paging.</li><li>Expose les methodes runtime pour insert/update/delete, sauvegarde batch, rollback et export.</li><li>C'est le point central auquel se raccordent <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> et <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selecteur :</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principaux :</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Etat/runtime principal :</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (annulable)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Methodes runtime les plus utilisees :</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Souscriptions/evenements runtime :</p><ul><li><code>fetchInfo$</code> : flux principal de l'etat donnees/metadonnees.</li><li><code>datasourceReady$</code> : emis lorsque l'instance de la datasource est prete.</li><li><code>afterFirstLoad$</code> : emis une seule fois au premier payload utile.</li><li><code>beforeSync$</code> : emis avant <code>insert/update/delete/clone/batch</code> ; vous pouvez annuler avec <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code> : emis apres une synchronisation reussie.</li></ul><p>Note de compatibilite :</p><ul><li>un alias <code>fetchInfo</code> (getter/setter) est egalement disponible, mais le nommage conseille est <code>fetchInfo$</code>.</li></ul><h2>Exemples</h2><h3>Exemple de base (route codee en dur + chargement automatique)</h3><p>Snippet 1:</p><h3>Exemple master-detail (datasource imbriquee)</h3><p>Snippet 2:</p><h3>Exemple operationnel (chargement manuel + synchronisation)</h3><p>Snippet 3:</p><h3>Exemple de souscriptions (hote)</h3><p>Snippet 4:</p><h2>Patch runtime de <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Pour modifier au runtime les metadonnees d'un nested data-source (par ex. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>sans toucher au DB</strong> — patch local sur la reference mise en cache par <code>MetadataProviderService</code>, latest-wins, scenario single-dialog — souscris au <code>fetchInfo$</code> du data-source :</p><p>Snippet 5:</p><p><strong>Scope</strong> : session-only + instance-only. Le patch survit uniquement tant que le cache <code>MetadataProviderService</code> n'est pas invalide ou que le composant n'est pas detruit. Utile lorsqu'un custom form veut une representation differente d'une nested grid par rapport au rendu standalone.</p><p><strong>Quand NE PAS utiliser les patch runtime</strong> : pour des modifications destinees a toutes les instances de la route (y compris standalone <code><route>/list</code>) → patch SQL direct sur les metadonnees ; pour des modifications persistantes apres restart → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Plusieurs <code><wuic-data-source></code> dans le meme composant</h2><p>Lorsqu'un composant custom contient plus d'un <code><wuic-data-source></code> (par ex. master + nested rows + nested echeances), le pattern <code>@ViewChild(DataSourceComponent)</code> by-type matche <strong>seulement le premier</strong> data-source dans l'ordre top-down du template. Pour les suivants il faut un <strong>template reference variable</strong> + <code>@ViewChild('refName')</code> :</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong> : <code>document.querySelectorAll('wuic-data-source')</code> + <code>window.ng.getComponent(el)</code> pour filtrer par <code>hardcodedRoute</code>. Fonctionne mais est fragile, depend de l'API debug <code>window.ng</code> (non garantie en production). Toujours utiliser les template refs.</p>",
|
|
14294
14875
|
"codeSamples": [
|
|
14295
14876
|
{
|
|
14296
14877
|
"id": "code_1",
|
|
@@ -14315,6 +14896,24 @@ const frameworkDocsContent = {
|
|
|
14315
14896
|
"title": "Snippet 4",
|
|
14316
14897
|
"language": "ts",
|
|
14317
14898
|
"code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Delete non consentito dal contesto host');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
|
|
14899
|
+
},
|
|
14900
|
+
{
|
|
14901
|
+
"id": "code_5",
|
|
14902
|
+
"title": "Snippet 5",
|
|
14903
|
+
"language": "ts",
|
|
14904
|
+
"code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup dans ngOnDestroy\n}"
|
|
14905
|
+
},
|
|
14906
|
+
{
|
|
14907
|
+
"id": "code_6",
|
|
14908
|
+
"title": "Snippet 6",
|
|
14909
|
+
"language": "html",
|
|
14910
|
+
"code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
|
|
14911
|
+
},
|
|
14912
|
+
{
|
|
14913
|
+
"id": "code_7",
|
|
14914
|
+
"title": "Snippet 7",
|
|
14915
|
+
"language": "ts",
|
|
14916
|
+
"code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Premier data-source (match by type) -> pas de template ref necessaire\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Deuxieme data-source -> requiert `#scadenzeDs` dans le template + ViewChild by-name\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
|
|
14318
14917
|
}
|
|
14319
14918
|
]
|
|
14320
14919
|
}
|
|
@@ -15884,6 +16483,82 @@ const frameworkDocsContent = {
|
|
|
15884
16483
|
],
|
|
15885
16484
|
"images": []
|
|
15886
16485
|
},
|
|
16486
|
+
{
|
|
16487
|
+
"slug": "multi-tenant",
|
|
16488
|
+
"lang": "fr-FR",
|
|
16489
|
+
"title": "Multi-tenant",
|
|
16490
|
+
"description": "Multi-tenant - documentazione operativa e tecnica",
|
|
16491
|
+
"audience": [
|
|
16492
|
+
"developer",
|
|
16493
|
+
"integration"
|
|
16494
|
+
],
|
|
16495
|
+
"status": "stable",
|
|
16496
|
+
"tags": [
|
|
16497
|
+
"advanced"
|
|
16498
|
+
],
|
|
16499
|
+
"keywords": [
|
|
16500
|
+
"Multi-tenant"
|
|
16501
|
+
],
|
|
16502
|
+
"toc": [
|
|
16503
|
+
{
|
|
16504
|
+
"id": "sec_1",
|
|
16505
|
+
"title": "Scope"
|
|
16506
|
+
},
|
|
16507
|
+
{
|
|
16508
|
+
"id": "sec_2",
|
|
16509
|
+
"title": "Activation"
|
|
16510
|
+
},
|
|
16511
|
+
{
|
|
16512
|
+
"id": "sec_3",
|
|
16513
|
+
"title": "Modèle de données"
|
|
16514
|
+
},
|
|
16515
|
+
{
|
|
16516
|
+
"id": "sec_4",
|
|
16517
|
+
"title": "Fallback de login"
|
|
16518
|
+
},
|
|
16519
|
+
{
|
|
16520
|
+
"id": "sec_5",
|
|
16521
|
+
"title": "Changement de locataire (superadmin)"
|
|
16522
|
+
},
|
|
16523
|
+
{
|
|
16524
|
+
"id": "sec_6",
|
|
16525
|
+
"title": "Caches scoped par locataire"
|
|
16526
|
+
},
|
|
16527
|
+
{
|
|
16528
|
+
"id": "sec_7",
|
|
16529
|
+
"title": "Anti-hijack"
|
|
16530
|
+
},
|
|
16531
|
+
{
|
|
16532
|
+
"id": "sec_8",
|
|
16533
|
+
"title": "Scheduler tenant-aware"
|
|
16534
|
+
},
|
|
16535
|
+
{
|
|
16536
|
+
"id": "sec_9",
|
|
16537
|
+
"title": "Propagation du scaffolding cross-locataire"
|
|
16538
|
+
},
|
|
16539
|
+
{
|
|
16540
|
+
"id": "sec_10",
|
|
16541
|
+
"title": "Comptes utilisateur non-superadmin sur plusieurs locataires"
|
|
16542
|
+
},
|
|
16543
|
+
{
|
|
16544
|
+
"id": "sec_11",
|
|
16545
|
+
"title": "Limitations connues"
|
|
16546
|
+
},
|
|
16547
|
+
{
|
|
16548
|
+
"id": "sec_12",
|
|
16549
|
+
"title": "AppSettings pertinents"
|
|
16550
|
+
}
|
|
16551
|
+
],
|
|
16552
|
+
"sections": [
|
|
16553
|
+
{
|
|
16554
|
+
"id": "overview",
|
|
16555
|
+
"title": "Overview",
|
|
16556
|
+
"html": "<h1>Multi-tenant</h1><p>Routage des connexions par utilisateur : chaque locataire dispose de ses propres chaînes de connexion vers des bases de métadonnées et de données distinctes, en préservant une compatibilité ascendante totale pour les installations mono-locataire existantes.</p><h2>Scope</h2><p>Le module multi-tenant permet à un backend unique de servir plusieurs locataires — chacun sur des bases métadonnées et données distinctes — sans dupliquer le code ni démarrer des instances séparées. Quand le flag est OFF le comportement est identique au pré-multi-tenant (aucun overhead, caches inchangés). Quand il est ON le framework :</p><ul><li>résout à l'exécution la bonne chaîne de connexion selon l'<code>id_azienda</code> de l'utilisateur connecté.</li><li>segrège les caches serveur (<code>Application[]</code>) et client (Dexie + localStorage) par locataire.</li><li>propage <code>TenantScope.Current</code> sur toutes les requêtes, le scheduler et les jobs en arrière-plan.</li></ul><h2>Activation</h2><p>Dans <code>appsettings.json</code> (section <code>AppSettings</code>) :</p><ul><li><code>"multiConnectionEnabled": "true"</code> — active le routage tenant-aware dans <code>MultiTenantHelpers</code> + middleware <code>TenantScope</code> dans <code>Startup.cs</code>.</li><li><code>"enableCookieAuthentication": "true"</code> — requis par le flux multi-tenant : le cookie <code>k-user</code> v:2 transporte <code>azienda_id</code> + <code>azienda_id_user</code> pour l'anti-hijack.</li></ul><p>Les modèles autonomes <code>appsettings.multi-tenant.mssql.json</code> et <code>appsettings.multi-tenant.mysql.json</code> sont disponibles (6 chaînes de connexion : 2 par défaut + 4 tenant <code>Tenant1_Meta</code>/<code>Tenant1_Data</code>/<code>Tenant2_Meta</code>/<code>Tenant2_Data</code>).</p><h2>Modèle de données</h2><p>Le mapping <code>id_azienda → (data, meta)</code> se trouve sur deux colonnes de la table <code>Aziende</code> de la base métadonnées primaire :</p><ul><li><code>Aziende.Connessione_DB_Dati</code> : NOM d'une entrée dans <code>appsettings.ConnectionStrings</code> pour la base données du locataire (PAS la chaîne de connexion littérale).</li><li><code>Aziende.CONNESSIONE_DB_Meta</code> : NOM d'une entrée dans <code>appsettings.ConnectionStrings</code> pour la base métadonnées du locataire.</li></ul><p>Exemple de peuplement :</p><ul><li><code>Aziende.ID_Azienda=1</code>, <code>Connessione_DB_Dati='Tenant1_Data'</code>, <code>CONNESSIONE_DB_Meta='Tenant1_Meta'</code>.</li><li><code>Aziende.ID_Azienda=100</code> (Sede principale), <code>Connessione_DB_Dati='DataSQLConnection'</code>, <code>CONNESSIONE_DB_Meta='MetaDataSQLConnection'</code> (alias par défaut → couvert par scope 0).</li></ul><p>Indirection par nom d'entrée :</p><ul><li>aucun mot de passe en clair dans la base.</li><li>identifiants portables par environnement (même base, différents <code>appsettings.<env>.json</code>).</li><li>cohérent avec <code>_metadati__tabelle.md_conn_name</code>.</li></ul><h2>Fallback de login</h2><p>Quand un utilisateur N'EXISTE PAS dans la base métadonnées primaire, le login fait un fallback via <code>_login_index</code> :</p><p>1. lookup utilisateur sur <code>MetaDataSQLConnection</code> (primaire) → trouvé ? login.</p><p>2. miss → requête <code>_login_index</code> (<code>SHA-256(LOWER(username))</code> → <code>id_azienda</code>).</p><p>3. pour chaque <code>id_azienda</code> candidat : push <code>TenantScope</code>, retry <code>metaQuery.login</code> sur la base métadonnées du locataire.</p><p>4. premier match gagne → cookie émis avec l'<code>azienda_id</code> du locataire.</p><p>La procédure stockée <code>sp__login_index_upsert(@username, @idAzienda)</code> peuple l'index. Hash SHA-256 calculé côté serveur (aligné avec le client C# <code>MultiTenantHelpers.ComputeUsernameHash</code> en UTF-8).</p><h2>Changement de locataire (superadmin)</h2><p>Les superadmins peuvent changer le locataire actif à l'exécution via <code>MetaService.switchAzienda(idAzienda)</code> :</p><ul><li>401 si pas superadmin.</li><li>400 si <code>idAzienda</code> n'a pas les deux colonnes <code>Aziende.Connessione_DB_Dati</code> / <code>CONNESSIONE_DB_Meta</code> renseignées.</li><li>réémet le cookie avec <code>azienda_id</code> mis à jour, en préservant <code>azienda_id_user</code> (snapshot au login).</li><li>le composant Angular <code><wuic-azienda-switcher></code> expose l'UI dropdown. Se masque automatiquement via <code>@HostBinding('attr.hidden')</code> pour les utilisateurs non-super ou si flag OFF.</li></ul><h2>Caches scoped par locataire</h2><p>Côté serveur (clés <code>Application[]</code>) :</p><ul><li><code>MultiTenantHelpers.TenantKey(baseKey)</code> ajoute le suffixe <code>__a<id></code> quand flag ON et <code>TenantScope.Current.AziendaId > 0</code>.</li><li>clés concernées : <code>userList</code>, <code>roleList</code>, <code>userRoleList</code>, <code>companyList</code>, <code>storedMeta</code>, <code>storedTableMeta</code>, <code>storedTableActionMeta</code>, <code>storedConditionGroupMeta</code>, <code>storedTableStyleMeta</code>, <code>SysInfo</code>.</li></ul><p>Côté client (lib framework Angular) :</p><ul><li>nom Dexie → <code>MetaDB__a<id></code> et <code>WuicClientSideCrudDB__a<id></code>.</li><li>clés localStorage → <code>menu_<userId>__a<id></code>, <code>wuic_custom_settings_<userId>__a<id></code>, etc.</li><li>cleanup orphans à l'init de <code>MetadataProviderService</code> : énumère <code>indexedDB.databases()</code> et supprime les bases des locataires non courants.</li></ul><p>Quand flag OFF : TenantKey retourne la baseKey inchangée → caches identiques au pré-multi-tenant.</p><h2>Anti-hijack</h2><p><code>RawHelpers.authenticate()</code> à chaque requête :</p><ul><li>exige un cookie v:2 avec <code>azienda_id</code> + <code>azienda_id_user</code> (cookie legacy → relogin forcé).</li><li>pour les utilisateurs non-super, <code>cookie.azienda_id</code> DOIT correspondre à <code>cookie.azienda_id_user</code> (manipulation → relogin + log de sécurité).</li><li>les superadmins sont exemptés (leur <code>azienda_id</code> change légitimement via <code>switchAzienda</code>).</li><li>peuple <code>TenantScope.Current</code> avec <code>(azienda_id, isSuperAdmin)</code> pour le reste de la requête.</li></ul><p>Le middleware <code>HydrateLegacyPrincipalFromKUserCookie</code> dans <code>Startup.cs</code> peuple aussi <code>TenantScope</code> pour les endpoints AsmxProxy qui n'appellent pas <code>authenticate()</code> explicitement (par ex. <code>readCustomSettings</code>, <code>getMenuByUserID</code>).</p><h2>Scheduler tenant-aware</h2><p><code>SchedulerHostedService</code> énumère les locataires à chaque cycle (<code>Scheduler:PollSeconds</code>, défaut 15s) :</p><ul><li>scope 0 (primaire / no-tenant) — toujours traité.</li><li>plus un scope par locataire avec mapping conn ≠ par défaut.</li><li>locataires mappés sur les entrées par défaut (<code>MetaDataSQLConnection</code>) sont skippés (couverts par scope 0).</li></ul><p>Les 4 types d'action sont supportés par locataire :</p><ul><li><code>1 | sql</code> : connexion données via routage tenant-aware.</li><li><code>2 | webservice</code> : utilisateur <code>__scheduler</code> créé dans chaque base locataire ; cookie session par locataire via appel HTTP <code>switchAzienda</code>.</li><li><code>3 | assembly method</code> : <code>TenantScope.Current</code> propagé via AsyncLocal à la méthode invoquée.</li><li><code>4 | mailing</code> : <code>_mailing_lists</code> / <code>_mail_recipients</code> lus depuis la base du locataire courant ; paramètres SMTP overridables par locataire via convention de nommage <code><key>__a<id></code> (par ex. <code>email-host__a1</code>).</li></ul><p>Le schéma scheduler (<code>scheduler</code>, <code>scheduler_execution</code>) est validé par locataire : locataires sans les tables sont skippés silencieusement jusqu'au redémarrage.</p><h2>Propagation du scaffolding cross-locataire</h2><p>Quand un superadmin scaffold une table ou une vue sur un locataire, par défaut les métadonnées (<code>_metadati__tabelle</code> + <code>_metadati__colonne</code>) sont écrites <strong>uniquement sur la base de métadonnées du locataire courant</strong>. Pour éviter de répéter manuellement le scaffolding sur chaque locataire, l'endpoint <code>scaffolding.scaffoldTable</code> / <code>scaffolding.scaffoldView</code> expose le paramètre booléen <code>propagateToTenants</code> :</p><ul><li><code>false</code> (par défaut) : scaffold uniquement sur le locataire courant (rétro-compat).</li><li><code>true</code> : après avoir scaffold le locataire courant, le backend itère sur tous les autres locataires actifs (<code>MultiTenantHelpers.GetAziendePrimary()</code>) et réplique le scaffolding sur la base métadonnées de chacun. Le schéma est lu depuis la base DONNÉES du locataire cible résolue via <code>Aziende.Connessione_DB_Dati</code>. Locataires sans la table physique sont skippés avec issue <code>SKIPPED:not-found</code> → opération best-effort, non transactionnelle cross-locataire.</li></ul><p>Sécurité :</p><ul><li>restreint au superadmin avec flag <code>multiConnectionEnabled=true</code> — pour les autres le paramètre est silencieusement ignoré.</li><li>entreprises mappées aux entrées par défaut (<code>MetaDataSQLConnection</code>) sont skippées (couvertes par le scope 0 du locataire courant si le superadmin y est déjà).</li></ul><p>Le résultat du scaffold inclut des clés <code>tenant_<id>:</code> avec valeur <code>OK</code> / <code>SKIPPED:<reason></code> / <code>FAILED:<message></code> pour le diagnostic par-locataire.</p><p>UI : le dialog de scaffolding (<code>md_id=1556</code>) expose une checkbox <strong>Propagate to all tenants</strong> dans le formulaire ; l'action <code>Scaffold Table</code> envoie le flag au backend. En mode mono-locataire (<code>multiConnectionEnabled=false</code>) le backend filtre la colonne de la réponse <code>getTableMetadata</code> → la checkbox n'apparaît pas dans le formulaire (aucune UI inutile à gérer).</p><h2>Comptes utilisateur non-superadmin sur plusieurs locataires</h2><p>Le sélecteur <code><wuic-azienda-switcher></code> est visible <strong>uniquement aux superadmins</strong>. Pour les utilisateurs non-superadmin, le contrôle anti-hijack (<code>cookie.azienda_id != user.azienda_id_user → reject</code>) empêche de changer de locataire à l'exécution : le cookie a <code>azienda_id_user</code> capturé au login et immuable.</p><p>Convention opérationnelle pour les scénarios où la même personne doit accéder à plusieurs locataires en tant qu'utilisateur standard :</p><ul><li><strong>créer un utilisateur distinct par locataire</strong>, avec un <strong>nom d'utilisateur différent</strong> (par ex. <code>mario.rossi@T1</code>, <code>mario.rossi@T2</code>, ou <code>mario.rossi.t1</code> / <code>mario.rossi.t2</code>).</li><li>chaque utilisateur vit dans <code>utenti</code> de sa propre base locataire, avec <code>id_azienda</code> correspondant.</li><li><code>_login_index</code> mappe chaque nom d'utilisateur à son <code>id_azienda</code> : aucune ambiguïté dans la fallback chain.</li><li>l'utilisateur choisit quel locataire utiliser en changeant le nom d'utilisateur au login.</li></ul><p>Anti-pattern à éviter :</p><ul><li><strong>même nom d'utilisateur sur deux locataires</strong> : le login fallback essaie les deux candidats <code>_login_index</code> par ordre croissant de <code>id_azienda</code> ; le premier qui valide le mot de passe gagne. L'utilisateur arrive toujours sur le locataire avec l'<code>id_azienda</code> le plus bas et ne peut pas atteindre l'autre sans le widget switcher. Le mot de passe reste partagé cross-locataire, exposé au brute-force sur les deux bases.</li></ul><p>Si la personne est superadmin, un utilisateur unique sur la base primaire (<code>id_azienda</code> = id de l'entreprise mère) suffit : le widget switcher couvre le changement de locataire à l'exécution.</p><h2>Limitations connues</h2><ul><li><strong>SGBD uniforme cross-locataire</strong> : tous les locataires DOIVENT partager le même SGBD que le primaire (<code>AppSettings.dbms</code> / <code>meta-dbms</code>). Le routage tenant-aware n'instradera que le <em>catalog</em> (quelle base), pas <em>quel provider</em>. Exemple : backend MSSQL + un <code>Tenant2_Meta</code> avec une connection string MySQL → <code>SqlException</code> au premier accès au locataire. Une validation fail-soft (WARN log) est exécutée au démarrage (<code>MultiTenantHelpers.ValidateTenantConnectionStrings</code>) : pour chaque cs référencée par <code>Aziende.{Connessione_DB_Dati,CONNESSIONE_DB_Meta}</code> détecte heuristiquement le SGBD cible (mots-clés comme <code>Port=3306</code>, <code>Initial Catalog=</code>, <code>(HOST=...)</code>, etc.) et logge un warning si différent de celui configuré. Mismatch → le boot continue mais le locataire désaligné sera inutilisable à l'exécution.</li><li>l'utilisateur <code>__scheduler</code> créé sur les bases locataires a <code>id_azienda=NULL</code> (non renseigné) : adéquat pour le flux webservice (le login part toujours du primaire) mais non résolvable via <code>_login_index</code> pour les cas inverses.</li><li>chaque nouveau locataire requiert une entrée dans <code>appsettings.ConnectionStrings</code> + redéploiement/reload config.</li><li>les jobs scheduler dans les locataires sans table <code>scheduler</code> sont skippés silencieusement (info-log au premier poll).</li></ul><h2>AppSettings pertinents</h2><p>Snippet (clés multi-tenant) :</p><ul><li><code>"AppSettings.multiConnectionEnabled": "true"</code></li><li><code>"AppSettings.enableCookieAuthentication": "true"</code></li><li><code>"ConnectionStrings.MetaDataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.DataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Data": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Data": "..."</code></li><li>(optionnel) <code>"AppSettings.email-host__a1": "smtp.tenant1.example"</code> — override SMTP pour le locataire 1.</li></ul>",
|
|
16557
|
+
"codeSamples": []
|
|
16558
|
+
}
|
|
16559
|
+
],
|
|
16560
|
+
"images": []
|
|
16561
|
+
},
|
|
15887
16562
|
{
|
|
15888
16563
|
"slug": "troubleshooting",
|
|
15889
16564
|
"lang": "fr-FR",
|
|
@@ -16775,26 +17450,34 @@ const frameworkDocsContent = {
|
|
|
16775
17450
|
},
|
|
16776
17451
|
{
|
|
16777
17452
|
"id": "sec_6",
|
|
16778
|
-
"title": "md_props_bag:
|
|
17453
|
+
"title": "md_props_bag: toolbar"
|
|
16779
17454
|
},
|
|
16780
17455
|
{
|
|
16781
17456
|
"id": "sec_7",
|
|
16782
|
-
"title": "
|
|
17457
|
+
"title": "md_props_bag: archetypes.list"
|
|
16783
17458
|
},
|
|
16784
17459
|
{
|
|
16785
17460
|
"id": "sec_8",
|
|
16786
|
-
"title": "
|
|
17461
|
+
"title": "Paging: md_pagesize y md_page_size_choice"
|
|
16787
17462
|
},
|
|
16788
17463
|
{
|
|
16789
17464
|
"id": "sec_9",
|
|
17465
|
+
"title": "Forzado de virtualización en page size alto"
|
|
17466
|
+
},
|
|
17467
|
+
{
|
|
17468
|
+
"id": "sec_10",
|
|
16790
17469
|
"title": "Eventos y subscriptions (host)"
|
|
17470
|
+
},
|
|
17471
|
+
{
|
|
17472
|
+
"id": "sec_11",
|
|
17473
|
+
"title": "`rowCustomSelect` — firma real (gotcha)"
|
|
16791
17474
|
}
|
|
16792
17475
|
],
|
|
16793
17476
|
"sections": [
|
|
16794
17477
|
{
|
|
16795
17478
|
"id": "overview",
|
|
16796
17479
|
"title": "Overview",
|
|
16797
|
-
"html": "<h1>List Grid</h1><p>Componente principal para listas tabulares con filtros, sorting, paging server-side y acciones de fila.</p><h2>Casos de uso</h2><ul><li>CRUD tabulares enterprise.</li><li>Informes operativos con filtros multi-columna.</li><li>Datasets muy grandes con <code>cursorMode</code>.</li></ul><h2>Screenshot de referencia (manual de usuario)</h2><ul><li><code>manual__grid__01.png</code>: modalidad <code>inline cell edit</code> en list-grid.</li><li><code>manual__grid__02.png</code>: modalidad <code>inline edit</code> (row-level) en list-grid.</li></ul><h2>Metadatos de inline editing</h2><p>Configuración en el <code>md_props_bag</code> de la tabla metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - habilita el editing inline de la fila en list-grid (celdas editables en contexto de fila, sin apertura de popup).</p><p> - útil cuando se quiere mantener la UX de tabla con edición rápida por registro.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - habilita el editing inline "celda por celda" (foco en la celda individual).</p><p> - nota: en configuraciones legacy puede aparecer como <code>md_inline_cell_editing</code>; el comportamiento runtime es el mismo.</p><p> - <strong>promoción runtime</strong>: cuando <code>md_inline_cell_edit</code> es <code>true</code>, el componente fuerza</p><p> en runtime también <code>md_inline_edit = true</code> independientemente del valor en DB.</p><p> Ambas UX necesitan la action column visible, por lo que son mutuamente</p><p> no-exclusivas a nivel de renderizado.</p><ul><li><code>md_batch_save</code></li></ul><p> - habilita el guardado batch de los cambios pendientes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si se usa junto con inline-cell, los cambios quedan pending hasta el guardado explícito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> tiene efecto <strong>solo</strong> si <code>md_inline_cell_edit</code> es <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> el flag se ignora en runtime y los botones</p><p> "Guardar cambios / Cancelar cambios" no se renderizan.</p><h3>Combinaciones válidas</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultado runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>cualquiera</td><td>Inline edit a nivel de fila con pencil. <code>md_batch_save</code> ignorado.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzado a <code>true</code> en runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Guardar / Cancelar cambios</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>cualquiera</td><td>Sin inline editing. <code>md_batch_save</code> ignorado.</td></tr></tbody></table><p>Ejemplo row-level inline edit:</p><p>Snippet 1:</p><p>Ejemplo cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - muestra diálogo de progreso con porcentaje en tiempo real;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar tarea</code>, <code>Interrumpir y descargar parcial</code>;</p><p> - en background crea una notificación con progreso; el clic reabre el diálogo.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>):</li></ul><p> - diálogo de progreso tras la confirmación;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar import (rollback)</code>, <code>Detener y commit parcial</code>;</p><p> - al finalizar el import crea una notificación de resumen que lleva a la route.</p><h2>Config metadata</h2><p>Ajustes clave en <code>md_props_bag</code> y metadatos de columna.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>El componente lee <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>valores aceptados: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>default runtime: <code>true</code> cuando ausente o vacío</li><li>efecto: distribuye los anchos de columna en porcentaje (en lugar de px) cuando no hay anchos de usuario persistidos.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> o <code>object</code></li><li>valores aceptados:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object con <code>enabled</code> (opcional) y <code>itemSize</code> (opcional)</li><li>default runtime: deshabilitado (<code>false</code>) cuando ausente</li><li><code>enabled</code>:</li><li>default: <code>true</code> si el nodo <code>virtualize</code> es un object sin <code>enabled</code>, de lo contrario parser tolerante</li><li>efecto: habilita <code>virtualScroll</code> en <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>efecto: establece <code>virtualScrollItemSize</code> (altura de fila virtual en px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>default runtime: <code>false</code></li><li>efecto:</li><li>cuando <code>true</code>, la grilla oculta los iconos de filtro de columna (<code>p-columnFilter</code>) y usa la <code>wuic-filter-bar</code> sobre la tabla (a nivel <code>data-repeater</code>/<code>bounded-repeater</code>) para aplicar los filtros.</li></ul><p>Nota operativa:</p><ul><li>el suggest <code>md_props_bag</code> en el metadata editor expone también el nodo checkeable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize y md_page_size_choice</h2><p>La grilla usa <code>md_pagesize</code> como tamaño de página por defecto y <code>md_page_size_choice</code> como lista de valores seleccionables en el paginador.</p><p>Reglas runtime:</p><ul><li>si <code>md_pagesize</code> es mayor que el valor máximo presente en <code>md_page_size_choice</code>, el framework agrega automáticamente <code>md_pagesize</code> a la lista de opciones;</li><li>la lista se normaliza (números válidos, deduplicación, orden ascendente).</li></ul><p>Ejemplo:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>resultado runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzado de virtualización en page size alto</h2><p>Cuando el usuario selecciona en el paginador un valor <code>pageSize >= 1000</code>:</p><ul><li>la virtualización se fuerza automáticamente aunque <code>md_props_bag.archetypes.list.virtualize</code> esté ausente o deshabilitado;</li><li><code>virtualScrollItemSize</code> se fuerza al valor predeterminado <code>44</code>.</li></ul><p>Este comportamiento protege el rendimiento de la tabla en páginas muy grandes.</p><h2>Eventos y subscriptions (host)</h2><p><code>wuic-list-grid</code> expone eventos runtime útiles para interceptar el ciclo de render y callbacks de p-table en el proyecto host.</p><p>Eventos disponibles:</p><ul><li><code>onAfterRender</code>: emitido al finalizar el binding de datos de la grilla (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitido antes del renderizado lógico de la fila individual; soporta cancelación mediante <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitido después del renderizado lógico de la fila individual.</li><li><code>onPaging</code>: emitido en los eventos de paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitido en los eventos de sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitido en los eventos de filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitido al cambiar la selección de filas.</li><li><code>onPTableRowExpand</code>: emitido al expandir una fila.</li><li><code>onPTableRowCollapse</code>: emitido al colapsar una fila.</li><li><code>onPTableColumnResize</code>: emitido al redimensionar una columna.</li><li><code>onPTableColumnReorder</code>: emitido al reordenar columnas.</li></ul><h3>Ejemplo 1: binding directo en el template</h3><p>Snippet 5:</p><h3>Ejemplo 2: subscribe vía ViewChild</h3><p>Snippet 6:</p>",
|
|
17480
|
+
"html": "<h1>List Grid</h1><p>Componente principal para listas tabulares con filtros, sorting, paging server-side y acciones de fila.</p><h2>Casos de uso</h2><ul><li>CRUD tabulares enterprise.</li><li>Informes operativos con filtros multi-columna.</li><li>Datasets muy grandes con <code>cursorMode</code>.</li></ul><h2>Screenshot de referencia (manual de usuario)</h2><ul><li><code>manual__grid__01.png</code>: modalidad <code>inline cell edit</code> en list-grid.</li><li><code>manual__grid__02.png</code>: modalidad <code>inline edit</code> (row-level) en list-grid.</li></ul><h2>Metadatos de inline editing</h2><p>Configuración en el <code>md_props_bag</code> de la tabla metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - habilita el editing inline de la fila en list-grid (celdas editables en contexto de fila, sin apertura de popup).</p><p> - útil cuando se quiere mantener la UX de tabla con edición rápida por registro.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - habilita el editing inline "celda por celda" (foco en la celda individual).</p><p> - nota: en configuraciones legacy puede aparecer como <code>md_inline_cell_editing</code>; el comportamiento runtime es el mismo.</p><p> - <strong>promoción runtime</strong>: cuando <code>md_inline_cell_edit</code> es <code>true</code>, el componente fuerza</p><p> en runtime también <code>md_inline_edit = true</code> independientemente del valor en DB.</p><p> Ambas UX necesitan la action column visible, por lo que son mutuamente</p><p> no-exclusivas a nivel de renderizado.</p><ul><li><code>md_batch_save</code></li></ul><p> - habilita el guardado batch de los cambios pendientes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si se usa junto con inline-cell, los cambios quedan pending hasta el guardado explícito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> tiene efecto <strong>solo</strong> si <code>md_inline_cell_edit</code> es <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> el flag se ignora en runtime y los botones</p><p> "Guardar cambios / Cancelar cambios" no se renderizan.</p><h3>Combinaciones válidas</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultado runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>cualquiera</td><td>Inline edit a nivel de fila con pencil. <code>md_batch_save</code> ignorado.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzado a <code>true</code> en runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Guardar / Cancelar cambios</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>cualquiera</td><td>Sin inline editing. <code>md_batch_save</code> ignorado.</td></tr></tbody></table><p>Ejemplo row-level inline edit:</p><p>Snippet 1:</p><p>Ejemplo cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - muestra diálogo de progreso con porcentaje en tiempo real;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar tarea</code>, <code>Interrumpir y descargar parcial</code>;</p><p> - en background crea una notificación con progreso; el clic reabre el diálogo.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>):</li></ul><p> - diálogo de progreso tras la confirmación;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar import (rollback)</code>, <code>Detener y commit parcial</code>;</p><p> - al finalizar el import crea una notificación de resumen que lleva a la route.</p><h2>Config metadata</h2><p>Ajustes clave en <code>md_props_bag</code> y metadatos de columna.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Flags opt-in que ocultan bloques de la toolbar de la list-grid (<code>caption-bar</code>). Todos bajo <code>md_props_bag.toolbar.*</code> (parseado en runtime como <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): oculta en la <strong>caption-right</strong> el botón "Gestionar estado" (icono bookmark) + el <code><select></code> de estados guardados. Útil para rutas hardcoded / demo donde el feature saved-state (persistencia por <code>user_id</code> + ruta vía <code>MetaService</code>) no tiene sentido — p. ej. Pattern 3 puro OData sin route metadata registrada.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): oculta en la <strong>caption-left</strong> los botones "Guardar cambios" (<code>pi-save</code>) + "Descartar cambios" (<code>pi-times</code>) + el indicador de conteo de changes (<code>grid-changes-indicator</code>, badge pencil + count). Generados por el framework cuando <code>md_inline_cell_editing</code> + <code>md_batch_save</code> están activos. Diseñado para <strong>nested grids dentro de un parametric-dialog</strong>: el save/cancel del padre persiste master + filas en un golpe vía batch save framework, y los botones duplicados en la nested grid confunden la UX.</li></ul><p>Ejemplo nested rows en custom edit-form (runtime patch):</p><p>Snippet 5:</p><p>Tipo TS en <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (propiedad <code>extraProps.toolbar</code>). Los flags NO deshabilitan la lógica <code>md_batch_save</code> subyacente (los changes siguen siendo trackeados); solo eliminan la UI de la toolbar — el save efectivo pasa por el flujo del padre.</p><h2>md_props_bag: archetypes.list</h2><p>El componente lee <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>valores aceptados: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>default runtime: <code>true</code> cuando ausente o vacío</li><li>efecto: distribuye los anchos de columna en porcentaje (en lugar de px) cuando no hay anchos de usuario persistidos.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> o <code>object</code></li><li>valores aceptados:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>object con <code>enabled</code> (opcional) y <code>itemSize</code> (opcional)</li><li>default runtime: deshabilitado (<code>false</code>) cuando ausente</li><li><code>enabled</code>:</li><li>default: <code>true</code> si el nodo <code>virtualize</code> es un object sin <code>enabled</code>, de lo contrario parser tolerante</li><li>efecto: habilita <code>virtualScroll</code> en <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>efecto: establece <code>virtualScrollItemSize</code> (altura de fila virtual en px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>default runtime: <code>false</code></li><li>efecto:</li><li>cuando <code>true</code>, la grilla oculta los iconos de filtro de columna (<code>p-columnFilter</code>) y usa la <code>wuic-filter-bar</code> sobre la tabla (a nivel <code>data-repeater</code>/<code>bounded-repeater</code>) para aplicar los filtros.</li></ul><p>Nota operativa:</p><ul><li>el suggest <code>md_props_bag</code> en el metadata editor expone también el nodo checkeable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize y md_page_size_choice</h2><p>La grilla usa <code>md_pagesize</code> como tamaño de página por defecto y <code>md_page_size_choice</code> como lista de valores seleccionables en el paginador.</p><p>Reglas runtime:</p><ul><li>si <code>md_pagesize</code> es mayor que el valor máximo presente en <code>md_page_size_choice</code>, el framework agrega automáticamente <code>md_pagesize</code> a la lista de opciones;</li><li>la lista se normaliza (números válidos, deduplicación, orden ascendente).</li></ul><p>Ejemplo:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>resultado runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzado de virtualización en page size alto</h2><p>Cuando el usuario selecciona en el paginador un valor <code>pageSize >= 1000</code>:</p><ul><li>la virtualización se fuerza automáticamente aunque <code>md_props_bag.archetypes.list.virtualize</code> esté ausente o deshabilitado;</li><li><code>virtualScrollItemSize</code> se fuerza al valor predeterminado <code>44</code>.</li></ul><p>Este comportamiento protege el rendimiento de la tabla en páginas muy grandes.</p><h2>Eventos y subscriptions (host)</h2><p><code>wuic-list-grid</code> expone eventos runtime útiles para interceptar el ciclo de render y callbacks de p-table en el proyecto host.</p><p>Eventos disponibles:</p><ul><li><code>onAfterRender</code>: emitido al finalizar el binding de datos de la grilla (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitido antes del renderizado lógico de la fila individual; soporta cancelación mediante <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitido después del renderizado lógico de la fila individual.</li><li><code>onPaging</code>: emitido en los eventos de paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitido en los eventos de sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitido en los eventos de filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitido al cambiar la selección de filas.</li><li><code>onPTableRowExpand</code>: emitido al expandir una fila.</li><li><code>onPTableRowCollapse</code>: emitido al colapsar una fila.</li><li><code>onPTableColumnResize</code>: emitido al redimensionar una columna.</li><li><code>onPTableColumnReorder</code>: emitido al reordenar columnas.</li></ul><h3>Ejemplo 1: binding directo en el template</h3><p>Snippet 7:</p><h3>Ejemplo 2: subscribe vía ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — firma real (gotcha)</h2><p><code><wuic-list-grid></code> acepta una entrada <code>[rowCustomSelect]</code> para interceptar la seleccion de una fila (apertura de un dialog "elige un documento", flujo master-detail, etc.). La firma TypeScript declarada en <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> es:</p><p>Snippet 9:</p><p><strong>Pero en runtime la callback se invoca con argumentos invertidos</strong> — el framework llama <code>rowCustomSelect($event, rowData, dt)</code> (ver <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> y <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Sintoma cuando el orden es erroneo: <code>rowData?.id</code> es undefined → guard temprano en tu handler → la callback retorna silenciosamente y tu dialog/accion no arranca. Ningun error en consola.</p><p>Forma correcta (alineada con la invocacion runtime):</p><p>Snippet 10:</p><p>Uso en el template:</p><p>Snippet 11:</p><p>> Nota: los tests en <code>designer.component.spec.ts</code> (p. ej. linea 512) invocan la callback con <code>({currentTarget: rowCell}, {id: 42}, null)</code>, confirmando el orden <code>($event, rowData, dt)</code>. Si en el futuro el framework alinea la firma declarada, esta pagina se actualizara.</p>",
|
|
16798
17481
|
"codeSamples": [
|
|
16799
17482
|
{
|
|
16800
17483
|
"id": "code_1",
|
|
@@ -16818,19 +17501,49 @@ const frameworkDocsContent = {
|
|
|
16818
17501
|
"id": "code_4",
|
|
16819
17502
|
"title": "Snippet 4",
|
|
16820
17503
|
"language": "json",
|
|
16821
|
-
"code": "{\n \"
|
|
17504
|
+
"code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
|
|
16822
17505
|
},
|
|
16823
17506
|
{
|
|
16824
17507
|
"id": "code_5",
|
|
16825
17508
|
"title": "Snippet 5",
|
|
16826
|
-
"language": "
|
|
16827
|
-
"code": "
|
|
17509
|
+
"language": "ts",
|
|
17510
|
+
"code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
|
|
16828
17511
|
},
|
|
16829
17512
|
{
|
|
16830
17513
|
"id": "code_6",
|
|
16831
17514
|
"title": "Snippet 6",
|
|
17515
|
+
"language": "json",
|
|
17516
|
+
"code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
|
|
17517
|
+
},
|
|
17518
|
+
{
|
|
17519
|
+
"id": "code_7",
|
|
17520
|
+
"title": "Snippet 7",
|
|
17521
|
+
"language": "html",
|
|
17522
|
+
"code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
|
|
17523
|
+
},
|
|
17524
|
+
{
|
|
17525
|
+
"id": "code_8",
|
|
17526
|
+
"title": "Snippet 8",
|
|
16832
17527
|
"language": "ts",
|
|
16833
17528
|
"code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // ejemplo cancelar fila\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
|
|
17529
|
+
},
|
|
17530
|
+
{
|
|
17531
|
+
"id": "code_9",
|
|
17532
|
+
"title": "Snippet 9",
|
|
17533
|
+
"language": "ts",
|
|
17534
|
+
"code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
|
|
17535
|
+
},
|
|
17536
|
+
{
|
|
17537
|
+
"id": "code_10",
|
|
17538
|
+
"title": "Snippet 10",
|
|
17539
|
+
"language": "ts",
|
|
17540
|
+
"code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData es el registro proporcionado por list-grid\n this.handleSelection(rowData);\n};"
|
|
17541
|
+
},
|
|
17542
|
+
{
|
|
17543
|
+
"id": "code_11",
|
|
17544
|
+
"title": "Snippet 11",
|
|
17545
|
+
"language": "html",
|
|
17546
|
+
"code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
|
|
16834
17547
|
}
|
|
16835
17548
|
]
|
|
16836
17549
|
}
|
|
@@ -17087,14 +17800,47 @@ const frameworkDocsContent = {
|
|
|
17087
17800
|
{
|
|
17088
17801
|
"id": "sec_3",
|
|
17089
17802
|
"title": "Referencia al designer"
|
|
17803
|
+
},
|
|
17804
|
+
{
|
|
17805
|
+
"id": "sec_4",
|
|
17806
|
+
"title": "Parametros del reporte (variables SQL filtradas)"
|
|
17807
|
+
},
|
|
17808
|
+
{
|
|
17809
|
+
"id": "sec_5",
|
|
17810
|
+
"title": "Configuracion backend obligatoria"
|
|
17090
17811
|
}
|
|
17091
17812
|
],
|
|
17092
17813
|
"sections": [
|
|
17093
17814
|
{
|
|
17094
17815
|
"id": "overview",
|
|
17095
17816
|
"title": "Overview",
|
|
17096
|
-
"html": "<h1>Reporting</h1><p>Reportística operativa en la route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot reference (manual de usuario)</h2><ul><li><code>manual__reporting__01.png</code>: apertura del report viewer con rendering del reporte y toolbar operativa.</li><li><code>manual__reporting__02.png</code>: ejemplo runtime con parámetros/filtro aplicados al reporte.</li></ul><h2>Report viewer</h2><p>El <code>report-viewer</code> ejecuta y muestra reportes Stimulsoft asociados a la route metadata actual.</p><p>Funcionalidades principales:</p><ul><li>apertura del reporte desde list/grid con nombre del reporte (<code>reportName</code>) y parámetros opcionales.</li><li>rendering de datos en runtime con toolbar del viewer (zoom, export, impresión).</li><li>soporte de filtros/parámetros propagados desde la página de origen.</li></ul><h2>Referencia al designer</h2><p>Para creación o modificación del reporte <code>.mrt</code>, ver la subsección</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> en <code>Designer & Workflow</code>.</p>",
|
|
17097
|
-
"codeSamples": [
|
|
17817
|
+
"html": "<h1>Reporting</h1><p>Reportística operativa en la route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot reference (manual de usuario)</h2><ul><li><code>manual__reporting__01.png</code>: apertura del report viewer con rendering del reporte y toolbar operativa.</li><li><code>manual__reporting__02.png</code>: ejemplo runtime con parámetros/filtro aplicados al reporte.</li></ul><h2>Report viewer</h2><p>El <code>report-viewer</code> ejecuta y muestra reportes Stimulsoft asociados a la route metadata actual.</p><p>Funcionalidades principales:</p><ul><li>apertura del reporte desde list/grid con nombre del reporte (<code>reportName</code>) y parámetros opcionales.</li><li>rendering de datos en runtime con toolbar del viewer (zoom, export, impresión).</li><li>soporte de filtros/parámetros propagados desde la página de origen.</li></ul><h2>Referencia al designer</h2><p>Para creación o modificación del reporte <code>.mrt</code>, ver la subsección</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> en <code>Designer & Workflow</code>.</p><h2>Parametros del reporte (variables SQL filtradas)</h2><p>Para filtrar un reporte sobre una entidad especifica (ej. imprimir una factura especifica), el patron canonico es:</p><p>1. <strong>En el `.mrt`</strong>: declarar una <strong>Variable</strong> en el Dictionary con <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Usar la variable como <code>@nombreParam</code> en las SqlCommand:</p><p>Snippet 1:</p><p> > <strong>Trampa</strong>: NO uses el bloque legacy <code><StiDataParameter></code> en el DataSource — ya no es soportado por la version actual de Stimulsoft y produce <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter 'value')</code> durante <code>report.Load()</code>.</p><p>2. <strong>En la URL del viewer</strong>: pasa el valor via el query param <code>parameters</code> (NO como query param directo):</p><p>Snippet 2:</p><p> Sintaxis <code>parameters</code> URL-encoded: <code>nombre||eq||valor</code> (separados por <code>@</code> para multiples parametros).</p><p>3. <strong>Desde una row action o link UI</strong>: usa el callback canonico para construir la URL:</p><p>Snippet 3:</p><h2>Configuracion backend obligatoria</h2><p>En el <code>appsettings.json</code> de la app, debe existir <code>AppSettings.reportQueryTimeout</code> (ej. <code>"120"</code>). Sin el, <code>ReportViewerController.prepareDataSql</code> lanza <code>int.Parse(null)</code> → 500. Ejemplo:</p><p>Snippet 4:</p>",
|
|
17818
|
+
"codeSamples": [
|
|
17819
|
+
{
|
|
17820
|
+
"id": "code_1",
|
|
17821
|
+
"title": "Snippet 1",
|
|
17822
|
+
"language": "sql",
|
|
17823
|
+
"code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
|
|
17824
|
+
},
|
|
17825
|
+
{
|
|
17826
|
+
"id": "code_2",
|
|
17827
|
+
"title": "Snippet 2",
|
|
17828
|
+
"language": "text",
|
|
17829
|
+
"code": " ✅ #/<route>/report-viewer?reportName=Report.mrt¶meters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignorado"
|
|
17830
|
+
},
|
|
17831
|
+
{
|
|
17832
|
+
"id": "code_3",
|
|
17833
|
+
"title": "Snippet 3",
|
|
17834
|
+
"language": "js",
|
|
17835
|
+
"code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt¶meters='\n + encodeURIComponent(params);"
|
|
17836
|
+
},
|
|
17837
|
+
{
|
|
17838
|
+
"id": "code_4",
|
|
17839
|
+
"title": "Snippet 4",
|
|
17840
|
+
"language": "json",
|
|
17841
|
+
"code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
|
|
17842
|
+
}
|
|
17843
|
+
]
|
|
17098
17844
|
}
|
|
17099
17845
|
],
|
|
17100
17846
|
"images": [
|
|
@@ -17827,28 +18573,54 @@ const frameworkDocsContent = {
|
|
|
17827
18573
|
},
|
|
17828
18574
|
{
|
|
17829
18575
|
"id": "sec_5",
|
|
17830
|
-
"title": "
|
|
18576
|
+
"title": "Combo slim — campos extra vía `mc_props_bag.slimCombo`"
|
|
17831
18577
|
},
|
|
17832
18578
|
{
|
|
17833
18579
|
"id": "sec_6",
|
|
17834
|
-
"title": "
|
|
18580
|
+
"title": "Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy"
|
|
17835
18581
|
},
|
|
17836
18582
|
{
|
|
17837
18583
|
"id": "sec_7",
|
|
18584
|
+
"title": "Efectos Cliente/Servidor"
|
|
18585
|
+
},
|
|
18586
|
+
{
|
|
18587
|
+
"id": "sec_8",
|
|
17838
18588
|
"title": "Notas operativas"
|
|
18589
|
+
},
|
|
18590
|
+
{
|
|
18591
|
+
"id": "sec_9",
|
|
18592
|
+
"title": "Auto-fetch sobre cambio programatico de FK"
|
|
17839
18593
|
}
|
|
17840
18594
|
],
|
|
17841
18595
|
"sections": [
|
|
17842
18596
|
{
|
|
17843
18597
|
"id": "overview",
|
|
17844
18598
|
"title": "Overview",
|
|
17845
|
-
"html": "<h1>Widget Lookup</h1><p>Widget de columna para relaciones (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicado <strong>Lookup</strong> en el metadata-editor de columnas.</p><h2>Ejemplos</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Patrón 3c — lookup puro-OData (State/Province desde `/odata/Stateprovinces`)</a> — demuestra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> y rendering inline mediante <code>$expand</code>.</li></ul><h2>Cuándo usarlo</h2><ul><li>selección de registros relacionados desde una tabla metadata externa.</li><li>escenarios con búsqueda server-side y paging de lookup.</li><li>soporte de selección simple (<code>lookupByID</code>) y selección múltiple (<code>multiple_check</code>).</li></ul><h2>Propiedades metadata de columna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata fuente del lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID devuelto.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descripción mostrado.</li><li><code>mc_ui_lookup_filter</code>: filtro base del lookup.</li><li><code>mc_serverside_operations</code>: búsqueda/paging server-side.</li><li><code>mc_ui_pagesize</code>: tamaño de página de resultados del lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: habilita edición de registro lookup desde el popup.</li><li><code>mc_ui_lookup_insert_allow</code>: habilita inserción de registro lookup desde el popup.</li><li><code>mc_ui_lookup_search_grid</code>: habilita grilla de búsqueda en el popup.</li><li><code>mc_logic_allow_navigation</code>: navegación a la route conectada.</li></ul><h2>mc_props_bag útil</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: restricción adicional del lado cliente/servidor en la query del lookup.</li><li><code>lookup.virtualize</code>: habilita virtual scroll en <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tolerante (<code>true/false</code>, <code>1/0</code>, cadenas equivalentes); default <code>false</code> cuando ausente.</li><li><code>lookup.virtualize.itemSize</code>: altura de fila virtual en px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override del provider de datos del dropdown. Cuando está configurado, el nested datasource del lookup-editor ya no pasa por el combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) sino directamente por el provider declarado en <code>type</code> con la URL en <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (el dispatcher enruta a <code>DataProviderOdataService.selectCombo</code> que emite GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> y <code>$filter=contains(textField,'<query>')</code> para la búsqueda server-side). Otros valores siguen la misma lógica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base del entity set (por ejemplo, <code>/odata/<EntitySet></code>), con o sin origen explícito. Los query params existentes se ignoran — el provider reconstruye los params en cada fetch.</li><li><code>form.columns</code>: ancho del campo en el formulario de edición.</li><li><code>style.editCss</code>: estilo custom del editor.</li></ul><h2>Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flujo operativo inline: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline de campos principales: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline de filtro rápido: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Efecto cliente: configuración acelerada del lookup en el metadata-editor y filtro coherente ya listo en el widget.</li><li>Efecto servidor: la query del lookup y la select clause quedan alineadas a los endpoints <code>MetaService.*</code> sin mapeo manual.</li></ul><p>Checklist operativa rápida:</p><p>1. clonar configuración base con <code>suggestLookup2</code>;</p><p>2. generar filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. refinar jerarquía/select clause desde el tree del lookup;</p><p>4. validar en runtime que la búsqueda y el mapeo value/text sean coherentes.</p><h2>Efectos Cliente/Servidor</h2><ul><li>Cliente: renderizado del widget lookup, búsqueda, binding etiqueta/valor.</li><li>Cliente: con <code>lookup.virtualize</code> activo, el autocomplete renderiza solo el subconjunto visual de las opciones.</li><li>Servidor: query de lookup paginada/filtrada mediante <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notas operativas</h2><ul><li>en <code>multiple_check</code> la parte de lookup es la misma, cambia el renderer multi-valor.</li><li>si falta <code>mc_ui_lookup_entity_name</code>, el widget no puede resolver la fuente.</li></ul>",
|
|
18599
|
+
"html": "<h1>Widget Lookup</h1><p>Widget de columna para relaciones (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicado <strong>Lookup</strong> en el metadata-editor de columnas.</p><h2>Ejemplos</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Patrón 3c — lookup puro-OData (State/Province desde `/odata/Stateprovinces`)</a> — demuestra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> y rendering inline mediante <code>$expand</code>.</li></ul><h2>Cuándo usarlo</h2><ul><li>selección de registros relacionados desde una tabla metadata externa.</li><li>escenarios con búsqueda server-side y paging de lookup.</li><li>soporte de selección simple (<code>lookupByID</code>) y selección múltiple (<code>multiple_check</code>).</li></ul><h2>Propiedades metadata de columna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata fuente del lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID devuelto.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descripción mostrado.</li><li><code>mc_ui_lookup_filter</code>: filtro base del lookup.</li><li><code>mc_serverside_operations</code>: búsqueda/paging server-side.</li><li><code>mc_ui_pagesize</code>: tamaño de página de resultados del lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: habilita edición de registro lookup desde el popup.</li><li><code>mc_ui_lookup_insert_allow</code>: habilita inserción de registro lookup desde el popup.</li><li><code>mc_ui_lookup_search_grid</code>: habilita grilla de búsqueda en el popup.</li><li><code>mc_logic_allow_navigation</code>: navegación a la route conectada.</li></ul><h2>mc_props_bag útil</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: restricción adicional del lado cliente/servidor en la query del lookup.</li><li><code>lookup.virtualize</code>: habilita virtual scroll en <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tolerante (<code>true/false</code>, <code>1/0</code>, cadenas equivalentes); default <code>false</code> cuando ausente.</li><li><code>lookup.virtualize.itemSize</code>: altura de fila virtual en px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override del provider de datos del dropdown. Cuando está configurado, el nested datasource del lookup-editor ya no pasa por el combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) sino directamente por el provider declarado en <code>type</code> con la URL en <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (el dispatcher enruta a <code>DataProviderOdataService.selectCombo</code> que emite GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> y <code>$filter=contains(textField,'<query>')</code> para la búsqueda server-side). Otros valores siguen la misma lógica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base del entity set (por ejemplo, <code>/odata/<EntitySet></code>), con o sin origen explícito. Los query params existentes se ignoran — el provider reconstruye los params en cada fetch.</li><li><code>form.columns</code>: ancho del campo en el formulario de edición.</li><li><code>style.editCss</code>: estilo custom del editor.</li></ul><h2>Combo slim — campos extra vía <code>mc_props_bag.slimCombo</code></h2><p>El dropdown lookup por defecto emite un SELECT mínimo sobre la tabla relacionada: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Reduce el payload del combo. Cuando el cliente necesita otras columnas de la tabla relacionada — típicamente para pre-poblar campos dependientes vía <code>mc_selection_changed_custom_function__fn</code> — se usa <code>mc_props_bag.slimCombo</code> como <strong>array de nombres de columnas extra</strong> a incluir en el SELECT del combo:</p><p>Snippet 2:</p><ul><li>Array: los nombres se añaden al <code>columnRestrictionList</code> de la respuesta combo (lógica en <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: deshabilita la restricción → el combo retorna <strong>todas</strong> las columnas. Útil para debug, payload más grande.</li><li>Si la columna tiene <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> valorado (fórmula display computada), <code>slimCombo</code> se ignora y el combo retorna todas las columnas (la fórmula puede referenciar campos arbitrarios).</li></ul><p>Los campos extra van a parar a <code>record['<col>__lookup_obj'].value</code> (registro completo de la tabla relacionada, ya con los campos declarados en <code>slimCombo</code>) y son accesibles desde el callback <code>mc_selection_changed_custom_function__fn</code>:</p><p>Snippet 3:</p><p>Tradeoff: payload combo ligeramente más grande pero sin roundtrip HTTP adicional (<code>getFlatRecordData</code> para recuperar el registro completo por id) en cada selección.</p><h2>Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flujo operativo inline: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline de campos principales: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline de filtro rápido: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Efecto cliente: configuración acelerada del lookup en el metadata-editor y filtro coherente ya listo en el widget.</li><li>Efecto servidor: la query del lookup y la select clause quedan alineadas a los endpoints <code>MetaService.*</code> sin mapeo manual.</li></ul><p>Checklist operativa rápida:</p><p>1. clonar configuración base con <code>suggestLookup2</code>;</p><p>2. generar filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. refinar jerarquía/select clause desde el tree del lookup;</p><p>4. validar en runtime que la búsqueda y el mapeo value/text sean coherentes.</p><h2>Efectos Cliente/Servidor</h2><ul><li>Cliente: renderizado del widget lookup, búsqueda, binding etiqueta/valor.</li><li>Cliente: con <code>lookup.virtualize</code> activo, el autocomplete renderiza solo el subconjunto visual de las opciones.</li><li>Servidor: query de lookup paginada/filtrada mediante <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notas operativas</h2><ul><li>en <code>multiple_check</code> la parte de lookup es la misma, cambia el renderer multi-valor.</li><li>si falta <code>mc_ui_lookup_entity_name</code>, el widget no puede resolver la fuente.</li></ul><h2>Auto-fetch sobre cambio programatico de FK</h2><p>El <code>wuic-lookup-editor</code> se suscribe automaticamente al <code>BehaviorSubject</code> de <code>record[mc_nome_colonna]</code> y maneja los cambios de FK programaticos (ej. import desde documento fuente, callback custom que hace <code>record.cliente_id.next(99)</code>):</p><ul><li>Si el nuevo valor esta <strong>ya en `items`</strong> (la pagina cargada del lookup): solo se actualiza <code>__lookup_obj</code> por coherencia, sin HTTP.</li><li>Si el nuevo valor <strong>no esta en items</strong> (ej. valor fuera de la pagina actual del combo, o aun no fetcheado): se dispara un <strong>fetch second-stage</strong> filtrado <code>WHERE <valueField> eqor <newValue></code> para recuperar el registro relacionado y llenar automaticamente el dropdown.</li></ul><p>Skip cases (no-op):</p><ul><li>Lookup multi-valor (<code>isFilter</code> con <code>eqor</code>, <code>multiple_check</code>)</li><li>Primer emit del BS (ya cubierto por el flujo init)</li><li>Valor null/undefined (usuario clearo)</li><li><code>hasActiveLookupQuery=true</code> (el usuario esta tipeando una query manual)</li></ul><p><strong>Implicacion para custom forms</strong>: setear solo el FK basta — ya no hacen falta workaround manuales para llenar <code>__lookup_obj</code> o el alias joined. El display del dropdown se actualiza solo.</p><p>Snippet 4:</p><p>> <strong>Nota</strong>: el auto-fetch solo se dispara cuando el <code><wuic-lookup-editor></code> esta <strong>montado</strong> (celda en edit mode). Para el display en <strong>lista</strong> (celda read-only), <code>formatGridViewValue</code> lee el alias joined <code><route>___<textField>__<col></code> directamente del record — ese debe seguir siendo llenado manualmente si no viene del backend.</p>",
|
|
17846
18600
|
"codeSamples": [
|
|
17847
18601
|
{
|
|
17848
18602
|
"id": "code_1",
|
|
17849
18603
|
"title": "Snippet 1",
|
|
17850
18604
|
"language": "json",
|
|
17851
18605
|
"code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
|
|
18606
|
+
},
|
|
18607
|
+
{
|
|
18608
|
+
"id": "code_2",
|
|
18609
|
+
"title": "Snippet 2",
|
|
18610
|
+
"language": "json",
|
|
18611
|
+
"code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
|
|
18612
|
+
},
|
|
18613
|
+
{
|
|
18614
|
+
"id": "code_3",
|
|
18615
|
+
"title": "Snippet 3",
|
|
18616
|
+
"language": "ts",
|
|
18617
|
+
"code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
|
|
18618
|
+
},
|
|
18619
|
+
{
|
|
18620
|
+
"id": "code_4",
|
|
18621
|
+
"title": "Snippet 4",
|
|
18622
|
+
"language": "ts",
|
|
18623
|
+
"code": "// ANTES (workaround manual requerido)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// AHORA (auto-fetch del lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor montado? Subscribe detecta el change → fetch filtrada → __lookup_obj llenado → dropdown muestra \"Acme S.r.l.\""
|
|
17852
18624
|
}
|
|
17853
18625
|
]
|
|
17854
18626
|
}
|
|
@@ -18111,10 +18883,14 @@ const frameworkDocsContent = {
|
|
|
18111
18883
|
},
|
|
18112
18884
|
{
|
|
18113
18885
|
"id": "sec_4",
|
|
18114
|
-
"title": "
|
|
18886
|
+
"title": "Menú contextual de los elementos"
|
|
18115
18887
|
},
|
|
18116
18888
|
{
|
|
18117
18889
|
"id": "sec_5",
|
|
18890
|
+
"title": "Flujo operativo recomendado"
|
|
18891
|
+
},
|
|
18892
|
+
{
|
|
18893
|
+
"id": "sec_6",
|
|
18118
18894
|
"title": "Notas operativas"
|
|
18119
18895
|
}
|
|
18120
18896
|
],
|
|
@@ -18122,7 +18898,7 @@ const frameworkDocsContent = {
|
|
|
18122
18898
|
{
|
|
18123
18899
|
"id": "overview",
|
|
18124
18900
|
"title": "Overview",
|
|
18125
|
-
"html": "<h1>Designer</h1><p>Editor visual para dashboards metadata-driven en la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composición de boards mediante drag-and-drop de nodos y bloques UI.</li><li>Layout y estilos controlados por <code>boardcontent</code> y metadata de tabla/columna.</li><li>Edición en el panel de propiedades sin hardcode en los componentes runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: fuente de datos metadata (<code>route</code>, filtros, orden, paginación).</li><li><code>DataRepeater</code>: visualización de lista/tarjeta vinculada a un datasource.</li><li><code>Dynamic Template</code>: renderizado de plantilla dinámica con inputs metadata-driven.</li><li><code>Action/Button</code>: acciones de usuario vinculadas a registros y callbacks.</li><li><code>Container/Layout</code>: bloques estructurales para columnas, filas y wrappers.</li></ul><h2>Funcionalidades principales</h2><ul><li>Drag-and-drop de nodos sobre el canvas con persistencia en el <code>boardcontent</code>.</li><li>Panel de propiedades contextual con inputs coherentes con los componentes seleccionados.</li><li>Binding entre nodos (<code>datasource -> repeater/template</code>) con validación mínima.</li><li>Preview runtime integrada y realineamiento de layout.</li><li>Gestión de cambios pendientes con guard antes de salir.</li></ul><h2>Flujo operativo recomendado</h2><ul><li>Abrir la route del dashboard objetivo.</li><li>Insertar/vincular nodos desde la palette.</li><li>Configurar los <code>inputs</code> esenciales (ej. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Guardar y validar el resultado en runtime.</li></ul><h2>Notas operativas</h2><ul><li>Para correcciones específicas del dashboard, privilegiar patches del <code>boardcontent</code> en lugar de modificaciones globales.</li><li>Evitar propiedades duplicadas entre <code>inputs</code> y estilos inline no coherentes.</li><li>Verificar siempre que los datasources tengan <code>inputs.route</code> valorizada.</li></ul>",
|
|
18901
|
+
"html": "<h1>Designer</h1><p>Editor visual para dashboards metadata-driven en la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composición de boards mediante drag-and-drop de nodos y bloques UI.</li><li>Layout y estilos controlados por <code>boardcontent</code> y metadata de tabla/columna.</li><li>Edición en el panel de propiedades sin hardcode en los componentes runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: fuente de datos metadata (<code>route</code>, filtros, orden, paginación).</li><li><code>DataRepeater</code>: visualización de lista/tarjeta vinculada a un datasource.</li><li><code>Dynamic Template</code>: renderizado de plantilla dinámica con inputs metadata-driven.</li><li><code>Action/Button</code>: acciones de usuario vinculadas a registros y callbacks.</li><li><code>Container/Layout</code>: bloques estructurales para columnas, filas y wrappers.</li></ul><h2>Funcionalidades principales</h2><ul><li>Drag-and-drop de nodos sobre el canvas con persistencia en el <code>boardcontent</code>.</li><li>Panel de propiedades contextual con inputs coherentes con los componentes seleccionados.</li><li>Binding entre nodos (<code>datasource -> repeater/template</code>) con validación mínima.</li><li>Preview runtime integrada y realineamiento de layout.</li><li>Gestión de cambios pendientes con guard antes de salir.</li></ul><h2>Menú contextual de los elementos</h2><p>Clic derecho sobre un elemento del canvas, sobre un elemento del breadcrumb inferior o sobre un nodo del árbol de jerarquía muestra un menú contextual con tres opciones:</p><ul><li><strong>Editar propiedades</strong> (pi-cog) — selecciona el elemento como "current tool" y muestra su panel de propiedades a la derecha; equivalente al clic en el árbol. Útil para editar propiedades de elementos anidados sin tener que buscarlos en la jerarquía.</li><li><strong>Renombrar</strong> (solo breadcrumb) — abre un diálogo para renombrar el <code>displayName</code>.</li><li><strong>Eliminar</strong> — borra el elemento del board.</li></ul><h2>Flujo operativo recomendado</h2><ul><li>Abrir la route del dashboard objetivo.</li><li>Insertar/vincular nodos desde la palette.</li><li>Configurar los <code>inputs</code> esenciales (ej. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Guardar y validar el resultado en runtime.</li></ul><h2>Notas operativas</h2><ul><li>Para correcciones específicas del dashboard, privilegiar patches del <code>boardcontent</code> en lugar de modificaciones globales.</li><li>Evitar propiedades duplicadas entre <code>inputs</code> y estilos inline no coherentes.</li><li>Verificar siempre que los datasources tengan <code>inputs.route</code> valorizada.</li></ul>",
|
|
18126
18902
|
"codeSamples": []
|
|
18127
18903
|
}
|
|
18128
18904
|
],
|
|
@@ -18186,14 +18962,18 @@ const frameworkDocsContent = {
|
|
|
18186
18962
|
},
|
|
18187
18963
|
{
|
|
18188
18964
|
"id": "sec_3",
|
|
18189
|
-
"title": "
|
|
18965
|
+
"title": "Pestaña View Builder — Agregaciones y GROUP BY (Tier 1, 2026-05-08)"
|
|
18190
18966
|
},
|
|
18191
18967
|
{
|
|
18192
18968
|
"id": "sec_4",
|
|
18193
|
-
"title": "
|
|
18969
|
+
"title": "Flujo operativo recomendado"
|
|
18194
18970
|
},
|
|
18195
18971
|
{
|
|
18196
18972
|
"id": "sec_5",
|
|
18973
|
+
"title": "Botones de acción"
|
|
18974
|
+
},
|
|
18975
|
+
{
|
|
18976
|
+
"id": "sec_6",
|
|
18197
18977
|
"title": "Notas de uso"
|
|
18198
18978
|
}
|
|
18199
18979
|
],
|
|
@@ -18201,7 +18981,7 @@ const frameworkDocsContent = {
|
|
|
18201
18981
|
{
|
|
18202
18982
|
"id": "overview",
|
|
18203
18983
|
"title": "Overview",
|
|
18204
|
-
"html": "<h1>Constructor Vista/Pivot</h1><p>El Pivot Builder permite construir vistas analíticas dinámicas partiendo de las routes metadata, sin escribir queries manuales del lado UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configuración y resultado del builder en runtime.</li><li><code>manual__pivot_builder__02.png</code>: detalle del diálogo de creación de tabla materializada y opciones operativas.</li></ul><h2>Funcionalidades principales</h2><ul><li>Selección de campos en áreas <code>rows</code>, <code>columns</code> y <code>values</code>.</li><li>Agregaciones disponibles sobre los valores (suma, conteo, promedio, mínimo, máximo).</li><li>Agrupamiento multidimensional con ordenamiento por dimensión.</li><li>Filtros interactivos para reducir el dataset analizado.</li><li>Actualización de la vista pivot en tiempo real tras cambio de layout o métrica.</li><li>Exportación/reutilización de la configuración pivot en el contexto operativo.</li></ul><h2>Flujo operativo recomendado</h2><p>1. Abre la route del pivot builder.</p><p>2. Selecciona datasource/route metadata de partida.</p><p>3. Arrastra los campos a las zonas <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura la agregación para cada métrica.</p><p>5. Aplica filtros y verifica los totales.</p><p>6. Guarda o reutiliza la configuración según el flujo aplicativo.</p><h2>Botones de acción</h2><p><code>Crear vista</code></p><ul><li>Genera una vista SQL a partir de la configuración pivot actual.</li><li>Útil cuando quieres reutilizar el resultado como fuente de datos estable en otras routes/reportes.</li><li>Mantiene la lógica centralizada en el DB, evitando reconstruir cada vez el layout manualmente.</li></ul><p><code>Crear tabla materializada</code></p><ul><li>Crea una tabla física con los datos agregados de la configuración pivot.</li><li>Útil para datasets grandes o dashboards donde se necesitan tiempos de lectura rápidos.</li><li>Genera automáticamente el scaffolding de la route asociada a la tabla física creada y genera una programación para refresh/alineación de datos.</li><li>Presionando Actualizar en el grid de la route asociada se fuerza una ejecución de la programación.</li></ul><h2>Notas de uso</h2><ul><li>Para análisis coherentes, usar columnas con tipología de datos correcta (numéricas para métricas, textuales/fechas para dimensiones).</li><li>En presencia de grandes volúmenes, preferir filtros y agregaciones específicas antes de expandir el detalle.</li></ul>",
|
|
18984
|
+
"html": "<h1>Constructor Vista/Pivot</h1><p>El Pivot Builder permite construir vistas analíticas dinámicas partiendo de las routes metadata, sin escribir queries manuales del lado UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configuración y resultado del builder en runtime.</li><li><code>manual__pivot_builder__02.png</code>: detalle del diálogo de creación de tabla materializada y opciones operativas.</li></ul><h2>Funcionalidades principales</h2><ul><li>Selección de campos en áreas <code>rows</code>, <code>columns</code> y <code>values</code>.</li><li>Agregaciones disponibles sobre los valores (suma, conteo, promedio, mínimo, máximo).</li><li>Agrupamiento multidimensional con ordenamiento por dimensión.</li><li>Filtros interactivos para reducir el dataset analizado.</li><li>Actualización de la vista pivot en tiempo real tras cambio de layout o métrica.</li><li>Exportación/reutilización de la configuración pivot en el contexto operativo.</li></ul><h2>Pestaña View Builder — Agregaciones y GROUP BY (Tier 1, 2026-05-08)</h2><p>La pestaña 1 "View Builder" ahora permite emitir <code>GROUP BY</code> con agregaciones <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> directamente desde la <code>ViewDefinition</code>, sin pasar a modo manualSql.</p><p>Panel bajo el canvas:</p><ul><li><strong>Agregaciones</strong>: formulario para añadir <code>función(columna) AS alias</code>. La columna fuente puede ser cualquier columna del canvas o <code>*</code> (para <code>COUNT(*)</code>); el alias de salida se genera automáticamente si está vacío.</li><li><strong>GROUP BY</strong>: checkbox por cada columna seleccionada en los nodos del canvas. Si ninguna está marcada explícitamente, el backend infiere las dimensiones de las columnas <code>selected=true</code> no agregadas.</li><li><strong>CASE WHEN para pivoting</strong>: para patrones como <code>SUM(CASE WHEN bucket='X' THEN importe ELSE 0 END)</code> usa una columna fórmula (clic derecho → Editar fórmula) como fuente de agregación. La fórmula se emite como argumento del agregado.</li></ul><p>Salida backend (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → emite <code><FN>(<col|fórmula>) AS <alias></code> como columnas SELECT adicionales</li><li><code>viewDefinition.groupByColumns[]</code> → emite la cláusula <code>GROUP BY</code></li><li>Inferencia automática de GROUP BY si hay <code>aggregations</code> sin <code>groupByColumns</code> explícito</li></ul><h3>Bloques UNION (Tier 2, 2026-05-08)</h3><p>Una extensión posterior permite concatenar varios SELECT con <code>UNION</code> o <code>UNION ALL</code> directamente desde una sola <code>ViewDefinition</code>:</p><ul><li><strong>Bloque 0</strong> = nivel superior (tablas/joins/agregaciones en el canvas) → define el <strong>vocabulario canónico</strong> de columnas SELECT</li><li><strong>Bloque N (N≥1)</strong> = cada <code>unionBlocks[]</code> adicional es una sub-definición de tabla única con un <code>columnMapping</code> obligatorio: para cada alias canónico del bloque 0, el bloque N especifica <code>sourceQualified</code> (columna del bloque) <strong>o</strong> <code>constantValue</code> (literal SQL como <code>'CLIENTE'</code>) <strong>o</strong> <code>formula</code> (expresión SQL para cast cross-block)</li><li><strong>Toggle UNION ALL / UNION</strong>: por defecto <code>unionAll: true</code> (sin dedup, mejor rendimiento); <code>false</code> para <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: se aplica a toda la UNION, envuelto en subquery <code>SELECT TOP N * FROM (<union>) AS u</code></li></ul><p>UI bajo el canvas: selector de ruta + botón "Añadir bloque UNION" → cada bloque aparece como acordeón con cabecera (caption + ruta) + matriz column-mapping (por alias canónico: dropdown de columna fuente del bloque, o input constante SQL).</p><p>Límites v0: cada bloque UNION es de tabla única (sin JOIN dentro del bloque). Para JOIN cross-block (p.ej. categoría 'AMBOS' de <code>vw_anagrafica_unificata</code> que requiere <code>clientes INNER JOIN proveedores ON nif</code>) se necesita Tier 2.5.</p><h2>Flujo operativo recomendado</h2><p>1. Abre la route del pivot builder.</p><p>2. Selecciona datasource/route metadata de partida.</p><p>3. Arrastra los campos a las zonas <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura la agregación para cada métrica.</p><p>5. Aplica filtros y verifica los totales.</p><p>6. Guarda o reutiliza la configuración según el flujo aplicativo.</p><h2>Botones de acción</h2><p><code>Crear vista</code></p><ul><li>Genera una vista SQL a partir de la configuración pivot actual.</li><li>Útil cuando quieres reutilizar el resultado como fuente de datos estable en otras routes/reportes.</li><li>Mantiene la lógica centralizada en el DB, evitando reconstruir cada vez el layout manualmente.</li></ul><p><code>Crear tabla materializada</code></p><ul><li>Crea una tabla física con los datos agregados de la configuración pivot.</li><li>Útil para datasets grandes o dashboards donde se necesitan tiempos de lectura rápidos.</li><li>Genera automáticamente el scaffolding de la route asociada a la tabla física creada y genera una programación para refresh/alineación de datos.</li><li>Presionando Actualizar en el grid de la route asociada se fuerza una ejecución de la programación.</li></ul><h2>Notas de uso</h2><ul><li>Para análisis coherentes, usar columnas con tipología de datos correcta (numéricas para métricas, textuales/fechas para dimensiones).</li><li>En presencia de grandes volúmenes, preferir filtros y agregaciones específicas antes de expandir el detalle.</li></ul>",
|
|
18205
18985
|
"codeSamples": []
|
|
18206
18986
|
}
|
|
18207
18987
|
],
|
|
@@ -19156,7 +19936,7 @@ const frameworkDocsContent = {
|
|
|
19156
19936
|
{
|
|
19157
19937
|
"id": "overview",
|
|
19158
19938
|
"title": "Overview",
|
|
19159
|
-
"html": "<h1>Component Catalog</h1><p>Total component folders detected:
|
|
19939
|
+
"html": "<h1>Component Catalog</h1><p>Total component folders detected: 50.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>azienda-switcher</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>azienda-switcher/azienda-switcher.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
|
|
19160
19940
|
"codeSamples": []
|
|
19161
19941
|
}
|
|
19162
19942
|
],
|
|
@@ -19277,13 +20057,21 @@ const frameworkDocsContent = {
|
|
|
19277
20057
|
{
|
|
19278
20058
|
"id": "sec_3",
|
|
19279
20059
|
"title": "Ejemplos"
|
|
20060
|
+
},
|
|
20061
|
+
{
|
|
20062
|
+
"id": "sec_4",
|
|
20063
|
+
"title": "Patch runtime de `metaInfo` via `fetchInfo$.subscribe`"
|
|
20064
|
+
},
|
|
20065
|
+
{
|
|
20066
|
+
"id": "sec_5",
|
|
20067
|
+
"title": "Multiples `<wuic-data-source>` en el mismo componente"
|
|
19280
20068
|
}
|
|
19281
20069
|
],
|
|
19282
20070
|
"sections": [
|
|
19283
20071
|
{
|
|
19284
20072
|
"id": "overview",
|
|
19285
20073
|
"title": "Overview",
|
|
19286
|
-
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> es el componente que gestiona metadata, fetch de datos, estado actual, tracking de cambios y sincronización CRUD.</p><h2>Descripción</h2><ul><li>Resuelve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carga metadata de tabla/columnas y publica el estado en <code>fetchInfo$</code>.</li><li>Gestiona <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) y cursor paging.</li><li>Expone métodos runtime para insert/update/delete, batch save, rollback y export.</li><li>Es el punto central al que se conectan <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> y <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principales:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Estado/runtime principal:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (cancelable)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Métodos runtime más usados:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventos runtime:</p><ul><li><code>fetchInfo$</code>: stream principal de estado datos/metadatos.</li><li><code>datasourceReady$</code>: emitido cuando la instancia datasource está lista.</li><li><code>afterFirstLoad$</code>: emitido una sola vez con el primer payload útil.</li><li><code>beforeSync$</code>: emitido antes de <code>insert/update/delete/clone/batch</code>; puedes cancelar con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitido después de una sincronización exitosa.</li></ul><p>Nota de compatibilidad:</p><ul><li>también está disponible el alias <code>fetchInfo</code> (getter/setter), pero el naming recomendado es <code>fetchInfo$</code>.</li></ul><h2>Ejemplos</h2><h3>Ejemplo básico (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Ejemplo master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Ejemplo operativo (fetch manual + sync)</h3><p>Snippet 3:</p><h3>Ejemplo de subscriptions (host)</h3><p>Snippet 4:</p>",
|
|
20074
|
+
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> es el componente que gestiona metadata, fetch de datos, estado actual, tracking de cambios y sincronización CRUD.</p><h2>Descripción</h2><ul><li>Resuelve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carga metadata de tabla/columnas y publica el estado en <code>fetchInfo$</code>.</li><li>Gestiona <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) y cursor paging.</li><li>Expone métodos runtime para insert/update/delete, batch save, rollback y export.</li><li>Es el punto central al que se conectan <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> y <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principales:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Estado/runtime principal:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (cancelable)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Métodos runtime más usados:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventos runtime:</p><ul><li><code>fetchInfo$</code>: stream principal de estado datos/metadatos.</li><li><code>datasourceReady$</code>: emitido cuando la instancia datasource está lista.</li><li><code>afterFirstLoad$</code>: emitido una sola vez con el primer payload útil.</li><li><code>beforeSync$</code>: emitido antes de <code>insert/update/delete/clone/batch</code>; puedes cancelar con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitido después de una sincronización exitosa.</li></ul><p>Nota de compatibilidad:</p><ul><li>también está disponible el alias <code>fetchInfo</code> (getter/setter), pero el naming recomendado es <code>fetchInfo$</code>.</li></ul><h2>Ejemplos</h2><h3>Ejemplo básico (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Ejemplo master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Ejemplo operativo (fetch manual + sync)</h3><p>Snippet 3:</p><h3>Ejemplo de subscriptions (host)</h3><p>Snippet 4:</p><h2>Patch runtime de <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Para modificar en runtime los metadatos de un nested data-source (ej. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>sin tocar la DB</strong> — patch local sobre la referencia cacheada por <code>MetadataProviderService</code>, latest-wins, escenario single-dialog — suscribete al <code>fetchInfo$</code> del data-source:</p><p>Snippet 5:</p><p><strong>Scope</strong>: session-only + instance-only. El patch sobrevive solo hasta que la cache de <code>MetadataProviderService</code> se invalida o el componente se destruye. Util cuando un custom form quiere una representacion diferente de un nested grid respecto al rendering standalone.</p><p><strong>Cuando NO usar patches runtime</strong>: para modificaciones destinadas a todas las instancias de la route (incluido standalone <code><route>/list</code>) → patch SQL directo sobre los metadatos; para modificaciones persistentes entre restart → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Multiples <code><wuic-data-source></code> en el mismo componente</h2><p>Cuando un componente custom contiene mas de un <code><wuic-data-source></code> (ej. master + nested rows + nested vencimientos), el patron <code>@ViewChild(DataSourceComponent)</code> by-type matchea <strong>solo el primer</strong> data-source en el orden top-down del template. Para los siguientes hace falta una <strong>template reference variable</strong> + <code>@ViewChild('refName')</code>:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipatron</strong>: <code>document.querySelectorAll('wuic-data-source')</code> + <code>window.ng.getComponent(el)</code> para filtrar por <code>hardcodedRoute</code>. Funciona pero es fragil, depende de la API debug <code>window.ng</code> (no garantizada en produccion). Usa siempre template refs.</p>",
|
|
19287
20075
|
"codeSamples": [
|
|
19288
20076
|
{
|
|
19289
20077
|
"id": "code_1",
|
|
@@ -19298,16 +20086,34 @@ const frameworkDocsContent = {
|
|
|
19298
20086
|
"code": "<wuic-data-source #masterDs [hardcodedRoute]=\"'orders'\" [autoload]=\"true\"></wuic-data-source>\n\n<wuic-data-source\n [hardcodedRoute]=\"'order_rows'\"\n [parentDatasource]=\"masterDs\"\n [parentRecord]=\"masterDs.resultInfo?.current\"\n [parentMetaInfo]=\"masterDs.metaInfo\"\n [autoload]=\"true\">\n</wuic-data-source>"
|
|
19299
20087
|
},
|
|
19300
20088
|
{
|
|
19301
|
-
"id": "code_3",
|
|
19302
|
-
"title": "Snippet 3",
|
|
19303
|
-
"language": "ts",
|
|
19304
|
-
"code": "await datasource.fetchData();\n\nconst row = datasource.addNewRecord({ customer_name: 'ACME' });\nawait datasource.syncData(row, null, false, false);"
|
|
20089
|
+
"id": "code_3",
|
|
20090
|
+
"title": "Snippet 3",
|
|
20091
|
+
"language": "ts",
|
|
20092
|
+
"code": "await datasource.fetchData();\n\nconst row = datasource.addNewRecord({ customer_name: 'ACME' });\nawait datasource.syncData(row, null, false, false);"
|
|
20093
|
+
},
|
|
20094
|
+
{
|
|
20095
|
+
"id": "code_4",
|
|
20096
|
+
"title": "Snippet 4",
|
|
20097
|
+
"language": "ts",
|
|
20098
|
+
"code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n // Delete no permitido desde el contexto host\n event.cancelSync('Delete non consentito dal contesto host');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
|
|
20099
|
+
},
|
|
20100
|
+
{
|
|
20101
|
+
"id": "code_5",
|
|
20102
|
+
"title": "Snippet 5",
|
|
20103
|
+
"language": "ts",
|
|
20104
|
+
"code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup en ngOnDestroy\n}"
|
|
20105
|
+
},
|
|
20106
|
+
{
|
|
20107
|
+
"id": "code_6",
|
|
20108
|
+
"title": "Snippet 6",
|
|
20109
|
+
"language": "html",
|
|
20110
|
+
"code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
|
|
19305
20111
|
},
|
|
19306
20112
|
{
|
|
19307
|
-
"id": "
|
|
19308
|
-
"title": "Snippet
|
|
20113
|
+
"id": "code_7",
|
|
20114
|
+
"title": "Snippet 7",
|
|
19309
20115
|
"language": "ts",
|
|
19310
|
-
"code": "
|
|
20116
|
+
"code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Primer data-source (match by type) -> no hace falta template ref\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Segundo data-source -> requiere `#scadenzeDs` en el template + ViewChild by-name\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
|
|
19311
20117
|
}
|
|
19312
20118
|
]
|
|
19313
20119
|
}
|
|
@@ -20877,6 +21683,82 @@ const frameworkDocsContent = {
|
|
|
20877
21683
|
],
|
|
20878
21684
|
"images": []
|
|
20879
21685
|
},
|
|
21686
|
+
{
|
|
21687
|
+
"slug": "multi-tenant",
|
|
21688
|
+
"lang": "es-ES",
|
|
21689
|
+
"title": "Multi-tenant",
|
|
21690
|
+
"description": "Multi-tenant - documentazione operativa e tecnica",
|
|
21691
|
+
"audience": [
|
|
21692
|
+
"developer",
|
|
21693
|
+
"integration"
|
|
21694
|
+
],
|
|
21695
|
+
"status": "stable",
|
|
21696
|
+
"tags": [
|
|
21697
|
+
"advanced"
|
|
21698
|
+
],
|
|
21699
|
+
"keywords": [
|
|
21700
|
+
"Multi-tenant"
|
|
21701
|
+
],
|
|
21702
|
+
"toc": [
|
|
21703
|
+
{
|
|
21704
|
+
"id": "sec_1",
|
|
21705
|
+
"title": "Scope"
|
|
21706
|
+
},
|
|
21707
|
+
{
|
|
21708
|
+
"id": "sec_2",
|
|
21709
|
+
"title": "Activación"
|
|
21710
|
+
},
|
|
21711
|
+
{
|
|
21712
|
+
"id": "sec_3",
|
|
21713
|
+
"title": "Modelo de datos"
|
|
21714
|
+
},
|
|
21715
|
+
{
|
|
21716
|
+
"id": "sec_4",
|
|
21717
|
+
"title": "Fallback de login"
|
|
21718
|
+
},
|
|
21719
|
+
{
|
|
21720
|
+
"id": "sec_5",
|
|
21721
|
+
"title": "Cambio de inquilino (superadmin)"
|
|
21722
|
+
},
|
|
21723
|
+
{
|
|
21724
|
+
"id": "sec_6",
|
|
21725
|
+
"title": "Cachés tenant-scoped"
|
|
21726
|
+
},
|
|
21727
|
+
{
|
|
21728
|
+
"id": "sec_7",
|
|
21729
|
+
"title": "Anti-hijack"
|
|
21730
|
+
},
|
|
21731
|
+
{
|
|
21732
|
+
"id": "sec_8",
|
|
21733
|
+
"title": "Scheduler tenant-aware"
|
|
21734
|
+
},
|
|
21735
|
+
{
|
|
21736
|
+
"id": "sec_9",
|
|
21737
|
+
"title": "Propagación del scaffolding cross-inquilino"
|
|
21738
|
+
},
|
|
21739
|
+
{
|
|
21740
|
+
"id": "sec_10",
|
|
21741
|
+
"title": "Cuentas de usuario no-superadmin a través de varios inquilinos"
|
|
21742
|
+
},
|
|
21743
|
+
{
|
|
21744
|
+
"id": "sec_11",
|
|
21745
|
+
"title": "Limitaciones conocidas"
|
|
21746
|
+
},
|
|
21747
|
+
{
|
|
21748
|
+
"id": "sec_12",
|
|
21749
|
+
"title": "AppSettings relevantes"
|
|
21750
|
+
}
|
|
21751
|
+
],
|
|
21752
|
+
"sections": [
|
|
21753
|
+
{
|
|
21754
|
+
"id": "overview",
|
|
21755
|
+
"title": "Overview",
|
|
21756
|
+
"html": "<h1>Multi-tenant</h1><p>Enrutamiento de conexiones por usuario: cada inquilino tiene sus propias cadenas de conexión hacia bases de metadatos y datos separadas, preservando la compatibilidad total hacia atrás para las instalaciones mono-inquilino existentes.</p><h2>Scope</h2><p>El módulo multi-tenant permite que un único backend sirva a varios inquilinos — cada uno en bases de metadatos y datos distintas — sin duplicar código ni arrancar instancias separadas. Cuando el flag está OFF el comportamiento es idéntico al pre-multi-tenant (sin overhead, cachés sin cambios). Cuando está ON el framework:</p><ul><li>resuelve en tiempo de ejecución la cadena de conexión correcta según el <code>id_azienda</code> del usuario autenticado.</li><li>segrega las cachés del servidor (<code>Application[]</code>) y del cliente (Dexie + localStorage) por inquilino.</li><li>propaga <code>TenantScope.Current</code> en todas las consultas, scheduler y trabajos en segundo plano.</li></ul><h2>Activación</h2><p>En <code>appsettings.json</code> (sección <code>AppSettings</code>):</p><ul><li><code>"multiConnectionEnabled": "true"</code> — activa el enrutamiento tenant-aware en <code>MultiTenantHelpers</code> + middleware <code>TenantScope</code> en <code>Startup.cs</code>.</li><li><code>"enableCookieAuthentication": "true"</code> — requerido por el flujo multi-tenant: la cookie <code>k-user</code> v:2 lleva <code>azienda_id</code> + <code>azienda_id_user</code> para anti-hijack.</li></ul><p>Están disponibles las plantillas stand-alone <code>appsettings.multi-tenant.mssql.json</code> y <code>appsettings.multi-tenant.mysql.json</code> (6 cadenas de conexión: 2 default + 4 tenant <code>Tenant1_Meta</code>/<code>Tenant1_Data</code>/<code>Tenant2_Meta</code>/<code>Tenant2_Data</code>).</p><h2>Modelo de datos</h2><p>El mapeo <code>id_azienda → (data, meta)</code> vive en dos columnas de la tabla <code>Aziende</code> de la base de metadatos primaria:</p><ul><li><code>Aziende.Connessione_DB_Dati</code>: NOMBRE de una entrada en <code>appsettings.ConnectionStrings</code> para la base de datos del inquilino (NO la cadena de conexión literal).</li><li><code>Aziende.CONNESSIONE_DB_Meta</code>: NOMBRE de una entrada en <code>appsettings.ConnectionStrings</code> para la base de metadatos del inquilino.</li></ul><p>Ejemplo de poblamiento:</p><ul><li><code>Aziende.ID_Azienda=1</code>, <code>Connessione_DB_Dati='Tenant1_Data'</code>, <code>CONNESSIONE_DB_Meta='Tenant1_Meta'</code>.</li><li><code>Aziende.ID_Azienda=100</code> (Sede principale), <code>Connessione_DB_Dati='DataSQLConnection'</code>, <code>CONNESSIONE_DB_Meta='MetaDataSQLConnection'</code> (aliases default → cubierto por scope 0).</li></ul><p>Indirección por nombre de entrada:</p><ul><li>ninguna contraseña en claro en la base.</li><li>credenciales portables por entorno (misma base, distintos <code>appsettings.<env>.json</code>).</li><li>coherente con <code>_metadati__tabelle.md_conn_name</code>.</li></ul><h2>Fallback de login</h2><p>Cuando un usuario NO existe en la base de metadatos primaria, el login hace fallback vía <code>_login_index</code>:</p><p>1. lookup usuario en <code>MetaDataSQLConnection</code> (primaria) → encontrado? login.</p><p>2. miss → consulta <code>_login_index</code> (<code>SHA-256(LOWER(username))</code> → <code>id_azienda</code>).</p><p>3. para cada <code>id_azienda</code> candidato: push <code>TenantScope</code>, reintenta <code>metaQuery.login</code> en la base de metadatos del inquilino.</p><p>4. primer match gana → cookie emitida con el <code>azienda_id</code> del inquilino.</p><p>El procedimiento <code>sp__login_index_upsert(@username, @idAzienda)</code> pobla el índice. Hash SHA-256 calculado del lado servidor (alineado con el cliente C# <code>MultiTenantHelpers.ComputeUsernameHash</code> en UTF-8).</p><h2>Cambio de inquilino (superadmin)</h2><p>Los superadmins pueden cambiar el inquilino activo en tiempo de ejecución vía <code>MetaService.switchAzienda(idAzienda)</code>:</p><ul><li>401 si no es superadmin.</li><li>400 si el <code>idAzienda</code> no tiene ambas columnas <code>Aziende.Connessione_DB_Dati</code> / <code>CONNESSIONE_DB_Meta</code> rellenas.</li><li>reemite cookie con <code>azienda_id</code> actualizado, preservando <code>azienda_id_user</code> (snapshot al login).</li><li>el componente Angular <code><wuic-azienda-switcher></code> expone el dropdown UI. Se auto-oculta vía <code>@HostBinding('attr.hidden')</code> para usuarios no-super o flag OFF.</li></ul><h2>Cachés tenant-scoped</h2><p>Lado servidor (claves <code>Application[]</code>):</p><ul><li><code>MultiTenantHelpers.TenantKey(baseKey)</code> añade el sufijo <code>__a<id></code> cuando el flag está ON y <code>TenantScope.Current.AziendaId > 0</code>.</li><li>claves afectadas: <code>userList</code>, <code>roleList</code>, <code>userRoleList</code>, <code>companyList</code>, <code>storedMeta</code>, <code>storedTableMeta</code>, <code>storedTableActionMeta</code>, <code>storedConditionGroupMeta</code>, <code>storedTableStyleMeta</code>, <code>SysInfo</code>.</li></ul><p>Lado cliente (lib framework Angular):</p><ul><li>nombre Dexie → <code>MetaDB__a<id></code> y <code>WuicClientSideCrudDB__a<id></code>.</li><li>claves localStorage → <code>menu_<userId>__a<id></code>, <code>wuic_custom_settings_<userId>__a<id></code>, etc.</li><li>limpieza huérfanos al init de <code>MetadataProviderService</code>: enumera <code>indexedDB.databases()</code> y elimina las DB de inquilinos ya no corrientes.</li></ul><p>Cuando flag OFF: TenantKey retorna la baseKey sin cambios → cachés idénticos al pre-multi-tenant.</p><h2>Anti-hijack</h2><p><code>RawHelpers.authenticate()</code> en cada petición:</p><ul><li>requiere cookie v:2 con <code>azienda_id</code> + <code>azienda_id_user</code> (cookie legacy → relogin forzado).</li><li>para usuarios no-super, <code>cookie.azienda_id</code> DEBE coincidir con <code>cookie.azienda_id_user</code> (manipulación → relogin + log de seguridad).</li><li>los superadmins están exentos (su <code>azienda_id</code> cambia legítimamente vía <code>switchAzienda</code>).</li><li>pobla <code>TenantScope.Current</code> con <code>(azienda_id, isSuperAdmin)</code> para el resto de la petición.</li></ul><p>El middleware <code>HydrateLegacyPrincipalFromKUserCookie</code> en <code>Startup.cs</code> también pobla <code>TenantScope</code> para endpoints AsmxProxy que no llaman a <code>authenticate()</code> explícitamente (por ej. <code>readCustomSettings</code>, <code>getMenuByUserID</code>).</p><h2>Scheduler tenant-aware</h2><p><code>SchedulerHostedService</code> enumera los inquilinos en cada ciclo (<code>Scheduler:PollSeconds</code>, default 15s):</p><ul><li>scope 0 (primario / sin-tenant) — siempre procesado.</li><li>más un scope por cada inquilino con mapping conn ≠ default.</li><li>inquilinos mapeados a las entradas default (<code>MetaDataSQLConnection</code>) son saltados (cubiertos por scope 0).</li></ul><p>Los 4 tipos de acción se soportan por inquilino:</p><ul><li><code>1 | sql</code>: conexión datos vía enrutamiento tenant-aware.</li><li><code>2 | webservice</code>: usuario <code>__scheduler</code> creado en cada base inquilino; cookie session por inquilino vía llamada HTTP <code>switchAzienda</code>.</li><li><code>3 | assembly method</code>: <code>TenantScope.Current</code> propagado vía AsyncLocal al método invocado.</li><li><code>4 | mailing</code>: <code>_mailing_lists</code> / <code>_mail_recipients</code> leídos desde la base del inquilino corriente; ajustes SMTP overridables por inquilino vía convención de naming <code><key>__a<id></code> (por ej. <code>email-host__a1</code>).</li></ul><p>El esquema scheduler (<code>scheduler</code>, <code>scheduler_execution</code>) se valida por inquilino: inquilinos sin las tablas se saltan silenciosamente hasta el restart.</p><h2>Propagación del scaffolding cross-inquilino</h2><p>Cuando un superadmin hace scaffolding de una tabla o vista en un inquilino, por defecto los metadata (<code>_metadati__tabelle</code> + <code>_metadati__colonne</code>) se escriben <strong>solo en la base de metadata del inquilino corriente</strong>. Para evitar repetir manualmente el scaffolding en cada inquilino, el endpoint <code>scaffolding.scaffoldTable</code> / <code>scaffolding.scaffoldView</code> expone el parámetro booleano <code>propagateToTenants</code>:</p><ul><li><code>false</code> (por defecto): scaffold solo sobre el inquilino corriente (back-compat).</li><li><code>true</code>: después de scaffold del inquilino corriente, el backend itera sobre todos los demás inquilinos activos (<code>MultiTenantHelpers.GetAziendePrimary()</code>) y replica el scaffolding sobre la base de metadata de cada uno. El esquema se lee desde la base DATOS del inquilino destino resuelta vía <code>Aziende.Connessione_DB_Dati</code>. Inquilinos sin la tabla física son skippados con resultado <code>SKIPPED:not-found</code> → operación best-effort, no transaccional cross-inquilino.</li></ul><p>Seguridad:</p><ul><li>restringido a superadmin con flag <code>multiConnectionEnabled=true</code> — para los demás el parámetro se ignora silenciosamente.</li><li>empresas mapeadas a las entradas default (<code>MetaDataSQLConnection</code>) son skippadas (cubiertas por scope 0 del inquilino corriente si el superadmin ya está ahí).</li></ul><p>El resultado del scaffold incluye claves <code>tenant_<id>:</code> con valor <code>OK</code> / <code>SKIPPED:<reason></code> / <code>FAILED:<message></code> para diagnosis por-inquilino.</p><p>UI: el dialog de scaffolding (<code>md_id=1556</code>) expone una checkbox <strong>Propagate to all tenants</strong> en el form; la action <code>Scaffold Table</code> envía el flag al backend. En modo mono-inquilino (<code>multiConnectionEnabled=false</code>) el backend filtra la columna de la respuesta <code>getTableMetadata</code> → la checkbox no aparece en el form (ninguna UI inútil que gestionar).</p><h2>Cuentas de usuario no-superadmin a través de varios inquilinos</h2><p>El selector <code><wuic-azienda-switcher></code> es visible <strong>solo para superadmins</strong>. Para los usuarios no-superadmin el chequeo anti-hijack (<code>cookie.azienda_id != user.azienda_id_user → reject</code>) impide cambiar de inquilino en tiempo de ejecución: la cookie lleva <code>azienda_id_user</code> capturado en el login e inmutable.</p><p>Convención operativa para escenarios en los que la misma persona necesita acceder a varios inquilinos como usuario estándar:</p><ul><li><strong>crear un usuario separado por inquilino</strong>, con un <strong>username distinto</strong> (por ej. <code>mario.rossi@T1</code>, <code>mario.rossi@T2</code>, o <code>mario.rossi.t1</code> / <code>mario.rossi.t2</code>).</li><li>cada usuario vive en <code>utenti</code> de su propia base inquilino, con <code>id_azienda</code> correspondiente.</li><li><code>_login_index</code> mapea cada username a su <code>id_azienda</code>: ninguna ambigüedad en la fallback chain.</li><li>el usuario elige a qué inquilino acceder cambiando el username en el login.</li></ul><p>Anti-pattern a evitar:</p><ul><li><strong>mismo username en dos inquilinos</strong>: el login fallback intenta ambos candidatos <code>_login_index</code> en orden ascendente de <code>id_azienda</code>; el primero que valida la contraseña gana. El usuario aterriza siempre en el inquilino con <code>id_azienda</code> más bajo y no puede alcanzar el otro sin el widget switcher. La contraseña queda compartida cross-inquilino, expuesta a brute-force sobre ambas bases.</li></ul><p>Si la persona es superadmin, un único usuario en la base primaria (<code>id_azienda</code> = id de la empresa madre) es suficiente: el widget switcher cubre el cambio de inquilino en tiempo de ejecución.</p><h2>Limitaciones conocidas</h2><ul><li><strong>DBMS uniforme cross-inquilino</strong>: todos los inquilinos DEBEN compartir el mismo DBMS que el primario (<code>AppSettings.dbms</code> / <code>meta-dbms</code>). El routing tenant-aware solo enruta el <em>catalog</em> (qué base), no <em>qué provider</em>. Ejemplo: backend MSSQL + un <code>Tenant2_Meta</code> con connection string MySQL → <code>SqlException</code> en el primer acceso al inquilino. Una validación fail-soft (WARN log) se ejecuta al arranque (<code>MultiTenantHelpers.ValidateTenantConnectionStrings</code>): para cada cs referenciada por <code>Aziende.{Connessione_DB_Dati,CONNESSIONE_DB_Meta}</code> detecta heurísticamente el DBMS objetivo (palabras clave como <code>Port=3306</code>, <code>Initial Catalog=</code>, <code>(HOST=...)</code>, etc.) y loggea un warning si difiere del configurado. Mismatch → el boot continúa pero el inquilino desalineado será inutilizable en runtime.</li><li>el usuario <code>__scheduler</code> creado en las bases inquilino tiene <code>id_azienda=NULL</code> (no rellenado): adecuado para el flujo webservice (el login siempre parte del primario) pero no resolvible vía <code>_login_index</code> para casos inversos.</li><li>cada nuevo inquilino requiere una entrada en <code>appsettings.ConnectionStrings</code> + redeploy/reload config.</li><li>los jobs scheduler en inquilinos sin tabla <code>scheduler</code> se saltan silenciosamente (info-log al primer poll).</li></ul><h2>AppSettings relevantes</h2><p>Snippet (claves multi-tenant):</p><ul><li><code>"AppSettings.multiConnectionEnabled": "true"</code></li><li><code>"AppSettings.enableCookieAuthentication": "true"</code></li><li><code>"ConnectionStrings.MetaDataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.DataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Data": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Data": "..."</code></li><li>(opcional) <code>"AppSettings.email-host__a1": "smtp.tenant1.example"</code> — override SMTP para tenant 1.</li></ul>",
|
|
21757
|
+
"codeSamples": []
|
|
21758
|
+
}
|
|
21759
|
+
],
|
|
21760
|
+
"images": []
|
|
21761
|
+
},
|
|
20880
21762
|
{
|
|
20881
21763
|
"slug": "troubleshooting",
|
|
20882
21764
|
"lang": "es-ES",
|
|
@@ -21768,26 +22650,34 @@ const frameworkDocsContent = {
|
|
|
21768
22650
|
},
|
|
21769
22651
|
{
|
|
21770
22652
|
"id": "sec_6",
|
|
21771
|
-
"title": "md_props_bag:
|
|
22653
|
+
"title": "md_props_bag: toolbar"
|
|
21772
22654
|
},
|
|
21773
22655
|
{
|
|
21774
22656
|
"id": "sec_7",
|
|
21775
|
-
"title": "
|
|
22657
|
+
"title": "md_props_bag: archetypes.list"
|
|
21776
22658
|
},
|
|
21777
22659
|
{
|
|
21778
22660
|
"id": "sec_8",
|
|
21779
|
-
"title": "
|
|
22661
|
+
"title": "Paging: md_pagesize und md_page_size_choice"
|
|
21780
22662
|
},
|
|
21781
22663
|
{
|
|
21782
22664
|
"id": "sec_9",
|
|
22665
|
+
"title": "Erzwungene Virtualisierung bei hoher Seitengroesse"
|
|
22666
|
+
},
|
|
22667
|
+
{
|
|
22668
|
+
"id": "sec_10",
|
|
21783
22669
|
"title": "Events und Subscriptions (Host)"
|
|
22670
|
+
},
|
|
22671
|
+
{
|
|
22672
|
+
"id": "sec_11",
|
|
22673
|
+
"title": "`rowCustomSelect` — tatsachliche Signatur (Falle)"
|
|
21784
22674
|
}
|
|
21785
22675
|
],
|
|
21786
22676
|
"sections": [
|
|
21787
22677
|
{
|
|
21788
22678
|
"id": "overview",
|
|
21789
22679
|
"title": "Overview",
|
|
21790
|
-
"html": "<h1>List Grid</h1><p>Hauptkomponente fuer tabellarische Listen mit Filtern, Sortierung, serverseitigem Paging und Zeilenaktionen.</p><h2>Anwendungsfaelle</h2><ul><li>Enterprise-CRUD-Tabellen.</li><li>Operative Reports mit Multi-Spalten-Filtern.</li><li>Sehr grosse Datasets mit <code>cursorMode</code>.</li></ul><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__grid__01.png</code>: Modus <code>inline cell edit</code> im List-Grid.</li><li><code>manual__grid__02.png</code>: Modus <code>inline edit</code> (Row-Level) im List-Grid.</li></ul><h2>Metadaten Inline-Editing</h2><p>Konfiguration im <code>md_props_bag</code> der Tabellen-Metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - aktiviert das Inline-Editing der Zeile im List-Grid (editierbare Zellen im Zeilenkontext, ohne Popup-Oeffnung).</p><p> - nuetzlich wenn die Tabellen-UX mit schnellem Editing pro Datensatz beibehalten werden soll.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - aktiviert das Inline-Editing "Zelle fuer Zelle" (Fokus auf einzelne Zelle).</p><p> - Hinweis: in Legacy-Konfigurationen kann dies als <code>md_inline_cell_editing</code> erscheinen; das Runtime-Verhalten ist identisch.</p><p> - <strong>Runtime-Promotion</strong>: wenn <code>md_inline_cell_edit</code> <code>true</code> ist, erzwingt die Komponente</p><p> zur Laufzeit auch <code>md_inline_edit = true</code>, unabhaengig vom DB-Wert.</p><p> Beide UX benoetigen die sichtbare Action-Spalte, daher sind sie auf Rendering-Ebene</p><p> nicht gegenseitig ausschliessend.</p><ul><li><code>md_batch_save</code></li></ul><p> - aktiviert das Batch-Speichern der ausstehenden Aenderungen (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - bei Verwendung zusammen mit Inline-Cell bleiben die Aenderungen ausstehend bis zum expliziten Speichern.</p><p> - <strong>Voraussetzung</strong>: <code>md_batch_save</code> wirkt <strong>nur</strong> wenn <code>md_inline_cell_edit</code> <code>true</code> ist.</p><p> Mit <code>md_inline_cell_edit:false</code> wird das Flag zur Laufzeit ignoriert und die Buttons</p><p> "Aenderungen speichern / Aenderungen verwerfen" werden nicht gerendert.</p><h3>Gueltige Kombinationen</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime-Ergebnis</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>beliebig</td><td>Row-Level Inline-Edit mit Pencil. <code>md_batch_save</code> ignoriert.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Zelle-fuer-Zelle Autosave bei Blur. <code>md_inline_edit</code> wird zur Laufzeit auf <code>true</code> erzwungen.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Zelle-fuer-Zelle mit Pending-Buffer + Toolbar <code>Speichern / Aenderungen verwerfen</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>beliebig</td><td>Kein Inline-Editing. <code>md_batch_save</code> ignoriert.</td></tr></tbody></table><p>Beispiel Row-Level Inline-Edit:</p><p>Snippet 1:</p><p>Beispiel Zelle-fuer-Zelle mit Batch-Save:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - zeigt Fortschrittsdialog mit Echtzeit-Prozentsatz;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Task abbrechen</code>, <code>Unterbrechen und teilweise herunterladen</code>;</p><p> - im Hintergrund wird eine Benachrichtigung mit Fortschritt erstellt; Klick oeffnet den Dialog erneut.</p><ul><li>Import XLS/XLSX (wenn <code>md_importable = true</code>):</li></ul><p> - Fortschrittsdialog nach Bestaetigung;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Import abbrechen (Rollback)</code>, <code>Stop und teilweise Commit</code>;</p><p> - nach Import-Ende wird eine Zusammenfassungs-Benachrichtigung erstellt, die zur Route fuehrt.</p><h2>Config Metadata</h2><p>Schluessel-Einstellungen in <code>md_props_bag</code> und Spalten-Metadaten.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>Die Komponente liest <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>akzeptierte Werte: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>Runtime-Default: <code>true</code> wenn absent oder leer</li><li>Effekt: verteilt die Spaltenbreiten prozentual (anstelle von px), wenn keine vom Benutzer persistierten Breiten vorhanden sind.</li></ul><ul><li><code>virtualize</code>:</li><li>Typ: <code>boolean</code> oder <code>object</code></li><li>akzeptierte Werte:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>Object mit <code>enabled</code> (optional) und <code>itemSize</code> (optional)</li><li>Runtime-Default: deaktiviert (<code>false</code>) wenn absent</li><li><code>enabled</code>:</li><li>Default: <code>true</code> wenn der <code>virtualize</code>-Knoten ein Object ohne <code>enabled</code> ist, sonst toleranter Parser</li><li>Effekt: aktiviert <code>virtualScroll</code> auf <code>p-table</code></li><li><code>itemSize</code>:</li><li>Default: <code>44</code></li><li>Effekt: setzt <code>virtualScrollItemSize</code> (virtuelle Zeilenhoehe in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>Runtime-Default: <code>false</code></li><li>Effekt:</li><li>wenn <code>true</code>, blendet das Grid die Spalten-Filter-Icons (<code>p-columnFilter</code>) aus und verwendet die <code>wuic-filter-bar</code> ueber der Tabelle (auf <code>data-repeater</code>/<code>bounded-repeater</code>-Ebene), um die Filter anzuwenden.</li></ul><p>Betriebshinweis:</p><ul><li>der <code>md_props_bag</code>-Suggest im Metadata-Editor stellt auch den checkbaren Knoten <code>archetypes.list.advancedFilter</code> bereit.</li></ul><h2>Paging: md_pagesize und md_page_size_choice</h2><p>Das Grid verwendet <code>md_pagesize</code> als Standard-Seitengroesse und <code>md_page_size_choice</code> als Liste der im Paginator auswaehlbaren Werte.</p><p>Runtime-Regeln:</p><ul><li>wenn <code>md_pagesize</code> groesser als der maximale Wert in <code>md_page_size_choice</code> ist, fuegt das Framework <code>md_pagesize</code> automatisch zur Optionsliste hinzu;</li><li>die Liste wird normalisiert (gueltige Zahlen, Deduplizierung, aufsteigende Sortierung).</li></ul><p>Beispiel:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>Runtime-Ergebnis: <code>10,25,50,100,200</code></li></ul><h2>Erzwungene Virtualisierung bei hoher Seitengroesse</h2><p>Wenn der Benutzer im Paginator einen Wert <code>pageSize >= 1000</code> auswaehlt:</p><ul><li>wird die Virtualisierung automatisch erzwungen, auch wenn <code>md_props_bag.archetypes.list.virtualize</code> absent oder deaktiviert ist;</li><li><code>virtualScrollItemSize</code> wird auf den Standardwert <code>44</code> gesetzt.</li></ul><p>Dieses Verhalten schuetzt das Rendering der Tabelle bei sehr grossen Seiten.</p><h2>Events und Subscriptions (Host)</h2><p><code>wuic-list-grid</code> stellt Runtime-Events bereit, die nuetzlich sind, um den Render-Zyklus und die p-table-Callbacks auf der Host-Projektseite zu intercepten.</p><p>Verfuegbare Events:</p><ul><li><code>onAfterRender</code>: wird am Ende des Datenbindings des Grids emittiert (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: wird vor dem logischen Rendering der einzelnen Zeile emittiert; unterstuetzt Cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: wird nach dem logischen Rendering der einzelnen Zeile emittiert.</li><li><code>onPaging</code>: wird bei Paging-Events emittiert (<code>p-table onPage</code>).</li><li><code>onSorting</code>: wird bei Sorting-Events emittiert (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: wird bei Filtering-Events emittiert (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: wird bei Aenderung der Zeilenauswahl emittiert.</li><li><code>onPTableRowExpand</code>: wird beim Expandieren einer Zeile emittiert.</li><li><code>onPTableRowCollapse</code>: wird beim Collapsen einer Zeile emittiert.</li><li><code>onPTableColumnResize</code>: wird beim Spalten-Resize emittiert.</li><li><code>onPTableColumnReorder</code>: wird beim Spalten-Reorder emittiert.</li></ul><h3>Beispiel 1: direktes Binding im Template</h3><p>Snippet 5:</p><h3>Beispiel 2: Subscribe via ViewChild</h3><p>Snippet 6:</p>",
|
|
22680
|
+
"html": "<h1>List Grid</h1><p>Hauptkomponente fuer tabellarische Listen mit Filtern, Sortierung, serverseitigem Paging und Zeilenaktionen.</p><h2>Anwendungsfaelle</h2><ul><li>Enterprise-CRUD-Tabellen.</li><li>Operative Reports mit Multi-Spalten-Filtern.</li><li>Sehr grosse Datasets mit <code>cursorMode</code>.</li></ul><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__grid__01.png</code>: Modus <code>inline cell edit</code> im List-Grid.</li><li><code>manual__grid__02.png</code>: Modus <code>inline edit</code> (Row-Level) im List-Grid.</li></ul><h2>Metadaten Inline-Editing</h2><p>Konfiguration im <code>md_props_bag</code> der Tabellen-Metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - aktiviert das Inline-Editing der Zeile im List-Grid (editierbare Zellen im Zeilenkontext, ohne Popup-Oeffnung).</p><p> - nuetzlich wenn die Tabellen-UX mit schnellem Editing pro Datensatz beibehalten werden soll.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - aktiviert das Inline-Editing "Zelle fuer Zelle" (Fokus auf einzelne Zelle).</p><p> - Hinweis: in Legacy-Konfigurationen kann dies als <code>md_inline_cell_editing</code> erscheinen; das Runtime-Verhalten ist identisch.</p><p> - <strong>Runtime-Promotion</strong>: wenn <code>md_inline_cell_edit</code> <code>true</code> ist, erzwingt die Komponente</p><p> zur Laufzeit auch <code>md_inline_edit = true</code>, unabhaengig vom DB-Wert.</p><p> Beide UX benoetigen die sichtbare Action-Spalte, daher sind sie auf Rendering-Ebene</p><p> nicht gegenseitig ausschliessend.</p><ul><li><code>md_batch_save</code></li></ul><p> - aktiviert das Batch-Speichern der ausstehenden Aenderungen (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - bei Verwendung zusammen mit Inline-Cell bleiben die Aenderungen ausstehend bis zum expliziten Speichern.</p><p> - <strong>Voraussetzung</strong>: <code>md_batch_save</code> wirkt <strong>nur</strong> wenn <code>md_inline_cell_edit</code> <code>true</code> ist.</p><p> Mit <code>md_inline_cell_edit:false</code> wird das Flag zur Laufzeit ignoriert und die Buttons</p><p> "Aenderungen speichern / Aenderungen verwerfen" werden nicht gerendert.</p><h3>Gueltige Kombinationen</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime-Ergebnis</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>beliebig</td><td>Row-Level Inline-Edit mit Pencil. <code>md_batch_save</code> ignoriert.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Zelle-fuer-Zelle Autosave bei Blur. <code>md_inline_edit</code> wird zur Laufzeit auf <code>true</code> erzwungen.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Zelle-fuer-Zelle mit Pending-Buffer + Toolbar <code>Speichern / Aenderungen verwerfen</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>beliebig</td><td>Kein Inline-Editing. <code>md_batch_save</code> ignoriert.</td></tr></tbody></table><p>Beispiel Row-Level Inline-Edit:</p><p>Snippet 1:</p><p>Beispiel Zelle-fuer-Zelle mit Batch-Save:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - zeigt Fortschrittsdialog mit Echtzeit-Prozentsatz;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Task abbrechen</code>, <code>Unterbrechen und teilweise herunterladen</code>;</p><p> - im Hintergrund wird eine Benachrichtigung mit Fortschritt erstellt; Klick oeffnet den Dialog erneut.</p><ul><li>Import XLS/XLSX (wenn <code>md_importable = true</code>):</li></ul><p> - Fortschrittsdialog nach Bestaetigung;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Import abbrechen (Rollback)</code>, <code>Stop und teilweise Commit</code>;</p><p> - nach Import-Ende wird eine Zusammenfassungs-Benachrichtigung erstellt, die zur Route fuehrt.</p><h2>Config Metadata</h2><p>Schluessel-Einstellungen in <code>md_props_bag</code> und Spalten-Metadaten.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Opt-in-Flags, die Bloecke der list-grid Toolbar (<code>caption-bar</code>) ausblenden. Alle unter <code>md_props_bag.toolbar.*</code> (zur Laufzeit als <code>tableMetadata.extraProps.toolbar</code> geparst).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): blendet in der <strong>caption-right</strong> den Button "Status verwalten" (Bookmark-Icon) + das <code><select></code> der gespeicherten Zustaende aus. Nuetzlich fuer hardcoded / Demo-Routes, bei denen das Saved-State-Feature (Persistenz pro <code>user_id</code> + Route via <code>MetaService</code>) keinen Sinn ergibt — z.B. Pattern 3 reines OData ohne registrierte Route-Metadaten.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): blendet in der <strong>caption-left</strong> die Buttons "Aenderungen speichern" (<code>pi-save</code>) + "Aenderungen verwerfen" (<code>pi-times</code>) + den Aenderungs-Zaehler-Indikator (<code>grid-changes-indicator</code>, Pencil-Badge + Count) aus. Vom Framework generiert, wenn <code>md_inline_cell_editing</code> + <code>md_batch_save</code> aktiv sind. Konzipiert fuer <strong>verschachtelte Grids in einem parametric-dialog</strong>: Save/Cancel des Parents persistiert Master + Zeilen in einem Schritt via Framework Batch Save, und die Duplikat-Buttons auf der nested Grid verwirren die UX.</li></ul><p>Beispiel nested rows in custom edit-form (Runtime-Patch):</p><p>Snippet 5:</p><p>TS-Typ in <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (<code>extraProps.toolbar</code> Property). Die Flags deaktivieren NICHT die zugrundeliegende <code>md_batch_save</code>-Logik (Aenderungen werden weiterhin getrackt); sie entfernen nur die Toolbar-UI — der eigentliche Save laeuft ueber den Parent-Flow.</p><h2>md_props_bag: archetypes.list</h2><p>Die Komponente liest <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>akzeptierte Werte: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>"true"</code>, <code>"false"</code>, <code>"1"</code>, <code>"0"</code></li><li>Runtime-Default: <code>true</code> wenn absent oder leer</li><li>Effekt: verteilt die Spaltenbreiten prozentual (anstelle von px), wenn keine vom Benutzer persistierten Breiten vorhanden sind.</li></ul><ul><li><code>virtualize</code>:</li><li>Typ: <code>boolean</code> oder <code>object</code></li><li>akzeptierte Werte:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>"true"</code>/<code>"false"</code>, <code>"1"</code>/<code>"0"</code></li><li>Object mit <code>enabled</code> (optional) und <code>itemSize</code> (optional)</li><li>Runtime-Default: deaktiviert (<code>false</code>) wenn absent</li><li><code>enabled</code>:</li><li>Default: <code>true</code> wenn der <code>virtualize</code>-Knoten ein Object ohne <code>enabled</code> ist, sonst toleranter Parser</li><li>Effekt: aktiviert <code>virtualScroll</code> auf <code>p-table</code></li><li><code>itemSize</code>:</li><li>Default: <code>44</code></li><li>Effekt: setzt <code>virtualScrollItemSize</code> (virtuelle Zeilenhoehe in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>Runtime-Default: <code>false</code></li><li>Effekt:</li><li>wenn <code>true</code>, blendet das Grid die Spalten-Filter-Icons (<code>p-columnFilter</code>) aus und verwendet die <code>wuic-filter-bar</code> ueber der Tabelle (auf <code>data-repeater</code>/<code>bounded-repeater</code>-Ebene), um die Filter anzuwenden.</li></ul><p>Betriebshinweis:</p><ul><li>der <code>md_props_bag</code>-Suggest im Metadata-Editor stellt auch den checkbaren Knoten <code>archetypes.list.advancedFilter</code> bereit.</li></ul><h2>Paging: md_pagesize und md_page_size_choice</h2><p>Das Grid verwendet <code>md_pagesize</code> als Standard-Seitengroesse und <code>md_page_size_choice</code> als Liste der im Paginator auswaehlbaren Werte.</p><p>Runtime-Regeln:</p><ul><li>wenn <code>md_pagesize</code> groesser als der maximale Wert in <code>md_page_size_choice</code> ist, fuegt das Framework <code>md_pagesize</code> automatisch zur Optionsliste hinzu;</li><li>die Liste wird normalisiert (gueltige Zahlen, Deduplizierung, aufsteigende Sortierung).</li></ul><p>Beispiel:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = "10,25,50,100"</code></li><li>Runtime-Ergebnis: <code>10,25,50,100,200</code></li></ul><h2>Erzwungene Virtualisierung bei hoher Seitengroesse</h2><p>Wenn der Benutzer im Paginator einen Wert <code>pageSize >= 1000</code> auswaehlt:</p><ul><li>wird die Virtualisierung automatisch erzwungen, auch wenn <code>md_props_bag.archetypes.list.virtualize</code> absent oder deaktiviert ist;</li><li><code>virtualScrollItemSize</code> wird auf den Standardwert <code>44</code> gesetzt.</li></ul><p>Dieses Verhalten schuetzt das Rendering der Tabelle bei sehr grossen Seiten.</p><h2>Events und Subscriptions (Host)</h2><p><code>wuic-list-grid</code> stellt Runtime-Events bereit, die nuetzlich sind, um den Render-Zyklus und die p-table-Callbacks auf der Host-Projektseite zu intercepten.</p><p>Verfuegbare Events:</p><ul><li><code>onAfterRender</code>: wird am Ende des Datenbindings des Grids emittiert (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: wird vor dem logischen Rendering der einzelnen Zeile emittiert; unterstuetzt Cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: wird nach dem logischen Rendering der einzelnen Zeile emittiert.</li><li><code>onPaging</code>: wird bei Paging-Events emittiert (<code>p-table onPage</code>).</li><li><code>onSorting</code>: wird bei Sorting-Events emittiert (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: wird bei Filtering-Events emittiert (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: wird bei Aenderung der Zeilenauswahl emittiert.</li><li><code>onPTableRowExpand</code>: wird beim Expandieren einer Zeile emittiert.</li><li><code>onPTableRowCollapse</code>: wird beim Collapsen einer Zeile emittiert.</li><li><code>onPTableColumnResize</code>: wird beim Spalten-Resize emittiert.</li><li><code>onPTableColumnReorder</code>: wird beim Spalten-Reorder emittiert.</li></ul><h3>Beispiel 1: direktes Binding im Template</h3><p>Snippet 7:</p><h3>Beispiel 2: Subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — tatsachliche Signatur (Falle)</h2><p><code><wuic-list-grid></code> akzeptiert ein <code>[rowCustomSelect]</code>-Input zum Abfangen der Zeilenauswahl (Offnen eines "Dokument auswahlen"-Dialogs, Master-Detail-Flow, etc.). Die TypeScript-Signatur in <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> lautet:</p><p>Snippet 9:</p><p><strong>Zur Laufzeit wird der Callback jedoch mit umgekehrten Argumenten aufgerufen</strong> — das Framework ruft <code>rowCustomSelect($event, rowData, dt)</code> auf (siehe <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> und <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Symptom bei falscher Reihenfolge: <code>rowData?.id</code> ist undefined → fruhzeitiger Guard im Handler → der Callback kehrt stillschweigend zuruck und der Dialog/die Aktion startet nicht. Kein Konsolenfehler.</p><p>Korrekte Form (auf Laufzeit-Invocation abgestimmt):</p><p>Snippet 10:</p><p>Verwendung im Template:</p><p>Snippet 11:</p><p>> Hinweis: Die Tests in <code>designer.component.spec.ts</code> (z.B. Zeile 512) rufen den Callback mit <code>({currentTarget: rowCell}, {id: 42}, null)</code> auf und bestatigen die Reihenfolge <code>($event, rowData, dt)</code>. Sollte das Framework die deklarierte Signatur kunftig angleichen, wird auch diese Seite aktualisiert.</p>",
|
|
21791
22681
|
"codeSamples": [
|
|
21792
22682
|
{
|
|
21793
22683
|
"id": "code_1",
|
|
@@ -21811,19 +22701,49 @@ const frameworkDocsContent = {
|
|
|
21811
22701
|
"id": "code_4",
|
|
21812
22702
|
"title": "Snippet 4",
|
|
21813
22703
|
"language": "json",
|
|
21814
|
-
"code": "{\n \"
|
|
22704
|
+
"code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
|
|
21815
22705
|
},
|
|
21816
22706
|
{
|
|
21817
22707
|
"id": "code_5",
|
|
21818
22708
|
"title": "Snippet 5",
|
|
21819
|
-
"language": "
|
|
21820
|
-
"code": "
|
|
22709
|
+
"language": "ts",
|
|
22710
|
+
"code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
|
|
21821
22711
|
},
|
|
21822
22712
|
{
|
|
21823
22713
|
"id": "code_6",
|
|
21824
22714
|
"title": "Snippet 6",
|
|
22715
|
+
"language": "json",
|
|
22716
|
+
"code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
|
|
22717
|
+
},
|
|
22718
|
+
{
|
|
22719
|
+
"id": "code_7",
|
|
22720
|
+
"title": "Snippet 7",
|
|
22721
|
+
"language": "html",
|
|
22722
|
+
"code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
|
|
22723
|
+
},
|
|
22724
|
+
{
|
|
22725
|
+
"id": "code_8",
|
|
22726
|
+
"title": "Snippet 8",
|
|
21825
22727
|
"language": "ts",
|
|
21826
22728
|
"code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // Beispiel Zeilen-Cancel\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
|
|
22729
|
+
},
|
|
22730
|
+
{
|
|
22731
|
+
"id": "code_9",
|
|
22732
|
+
"title": "Snippet 9",
|
|
22733
|
+
"language": "ts",
|
|
22734
|
+
"code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
|
|
22735
|
+
},
|
|
22736
|
+
{
|
|
22737
|
+
"id": "code_10",
|
|
22738
|
+
"title": "Snippet 10",
|
|
22739
|
+
"language": "ts",
|
|
22740
|
+
"code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData ist der von list-grid gelieferte Datensatz\n this.handleSelection(rowData);\n};"
|
|
22741
|
+
},
|
|
22742
|
+
{
|
|
22743
|
+
"id": "code_11",
|
|
22744
|
+
"title": "Snippet 11",
|
|
22745
|
+
"language": "html",
|
|
22746
|
+
"code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
|
|
21827
22747
|
}
|
|
21828
22748
|
]
|
|
21829
22749
|
}
|
|
@@ -22080,14 +23000,47 @@ const frameworkDocsContent = {
|
|
|
22080
23000
|
{
|
|
22081
23001
|
"id": "sec_3",
|
|
22082
23002
|
"title": "Verweis auf den Designer"
|
|
23003
|
+
},
|
|
23004
|
+
{
|
|
23005
|
+
"id": "sec_4",
|
|
23006
|
+
"title": "Report-Parameter (gefilterte SQL-Variablen)"
|
|
23007
|
+
},
|
|
23008
|
+
{
|
|
23009
|
+
"id": "sec_5",
|
|
23010
|
+
"title": "Erforderliche Backend-Konfiguration"
|
|
22083
23011
|
}
|
|
22084
23012
|
],
|
|
22085
23013
|
"sections": [
|
|
22086
23014
|
{
|
|
22087
23015
|
"id": "overview",
|
|
22088
23016
|
"title": "Overview",
|
|
22089
|
-
"html": "<h1>Reporting</h1><p>Operative Berichterstellung auf Route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__reporting__01.png</code>: Öffnung des Report-Viewers mit Report-Rendering und operativer Toolbar.</li><li><code>manual__reporting__02.png</code>: Runtime-Beispiel mit auf den Report angewendeten Parametern/Filtern.</li></ul><h2>Report Viewer</h2><p>Der <code>report-viewer</code> führt Stimulsoft-Reports aus und zeigt sie an, die der aktuellen Metadata-Route zugeordnet sind.</p><p>Hauptfunktionen:</p><ul><li>Report-Öffnung aus List/Grid mit Reportname (<code>reportName</code>) und optionalen Parametern.</li><li>Datenrendering zur Laufzeit mit Viewer-Toolbar (Zoom, Export, Druck).</li><li>Unterstützung für Filter/Parameter, die von der aufrufenden Seite propagiert werden.</li></ul><h2>Verweis auf den Designer</h2><p>Für die Erstellung oder Bearbeitung der <code>.mrt</code>-Reportdatei siehe den Unterabschnitt</p><p><a href=\"#/framework-docs/report-designer\">Report Designer</a> unter <code>Designer & Workflow</code>.</p>",
|
|
22090
|
-
"codeSamples": [
|
|
23017
|
+
"html": "<h1>Reporting</h1><p>Operative Berichterstellung auf Route <code>#/<route>/report-viewer</code>.</p><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__reporting__01.png</code>: Öffnung des Report-Viewers mit Report-Rendering und operativer Toolbar.</li><li><code>manual__reporting__02.png</code>: Runtime-Beispiel mit auf den Report angewendeten Parametern/Filtern.</li></ul><h2>Report Viewer</h2><p>Der <code>report-viewer</code> führt Stimulsoft-Reports aus und zeigt sie an, die der aktuellen Metadata-Route zugeordnet sind.</p><p>Hauptfunktionen:</p><ul><li>Report-Öffnung aus List/Grid mit Reportname (<code>reportName</code>) und optionalen Parametern.</li><li>Datenrendering zur Laufzeit mit Viewer-Toolbar (Zoom, Export, Druck).</li><li>Unterstützung für Filter/Parameter, die von der aufrufenden Seite propagiert werden.</li></ul><h2>Verweis auf den Designer</h2><p>Für die Erstellung oder Bearbeitung der <code>.mrt</code>-Reportdatei siehe den Unterabschnitt</p><p><a href=\"#/framework-docs/report-designer\">Report Designer</a> unter <code>Designer & Workflow</code>.</p><h2>Report-Parameter (gefilterte SQL-Variablen)</h2><p>Um einen Report auf eine bestimmte Entitat zu filtern (z.B. Druck einer einzelnen Rechnung), lautet das kanonische Muster:</p><p>1. <strong>In der `.mrt`-Datei</strong>: deklariere eine <strong>Variable</strong> im Dictionary mit <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Verwende die Variable als <code>@paramName</code> in den SqlCommands:</p><p>Snippet 1:</p><p> > <strong>Falle</strong>: KEINE Verwendung des Legacy-Blocks <code><StiDataParameter></code> im DataSource — er wird von der aktuellen Stimulsoft-Version nicht mehr unterstutzt und produziert <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter 'value')</code> wahrend <code>report.Load()</code>.</p><p>2. <strong>In der Viewer-URL</strong>: ubergib den Wert uber den <code>parameters</code>-Query-Param (NICHT als direkten Query-Param):</p><p>Snippet 2:</p><p> <code>parameters</code>-URL-encoded-Syntax: <code>name||eq||value</code> (durch <code>@</code> getrennt fur mehrere Parameter).</p><p>3. <strong>Aus einer Row-Action oder einem UI-Link</strong>: verwende den kanonischen Callback zum Erstellen der URL:</p><p>Snippet 3:</p><h2>Erforderliche Backend-Konfiguration</h2><p>In der <code>appsettings.json</code> der App muss die Einstellung <code>AppSettings.reportQueryTimeout</code> vorhanden sein (z.B. <code>"120"</code>). Ohne sie wirft <code>ReportViewerController.prepareDataSql</code> <code>int.Parse(null)</code> → 500. Beispiel:</p><p>Snippet 4:</p>",
|
|
23018
|
+
"codeSamples": [
|
|
23019
|
+
{
|
|
23020
|
+
"id": "code_1",
|
|
23021
|
+
"title": "Snippet 1",
|
|
23022
|
+
"language": "sql",
|
|
23023
|
+
"code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
|
|
23024
|
+
},
|
|
23025
|
+
{
|
|
23026
|
+
"id": "code_2",
|
|
23027
|
+
"title": "Snippet 2",
|
|
23028
|
+
"language": "text",
|
|
23029
|
+
"code": " ✅ #/<route>/report-viewer?reportName=Report.mrt¶meters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignoriert"
|
|
23030
|
+
},
|
|
23031
|
+
{
|
|
23032
|
+
"id": "code_3",
|
|
23033
|
+
"title": "Snippet 3",
|
|
23034
|
+
"language": "js",
|
|
23035
|
+
"code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt¶meters='\n + encodeURIComponent(params);"
|
|
23036
|
+
},
|
|
23037
|
+
{
|
|
23038
|
+
"id": "code_4",
|
|
23039
|
+
"title": "Snippet 4",
|
|
23040
|
+
"language": "json",
|
|
23041
|
+
"code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
|
|
23042
|
+
}
|
|
23043
|
+
]
|
|
22091
23044
|
}
|
|
22092
23045
|
],
|
|
22093
23046
|
"images": [
|
|
@@ -22820,28 +23773,54 @@ const frameworkDocsContent = {
|
|
|
22820
23773
|
},
|
|
22821
23774
|
{
|
|
22822
23775
|
"id": "sec_5",
|
|
22823
|
-
"title": "
|
|
23776
|
+
"title": "Slim-Combo — Zusatzfelder via `mc_props_bag.slimCombo`"
|
|
22824
23777
|
},
|
|
22825
23778
|
{
|
|
22826
23779
|
"id": "sec_6",
|
|
22827
|
-
"title": "
|
|
23780
|
+
"title": "Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie"
|
|
22828
23781
|
},
|
|
22829
23782
|
{
|
|
22830
23783
|
"id": "sec_7",
|
|
23784
|
+
"title": "Client-/Server-Effekte"
|
|
23785
|
+
},
|
|
23786
|
+
{
|
|
23787
|
+
"id": "sec_8",
|
|
22831
23788
|
"title": "Betriebshinweise"
|
|
23789
|
+
},
|
|
23790
|
+
{
|
|
23791
|
+
"id": "sec_9",
|
|
23792
|
+
"title": "Auto-Fetch bei programmatischen FK-Aenderungen"
|
|
22832
23793
|
}
|
|
22833
23794
|
],
|
|
22834
23795
|
"sections": [
|
|
22835
23796
|
{
|
|
22836
23797
|
"id": "overview",
|
|
22837
23798
|
"title": "Overview",
|
|
22838
|
-
"html": "<h1>Widget Lookup</h1><p>Spalten-Widget fuer Beziehungen (<code>lookupByID</code>, <code>multiple_check</code>) mit dediziertem <strong>Lookup</strong>-Tab im Metadata-Editor fuer Spalten.</p><h2>Beispiele</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — reines OData-Lookup (State/Province aus `/odata/Stateprovinces`)</a> — demonstriert <code>mc_props_bag.lookup.endpoint</code> mit Provider <code>odata</code> und Inline-Rendering via <code>$expand</code>.</li></ul><h2>Wann verwenden</h2><ul><li>Auswahl verknuepfter Datensaetze aus einer externen Metadata-Tabelle.</li><li>Szenarien mit serverseitiger Suche und Lookup-Paging.</li><li>Unterstuetzung von Einzelauswahl (<code>lookupByID</code>) und Mehrfachauswahl (<code>multiple_check</code>).</li></ul><h2>Spalten-Metadata-Eigenschaften (Tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: Metadata-Route der Lookup-Quelle.</li><li><code>mc_ui_lookup_dataValueField</code>: zurueckgegebenes ID-Feld.</li><li><code>mc_ui_lookup_dataTextField</code>: angezeigtes Beschreibungsfeld.</li><li><code>mc_ui_lookup_filter</code>: Lookup-Basisfilter.</li><li><code>mc_serverside_operations</code>: serverseitige Suche/Paging.</li><li><code>mc_ui_pagesize</code>: Seitengroesse der Lookup-Ergebnisse.</li><li><code>mc_ui_lookup_edit_allow</code>: aktiviert Bearbeitung des Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_insert_allow</code>: aktiviert Einfuegung eines Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_search_grid</code>: aktiviert Such-Grid im Popup.</li><li><code>mc_logic_allow_navigation</code>: Navigation zur verknuepften Route.</li></ul><h2>Nuetzlicher mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: zusaetzliche Einschraenkung client-/serverseitig in der Lookup-Abfrage.</li><li><code>lookup.virtualize</code>: aktiviert virtuelles Scrollen auf <code>p-autocomplete</code> des Lookup-Widgets.</li><li><code>lookup.virtualize.enabled</code>: toleranter Parser (<code>true/false</code>, <code>1/0</code>, aequivalente Strings); Standard <code>false</code> wenn absent.</li><li><code>lookup.virtualize.itemSize</code>: virtuelle Zeilenhoehe in px; Standard <code>44</code>.</li><li><code>lookup.endpoint</code>: Override des Datenproviders fuer das Dropdown. Wenn gesetzt, geht das nested Datasource des Lookup-Editors nicht mehr ueber den Metadata-Combo-Endpoint (<code>MetaService.getFlatRecordComboData</code>), sondern direkt ueber den in <code>type</code> deklarierten Provider mit der URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (Dispatcher leitet an <code>DataProviderOdataService.selectCombo</code> weiter, das GET mit <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> und <code>$filter=contains(textField,'<query>')</code> fuer die serverseitige Suche emittiert). Andere Werte folgen derselben Logik wie der Tabellen-Level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: Basis-URL des Entity-Sets (z.B. <code>/odata/<EntitySet></code>), mit oder ohne explizite Origin. Bereits vorhandene Query-Params werden ignoriert — der Provider rekonstruiert die Params bei jedem Fetch.</li><li><code>form.columns</code>: Feldbreite im Bearbeitungsformular.</li><li><code>style.editCss</code>: benutzerdefinierter Editor-Stil.</li></ul><h2>Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie</h2><ul><li>Operativer Inline-Flow: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Inline-Snippet Hauptfelder: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline-Snippet Schnellfilter: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Client-Effekt: beschleunigtes Lookup-Setup im Metadata-Editor und konsistenter, sofort einsatzbereiter Filter im Widget.</li><li>Server-Effekt: Lookup-Abfrage und Select-Clause bleiben mit den <code>MetaService.*</code>-Endpoints abgeglichen, ohne manuelles Mapping.</li></ul><p>Schnelle operative Checkliste:</p><p>1. Basiskonfiguration mit <code>suggestLookup2</code> klonen;</p><p>2. Standard-Filter mit <code>suggestLookupDefaultFilter</code> generieren;</p><p>3. Hierarchie/Select-Clause vom Lookup-Tree verfeinern;</p><p>4. Zur Laufzeit validieren, dass Suche und Value/Text-Mapping konsistent sind.</p><h2>Client-/Server-Effekte</h2><ul><li>Client: Rendering des Lookup-Widgets, Suche, Label-/Value-Binding.</li><li>Client: bei aktiviertem <code>lookup.virtualize</code> rendert das Autocomplete nur das visuell sichtbare Subset der Optionen.</li><li>Server: paginierte/gefilterte Lookup-Abfrage ueber <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Betriebshinweise</h2><ul><li>Bei <code>multiple_check</code> bleibt der Lookup-Teil identisch, nur der Multi-Value-Renderer aendert sich.</li><li>Wenn <code>mc_ui_lookup_entity_name</code> fehlt, kann das Widget die Quelle nicht aufloesen.</li></ul>",
|
|
23799
|
+
"html": "<h1>Widget Lookup</h1><p>Spalten-Widget fuer Beziehungen (<code>lookupByID</code>, <code>multiple_check</code>) mit dediziertem <strong>Lookup</strong>-Tab im Metadata-Editor fuer Spalten.</p><h2>Beispiele</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — reines OData-Lookup (State/Province aus `/odata/Stateprovinces`)</a> — demonstriert <code>mc_props_bag.lookup.endpoint</code> mit Provider <code>odata</code> und Inline-Rendering via <code>$expand</code>.</li></ul><h2>Wann verwenden</h2><ul><li>Auswahl verknuepfter Datensaetze aus einer externen Metadata-Tabelle.</li><li>Szenarien mit serverseitiger Suche und Lookup-Paging.</li><li>Unterstuetzung von Einzelauswahl (<code>lookupByID</code>) und Mehrfachauswahl (<code>multiple_check</code>).</li></ul><h2>Spalten-Metadata-Eigenschaften (Tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: Metadata-Route der Lookup-Quelle.</li><li><code>mc_ui_lookup_dataValueField</code>: zurueckgegebenes ID-Feld.</li><li><code>mc_ui_lookup_dataTextField</code>: angezeigtes Beschreibungsfeld.</li><li><code>mc_ui_lookup_filter</code>: Lookup-Basisfilter.</li><li><code>mc_serverside_operations</code>: serverseitige Suche/Paging.</li><li><code>mc_ui_pagesize</code>: Seitengroesse der Lookup-Ergebnisse.</li><li><code>mc_ui_lookup_edit_allow</code>: aktiviert Bearbeitung des Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_insert_allow</code>: aktiviert Einfuegung eines Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_search_grid</code>: aktiviert Such-Grid im Popup.</li><li><code>mc_logic_allow_navigation</code>: Navigation zur verknuepften Route.</li></ul><h2>Nuetzlicher mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: zusaetzliche Einschraenkung client-/serverseitig in der Lookup-Abfrage.</li><li><code>lookup.virtualize</code>: aktiviert virtuelles Scrollen auf <code>p-autocomplete</code> des Lookup-Widgets.</li><li><code>lookup.virtualize.enabled</code>: toleranter Parser (<code>true/false</code>, <code>1/0</code>, aequivalente Strings); Standard <code>false</code> wenn absent.</li><li><code>lookup.virtualize.itemSize</code>: virtuelle Zeilenhoehe in px; Standard <code>44</code>.</li><li><code>lookup.endpoint</code>: Override des Datenproviders fuer das Dropdown. Wenn gesetzt, geht das nested Datasource des Lookup-Editors nicht mehr ueber den Metadata-Combo-Endpoint (<code>MetaService.getFlatRecordComboData</code>), sondern direkt ueber den in <code>type</code> deklarierten Provider mit der URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>'odata'</code> (Dispatcher leitet an <code>DataProviderOdataService.selectCombo</code> weiter, das GET mit <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> und <code>$filter=contains(textField,'<query>')</code> fuer die serverseitige Suche emittiert). Andere Werte folgen derselben Logik wie der Tabellen-Level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: Basis-URL des Entity-Sets (z.B. <code>/odata/<EntitySet></code>), mit oder ohne explizite Origin. Bereits vorhandene Query-Params werden ignoriert — der Provider rekonstruiert die Params bei jedem Fetch.</li><li><code>form.columns</code>: Feldbreite im Bearbeitungsformular.</li><li><code>style.editCss</code>: benutzerdefinierter Editor-Stil.</li></ul><h2>Slim-Combo — Zusatzfelder via <code>mc_props_bag.slimCombo</code></h2><p>Das Lookup-Dropdown emittiert standardmaessig ein minimales SELECT auf der verknuepften Tabelle: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Das reduziert den Combo-Payload. Wenn der Client weitere Spalten aus der verknuepften Tabelle benoetigt — typischerweise um abhaengige Felder via <code>mc_selection_changed_custom_function__fn</code> vorzubefuellen — wird <code>mc_props_bag.slimCombo</code> als <strong>Array zusaetzlicher Spaltennamen</strong> verwendet, die im SELECT des Combos eingeschlossen werden:</p><p>Snippet 2:</p><ul><li>Array: die Namen werden der <code>columnRestrictionList</code> der Combo-Response hinzugefuegt (Logik in <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: deaktiviert die Restriktion → der Combo gibt <strong>alle</strong> Spalten zurueck. Nuetzlich beim Debugging, groesserer Payload.</li><li>Hat die Spalte <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> gesetzt (berechnete Display-Formel), wird <code>slimCombo</code> ignoriert und der Combo gibt alle Spalten zurueck (die Formel kann beliebige Felder referenzieren).</li></ul><p>Die Zusatzfelder landen in <code>record['<col>__lookup_obj'].value</code> (vollstaendiger Datensatz aus der verknuepften Tabelle, bereits mit den in <code>slimCombo</code> deklarierten Feldern) und sind aus dem Callback <code>mc_selection_changed_custom_function__fn</code> zugaenglich:</p><p>Snippet 3:</p><p>Tradeoff: Combo-Payload etwas groesser, aber kein zusaetzlicher HTTP-Roundtrip (<code>getFlatRecordData</code> zum Abrufen des vollstaendigen Datensatzes per id) bei jeder Auswahl.</p><h2>Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie</h2><ul><li>Operativer Inline-Flow: <code>suggestLookup2 -> suggestLookupDefaultFilter -> getLookupListByRoute -> getSeletClauseByLookupHierarchy</code>.</li><li>Inline-Snippet Hauptfelder: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline-Snippet Schnellfilter: <code>lookup.filter: {"logic":"AND","filters":[{"field":"is_active","operatore":"eq","value":true}]}</code>.</li><li>Client-Effekt: beschleunigtes Lookup-Setup im Metadata-Editor und konsistenter, sofort einsatzbereiter Filter im Widget.</li><li>Server-Effekt: Lookup-Abfrage und Select-Clause bleiben mit den <code>MetaService.*</code>-Endpoints abgeglichen, ohne manuelles Mapping.</li></ul><p>Schnelle operative Checkliste:</p><p>1. Basiskonfiguration mit <code>suggestLookup2</code> klonen;</p><p>2. Standard-Filter mit <code>suggestLookupDefaultFilter</code> generieren;</p><p>3. Hierarchie/Select-Clause vom Lookup-Tree verfeinern;</p><p>4. Zur Laufzeit validieren, dass Suche und Value/Text-Mapping konsistent sind.</p><h2>Client-/Server-Effekte</h2><ul><li>Client: Rendering des Lookup-Widgets, Suche, Label-/Value-Binding.</li><li>Client: bei aktiviertem <code>lookup.virtualize</code> rendert das Autocomplete nur das visuell sichtbare Subset der Optionen.</li><li>Server: paginierte/gefilterte Lookup-Abfrage ueber <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Betriebshinweise</h2><ul><li>Bei <code>multiple_check</code> bleibt der Lookup-Teil identisch, nur der Multi-Value-Renderer aendert sich.</li><li>Wenn <code>mc_ui_lookup_entity_name</code> fehlt, kann das Widget die Quelle nicht aufloesen.</li></ul><h2>Auto-Fetch bei programmatischen FK-Aenderungen</h2><p>Der <code>wuic-lookup-editor</code> abonniert automatisch das <code>BehaviorSubject</code> von <code>record[mc_nome_colonna]</code> und behandelt programmatische FK-Aenderungen (z.B. Import aus Quelldokument, Custom-Callback der <code>record.cliente_id.next(99)</code> aufruft):</p><ul><li>Wenn der neue Wert <strong>bereits in `items`</strong> vorhanden ist (geladene Lookup-Seite): nur <code>__lookup_obj</code> wird zur Konsistenz aktualisiert, kein HTTP-Aufruf.</li><li>Wenn der neue Wert <strong>nicht in items</strong> ist (z.B. Wert ausserhalb der aktuellen Combo-Seite oder noch nicht abgerufen): ein <strong>Second-Stage-Fetch</strong> mit Filter <code>WHERE <valueField> eqor <newValue></code> wird ausgeloest, um den verknuepften Datensatz abzurufen und das Dropdown automatisch zu fuellen.</li></ul><p>Skip-Faelle (no-op):</p><ul><li>Multi-Value-Lookup (<code>isFilter</code> mit <code>eqor</code>, <code>multiple_check</code>)</li><li>Erstes Emit des BS (bereits durch den Init-Flow abgedeckt)</li><li>null/undefined-Wert (Benutzer hat geleert)</li><li><code>hasActiveLookupQuery=true</code> (Benutzer tippt eine manuelle Abfrage)</li></ul><p><strong>Implikation fuer Custom-Forms</strong>: nur den FK zu setzen genuegt — manuelle Workarounds zum Befuellen von <code>__lookup_obj</code> oder dem Joined-Alias sind nicht mehr noetig. Die Dropdown-Anzeige aktualisiert sich von selbst.</p><p>Snippet 4:</p><p>> <strong>Hinweis</strong>: der Auto-Fetch wird nur ausgeloest, wenn der <code><wuic-lookup-editor></code> <strong>montiert</strong> ist (Zelle im Edit-Modus). Fuer die <strong>Listenanzeige</strong> (read-only Zelle) liest <code>formatGridViewValue</code> den Joined-Alias <code><route>___<textField>__<col></code> direkt aus dem Record — dieser muss weiterhin manuell befuellt werden, wenn er nicht vom Backend kommt.</p>",
|
|
22839
23800
|
"codeSamples": [
|
|
22840
23801
|
{
|
|
22841
23802
|
"id": "code_1",
|
|
22842
23803
|
"title": "Snippet 1",
|
|
22843
23804
|
"language": "json",
|
|
22844
23805
|
"code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
|
|
23806
|
+
},
|
|
23807
|
+
{
|
|
23808
|
+
"id": "code_2",
|
|
23809
|
+
"title": "Snippet 2",
|
|
23810
|
+
"language": "json",
|
|
23811
|
+
"code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
|
|
23812
|
+
},
|
|
23813
|
+
{
|
|
23814
|
+
"id": "code_3",
|
|
23815
|
+
"title": "Snippet 3",
|
|
23816
|
+
"language": "ts",
|
|
23817
|
+
"code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
|
|
23818
|
+
},
|
|
23819
|
+
{
|
|
23820
|
+
"id": "code_4",
|
|
23821
|
+
"title": "Snippet 4",
|
|
23822
|
+
"language": "ts",
|
|
23823
|
+
"code": "// FRUEHER (manueller Workaround erforderlich)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// JETZT (Auto-Fetch durch lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor montiert? Subscribe erkennt Change → gefilterter Fetch → __lookup_obj befuellt → Dropdown zeigt \"Acme S.r.l.\""
|
|
22845
23824
|
}
|
|
22846
23825
|
]
|
|
22847
23826
|
}
|
|
@@ -23104,10 +24083,14 @@ const frameworkDocsContent = {
|
|
|
23104
24083
|
},
|
|
23105
24084
|
{
|
|
23106
24085
|
"id": "sec_4",
|
|
23107
|
-
"title": "
|
|
24086
|
+
"title": "Kontextmenue der Elemente"
|
|
23108
24087
|
},
|
|
23109
24088
|
{
|
|
23110
24089
|
"id": "sec_5",
|
|
24090
|
+
"title": "Empfohlener operativer Ablauf"
|
|
24091
|
+
},
|
|
24092
|
+
{
|
|
24093
|
+
"id": "sec_6",
|
|
23111
24094
|
"title": "Betriebshinweise"
|
|
23112
24095
|
}
|
|
23113
24096
|
],
|
|
@@ -23115,7 +24098,7 @@ const frameworkDocsContent = {
|
|
|
23115
24098
|
{
|
|
23116
24099
|
"id": "overview",
|
|
23117
24100
|
"title": "Overview",
|
|
23118
|
-
"html": "<h1>Designer</h1><p>Visueller Editor fuer metadata-gesteuerte Dashboards auf Route <code>#/cities/dashboard</code>.</p><h2>Geltungsbereich</h2><ul><li>Board-Komposition via Drag-and-Drop von Knoten und UI-Bloecken.</li><li>Layout und Styling gesteuert durch <code>boardcontent</code> und Tabellen-/Spalten-Metadata.</li><li>Property-Panel-Editing ohne Hardcoding in den Runtime-Komponenten.</li></ul><h2>Designer-Palette</h2><ul><li><code>Datasource</code>: Metadata-Datenquelle (<code>route</code>, Filter, Sortierung, Paging).</li><li><code>DataRepeater</code>: Listen-/Karten-Visualisierung verbunden mit Datasource.</li><li><code>Dynamic Template</code>: dynamisches Template-Rendering mit metadata-gesteuerten Inputs.</li><li><code>Action/Button</code>: Benutzeraktionen verknuepft mit Datensaetzen und Callbacks.</li><li><code>Container/Layout</code>: strukturelle Bloecke fuer Spalten, Zeilen und Wrapper.</li></ul><h2>Hauptfunktionen</h2><ul><li>Drag-and-Drop von Knoten auf die Canvas mit Persistierung im <code>boardcontent</code>.</li><li>Kontextbezogenes Property-Panel mit zu den ausgewaehlten Komponenten konsistenten Inputs.</li><li>Binding zwischen Knoten (<code>datasource -> repeater/template</code>) mit Minimalvalidierung.</li><li>Integrierte Runtime-Vorschau und Layout-Neuausrichtung.</li><li>Verwaltung ausstehender Aenderungen mit Guard vor dem Verlassen.</li></ul><h2>Empfohlener operativer Ablauf</h2><ul><li>Ziel-Dashboard-Route oeffnen.</li><li>Knoten aus der Palette einfuegen/verbinden.</li><li>Wesentliche <code>inputs</code> konfigurieren (z.B. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Speichern und Ergebnis in der Runtime validieren.</li></ul><h2>Betriebshinweise</h2><ul><li>Fuer dashboard-spezifische Fixes <code>boardcontent</code>-Patches gegenueber globalen Aenderungen bevorzugen.</li><li>Doppelte Eigenschaften zwischen <code>inputs</code> und inkonsistentem Inline-Stil vermeiden.</li><li>Immer ueberpruefen, dass Datasources eine gesetzte <code>inputs.route</code> haben.</li></ul>",
|
|
24101
|
+
"html": "<h1>Designer</h1><p>Visueller Editor fuer metadata-gesteuerte Dashboards auf Route <code>#/cities/dashboard</code>.</p><h2>Geltungsbereich</h2><ul><li>Board-Komposition via Drag-and-Drop von Knoten und UI-Bloecken.</li><li>Layout und Styling gesteuert durch <code>boardcontent</code> und Tabellen-/Spalten-Metadata.</li><li>Property-Panel-Editing ohne Hardcoding in den Runtime-Komponenten.</li></ul><h2>Designer-Palette</h2><ul><li><code>Datasource</code>: Metadata-Datenquelle (<code>route</code>, Filter, Sortierung, Paging).</li><li><code>DataRepeater</code>: Listen-/Karten-Visualisierung verbunden mit Datasource.</li><li><code>Dynamic Template</code>: dynamisches Template-Rendering mit metadata-gesteuerten Inputs.</li><li><code>Action/Button</code>: Benutzeraktionen verknuepft mit Datensaetzen und Callbacks.</li><li><code>Container/Layout</code>: strukturelle Bloecke fuer Spalten, Zeilen und Wrapper.</li></ul><h2>Hauptfunktionen</h2><ul><li>Drag-and-Drop von Knoten auf die Canvas mit Persistierung im <code>boardcontent</code>.</li><li>Kontextbezogenes Property-Panel mit zu den ausgewaehlten Komponenten konsistenten Inputs.</li><li>Binding zwischen Knoten (<code>datasource -> repeater/template</code>) mit Minimalvalidierung.</li><li>Integrierte Runtime-Vorschau und Layout-Neuausrichtung.</li><li>Verwaltung ausstehender Aenderungen mit Guard vor dem Verlassen.</li></ul><h2>Kontextmenue der Elemente</h2><p>Rechtsklick auf ein Element der Canvas, auf einen Eintrag der unteren Breadcrumb oder auf einen Knoten im Hierarchiebaum zeigt ein Kontextmenue mit drei Eintraegen:</p><ul><li><strong>Eigenschaften bearbeiten</strong> (pi-cog) - waehlt das Element als "current tool" aus und zeigt sein Property-Panel rechts; entspricht dem Klick im Baum. Nuetzlich, um Eigenschaften verschachtelter Elemente zu bearbeiten, ohne sie in der Hierarchie suchen zu muessen.</li><li><strong>Umbenennen</strong> (nur Breadcrumb) - oeffnet einen Dialog zum Umbenennen des <code>displayName</code>.</li><li><strong>Entfernen</strong> - loescht das Element vom Board.</li></ul><h2>Empfohlener operativer Ablauf</h2><ul><li>Ziel-Dashboard-Route oeffnen.</li><li>Knoten aus der Palette einfuegen/verbinden.</li><li>Wesentliche <code>inputs</code> konfigurieren (z.B. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Speichern und Ergebnis in der Runtime validieren.</li></ul><h2>Betriebshinweise</h2><ul><li>Fuer dashboard-spezifische Fixes <code>boardcontent</code>-Patches gegenueber globalen Aenderungen bevorzugen.</li><li>Doppelte Eigenschaften zwischen <code>inputs</code> und inkonsistentem Inline-Stil vermeiden.</li><li>Immer ueberpruefen, dass Datasources eine gesetzte <code>inputs.route</code> haben.</li></ul>",
|
|
23119
24102
|
"codeSamples": []
|
|
23120
24103
|
}
|
|
23121
24104
|
],
|
|
@@ -23179,14 +24162,18 @@ const frameworkDocsContent = {
|
|
|
23179
24162
|
},
|
|
23180
24163
|
{
|
|
23181
24164
|
"id": "sec_3",
|
|
23182
|
-
"title": "
|
|
24165
|
+
"title": "View-Builder-Tab — Aggregationen und GROUP BY (Tier 1, 2026-05-08)"
|
|
23183
24166
|
},
|
|
23184
24167
|
{
|
|
23185
24168
|
"id": "sec_4",
|
|
23186
|
-
"title": "
|
|
24169
|
+
"title": "Empfohlener operativer Ablauf"
|
|
23187
24170
|
},
|
|
23188
24171
|
{
|
|
23189
24172
|
"id": "sec_5",
|
|
24173
|
+
"title": "Aktionsbuttons"
|
|
24174
|
+
},
|
|
24175
|
+
{
|
|
24176
|
+
"id": "sec_6",
|
|
23190
24177
|
"title": "Nutzungshinweise"
|
|
23191
24178
|
}
|
|
23192
24179
|
],
|
|
@@ -23194,7 +24181,7 @@ const frameworkDocsContent = {
|
|
|
23194
24181
|
{
|
|
23195
24182
|
"id": "overview",
|
|
23196
24183
|
"title": "Overview",
|
|
23197
|
-
"html": "<h1>Ansichts-/Pivot-Builder</h1><p>Der Pivot Builder ermöglicht die Erstellung dynamischer analytischer Ansichten ausgehend von Metadata-Routen, ohne manuelle Abfragen auf UI-Seite schreiben zu müssen.</p><h2>Screenshot-Referenz</h2><ul><li><code>manual__pivot_builder__01.png</code>: Konfiguration und Ergebnis des Builders zur Laufzeit.</li><li><code>manual__pivot_builder__02.png</code>: Detail des Dialogs zur Erstellung materialisierter Tabellen und operative Optionen.</li></ul><h2>Hauptfunktionen</h2><ul><li>Feldauswahl in den Bereichen <code>rows</code>, <code>columns</code> und <code>values</code>.</li><li>Verfügbare Aggregationen auf Werten (Summe, Anzahl, Durchschnitt, Minimum, Maximum).</li><li>Mehrdimensionale Gruppierung mit Sortierung nach Dimension.</li><li>Interaktive Filter zur Eingrenzung des analysierten Datasets.</li><li>Echtzeit-Aktualisierung der Pivot-Ansicht nach Layout- oder Metrik-Änderung.</li><li>Export/Wiederverwendung der Pivot-Konfiguration im operativen Kontext.</li></ul><h2>Empfohlener operativer Ablauf</h2><p>1. Pivot-Builder-Route öffnen.</p><p>2. Ausgangs-Datasource/Metadata-Route auswählen.</p><p>3. Felder in die Zonen <code>rows</code>, <code>columns</code>, <code>values</code> ziehen.</p><p>4. Aggregation für jede Metrik konfigurieren.</p><p>5. Filter anwenden und Gesamtsummen überprüfen.</p><p>6. Konfiguration gemäß dem Anwendungsablauf speichern oder wiederverwenden.</p><h2>Aktionsbuttons</h2><p><code>Ansicht erstellen</code></p><ul><li>Generiert eine SQL-View aus der aktuellen Pivot-Konfiguration.</li><li>Nützlich, wenn das Ergebnis als stabile Datenquelle in anderen Routen/Reports wiederverwendet werden soll.</li><li>Hält die Logik zentral in der DB, ohne das Layout jedes Mal manuell neu aufbauen zu müssen.</li></ul><p><code>Materialisierte Tabelle erstellen</code></p><ul><li>Erstellt eine physische Tabelle mit den aggregierten Daten der Pivot-Konfiguration.</li><li>Nützlich für große Datasets oder Dashboards, die schnelle Lesezeiten erfordern.</li><li>Scaffoldet automatisch die der erstellten physischen Tabelle zugeordnete Route und generiert eine Schedulierung für Daten-Refresh/Abgleich.</li><li>Durch Drücken von Aktualisieren im Grid der zugehörigen Route wird ein Lauf der Schedulierung erzwungen.</li></ul><h2>Nutzungshinweise</h2><ul><li>Für konsistente Analysen Spalten mit korrektem Datentyp verwenden (numerisch für Metriken, Text/Datum für Dimensionen).</li><li>Bei großen Datenmengen gezielte Filter und Aggregationen bevorzugen, bevor das Detail erweitert wird.</li></ul>",
|
|
24184
|
+
"html": "<h1>Ansichts-/Pivot-Builder</h1><p>Der Pivot Builder ermöglicht die Erstellung dynamischer analytischer Ansichten ausgehend von Metadata-Routen, ohne manuelle Abfragen auf UI-Seite schreiben zu müssen.</p><h2>Screenshot-Referenz</h2><ul><li><code>manual__pivot_builder__01.png</code>: Konfiguration und Ergebnis des Builders zur Laufzeit.</li><li><code>manual__pivot_builder__02.png</code>: Detail des Dialogs zur Erstellung materialisierter Tabellen und operative Optionen.</li></ul><h2>Hauptfunktionen</h2><ul><li>Feldauswahl in den Bereichen <code>rows</code>, <code>columns</code> und <code>values</code>.</li><li>Verfügbare Aggregationen auf Werten (Summe, Anzahl, Durchschnitt, Minimum, Maximum).</li><li>Mehrdimensionale Gruppierung mit Sortierung nach Dimension.</li><li>Interaktive Filter zur Eingrenzung des analysierten Datasets.</li><li>Echtzeit-Aktualisierung der Pivot-Ansicht nach Layout- oder Metrik-Änderung.</li><li>Export/Wiederverwendung der Pivot-Konfiguration im operativen Kontext.</li></ul><h2>View-Builder-Tab — Aggregationen und GROUP BY (Tier 1, 2026-05-08)</h2><p>Tab 1 "View Builder" erlaubt nun die direkte Ausgabe von <code>GROUP BY</code> mit <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code>-Aggregationen aus der <code>ViewDefinition</code>, ohne in den manualSql-Modus zu wechseln.</p><p>Panel unter dem Canvas:</p><ul><li><strong>Aggregationen</strong>: Formular zum Hinzufügen von <code>Funktion(Spalte) AS Alias</code>. Die Quellspalte kann jede Spalte auf der Canvas sein oder <code>*</code> (für <code>COUNT(*)</code>); der Output-Alias wird automatisch erzeugt, wenn leer.</li><li><strong>GROUP BY</strong>: Checkbox pro selektierter Spalte auf den Canvas-Knoten. Ist keine explizit angekreuzt, leitet das Backend die Dimensionen aus den <code>selected=true</code> nicht-aggregierten Spalten ab.</li><li><strong>CASE WHEN für Pivoting</strong>: Für Muster wie <code>SUM(CASE WHEN bucket='X' THEN betrag ELSE 0 END)</code> verwende eine Formel-Spalte (Rechtsklick → Formel bearbeiten) als Aggregationsquelle. Die Formel wird als Aggregat-Argument ausgegeben.</li></ul><p>Backend-Ausgabe (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → gibt <code><FN>(<Spalte|Formel>) AS <Alias></code> als zusätzliche SELECT-Spalten aus</li><li><code>viewDefinition.groupByColumns[]</code> → gibt die <code>GROUP BY</code>-Klausel aus</li><li>Automatische GROUP BY-Inferenz bei vorhandenen <code>aggregations</code> ohne explizite <code>groupByColumns</code></li></ul><h3>UNION-Blöcke (Tier 2, 2026-05-08)</h3><p>Eine spätere Erweiterung erlaubt das Verketten mehrerer SELECTs mit <code>UNION</code> oder <code>UNION ALL</code> direkt aus einer einzigen <code>ViewDefinition</code>:</p><ul><li><strong>Block 0</strong> = Top-Level (Canvas-Tabellen/Joins/Aggregationen) → definiert das <strong>kanonische Vokabular</strong> der SELECT-Spalten</li><li><strong>Block N (N≥1)</strong> = jeder zusätzliche <code>unionBlocks[]</code> ist eine Single-Table-Sub-Definition mit einem erforderlichen <code>columnMapping</code>: für jeden kanonischen Alias von Block 0 gibt Block N <code>sourceQualified</code> (Spalte aus dem Block) <strong>oder</strong> <code>constantValue</code> (SQL-Literal wie <code>'KUNDE'</code>) <strong>oder</strong> <code>formula</code> (SQL-Ausdruck für Cross-Block-Cast) an</li><li><strong>UNION ALL / UNION-Umschaltung</strong>: Standard <code>unionAll: true</code> (kein Dedup, bessere Performance); <code>false</code> für <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: wird auf die gesamte UNION angewandt, umschlossen in der Subquery <code>SELECT TOP N * FROM (<union>) AS u</code></li></ul><p>UI unterhalb des Canvas: Route-Auswahl + Button "UNION-Block hinzufügen" → jeder Block erscheint als Akkordeon mit Header (Caption + Route) + Spaltenzuordnungs-Matrix (pro kanonischem Alias: Dropdown der Block-Quellspalte oder konstanter SQL-Eingabe).</p><p>v0-Einschränkungen: jeder UNION-Block ist Single-Table (kein JOIN innerhalb des Blocks). Für Cross-Block-JOIN (z.B. Kategorie 'BEIDES' von <code>vw_anagrafica_unificata</code>, die <code>kunden INNER JOIN lieferanten ON ust_id</code> erfordert) wird Tier 2.5 benötigt.</p><h2>Empfohlener operativer Ablauf</h2><p>1. Pivot-Builder-Route öffnen.</p><p>2. Ausgangs-Datasource/Metadata-Route auswählen.</p><p>3. Felder in die Zonen <code>rows</code>, <code>columns</code>, <code>values</code> ziehen.</p><p>4. Aggregation für jede Metrik konfigurieren.</p><p>5. Filter anwenden und Gesamtsummen überprüfen.</p><p>6. Konfiguration gemäß dem Anwendungsablauf speichern oder wiederverwenden.</p><h2>Aktionsbuttons</h2><p><code>Ansicht erstellen</code></p><ul><li>Generiert eine SQL-View aus der aktuellen Pivot-Konfiguration.</li><li>Nützlich, wenn das Ergebnis als stabile Datenquelle in anderen Routen/Reports wiederverwendet werden soll.</li><li>Hält die Logik zentral in der DB, ohne das Layout jedes Mal manuell neu aufbauen zu müssen.</li></ul><p><code>Materialisierte Tabelle erstellen</code></p><ul><li>Erstellt eine physische Tabelle mit den aggregierten Daten der Pivot-Konfiguration.</li><li>Nützlich für große Datasets oder Dashboards, die schnelle Lesezeiten erfordern.</li><li>Scaffoldet automatisch die der erstellten physischen Tabelle zugeordnete Route und generiert eine Schedulierung für Daten-Refresh/Abgleich.</li><li>Durch Drücken von Aktualisieren im Grid der zugehörigen Route wird ein Lauf der Schedulierung erzwungen.</li></ul><h2>Nutzungshinweise</h2><ul><li>Für konsistente Analysen Spalten mit korrektem Datentyp verwenden (numerisch für Metriken, Text/Datum für Dimensionen).</li><li>Bei großen Datenmengen gezielte Filter und Aggregationen bevorzugen, bevor das Detail erweitert wird.</li></ul>",
|
|
23198
24185
|
"codeSamples": []
|
|
23199
24186
|
}
|
|
23200
24187
|
],
|
|
@@ -24149,7 +25136,7 @@ const frameworkDocsContent = {
|
|
|
24149
25136
|
{
|
|
24150
25137
|
"id": "overview",
|
|
24151
25138
|
"title": "Overview",
|
|
24152
|
-
"html": "<h1>Component Catalog</h1><p>Total component folders detected:
|
|
25139
|
+
"html": "<h1>Component Catalog</h1><p>Total component folders detected: 50.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>azienda-switcher</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>azienda-switcher/azienda-switcher.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
|
|
24153
25140
|
"codeSamples": []
|
|
24154
25141
|
}
|
|
24155
25142
|
],
|
|
@@ -24270,13 +25257,21 @@ const frameworkDocsContent = {
|
|
|
24270
25257
|
{
|
|
24271
25258
|
"id": "sec_3",
|
|
24272
25259
|
"title": "Beispiele"
|
|
25260
|
+
},
|
|
25261
|
+
{
|
|
25262
|
+
"id": "sec_4",
|
|
25263
|
+
"title": "Runtime-Patches von `metaInfo` via `fetchInfo$.subscribe`"
|
|
25264
|
+
},
|
|
25265
|
+
{
|
|
25266
|
+
"id": "sec_5",
|
|
25267
|
+
"title": "Mehrere `<wuic-data-source>` in derselben Komponente"
|
|
24273
25268
|
}
|
|
24274
25269
|
],
|
|
24275
25270
|
"sections": [
|
|
24276
25271
|
{
|
|
24277
25272
|
"id": "overview",
|
|
24278
25273
|
"title": "Overview",
|
|
24279
|
-
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> ist die Komponente, die Metadata, Datenabruf, aktuellen Zustand, Änderungsverfolgung und CRUD-Synchronisation verwaltet.</p><h2>Beschreibung</h2><ul><li>Löst die Route auf (<code>route</code>/<code>hardcodedRoute</code> oder URL-Routing).</li><li>Lädt Tabellen-/Spalten-Metadata und veröffentlicht den Zustand über <code>fetchInfo$</code>.</li><li>Verwaltet <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, Paging (<code>pageSize</code>, <code>currentPage</code>) und Cursor-Paging.</li><li>Stellt Runtime-Methoden für Insert/Update/Delete, Batch-Save, Rollback und Export bereit.</li><li>Ist der zentrale Punkt, an den sich <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> und <code>wuic-pager</code> anhängen.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Haupt-Inputs:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Haupt-Zustand/Runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (abbruchfähig)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Meistverwendete Runtime-Methoden:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/Runtime-Events:</p><ul><li><code>fetchInfo$</code>: Haupt-Stream für Daten-/Metadata-Zustand.</li><li><code>datasourceReady$</code>: wird emittiert, wenn die Datasource-Instanz bereit ist.</li><li><code>afterFirstLoad$</code>: wird nur einmal beim ersten nützlichen Payload emittiert.</li><li><code>beforeSync$</code>: wird vor <code>insert/update/delete/clone/batch</code> emittiert; kann mit <code>event.cancelSync(...)</code> abgebrochen werden.</li><li><code>afterSync$</code>: wird nach erfolgreicher Synchronisation emittiert.</li></ul><p>Kompatibilitätshinweis:</p><ul><li>Der Alias <code>fetchInfo</code> (Getter/Setter) ist ebenfalls verfügbar, aber die empfohlene Benennung ist <code>fetchInfo$</code>.</li></ul><h2>Beispiele</h2><h3>Einfaches Beispiel (hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail-Beispiel (verschachtelter Datasource)</h3><p>Snippet 2:</p><h3>Operatives Beispiel (manueller Fetch + Sync)</h3><p>Snippet 3:</p><h3>Beispiel Subscriptions (Host)</h3><p>Snippet 4:</p>",
|
|
25274
|
+
"html": "<h1>Datasource</h1><p><code>wuic-data-source</code> ist die Komponente, die Metadata, Datenabruf, aktuellen Zustand, Änderungsverfolgung und CRUD-Synchronisation verwaltet.</p><h2>Beschreibung</h2><ul><li>Löst die Route auf (<code>route</code>/<code>hardcodedRoute</code> oder URL-Routing).</li><li>Lädt Tabellen-/Spalten-Metadata und veröffentlicht den Zustand über <code>fetchInfo$</code>.</li><li>Verwaltet <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, Paging (<code>pageSize</code>, <code>currentPage</code>) und Cursor-Paging.</li><li>Stellt Runtime-Methoden für Insert/Update/Delete, Batch-Save, Rollback und Export bereit.</li><li>Ist der zentrale Punkt, an den sich <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> und <code>wuic-pager</code> anhängen.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Haupt-Inputs:</p><ul><li><code>route: BehaviorSubject<string></code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject<boolean></code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject<{ component, id, name, uniqueName }></code></li></ul><p>Haupt-Zustand/Runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject<{ resultInfo, metaInfo, filterDescriptor }></code></li><li><code>datasourceReady$: BehaviorSubject<DataSourceComponent | null></code></li><li><code>afterFirstLoad$: BehaviorSubject<any></code></li><li><code>beforeSync$: Subject<DataSourceBeforeSyncEvent></code> (abbruchfähig)</li><li><code>afterSync$: Subject<DataSourceAfterSyncEvent></code></li></ul><p>Meistverwendete Runtime-Methoden:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/Runtime-Events:</p><ul><li><code>fetchInfo$</code>: Haupt-Stream für Daten-/Metadata-Zustand.</li><li><code>datasourceReady$</code>: wird emittiert, wenn die Datasource-Instanz bereit ist.</li><li><code>afterFirstLoad$</code>: wird nur einmal beim ersten nützlichen Payload emittiert.</li><li><code>beforeSync$</code>: wird vor <code>insert/update/delete/clone/batch</code> emittiert; kann mit <code>event.cancelSync(...)</code> abgebrochen werden.</li><li><code>afterSync$</code>: wird nach erfolgreicher Synchronisation emittiert.</li></ul><p>Kompatibilitätshinweis:</p><ul><li>Der Alias <code>fetchInfo</code> (Getter/Setter) ist ebenfalls verfügbar, aber die empfohlene Benennung ist <code>fetchInfo$</code>.</li></ul><h2>Beispiele</h2><h3>Einfaches Beispiel (hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail-Beispiel (verschachtelter Datasource)</h3><p>Snippet 2:</p><h3>Operatives Beispiel (manueller Fetch + Sync)</h3><p>Snippet 3:</p><h3>Beispiel Subscriptions (Host)</h3><p>Snippet 4:</p><h2>Runtime-Patches von <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Um Metadaten eines nested Data-Sources zur Laufzeit zu modifizieren (z.B. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>ohne die DB anzufassen</strong> — lokaler Patch auf der vom <code>MetadataProviderService</code> gecachten Referenz, latest-wins, Single-Dialog-Szenario — abonniere den <code>fetchInfo$</code> des Data-Sources:</p><p>Snippet 5:</p><p><strong>Scope</strong>: session-only + instance-only. Der Patch ueberlebt nur bis der <code>MetadataProviderService</code>-Cache invalidiert wird oder die Komponente zerstoert wird. Nuetzlich, wenn ein Custom-Form eine andere Darstellung eines nested Grids gegenueber dem Standalone-Rendering haben moechte.</p><p><strong>Wann KEINE Runtime-Patches verwenden</strong>: fuer Aenderungen, die alle Instanzen der Route betreffen (auch standalone <code><route>/list</code>) → direkter SQL-Patch auf den Metadaten; fuer Aenderungen, die nach einem Restart bestehen bleiben → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Mehrere <code><wuic-data-source></code> in derselben Komponente</h2><p>Wenn eine Custom-Komponente mehr als einen <code><wuic-data-source></code> enthaelt (z.B. Master + nested Rows + nested Faelligkeiten), matcht das Pattern <code>@ViewChild(DataSourceComponent)</code> by-type <strong>nur den ersten</strong> Data-Source in der Top-Down-Reihenfolge des Templates. Fuer die folgenden wird eine <strong>Template-Reference-Variable</strong> + <code>@ViewChild('refName')</code> benoetigt:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong>: <code>document.querySelectorAll('wuic-data-source')</code> + <code>window.ng.getComponent(el)</code> zum Filtern nach <code>hardcodedRoute</code>. Funktioniert, ist aber fragil, da es von der <code>window.ng</code>-Debug-API abhaengt (in Production nicht garantiert). Immer Template-Refs verwenden.</p>",
|
|
24280
25275
|
"codeSamples": [
|
|
24281
25276
|
{
|
|
24282
25277
|
"id": "code_1",
|
|
@@ -24301,6 +25296,24 @@ const frameworkDocsContent = {
|
|
|
24301
25296
|
"title": "Snippet 4",
|
|
24302
25297
|
"language": "ts",
|
|
24303
25298
|
"code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Löschen vom Host-Kontext nicht erlaubt');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
|
|
25299
|
+
},
|
|
25300
|
+
{
|
|
25301
|
+
"id": "code_5",
|
|
25302
|
+
"title": "Snippet 5",
|
|
25303
|
+
"language": "ts",
|
|
25304
|
+
"code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup in ngOnDestroy\n}"
|
|
25305
|
+
},
|
|
25306
|
+
{
|
|
25307
|
+
"id": "code_6",
|
|
25308
|
+
"title": "Snippet 6",
|
|
25309
|
+
"language": "html",
|
|
25310
|
+
"code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
|
|
25311
|
+
},
|
|
25312
|
+
{
|
|
25313
|
+
"id": "code_7",
|
|
25314
|
+
"title": "Snippet 7",
|
|
25315
|
+
"language": "ts",
|
|
25316
|
+
"code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Erster Data-Source (Match by Type) -> keine Template-Ref noetig\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Zweiter Data-Source -> benoetigt `#scadenzeDs` im Template + ViewChild by-name\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
|
|
24304
25317
|
}
|
|
24305
25318
|
]
|
|
24306
25319
|
}
|
|
@@ -25870,6 +26883,82 @@ const frameworkDocsContent = {
|
|
|
25870
26883
|
],
|
|
25871
26884
|
"images": []
|
|
25872
26885
|
},
|
|
26886
|
+
{
|
|
26887
|
+
"slug": "multi-tenant",
|
|
26888
|
+
"lang": "de-DE",
|
|
26889
|
+
"title": "Mandantenfaehigkeit",
|
|
26890
|
+
"description": "Mandantenfaehigkeit - documentazione operativa e tecnica",
|
|
26891
|
+
"audience": [
|
|
26892
|
+
"developer",
|
|
26893
|
+
"integration"
|
|
26894
|
+
],
|
|
26895
|
+
"status": "stable",
|
|
26896
|
+
"tags": [
|
|
26897
|
+
"advanced"
|
|
26898
|
+
],
|
|
26899
|
+
"keywords": [
|
|
26900
|
+
"Mandantenfaehigkeit"
|
|
26901
|
+
],
|
|
26902
|
+
"toc": [
|
|
26903
|
+
{
|
|
26904
|
+
"id": "sec_1",
|
|
26905
|
+
"title": "Geltungsbereich"
|
|
26906
|
+
},
|
|
26907
|
+
{
|
|
26908
|
+
"id": "sec_2",
|
|
26909
|
+
"title": "Aktivierung"
|
|
26910
|
+
},
|
|
26911
|
+
{
|
|
26912
|
+
"id": "sec_3",
|
|
26913
|
+
"title": "Datenmodell"
|
|
26914
|
+
},
|
|
26915
|
+
{
|
|
26916
|
+
"id": "sec_4",
|
|
26917
|
+
"title": "Login-Fallback"
|
|
26918
|
+
},
|
|
26919
|
+
{
|
|
26920
|
+
"id": "sec_5",
|
|
26921
|
+
"title": "Mandantenwechsel (Superadmin)"
|
|
26922
|
+
},
|
|
26923
|
+
{
|
|
26924
|
+
"id": "sec_6",
|
|
26925
|
+
"title": "Tenant-scoped Caches"
|
|
26926
|
+
},
|
|
26927
|
+
{
|
|
26928
|
+
"id": "sec_7",
|
|
26929
|
+
"title": "Anti-Hijack"
|
|
26930
|
+
},
|
|
26931
|
+
{
|
|
26932
|
+
"id": "sec_8",
|
|
26933
|
+
"title": "Tenant-aware Scheduler"
|
|
26934
|
+
},
|
|
26935
|
+
{
|
|
26936
|
+
"id": "sec_9",
|
|
26937
|
+
"title": "Cross-Mandant Scaffolding-Propagation"
|
|
26938
|
+
},
|
|
26939
|
+
{
|
|
26940
|
+
"id": "sec_10",
|
|
26941
|
+
"title": "Nicht-Superadmin Benutzerkonten ueber mehrere Mandanten"
|
|
26942
|
+
},
|
|
26943
|
+
{
|
|
26944
|
+
"id": "sec_11",
|
|
26945
|
+
"title": "Bekannte Einschraenkungen"
|
|
26946
|
+
},
|
|
26947
|
+
{
|
|
26948
|
+
"id": "sec_12",
|
|
26949
|
+
"title": "Relevante AppSettings"
|
|
26950
|
+
}
|
|
26951
|
+
],
|
|
26952
|
+
"sections": [
|
|
26953
|
+
{
|
|
26954
|
+
"id": "overview",
|
|
26955
|
+
"title": "Overview",
|
|
26956
|
+
"html": "<h1>Multi-tenant</h1><p>Verbindungsrouting pro Benutzer: jeder Mandant verfuegt ueber eigene Connection Strings zu getrennten Metadaten- und Datenbanken, bei voller Rueckwaertskompatibilitaet fuer bestehende Single-Tenant-Installationen.</p><h2>Geltungsbereich</h2><p>Das Multi-Tenant-Modul ermoeglicht es einem einzigen Backend, mehrere Mandanten zu bedienen — jeder auf eigenen Metadaten- und Datenbanken — ohne Code zu duplizieren oder separate Instanzen zu starten. Wenn das Flag OFF ist, ist das Verhalten identisch mit dem pre-Multi-Tenant (kein Overhead, Caches unveraendert). Bei ON:</p><ul><li>ermittelt das Framework zur Laufzeit die korrekte Connection String basierend auf <code>id_azienda</code> des angemeldeten Benutzers.</li><li>trennt Server-Caches (<code>Application[]</code>) und Client-Caches (Dexie + localStorage) pro Mandant.</li><li>propagiert <code>TenantScope.Current</code> ueber alle Abfragen, Scheduler und Background-Jobs.</li></ul><h2>Aktivierung</h2><p>In <code>appsettings.json</code> (Abschnitt <code>AppSettings</code>):</p><ul><li><code>"multiConnectionEnabled": "true"</code> — aktiviert das tenant-aware Routing in <code>MultiTenantHelpers</code> + <code>TenantScope</code> Middleware in <code>Startup.cs</code>.</li><li><code>"enableCookieAuthentication": "true"</code> — vom Multi-Tenant-Flow benoetigt: der <code>k-user</code> v:2 Cookie traegt <code>azienda_id</code> + <code>azienda_id_user</code> fuer Anti-Hijack.</li></ul><p>Stand-alone Vorlagen <code>appsettings.multi-tenant.mssql.json</code> und <code>appsettings.multi-tenant.mysql.json</code> sind verfuegbar (6 Connection Strings: 2 Default + 4 Tenant <code>Tenant1_Meta</code>/<code>Tenant1_Data</code>/<code>Tenant2_Meta</code>/<code>Tenant2_Data</code>).</p><h2>Datenmodell</h2><p>Das Mapping <code>id_azienda → (data, meta)</code> liegt auf zwei Spalten der Tabelle <code>Aziende</code> der primaeren Metadatenbank:</p><ul><li><code>Aziende.Connessione_DB_Dati</code>: NAME eines Eintrags in <code>appsettings.ConnectionStrings</code> fuer die Daten-DB des Mandanten (NICHT der literale Connection String).</li><li><code>Aziende.CONNESSIONE_DB_Meta</code>: NAME eines Eintrags in <code>appsettings.ConnectionStrings</code> fuer die Metadatenbank des Mandanten.</li></ul><p>Beispielbefuellung:</p><ul><li><code>Aziende.ID_Azienda=1</code>, <code>Connessione_DB_Dati='Tenant1_Data'</code>, <code>CONNESSIONE_DB_Meta='Tenant1_Meta'</code>.</li><li><code>Aziende.ID_Azienda=100</code> (Sede principale), <code>Connessione_DB_Dati='DataSQLConnection'</code>, <code>CONNESSIONE_DB_Meta='MetaDataSQLConnection'</code> (Default-Aliase → durch Scope 0 abgedeckt).</li></ul><p>Indirektion via Eintragsname:</p><ul><li>keine Klartextpasswoerter in der DB.</li><li>portable Credentials pro Umgebung (gleiche DB, unterschiedliche <code>appsettings.<env>.json</code>).</li><li>konsistent mit <code>_metadati__tabelle.md_conn_name</code>.</li></ul><h2>Login-Fallback</h2><p>Wenn ein Benutzer auf der primaeren Metadatenbank NICHT existiert, faellt der Login ueber <code>_login_index</code> zurueck:</p><p>1. Benutzer-Lookup auf <code>MetaDataSQLConnection</code> (primaer) → gefunden? Login.</p><p>2. Miss → Abfrage <code>_login_index</code> (<code>SHA-256(LOWER(username))</code> → <code>id_azienda</code>).</p><p>3. fuer jedes Kandidat-<code>id_azienda</code>: Push <code>TenantScope</code>, Retry <code>metaQuery.login</code> auf der Metadatenbank des Mandanten.</p><p>4. Erster Match gewinnt → Cookie mit Mandanten-<code>azienda_id</code> ausgestellt.</p><p>Die Stored Procedure <code>sp__login_index_upsert(@username, @idAzienda)</code> befuellt den Index. SHA-256 Hash serverseitig berechnet (abgestimmt mit C#-Client <code>MultiTenantHelpers.ComputeUsernameHash</code> in UTF-8).</p><h2>Mandantenwechsel (Superadmin)</h2><p>Superadmins koennen den aktiven Mandanten zur Laufzeit ueber <code>MetaService.switchAzienda(idAzienda)</code> aendern:</p><ul><li>401 wenn kein Superadmin.</li><li>400 wenn <code>idAzienda</code> nicht beide Spalten <code>Aziende.Connessione_DB_Dati</code> / <code>CONNESSIONE_DB_Meta</code> befuellt hat.</li><li>emittiert Cookie mit aktualisierter <code>azienda_id</code> neu, unter Beibehaltung von <code>azienda_id_user</code> (Snapshot beim Login).</li><li>Angular-Komponente <code><wuic-azienda-switcher></code> exponiert das Dropdown-UI. Versteckt sich automatisch via <code>@HostBinding('attr.hidden')</code> fuer Nicht-Super-Benutzer oder Flag OFF.</li></ul><h2>Tenant-scoped Caches</h2><p>Serverseite (<code>Application[]</code> Keys):</p><ul><li><code>MultiTenantHelpers.TenantKey(baseKey)</code> haengt Suffix <code>__a<id></code> an, wenn Flag ON und <code>TenantScope.Current.AziendaId > 0</code>.</li><li>betroffene Keys: <code>userList</code>, <code>roleList</code>, <code>userRoleList</code>, <code>companyList</code>, <code>storedMeta</code>, <code>storedTableMeta</code>, <code>storedTableActionMeta</code>, <code>storedConditionGroupMeta</code>, <code>storedTableStyleMeta</code>, <code>SysInfo</code>.</li></ul><p>Clientseite (Angular Framework Lib):</p><ul><li>Dexie-Name → <code>MetaDB__a<id></code> und <code>WuicClientSideCrudDB__a<id></code>.</li><li>localStorage Keys → <code>menu_<userId>__a<id></code>, <code>wuic_custom_settings_<userId>__a<id></code>, usw.</li><li>Orphan Cleanup beim Init von <code>MetadataProviderService</code>: zaehlt <code>indexedDB.databases()</code> auf und loescht DBs von Mandanten, die nicht mehr aktuell sind.</li></ul><p>Bei Flag OFF: TenantKey gibt baseKey unveraendert zurueck → Caches identisch zum pre-Multi-Tenant.</p><h2>Anti-Hijack</h2><p><code>RawHelpers.authenticate()</code> bei jeder Anfrage:</p><ul><li>verlangt Cookie v:2 mit <code>azienda_id</code> + <code>azienda_id_user</code> (Legacy-Cookie → erzwungenes Relogin).</li><li>fuer Nicht-Super-Benutzer MUSS <code>cookie.azienda_id</code> mit <code>cookie.azienda_id_user</code> uebereinstimmen (Manipulation → Relogin + Security Log).</li><li>Superadmins sind ausgenommen (ihr <code>azienda_id</code> aendert sich legitim via <code>switchAzienda</code>).</li><li>befuellt <code>TenantScope.Current</code> mit <code>(azienda_id, isSuperAdmin)</code> fuer den Rest des Requests.</li></ul><p>Die Middleware <code>HydrateLegacyPrincipalFromKUserCookie</code> in <code>Startup.cs</code> befuellt <code>TenantScope</code> auch fuer AsmxProxy-Endpoints, die <code>authenticate()</code> nicht explizit aufrufen (z.B. <code>readCustomSettings</code>, <code>getMenuByUserID</code>).</p><h2>Tenant-aware Scheduler</h2><p><code>SchedulerHostedService</code> zaehlt die Mandanten in jedem Zyklus auf (<code>Scheduler:PollSeconds</code>, Default 15s):</p><ul><li>Scope 0 (primaer / no-tenant) — immer verarbeitet.</li><li>plus ein Scope pro Mandant mit Conn-Mapping ≠ Default.</li><li>Mandanten, die auf die Default-Eintraege (<code>MetaDataSQLConnection</code>) gemappt sind, werden uebersprungen (durch Scope 0 abgedeckt).</li></ul><p>Alle 4 Action-Types werden pro Mandant unterstuetzt:</p><ul><li><code>1 | sql</code>: Datenverbindung via tenant-aware Routing.</li><li><code>2 | webservice</code>: Benutzer <code>__scheduler</code> in jeder Mandanten-DB erstellt; Session-Cookie pro Mandant via <code>switchAzienda</code> HTTP-Aufruf.</li><li><code>3 | assembly method</code>: <code>TenantScope.Current</code> via AsyncLocal an die aufgerufene Methode propagiert.</li><li><code>4 | mailing</code>: <code>_mailing_lists</code> / <code>_mail_recipients</code> aus der aktuellen Mandanten-DB gelesen; SMTP-Einstellungen pro Mandant ueberschreibbar via Naming-Konvention <code><key>__a<id></code> (z.B. <code>email-host__a1</code>).</li></ul><p>Das Scheduler-Schema (<code>scheduler</code>, <code>scheduler_execution</code>) wird pro Mandant validiert: Mandanten ohne die Tabellen werden bis zum Restart stillschweigend uebersprungen.</p><h2>Cross-Mandant Scaffolding-Propagation</h2><p>Wenn ein Superadmin eine Tabelle oder View auf einem Mandanten scaffolded, werden die Metadaten (<code>_metadati__tabelle</code> + <code>_metadati__colonne</code>) standardmaessig <strong>nur in die Metadatenbank des aktuellen Mandanten</strong> geschrieben. Um das manuelle Wiederholen des Scaffoldings auf jedem Mandanten zu vermeiden, exponiert der Endpoint <code>scaffolding.scaffoldTable</code> / <code>scaffolding.scaffoldView</code> den booleschen Parameter <code>propagateToTenants</code>:</p><ul><li><code>false</code> (default): scaffolding nur auf dem aktuellen Mandanten (Rueckwaertskompatibilitaet).</li><li><code>true</code>: nach dem Scaffolding des aktuellen Mandanten iteriert das Backend ueber alle anderen aktiven Mandanten (<code>MultiTenantHelpers.GetAziendePrimary()</code>) und repliziert das Scaffolding auf der Metadatenbank jedes einzelnen. Das Schema wird aus der DATEN-DB des Ziel-Mandanten gelesen, aufgeloest via <code>Aziende.Connessione_DB_Dati</code>. Mandanten ohne die physische Tabelle werden mit Ergebnis <code>SKIPPED:not-found</code> uebersprungen → best-effort Operation, nicht cross-Mandant transaktional.</li></ul><p>Sicherheit:</p><ul><li>nur fuer Superadmin mit Flag <code>multiConnectionEnabled=true</code> — fuer andere wird der Parameter stillschweigend ignoriert.</li><li>Firmen, die auf die Default-Eintraege (<code>MetaDataSQLConnection</code>) gemappt sind, werden uebersprungen (durch Scope 0 des aktuellen Mandanten abgedeckt, wenn der Superadmin schon dort ist).</li></ul><p>Das Scaffold-Ergebnis enthaelt <code>tenant_<id>:</code> Keys mit Wert <code>OK</code> / <code>SKIPPED:<reason></code> / <code>FAILED:<message></code> zur Diagnose pro Mandant.</p><p>UI: der Scaffolding-Dialog (<code>md_id=1556</code>) zeigt eine Checkbox <strong>Propagate to all tenants</strong> im Formular; die Action <code>Scaffold Table</code> sendet das Flag an das Backend. Im Single-Tenant-Modus (<code>multiConnectionEnabled=false</code>) filtert das Backend die Spalte aus der <code>getTableMetadata</code>-Antwort → die Checkbox erscheint nicht im Formular (keine unnoetige UI).</p><h2>Nicht-Superadmin Benutzerkonten ueber mehrere Mandanten</h2><p>Der Selektor <code><wuic-azienda-switcher></code> ist <strong>nur fuer Superadmins</strong> sichtbar. Fuer Nicht-Superadmin-Benutzer verhindert die Anti-Hijack-Pruefung (<code>cookie.azienda_id != user.azienda_id_user → reject</code>) den Mandantenwechsel zur Laufzeit: der Cookie haelt <code>azienda_id_user</code> zum Login-Zeitpunkt als Snapshot und unveraenderbar.</p><p>Operative Konvention fuer Szenarien, in denen dieselbe Person mehrere Mandanten als Standardbenutzer erreichen muss:</p><ul><li><strong>einen separaten Benutzer pro Mandant</strong> anlegen, mit <strong>unterschiedlichem Benutzernamen</strong> (z.B. <code>mario.rossi@T1</code>, <code>mario.rossi@T2</code>, oder <code>mario.rossi.t1</code> / <code>mario.rossi.t2</code>).</li><li>jeder Benutzer lebt in der <code>utenti</code> Tabelle der eigenen Mandanten-DB, mit passender <code>id_azienda</code>.</li><li><code>_login_index</code> bildet jeden Benutzernamen auf seine <code>id_azienda</code> ab: keine Mehrdeutigkeit in der Fallback Chain.</li><li>der Benutzer waehlt den Mandanten durch Aenderung des Benutzernamens beim Login.</li></ul><p>Zu vermeidendes Anti-Pattern:</p><ul><li><strong>gleicher Benutzername auf zwei Mandanten</strong>: der Fallback Login probiert beide <code>_login_index</code> Kandidaten in aufsteigender Reihenfolge der <code>id_azienda</code>; der erste, der das Passwort validiert, gewinnt. Der Benutzer landet immer beim Mandanten mit der niedrigsten <code>id_azienda</code> und kann den anderen ohne Switcher-Widget nicht erreichen. Das Passwort bleibt cross-tenant geteilt und ist Brute-Force auf beiden DBs ausgesetzt.</li></ul><p>Ist die Person Superadmin, reicht ein einziger Benutzer auf der primaeren DB (<code>id_azienda</code> = ID der Mutterfirma): das Switcher-Widget deckt den Mandantenwechsel zur Laufzeit ab.</p><h2>Bekannte Einschraenkungen</h2><ul><li><strong>Einheitliches DBMS cross-Mandant</strong>: alle Mandanten MUESSEN dasselbe DBMS wie das Primaere verwenden (<code>AppSettings.dbms</code> / <code>meta-dbms</code>). Das tenant-aware Routing instradiert nur das <em>Catalog</em> (welche DB), nicht <em>welchen Provider</em>. Beispiel: MSSQL Backend + ein <code>Tenant2_Meta</code> mit MySQL Connection String → <code>SqlException</code> beim ersten Zugriff auf den Mandanten. Eine fail-soft Validierung (WARN Log) laeuft beim Start (<code>MultiTenantHelpers.ValidateTenantConnectionStrings</code>): fuer jede cs, die von <code>Aziende.{Connessione_DB_Dati,CONNESSIONE_DB_Meta}</code> referenziert wird, erkennt sie heuristisch das Ziel-DBMS (Keywords wie <code>Port=3306</code>, <code>Initial Catalog=</code>, <code>(HOST=...)</code>, usw.) und loggt eine Warnung, wenn es vom konfigurierten abweicht. Mismatch → Boot fahrt fort, aber der nicht-uebereinstimmende Mandant ist zur Laufzeit unbrauchbar.</li><li>der <code>__scheduler</code> Benutzer auf Mandanten-DBs hat <code>id_azienda=NULL</code> (nicht befuellt): angemessen fuer den Webservice-Flow (Login startet immer vom Primaeren), aber nicht via <code>_login_index</code> fuer umgekehrte Faelle aufloesbar.</li><li>jeder neue Mandant erfordert einen Eintrag in <code>appsettings.ConnectionStrings</code> + Redeploy/Config-Reload.</li><li>Scheduler-Jobs in Mandanten ohne <code>scheduler</code> Tabelle werden stillschweigend uebersprungen (Info-Log beim ersten Poll).</li></ul><h2>Relevante AppSettings</h2><p>Snippet (Multi-Tenant Keys):</p><ul><li><code>"AppSettings.multiConnectionEnabled": "true"</code></li><li><code>"AppSettings.enableCookieAuthentication": "true"</code></li><li><code>"ConnectionStrings.MetaDataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.DataSQLConnection": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant1_Data": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Meta": "..."</code></li><li><code>"ConnectionStrings.Tenant2_Data": "..."</code></li><li>(optional) <code>"AppSettings.email-host__a1": "smtp.tenant1.example"</code> — SMTP-Override fuer Mandant 1.</li></ul>",
|
|
26957
|
+
"codeSamples": []
|
|
26958
|
+
}
|
|
26959
|
+
],
|
|
26960
|
+
"images": []
|
|
26961
|
+
},
|
|
25873
26962
|
{
|
|
25874
26963
|
"slug": "troubleshooting",
|
|
25875
26964
|
"lang": "de-DE",
|
|
@@ -26406,10 +27495,10 @@ class FrameworkDocsComponent {
|
|
|
26406
27495
|
}
|
|
26407
27496
|
return work;
|
|
26408
27497
|
}
|
|
26409
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
26410
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: FrameworkDocsComponent, isStandalone: true, selector: "wuic-framework-docs", viewQueries: [{ propertyName: "contentScroller", first: true, predicate: ["contentScroller"], descendants: true }], ngImport: i0, template: "<div class=\"fw-docs\" [class.dark]=\"dark()\">\n <aside class=\"sidebar\">\n <div class=\"brand\">\n <h2>{{ 'docs.title' | translate }}</h2>\n <p>{{ 'docs.subtitle' | translate }}</p>\n </div>\n\n <div class=\"search\">\n <input pInputText type=\"text\" [ngModel]=\"query()\" (ngModelChange)=\"query.set($event || '')\" [placeholder]=\"t('docs.search_placeholder','Search feature, tag, keyword')\" />\n <button pButton type=\"button\" icon=\"pi pi-sun\" *ngIf=\"dark()\" (click)=\"toggleTheme()\"></button>\n <button pButton type=\"button\" icon=\"pi pi-moon\" *ngIf=\"!dark()\" (click)=\"toggleTheme()\"></button>\n </div>\n\n <div class=\"nav-groups\">\n @for (group of filteredGroups(); track group.id) {\n <section>\n <h3>{{ group.title }}</h3>\n <ul>\n @for (item of group.items; track item.slug) {\n <li>\n <a [class.active]=\"item.slug === currentSlug()\" [href]=\"'#/docs/' + item.slug\" (click)=\"openPage(item.slug); $event.preventDefault()\">\n <i class=\"pi\" [ngClass]=\"item.icon || 'pi-file'\"></i>\n <span>{{ item.title }}</span>\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n </aside>\n\n <main class=\"content\" #contentScroller *ngIf=\"currentPage() as page\">\n <nav class=\"breadcrumbs\">{{ breadcrumbs().join(' / ') }}</nav>\n\n <header>\n <h1>{{ page.title }}</h1>\n <p>{{ page.description }}</p>\n <div class=\"tags\">\n <p-tag *ngFor=\"let tag of page.tags\" [value]=\"tag\"></p-tag>\n <p-tag\n [value]=\"page.status\"\n [severity]=\"page.status === 'beta' ? 'warn' : 'success'\"\n [pTooltip]=\"page.status === 'beta' ? 'Feature in beta: API e comportamento possono cambiare senza preavviso' : ''\"\n tooltipPosition=\"top\"></p-tag>\n <p-tag *ngIf=\"page.tier === 'pro'\" value=\"Pro only\" styleClass=\"tier-tag tier-tag-pro\"></p-tag>\n <p-tag *ngIf=\"!page.tier || page.tier === 'dev-and-pro'\" value=\"Dev & Pro\" styleClass=\"tier-tag tier-tag-base\"></p-tag>\n </div>\n </header>\n\n <div class=\"body-grid\">\n <article>\n @for (section of page.sections; track section.id) {\n <section [attr.id]=\"section.id\" class=\"doc-section\">\n <h3>{{ section.title }}</h3>\n @for (part of sectionParts(section); track $index) {\n @if (part.kind === 'html') {\n <div class=\"section-html\" [innerHTML]=\"getHighlightedHtml(part.html)\"></div>\n } @else {\n @let sampleKey = getSampleKey(page.slug, section.id, part.sample.id);\n <div class=\"code-block\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <div class=\"code-header\">\n <div class=\"code-meta\">\n <span>{{ part.sample.title }}</span>\n <span class=\"snippet-lang\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <i class=\"pi\" [ngClass]=\"getSnippetLanguageIconClass(part.sample.language)\"></i>\n {{ getSnippetLanguageLabel(part.sample.language) }}\n </span>\n </div>\n <div class=\"code-actions\">\n @if (isSampleCollapsible(part.sample.code)) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n severity=\"secondary\"\n [icon]=\"isSampleExpanded(sampleKey) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n [label]=\"isSampleExpanded(sampleKey) ? t('docs.collapse','Collapse') : (t('docs.expand','Expand') + ' (+' + getCollapsedHiddenLines(part.sample.code) + ')')\"\n (click)=\"toggleSampleExpanded(sampleKey)\">\n </button>\n }\n <button pButton type=\"button\" size=\"small\" icon=\"pi pi-copy\" [label]=\"t('docs.copy','Copy')\" (click)=\"copyCode(part.sample.code)\"></button>\n </div>\n </div>\n <pre><code [innerHTML]=\"getVisibleSampleHighlightedCode(part.sample.code, sampleKey, part.sample.language)\"></code></pre>\n </div>\n }\n }\n </section>\n }\n\n @if (page.slug === 'themes') {\n <section class=\"themes-catalog\">\n <h3>{{ 'docs.themes_catalog' | translate }}</h3>\n <div class=\"themes-catalog-grid\">\n @for (theme of allThemes; track theme.value) {\n <div class=\"themes-catalog-item\">\n <span class=\"theme-dot\" [style.backgroundColor]=\"theme.primaryHex\"></span>\n <span>{{ theme.label }}</span>\n </div>\n }\n </div>\n </section>\n }\n\n @if (page.images?.length) {\n <section class=\"screenshots\">\n <h3>{{ 'docs.feature_screenshots' | translate }}</h3>\n <div class=\"shots-grid\">\n @for (img of page.images; track img.id) {\n <figure>\n <p-image\n *ngIf=\"img.status === 'available'\"\n [preview]=\"true\"\n [src]=\"img.path\"\n [previewImageSrc]=\"img.path\"\n [alt]=\"img.caption\"\n />\n <div class=\"missing\" *ngIf=\"img.status !== 'available'\">{{ 'docs.snapshot_unavailable' | translate }}</div>\n <figcaption [innerHTML]=\"getHighlightedText(formatScreenshotCaption(img.caption))\"></figcaption>\n </figure>\n }\n </div>\n </section>\n }\n </article>\n\n <aside class=\"toc\">\n <h4>{{ 'docs.on_this_page' | translate }}</h4>\n <ul>\n @for (item of page.toc; track item.id) {\n <li><a [href]=\"'#' + item.id\" (click)=\"scrollToSection($event, item.id, item.title)\">{{ item.title }}</a></li>\n }\n </ul>\n </aside>\n </div>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.fw-docs{--bg: linear-gradient(145deg, #f7f9ff 0%, #edf3ff 40%, #fefbf4 100%);--panel: rgba(255, 255, 255, .86);--text: #18304a;--muted: #4d6278;--accent: #0d6efd;--border: #dce7fb;height:calc(100vh - 70px);display:grid;grid-template-columns:320px 1fr;background:var(--bg);color:var(--text);overflow:hidden}.fw-docs.dark,:host-context(.theme-dark) .fw-docs,:host-context(.dark-theme) .fw-docs,:host-context([data-theme-mode=dark]) .fw-docs{--bg: linear-gradient(145deg, #121b2a 0%, #0f2233 55%, #222134 100%);--panel: rgba(22, 35, 55, .85);--text: #dbe8ff;--muted: #9eb4d2;--accent: #7eb2ff;--border: #294869}.sidebar{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--panel);border-right:1px solid var(--border);padding:18px;position:sticky;top:0;height:calc(100vh - 70px);overflow:auto}.brand h1{margin:0;font-size:1.1rem}.brand p{margin:6px 0 14px;color:var(--muted);font-size:.88rem}.search{display:grid;grid-template-columns:1fr auto;gap:6px;margin-bottom:12px}.nav-groups h3{margin:14px 0 8px;font-size:.86rem;text-transform:uppercase;color:var(--muted);letter-spacing:.06em}.nav-groups ul{list-style:none;padding:0;margin:0}.nav-groups a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:10px;color:inherit;text-decoration:none;cursor:pointer}.nav-groups a.active,.nav-groups a:hover{background:#0d6efd1f}.content{padding:26px 30px 36px;height:100%;overflow-y:scroll;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent;scrollbar-gutter:stable}.content::-webkit-scrollbar,.sidebar::-webkit-scrollbar{width:10px;height:10px}.content::-webkit-scrollbar-track,.sidebar::-webkit-scrollbar-track{background:transparent}.content::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 65%,transparent);border-radius:6px;border:2px solid transparent;background-clip:content-box}.content::-webkit-scrollbar-thumb:hover,.sidebar::-webkit-scrollbar-thumb:hover{background-color:var(--p-text-muted-color, #64748b)}.sidebar{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent}.breadcrumbs{font-size:.85rem;color:var(--muted);margin-bottom:10px}header h2{margin:0;font-size:2rem}header p{margin:8px 0 12px;color:var(--muted)}.tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}::ng-deep .tier-tag.p-tag{font-weight:700;letter-spacing:.02em}::ng-deep .tier-tag-base.p-tag{background:color-mix(in srgb,#6366f1 15%,var(--panel, #fff));color:color-mix(in srgb,#4338ca 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#6366f1 45%,transparent)}::ng-deep .tier-tag-pro.p-tag{background:color-mix(in srgb,#f59e0b 18%,var(--panel, #fff));color:color-mix(in srgb,#b45309 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#f59e0b 55%,transparent)}.body-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,360px);gap:20px}article{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:20px}.doc-section{padding:6px 0 16px;border-bottom:1px dashed var(--border)}.doc-section:last-child{border-bottom:0}.doc-section h3{margin:8px 0}.section-html{color:var(--text)}:host ::ng-deep .section-html p{line-height:1.58}:host ::ng-deep .section-html table{width:100%;border-collapse:collapse;margin:16px 0 24px;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:.92rem;box-shadow:0 1px 2px color-mix(in srgb,var(--text) 6%,transparent)}:host ::ng-deep .section-html thead{background:color-mix(in srgb,var(--accent) 14%,var(--panel))}:host ::ng-deep .section-html thead th{text-align:left;color:var(--text);font-weight:700;padding:12px 16px;border:1px solid var(--border);border-bottom:2px solid color-mix(in srgb,var(--accent) 35%,var(--border));text-transform:uppercase;font-size:.78rem;letter-spacing:.4px;white-space:nowrap}:host ::ng-deep .section-html tbody td{padding:11px 16px;border:1px solid color-mix(in srgb,var(--border) 80%,transparent);vertical-align:top;line-height:1.55;color:var(--text)}:host ::ng-deep .section-html tbody tr:nth-child(odd) td{background:color-mix(in srgb,var(--panel) 92%,transparent)}:host ::ng-deep .section-html tbody tr:nth-child(2n) td{background:color-mix(in srgb,var(--panel) 100%,transparent)}:host ::ng-deep .section-html tbody tr:hover td{background:color-mix(in srgb,var(--accent) 8%,var(--panel))}:host ::ng-deep .section-html table code{font-size:.85em;padding:1px 5px}:host ::ng-deep .section-html strong{color:var(--text);font-weight:600}:host ::ng-deep .section-html em{font-style:italic}:host ::ng-deep .section-html a.wuic-demo-link{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;margin:0 2px;background:color-mix(in srgb,var(--accent) 14%,var(--panel));border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));border-radius:6px;color:var(--accent, #4f46e5);font-size:.85rem;font-weight:600;text-decoration:none;line-height:1.2;transition:background .15s ease}:host ::ng-deep .section-html a.wuic-demo-link:before{content:\"\\25b6\";font-size:.7rem;color:var(--accent, #4f46e5)}:host ::ng-deep .section-html a.wuic-demo-link:hover{background:color-mix(in srgb,var(--accent) 22%,var(--panel));text-decoration:none}:host ::ng-deep .section-html :where(ul,ol){margin:12px 0 16px;padding-left:0}:host ::ng-deep .section-html ul{list-style:none}:host ::ng-deep .section-html ul>li{position:relative;margin:8px 0;padding:8px 12px 8px 34px;border:1px solid color-mix(in srgb,var(--border) 78%,transparent);border-radius:10px;background:color-mix(in srgb,var(--panel) 90%,transparent);line-height:1.52}:host ::ng-deep .section-html ul>li:nth-child(odd){background:color-mix(in srgb,var(--panel) 94%,transparent)}:host ::ng-deep .section-html ul>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 5%,var(--panel));border-color:color-mix(in srgb,var(--accent) 18%,var(--border))}:host ::ng-deep .section-html ul>li:before{content:\"\";position:absolute;left:12px;top:50%;width:10px;height:10px;border-radius:999px;transform:translateY(-50%);background:radial-gradient(circle at 35% 35%,#fff 0 24%,color-mix(in srgb,var(--accent) 80%,#4aa3ff) 25% 100%);box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 32%,transparent)}:host ::ng-deep .section-html ol{list-style:none;counter-reset:wuic-doc-step}:host ::ng-deep .section-html ol>li{position:relative;counter-increment:wuic-doc-step;margin:8px 0;padding:8px 12px 8px 42px;border-left:3px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));border-radius:10px;line-height:1.52}:host ::ng-deep .section-html ol>li:nth-child(odd){background:color-mix(in srgb,var(--accent) 7%,var(--panel))}:host ::ng-deep .section-html ol>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 11%,var(--panel));border-left-color:color-mix(in srgb,var(--accent) 72%,transparent)}:host ::ng-deep .section-html ol>li:before{content:counter(wuic-doc-step);position:absolute;left:10px;top:50%;width:22px;height:22px;border-radius:999px;transform:translateY(-50%);display:grid;place-items:center;font-size:.78rem;font-weight:700;color:#fff;background:linear-gradient(160deg,color-mix(in srgb,var(--accent) 80%,#4aa3ff),color-mix(in srgb,var(--accent) 95%,#125dcc));box-shadow:0 2px 10px color-mix(in srgb,var(--accent) 35%,transparent)}:host ::ng-deep .section-html li>:where(ul,ol){margin-top:8px}.code-block{margin:10px 0;border:1px solid var(--border);border-radius:12px;overflow:hidden}.code-header{display:flex;justify-content:space-between;align-items:center;background:#0d6efd14;padding:8px 10px}.code-block pre{margin:0;padding:12px;overflow:auto;background:#1e1e1e}.code-block code{display:block;font-family:Consolas,Cascadia Code,Fira Code,Courier New,monospace;font-size:.88rem;line-height:1.55;color:#d4d4d4;white-space:pre}.code-actions{display:inline-flex;gap:8px;align-items:center}.code-meta{display:inline-flex;align-items:center;gap:10px}.snippet-lang{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:22px;padding:0 8px;border-radius:999px;font-size:.72rem;font-weight:700;letter-spacing:.04em;border:1px solid transparent;background:color-mix(in srgb,var(--text) 8%,transparent);color:var(--text)}.snippet-lang.lang-html{background:#e36c0924;border-color:#e36c0952;color:#b25d0e}.snippet-lang.lang-json{background:#00808024;border-color:#00808052;color:#0e6b6b}.snippet-lang.lang-ts{background:#3178c624;border-color:#3178c652;color:#1f5f9f}.snippet-lang.lang-cs{background:#68217a24;border-color:#68217a52;color:#6a2b82}.snippet-lang i{font-size:.72rem}:host ::ng-deep .code-block code .tok-comment{color:#6a9955}:host ::ng-deep .code-block code .tok-string{color:#ce9178}:host ::ng-deep .code-block code .tok-number{color:#b5cea8}:host ::ng-deep .code-block code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block code .tok-op{color:#d4d4d4}:host ::ng-deep .code-block.lang-html code .tok-tag{color:#569cd6}:host ::ng-deep .code-block.lang-html code .tok-attr{color:#9cdcfe}:host ::ng-deep .code-block.lang-html code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-key{color:#9cdcfe}:host ::ng-deep .code-block.lang-json code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-json code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-ts code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-ts code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block.lang-ts code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-ts code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-ts code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-ts code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-ts code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-cs code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-cs code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-cs code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-cs code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-cs code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-cs code .tok-number{color:#b5cea8}.screenshots .shots-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;align-items:start}.screenshots figure{margin:0;display:flex;flex-direction:column;gap:8px}.screenshots img{width:100%;border-radius:12px;border:1px solid var(--border)}:host ::ng-deep .screenshots .p-image,:host ::ng-deep .screenshots .p-image img{width:100%;display:block}:host ::ng-deep .screenshots .p-image{aspect-ratio:16/9;overflow:hidden;border-radius:12px;border:1px solid var(--border);background:color-mix(in srgb,var(--panel) 90%,transparent)}:host ::ng-deep .screenshots .p-image img{width:100%;height:100%;object-fit:contain;object-position:center;border:0}:host ::ng-deep .screenshots .p-image .p-image-preview-mask,:host ::ng-deep .screenshots .p-image .p-image-mask{inset:0;width:100%;height:100%}.missing{border:1px dashed var(--border);border-radius:12px;padding:36px 10px;text-align:center;color:var(--muted)}:host ::ng-deep .p-image-mask,:host ::ng-deep .p-image-preview-mask{background:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .p-image-toolbar{background:#0c1220!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;filter:none!important;border:1px solid rgba(255,255,255,.14)!important;box-shadow:0 8px 24px #00000059!important}:host ::ng-deep .p-image-action{color:#eaf1ff!important;opacity:1!important;text-shadow:none!important}:host ::ng-deep .p-image-preview{background:#7eb2ff2e!important}::ng-deep .p-image-mask.p-component-overlay,::ng-deep .p-image-preview-mask.p-component-overlay{background:#0c121efa!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .screenshots .p-image .p-image-mask,:host ::ng-deep .screenshots .p-image .p-image-preview-indicator{background:#0c121e42!important}.themes-catalog{margin-top:14px;padding:6px 0 16px;border-top:1px dashed var(--border)}.themes-catalog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}.themes-catalog-item{display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;padding:.45rem .65rem;border:1px solid var(--border);border-radius:10px;background:color-mix(in srgb,var(--panel) 92%,transparent)}.theme-dot{width:.8rem;height:.8rem;border-radius:999px;border:1px solid color-mix(in srgb,var(--text) 25%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,#ffffff 55%,transparent) inset;flex:0 0 auto}.toc{position:sticky;top:20px;max-height:calc(100vh - 120px);overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px}.toc h4{margin:0 0 8px}.toc ul{margin:0;padding-left:18px;list-style-position:outside}.toc a{display:block;color:var(--text);text-decoration:none;line-height:1.25;overflow-wrap:anywhere;word-break:break-word}.toc li{margin:0 0 6px}.toc li:last-child{margin-bottom:0}@media(max-width:1360px)and (min-width:1101px){.body-grid{grid-template-columns:minmax(0,1fr) minmax(280px,320px)}}@media(max-width:1100px){.fw-docs{grid-template-columns:1fr;height:auto;overflow:visible}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--border)}.content{height:auto;overflow:visible}.body-grid{grid-template-columns:1fr}.toc{position:static}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i10.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: ImageModule }, { kind: "component", type: i1$3.Image, selector: "p-image", inputs: ["imageClass", "imageStyle", "styleClass", "src", "srcSet", "sizes", "previewImageSrc", "previewImageSrcSet", "previewImageSizes", "alt", "width", "height", "loading", "preview", "showTransitionOptions", "hideTransitionOptions", "modalEnterAnimation", "modalLeaveAnimation", "appendTo", "maskMotionOptions", "motionOptions"], outputs: ["onShow", "onHide", "onImageError"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
|
|
27498
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FrameworkDocsComponent, deps: [{ token: i1.Router }, { token: i1.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
|
|
27499
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FrameworkDocsComponent, isStandalone: true, selector: "wuic-framework-docs", viewQueries: [{ propertyName: "contentScroller", first: true, predicate: ["contentScroller"], descendants: true }], ngImport: i0, template: "<div class=\"fw-docs\" [class.dark]=\"dark()\">\n <aside class=\"sidebar\">\n <div class=\"brand\">\n <h2>{{ 'docs.title' | translate }}</h2>\n <p>{{ 'docs.subtitle' | translate }}</p>\n </div>\n\n <div class=\"search\">\n <input pInputText type=\"text\" [ngModel]=\"query()\" (ngModelChange)=\"query.set($event || '')\" [placeholder]=\"t('docs.search_placeholder','Search feature, tag, keyword')\" />\n <button pButton type=\"button\" icon=\"pi pi-sun\" *ngIf=\"dark()\" (click)=\"toggleTheme()\"></button>\n <button pButton type=\"button\" icon=\"pi pi-moon\" *ngIf=\"!dark()\" (click)=\"toggleTheme()\"></button>\n </div>\n\n <div class=\"nav-groups\">\n @for (group of filteredGroups(); track group.id) {\n <section>\n <h3>{{ group.title }}</h3>\n <ul>\n @for (item of group.items; track item.slug) {\n <li>\n <a [class.active]=\"item.slug === currentSlug()\" [href]=\"'#/docs/' + item.slug\" (click)=\"openPage(item.slug); $event.preventDefault()\">\n <i class=\"pi\" [ngClass]=\"item.icon || 'pi-file'\"></i>\n <span>{{ item.title }}</span>\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n </aside>\n\n <main class=\"content\" #contentScroller *ngIf=\"currentPage() as page\">\n <nav class=\"breadcrumbs\">{{ breadcrumbs().join(' / ') }}</nav>\n\n <header>\n <h1>{{ page.title }}</h1>\n <p>{{ page.description }}</p>\n <div class=\"tags\">\n <p-tag *ngFor=\"let tag of page.tags\" [value]=\"tag\"></p-tag>\n <p-tag\n [value]=\"page.status\"\n [severity]=\"page.status === 'beta' ? 'warn' : 'success'\"\n [pTooltip]=\"page.status === 'beta' ? 'Feature in beta: API e comportamento possono cambiare senza preavviso' : ''\"\n tooltipPosition=\"top\"></p-tag>\n <p-tag *ngIf=\"page.tier === 'pro'\" value=\"Pro only\" styleClass=\"tier-tag tier-tag-pro\"></p-tag>\n <p-tag *ngIf=\"!page.tier || page.tier === 'dev-and-pro'\" value=\"Dev & Pro\" styleClass=\"tier-tag tier-tag-base\"></p-tag>\n </div>\n </header>\n\n <div class=\"body-grid\">\n <article>\n @for (section of page.sections; track section.id) {\n <section [attr.id]=\"section.id\" class=\"doc-section\">\n <h3>{{ section.title }}</h3>\n @for (part of sectionParts(section); track $index) {\n @if (part.kind === 'html') {\n <div class=\"section-html\" [innerHTML]=\"getHighlightedHtml(part.html)\"></div>\n } @else {\n @let sampleKey = getSampleKey(page.slug, section.id, part.sample.id);\n <div class=\"code-block\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <div class=\"code-header\">\n <div class=\"code-meta\">\n <span>{{ part.sample.title }}</span>\n <span class=\"snippet-lang\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <i class=\"pi\" [ngClass]=\"getSnippetLanguageIconClass(part.sample.language)\"></i>\n {{ getSnippetLanguageLabel(part.sample.language) }}\n </span>\n </div>\n <div class=\"code-actions\">\n @if (isSampleCollapsible(part.sample.code)) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n severity=\"secondary\"\n [icon]=\"isSampleExpanded(sampleKey) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n [label]=\"isSampleExpanded(sampleKey) ? t('docs.collapse','Collapse') : (t('docs.expand','Expand') + ' (+' + getCollapsedHiddenLines(part.sample.code) + ')')\"\n (click)=\"toggleSampleExpanded(sampleKey)\">\n </button>\n }\n <button pButton type=\"button\" size=\"small\" icon=\"pi pi-copy\" [label]=\"t('docs.copy','Copy')\" (click)=\"copyCode(part.sample.code)\"></button>\n </div>\n </div>\n <pre><code [innerHTML]=\"getVisibleSampleHighlightedCode(part.sample.code, sampleKey, part.sample.language)\"></code></pre>\n </div>\n }\n }\n </section>\n }\n\n @if (page.slug === 'themes') {\n <section class=\"themes-catalog\">\n <h3>{{ 'docs.themes_catalog' | translate }}</h3>\n <div class=\"themes-catalog-grid\">\n @for (theme of allThemes; track theme.value) {\n <div class=\"themes-catalog-item\">\n <span class=\"theme-dot\" [style.backgroundColor]=\"theme.primaryHex\"></span>\n <span>{{ theme.label }}</span>\n </div>\n }\n </div>\n </section>\n }\n\n @if (page.images?.length) {\n <section class=\"screenshots\">\n <h3>{{ 'docs.feature_screenshots' | translate }}</h3>\n <div class=\"shots-grid\">\n @for (img of page.images; track img.id) {\n <figure>\n <p-image\n *ngIf=\"img.status === 'available'\"\n [preview]=\"true\"\n [src]=\"img.path\"\n [previewImageSrc]=\"img.path\"\n [alt]=\"img.caption\"\n />\n <div class=\"missing\" *ngIf=\"img.status !== 'available'\">{{ 'docs.snapshot_unavailable' | translate }}</div>\n <figcaption [innerHTML]=\"getHighlightedText(formatScreenshotCaption(img.caption))\"></figcaption>\n </figure>\n }\n </div>\n </section>\n }\n </article>\n\n <aside class=\"toc\">\n <h4>{{ 'docs.on_this_page' | translate }}</h4>\n <ul>\n @for (item of page.toc; track item.id) {\n <li><a [href]=\"'#' + item.id\" (click)=\"scrollToSection($event, item.id, item.title)\">{{ item.title }}</a></li>\n }\n </ul>\n </aside>\n </div>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.fw-docs{--bg: linear-gradient(145deg, #f7f9ff 0%, #edf3ff 40%, #fefbf4 100%);--panel: rgba(255, 255, 255, .86);--text: #18304a;--muted: #4d6278;--accent: #0d6efd;--border: #dce7fb;height:calc(100vh - 70px);display:grid;grid-template-columns:320px 1fr;background:var(--bg);color:var(--text);overflow:hidden}.fw-docs.dark,:host-context(.theme-dark) .fw-docs,:host-context(.dark-theme) .fw-docs,:host-context([data-theme-mode=dark]) .fw-docs{--bg: linear-gradient(145deg, #121b2a 0%, #0f2233 55%, #222134 100%);--panel: rgba(22, 35, 55, .85);--text: #dbe8ff;--muted: #9eb4d2;--accent: #7eb2ff;--border: #294869}.sidebar{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--panel);border-right:1px solid var(--border);padding:18px;position:sticky;top:0;height:calc(100vh - 70px);overflow:auto}.brand h1{margin:0;font-size:1.1rem}.brand p{margin:6px 0 14px;color:var(--muted);font-size:.88rem}.search{display:grid;grid-template-columns:1fr auto;gap:6px;margin-bottom:12px}.nav-groups h3{margin:14px 0 8px;font-size:.86rem;text-transform:uppercase;color:var(--muted);letter-spacing:.06em}.nav-groups ul{list-style:none;padding:0;margin:0}.nav-groups a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:10px;color:inherit;text-decoration:none;cursor:pointer}.nav-groups a.active,.nav-groups a:hover{background:#0d6efd1f}.content{padding:26px 30px 36px;height:100%;overflow-y:scroll;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent;scrollbar-gutter:stable}.content::-webkit-scrollbar,.sidebar::-webkit-scrollbar{width:10px;height:10px}.content::-webkit-scrollbar-track,.sidebar::-webkit-scrollbar-track{background:transparent}.content::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 65%,transparent);border-radius:6px;border:2px solid transparent;background-clip:content-box}.content::-webkit-scrollbar-thumb:hover,.sidebar::-webkit-scrollbar-thumb:hover{background-color:var(--p-text-muted-color, #64748b)}.sidebar{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent}.breadcrumbs{font-size:.85rem;color:var(--muted);margin-bottom:10px}header h2{margin:0;font-size:2rem}header p{margin:8px 0 12px;color:var(--muted)}.tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}::ng-deep .tier-tag.p-tag{font-weight:700;letter-spacing:.02em}::ng-deep .tier-tag-base.p-tag{background:color-mix(in srgb,#6366f1 15%,var(--panel, #fff));color:color-mix(in srgb,#4338ca 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#6366f1 45%,transparent)}::ng-deep .tier-tag-pro.p-tag{background:color-mix(in srgb,#f59e0b 18%,var(--panel, #fff));color:color-mix(in srgb,#b45309 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#f59e0b 55%,transparent)}.body-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,360px);gap:20px}article{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:20px}.doc-section{padding:6px 0 16px;border-bottom:1px dashed var(--border)}.doc-section:last-child{border-bottom:0}.doc-section h3{margin:8px 0}.section-html{color:var(--text)}:host ::ng-deep .section-html p{line-height:1.58}:host ::ng-deep .section-html table{width:100%;border-collapse:collapse;margin:16px 0 24px;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:.92rem;box-shadow:0 1px 2px color-mix(in srgb,var(--text) 6%,transparent)}:host ::ng-deep .section-html thead{background:color-mix(in srgb,var(--accent) 14%,var(--panel))}:host ::ng-deep .section-html thead th{text-align:left;color:var(--text);font-weight:700;padding:12px 16px;border:1px solid var(--border);border-bottom:2px solid color-mix(in srgb,var(--accent) 35%,var(--border));text-transform:uppercase;font-size:.78rem;letter-spacing:.4px;white-space:nowrap}:host ::ng-deep .section-html tbody td{padding:11px 16px;border:1px solid color-mix(in srgb,var(--border) 80%,transparent);vertical-align:top;line-height:1.55;color:var(--text)}:host ::ng-deep .section-html tbody tr:nth-child(odd) td{background:color-mix(in srgb,var(--panel) 92%,transparent)}:host ::ng-deep .section-html tbody tr:nth-child(2n) td{background:color-mix(in srgb,var(--panel) 100%,transparent)}:host ::ng-deep .section-html tbody tr:hover td{background:color-mix(in srgb,var(--accent) 8%,var(--panel))}:host ::ng-deep .section-html table code{font-size:.85em;padding:1px 5px}:host ::ng-deep .section-html strong{color:var(--text);font-weight:600}:host ::ng-deep .section-html em{font-style:italic}:host ::ng-deep .section-html a.wuic-demo-link{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;margin:0 2px;background:color-mix(in srgb,var(--accent) 14%,var(--panel));border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));border-radius:6px;color:var(--accent, #4f46e5);font-size:.85rem;font-weight:600;text-decoration:none;line-height:1.2;transition:background .15s ease}:host ::ng-deep .section-html a.wuic-demo-link:before{content:\"\\25b6\";font-size:.7rem;color:var(--accent, #4f46e5)}:host ::ng-deep .section-html a.wuic-demo-link:hover{background:color-mix(in srgb,var(--accent) 22%,var(--panel));text-decoration:none}:host ::ng-deep .section-html :where(ul,ol){margin:12px 0 16px;padding-left:0}:host ::ng-deep .section-html ul{list-style:none}:host ::ng-deep .section-html ul>li{position:relative;margin:8px 0;padding:8px 12px 8px 34px;border:1px solid color-mix(in srgb,var(--border) 78%,transparent);border-radius:10px;background:color-mix(in srgb,var(--panel) 90%,transparent);line-height:1.52}:host ::ng-deep .section-html ul>li:nth-child(odd){background:color-mix(in srgb,var(--panel) 94%,transparent)}:host ::ng-deep .section-html ul>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 5%,var(--panel));border-color:color-mix(in srgb,var(--accent) 18%,var(--border))}:host ::ng-deep .section-html ul>li:before{content:\"\";position:absolute;left:12px;top:50%;width:10px;height:10px;border-radius:999px;transform:translateY(-50%);background:radial-gradient(circle at 35% 35%,#fff 0 24%,color-mix(in srgb,var(--accent) 80%,#4aa3ff) 25% 100%);box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 32%,transparent)}:host ::ng-deep .section-html ol{list-style:none;counter-reset:wuic-doc-step}:host ::ng-deep .section-html ol>li{position:relative;counter-increment:wuic-doc-step;margin:8px 0;padding:8px 12px 8px 42px;border-left:3px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));border-radius:10px;line-height:1.52}:host ::ng-deep .section-html ol>li:nth-child(odd){background:color-mix(in srgb,var(--accent) 7%,var(--panel))}:host ::ng-deep .section-html ol>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 11%,var(--panel));border-left-color:color-mix(in srgb,var(--accent) 72%,transparent)}:host ::ng-deep .section-html ol>li:before{content:counter(wuic-doc-step);position:absolute;left:10px;top:50%;width:22px;height:22px;border-radius:999px;transform:translateY(-50%);display:grid;place-items:center;font-size:.78rem;font-weight:700;color:#fff;background:linear-gradient(160deg,color-mix(in srgb,var(--accent) 80%,#4aa3ff),color-mix(in srgb,var(--accent) 95%,#125dcc));box-shadow:0 2px 10px color-mix(in srgb,var(--accent) 35%,transparent)}:host ::ng-deep .section-html li>:where(ul,ol){margin-top:8px}.code-block{margin:10px 0;border:1px solid var(--border);border-radius:12px;overflow:hidden}.code-header{display:flex;justify-content:space-between;align-items:center;background:#0d6efd14;padding:8px 10px}.code-block pre{margin:0;padding:12px;overflow:auto;background:#1e1e1e}.code-block code{display:block;font-family:Consolas,Cascadia Code,Fira Code,Courier New,monospace;font-size:.88rem;line-height:1.55;color:#d4d4d4;white-space:pre}.code-actions{display:inline-flex;gap:8px;align-items:center}.code-meta{display:inline-flex;align-items:center;gap:10px}.snippet-lang{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:22px;padding:0 8px;border-radius:999px;font-size:.72rem;font-weight:700;letter-spacing:.04em;border:1px solid transparent;background:color-mix(in srgb,var(--text) 8%,transparent);color:var(--text)}.snippet-lang.lang-html{background:#e36c0924;border-color:#e36c0952;color:#b25d0e}.snippet-lang.lang-json{background:#00808024;border-color:#00808052;color:#0e6b6b}.snippet-lang.lang-ts{background:#3178c624;border-color:#3178c652;color:#1f5f9f}.snippet-lang.lang-cs{background:#68217a24;border-color:#68217a52;color:#6a2b82}.snippet-lang i{font-size:.72rem}:host ::ng-deep .code-block code .tok-comment{color:#6a9955}:host ::ng-deep .code-block code .tok-string{color:#ce9178}:host ::ng-deep .code-block code .tok-number{color:#b5cea8}:host ::ng-deep .code-block code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block code .tok-op{color:#d4d4d4}:host ::ng-deep .code-block.lang-html code .tok-tag{color:#569cd6}:host ::ng-deep .code-block.lang-html code .tok-attr{color:#9cdcfe}:host ::ng-deep .code-block.lang-html code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-key{color:#9cdcfe}:host ::ng-deep .code-block.lang-json code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-json code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-ts code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-ts code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block.lang-ts code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-ts code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-ts code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-ts code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-ts code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-cs code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-cs code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-cs code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-cs code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-cs code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-cs code .tok-number{color:#b5cea8}.screenshots .shots-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;align-items:start}.screenshots figure{margin:0;display:flex;flex-direction:column;gap:8px}.screenshots img{width:100%;border-radius:12px;border:1px solid var(--border)}:host ::ng-deep .screenshots .p-image,:host ::ng-deep .screenshots .p-image img{width:100%;display:block}:host ::ng-deep .screenshots .p-image{aspect-ratio:16/9;overflow:hidden;border-radius:12px;border:1px solid var(--border);background:color-mix(in srgb,var(--panel) 90%,transparent)}:host ::ng-deep .screenshots .p-image img{width:100%;height:100%;object-fit:contain;object-position:center;border:0}:host ::ng-deep .screenshots .p-image .p-image-preview-mask,:host ::ng-deep .screenshots .p-image .p-image-mask{inset:0;width:100%;height:100%}.missing{border:1px dashed var(--border);border-radius:12px;padding:36px 10px;text-align:center;color:var(--muted)}:host ::ng-deep .p-image-mask,:host ::ng-deep .p-image-preview-mask{background:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .p-image-toolbar{background:#0c1220!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;filter:none!important;border:1px solid rgba(255,255,255,.14)!important;box-shadow:0 8px 24px #00000059!important}:host ::ng-deep .p-image-action{color:#eaf1ff!important;opacity:1!important;text-shadow:none!important}:host ::ng-deep .p-image-preview{background:#7eb2ff2e!important}::ng-deep .p-image-mask.p-component-overlay,::ng-deep .p-image-preview-mask.p-component-overlay{background:#0c121efa!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .screenshots .p-image .p-image-mask,:host ::ng-deep .screenshots .p-image .p-image-preview-indicator{background:#0c121e42!important}.themes-catalog{margin-top:14px;padding:6px 0 16px;border-top:1px dashed var(--border)}.themes-catalog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}.themes-catalog-item{display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;padding:.45rem .65rem;border:1px solid var(--border);border-radius:10px;background:color-mix(in srgb,var(--panel) 92%,transparent)}.theme-dot{width:.8rem;height:.8rem;border-radius:999px;border:1px solid color-mix(in srgb,var(--text) 25%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,#ffffff 55%,transparent) inset;flex:0 0 auto}.toc{position:sticky;top:20px;max-height:calc(100vh - 120px);overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px}.toc h4{margin:0 0 8px}.toc ul{margin:0;padding-left:18px;list-style-position:outside}.toc a{display:block;color:var(--text);text-decoration:none;line-height:1.25;overflow-wrap:anywhere;word-break:break-word}.toc li{margin:0 0 6px}.toc li:last-child{margin-bottom:0}@media(max-width:1360px)and (min-width:1101px){.body-grid{grid-template-columns:minmax(0,1fr) minmax(280px,320px)}}@media(max-width:1100px){.fw-docs{grid-template-columns:1fr;height:auto;overflow:visible}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--border)}.content{height:auto;overflow:visible}.body-grid{grid-template-columns:1fr}.toc{position:static}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i10.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: ImageModule }, { kind: "component", type: i1$3.Image, selector: "p-image", inputs: ["imageClass", "imageStyle", "styleClass", "src", "srcSet", "sizes", "previewImageSrc", "previewImageSrcSet", "previewImageSizes", "alt", "width", "height", "loading", "preview", "showTransitionOptions", "hideTransitionOptions", "modalEnterAnimation", "modalLeaveAnimation", "appendTo", "maskMotionOptions", "motionOptions"], outputs: ["onShow", "onHide", "onImageError"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }] });
|
|
26411
27500
|
}
|
|
26412
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
27501
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FrameworkDocsComponent, decorators: [{
|
|
26413
27502
|
type: Component,
|
|
26414
27503
|
args: [{ selector: 'wuic-framework-docs', standalone: true, imports: [CommonModule, FormsModule, ButtonModule, InputTextModule, TagModule, TooltipModule, ImageModule, TranslateModule], template: "<div class=\"fw-docs\" [class.dark]=\"dark()\">\n <aside class=\"sidebar\">\n <div class=\"brand\">\n <h2>{{ 'docs.title' | translate }}</h2>\n <p>{{ 'docs.subtitle' | translate }}</p>\n </div>\n\n <div class=\"search\">\n <input pInputText type=\"text\" [ngModel]=\"query()\" (ngModelChange)=\"query.set($event || '')\" [placeholder]=\"t('docs.search_placeholder','Search feature, tag, keyword')\" />\n <button pButton type=\"button\" icon=\"pi pi-sun\" *ngIf=\"dark()\" (click)=\"toggleTheme()\"></button>\n <button pButton type=\"button\" icon=\"pi pi-moon\" *ngIf=\"!dark()\" (click)=\"toggleTheme()\"></button>\n </div>\n\n <div class=\"nav-groups\">\n @for (group of filteredGroups(); track group.id) {\n <section>\n <h3>{{ group.title }}</h3>\n <ul>\n @for (item of group.items; track item.slug) {\n <li>\n <a [class.active]=\"item.slug === currentSlug()\" [href]=\"'#/docs/' + item.slug\" (click)=\"openPage(item.slug); $event.preventDefault()\">\n <i class=\"pi\" [ngClass]=\"item.icon || 'pi-file'\"></i>\n <span>{{ item.title }}</span>\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n </aside>\n\n <main class=\"content\" #contentScroller *ngIf=\"currentPage() as page\">\n <nav class=\"breadcrumbs\">{{ breadcrumbs().join(' / ') }}</nav>\n\n <header>\n <h1>{{ page.title }}</h1>\n <p>{{ page.description }}</p>\n <div class=\"tags\">\n <p-tag *ngFor=\"let tag of page.tags\" [value]=\"tag\"></p-tag>\n <p-tag\n [value]=\"page.status\"\n [severity]=\"page.status === 'beta' ? 'warn' : 'success'\"\n [pTooltip]=\"page.status === 'beta' ? 'Feature in beta: API e comportamento possono cambiare senza preavviso' : ''\"\n tooltipPosition=\"top\"></p-tag>\n <p-tag *ngIf=\"page.tier === 'pro'\" value=\"Pro only\" styleClass=\"tier-tag tier-tag-pro\"></p-tag>\n <p-tag *ngIf=\"!page.tier || page.tier === 'dev-and-pro'\" value=\"Dev & Pro\" styleClass=\"tier-tag tier-tag-base\"></p-tag>\n </div>\n </header>\n\n <div class=\"body-grid\">\n <article>\n @for (section of page.sections; track section.id) {\n <section [attr.id]=\"section.id\" class=\"doc-section\">\n <h3>{{ section.title }}</h3>\n @for (part of sectionParts(section); track $index) {\n @if (part.kind === 'html') {\n <div class=\"section-html\" [innerHTML]=\"getHighlightedHtml(part.html)\"></div>\n } @else {\n @let sampleKey = getSampleKey(page.slug, section.id, part.sample.id);\n <div class=\"code-block\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <div class=\"code-header\">\n <div class=\"code-meta\">\n <span>{{ part.sample.title }}</span>\n <span class=\"snippet-lang\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <i class=\"pi\" [ngClass]=\"getSnippetLanguageIconClass(part.sample.language)\"></i>\n {{ getSnippetLanguageLabel(part.sample.language) }}\n </span>\n </div>\n <div class=\"code-actions\">\n @if (isSampleCollapsible(part.sample.code)) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n severity=\"secondary\"\n [icon]=\"isSampleExpanded(sampleKey) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n [label]=\"isSampleExpanded(sampleKey) ? t('docs.collapse','Collapse') : (t('docs.expand','Expand') + ' (+' + getCollapsedHiddenLines(part.sample.code) + ')')\"\n (click)=\"toggleSampleExpanded(sampleKey)\">\n </button>\n }\n <button pButton type=\"button\" size=\"small\" icon=\"pi pi-copy\" [label]=\"t('docs.copy','Copy')\" (click)=\"copyCode(part.sample.code)\"></button>\n </div>\n </div>\n <pre><code [innerHTML]=\"getVisibleSampleHighlightedCode(part.sample.code, sampleKey, part.sample.language)\"></code></pre>\n </div>\n }\n }\n </section>\n }\n\n @if (page.slug === 'themes') {\n <section class=\"themes-catalog\">\n <h3>{{ 'docs.themes_catalog' | translate }}</h3>\n <div class=\"themes-catalog-grid\">\n @for (theme of allThemes; track theme.value) {\n <div class=\"themes-catalog-item\">\n <span class=\"theme-dot\" [style.backgroundColor]=\"theme.primaryHex\"></span>\n <span>{{ theme.label }}</span>\n </div>\n }\n </div>\n </section>\n }\n\n @if (page.images?.length) {\n <section class=\"screenshots\">\n <h3>{{ 'docs.feature_screenshots' | translate }}</h3>\n <div class=\"shots-grid\">\n @for (img of page.images; track img.id) {\n <figure>\n <p-image\n *ngIf=\"img.status === 'available'\"\n [preview]=\"true\"\n [src]=\"img.path\"\n [previewImageSrc]=\"img.path\"\n [alt]=\"img.caption\"\n />\n <div class=\"missing\" *ngIf=\"img.status !== 'available'\">{{ 'docs.snapshot_unavailable' | translate }}</div>\n <figcaption [innerHTML]=\"getHighlightedText(formatScreenshotCaption(img.caption))\"></figcaption>\n </figure>\n }\n </div>\n </section>\n }\n </article>\n\n <aside class=\"toc\">\n <h4>{{ 'docs.on_this_page' | translate }}</h4>\n <ul>\n @for (item of page.toc; track item.id) {\n <li><a [href]=\"'#' + item.id\" (click)=\"scrollToSection($event, item.id, item.title)\">{{ item.title }}</a></li>\n }\n </ul>\n </aside>\n </div>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.fw-docs{--bg: linear-gradient(145deg, #f7f9ff 0%, #edf3ff 40%, #fefbf4 100%);--panel: rgba(255, 255, 255, .86);--text: #18304a;--muted: #4d6278;--accent: #0d6efd;--border: #dce7fb;height:calc(100vh - 70px);display:grid;grid-template-columns:320px 1fr;background:var(--bg);color:var(--text);overflow:hidden}.fw-docs.dark,:host-context(.theme-dark) .fw-docs,:host-context(.dark-theme) .fw-docs,:host-context([data-theme-mode=dark]) .fw-docs{--bg: linear-gradient(145deg, #121b2a 0%, #0f2233 55%, #222134 100%);--panel: rgba(22, 35, 55, .85);--text: #dbe8ff;--muted: #9eb4d2;--accent: #7eb2ff;--border: #294869}.sidebar{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--panel);border-right:1px solid var(--border);padding:18px;position:sticky;top:0;height:calc(100vh - 70px);overflow:auto}.brand h1{margin:0;font-size:1.1rem}.brand p{margin:6px 0 14px;color:var(--muted);font-size:.88rem}.search{display:grid;grid-template-columns:1fr auto;gap:6px;margin-bottom:12px}.nav-groups h3{margin:14px 0 8px;font-size:.86rem;text-transform:uppercase;color:var(--muted);letter-spacing:.06em}.nav-groups ul{list-style:none;padding:0;margin:0}.nav-groups a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:10px;color:inherit;text-decoration:none;cursor:pointer}.nav-groups a.active,.nav-groups a:hover{background:#0d6efd1f}.content{padding:26px 30px 36px;height:100%;overflow-y:scroll;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent;scrollbar-gutter:stable}.content::-webkit-scrollbar,.sidebar::-webkit-scrollbar{width:10px;height:10px}.content::-webkit-scrollbar-track,.sidebar::-webkit-scrollbar-track{background:transparent}.content::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 65%,transparent);border-radius:6px;border:2px solid transparent;background-clip:content-box}.content::-webkit-scrollbar-thumb:hover,.sidebar::-webkit-scrollbar-thumb:hover{background-color:var(--p-text-muted-color, #64748b)}.sidebar{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent}.breadcrumbs{font-size:.85rem;color:var(--muted);margin-bottom:10px}header h2{margin:0;font-size:2rem}header p{margin:8px 0 12px;color:var(--muted)}.tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}::ng-deep .tier-tag.p-tag{font-weight:700;letter-spacing:.02em}::ng-deep .tier-tag-base.p-tag{background:color-mix(in srgb,#6366f1 15%,var(--panel, #fff));color:color-mix(in srgb,#4338ca 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#6366f1 45%,transparent)}::ng-deep .tier-tag-pro.p-tag{background:color-mix(in srgb,#f59e0b 18%,var(--panel, #fff));color:color-mix(in srgb,#b45309 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#f59e0b 55%,transparent)}.body-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,360px);gap:20px}article{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:20px}.doc-section{padding:6px 0 16px;border-bottom:1px dashed var(--border)}.doc-section:last-child{border-bottom:0}.doc-section h3{margin:8px 0}.section-html{color:var(--text)}:host ::ng-deep .section-html p{line-height:1.58}:host ::ng-deep .section-html table{width:100%;border-collapse:collapse;margin:16px 0 24px;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:.92rem;box-shadow:0 1px 2px color-mix(in srgb,var(--text) 6%,transparent)}:host ::ng-deep .section-html thead{background:color-mix(in srgb,var(--accent) 14%,var(--panel))}:host ::ng-deep .section-html thead th{text-align:left;color:var(--text);font-weight:700;padding:12px 16px;border:1px solid var(--border);border-bottom:2px solid color-mix(in srgb,var(--accent) 35%,var(--border));text-transform:uppercase;font-size:.78rem;letter-spacing:.4px;white-space:nowrap}:host ::ng-deep .section-html tbody td{padding:11px 16px;border:1px solid color-mix(in srgb,var(--border) 80%,transparent);vertical-align:top;line-height:1.55;color:var(--text)}:host ::ng-deep .section-html tbody tr:nth-child(odd) td{background:color-mix(in srgb,var(--panel) 92%,transparent)}:host ::ng-deep .section-html tbody tr:nth-child(2n) td{background:color-mix(in srgb,var(--panel) 100%,transparent)}:host ::ng-deep .section-html tbody tr:hover td{background:color-mix(in srgb,var(--accent) 8%,var(--panel))}:host ::ng-deep .section-html table code{font-size:.85em;padding:1px 5px}:host ::ng-deep .section-html strong{color:var(--text);font-weight:600}:host ::ng-deep .section-html em{font-style:italic}:host ::ng-deep .section-html a.wuic-demo-link{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;margin:0 2px;background:color-mix(in srgb,var(--accent) 14%,var(--panel));border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));border-radius:6px;color:var(--accent, #4f46e5);font-size:.85rem;font-weight:600;text-decoration:none;line-height:1.2;transition:background .15s ease}:host ::ng-deep .section-html a.wuic-demo-link:before{content:\"\\25b6\";font-size:.7rem;color:var(--accent, #4f46e5)}:host ::ng-deep .section-html a.wuic-demo-link:hover{background:color-mix(in srgb,var(--accent) 22%,var(--panel));text-decoration:none}:host ::ng-deep .section-html :where(ul,ol){margin:12px 0 16px;padding-left:0}:host ::ng-deep .section-html ul{list-style:none}:host ::ng-deep .section-html ul>li{position:relative;margin:8px 0;padding:8px 12px 8px 34px;border:1px solid color-mix(in srgb,var(--border) 78%,transparent);border-radius:10px;background:color-mix(in srgb,var(--panel) 90%,transparent);line-height:1.52}:host ::ng-deep .section-html ul>li:nth-child(odd){background:color-mix(in srgb,var(--panel) 94%,transparent)}:host ::ng-deep .section-html ul>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 5%,var(--panel));border-color:color-mix(in srgb,var(--accent) 18%,var(--border))}:host ::ng-deep .section-html ul>li:before{content:\"\";position:absolute;left:12px;top:50%;width:10px;height:10px;border-radius:999px;transform:translateY(-50%);background:radial-gradient(circle at 35% 35%,#fff 0 24%,color-mix(in srgb,var(--accent) 80%,#4aa3ff) 25% 100%);box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 32%,transparent)}:host ::ng-deep .section-html ol{list-style:none;counter-reset:wuic-doc-step}:host ::ng-deep .section-html ol>li{position:relative;counter-increment:wuic-doc-step;margin:8px 0;padding:8px 12px 8px 42px;border-left:3px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));border-radius:10px;line-height:1.52}:host ::ng-deep .section-html ol>li:nth-child(odd){background:color-mix(in srgb,var(--accent) 7%,var(--panel))}:host ::ng-deep .section-html ol>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 11%,var(--panel));border-left-color:color-mix(in srgb,var(--accent) 72%,transparent)}:host ::ng-deep .section-html ol>li:before{content:counter(wuic-doc-step);position:absolute;left:10px;top:50%;width:22px;height:22px;border-radius:999px;transform:translateY(-50%);display:grid;place-items:center;font-size:.78rem;font-weight:700;color:#fff;background:linear-gradient(160deg,color-mix(in srgb,var(--accent) 80%,#4aa3ff),color-mix(in srgb,var(--accent) 95%,#125dcc));box-shadow:0 2px 10px color-mix(in srgb,var(--accent) 35%,transparent)}:host ::ng-deep .section-html li>:where(ul,ol){margin-top:8px}.code-block{margin:10px 0;border:1px solid var(--border);border-radius:12px;overflow:hidden}.code-header{display:flex;justify-content:space-between;align-items:center;background:#0d6efd14;padding:8px 10px}.code-block pre{margin:0;padding:12px;overflow:auto;background:#1e1e1e}.code-block code{display:block;font-family:Consolas,Cascadia Code,Fira Code,Courier New,monospace;font-size:.88rem;line-height:1.55;color:#d4d4d4;white-space:pre}.code-actions{display:inline-flex;gap:8px;align-items:center}.code-meta{display:inline-flex;align-items:center;gap:10px}.snippet-lang{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:22px;padding:0 8px;border-radius:999px;font-size:.72rem;font-weight:700;letter-spacing:.04em;border:1px solid transparent;background:color-mix(in srgb,var(--text) 8%,transparent);color:var(--text)}.snippet-lang.lang-html{background:#e36c0924;border-color:#e36c0952;color:#b25d0e}.snippet-lang.lang-json{background:#00808024;border-color:#00808052;color:#0e6b6b}.snippet-lang.lang-ts{background:#3178c624;border-color:#3178c652;color:#1f5f9f}.snippet-lang.lang-cs{background:#68217a24;border-color:#68217a52;color:#6a2b82}.snippet-lang i{font-size:.72rem}:host ::ng-deep .code-block code .tok-comment{color:#6a9955}:host ::ng-deep .code-block code .tok-string{color:#ce9178}:host ::ng-deep .code-block code .tok-number{color:#b5cea8}:host ::ng-deep .code-block code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block code .tok-op{color:#d4d4d4}:host ::ng-deep .code-block.lang-html code .tok-tag{color:#569cd6}:host ::ng-deep .code-block.lang-html code .tok-attr{color:#9cdcfe}:host ::ng-deep .code-block.lang-html code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-key{color:#9cdcfe}:host ::ng-deep .code-block.lang-json code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-json code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-ts code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-ts code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block.lang-ts code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-ts code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-ts code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-ts code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-ts code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-cs code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-cs code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-cs code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-cs code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-cs code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-cs code .tok-number{color:#b5cea8}.screenshots .shots-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;align-items:start}.screenshots figure{margin:0;display:flex;flex-direction:column;gap:8px}.screenshots img{width:100%;border-radius:12px;border:1px solid var(--border)}:host ::ng-deep .screenshots .p-image,:host ::ng-deep .screenshots .p-image img{width:100%;display:block}:host ::ng-deep .screenshots .p-image{aspect-ratio:16/9;overflow:hidden;border-radius:12px;border:1px solid var(--border);background:color-mix(in srgb,var(--panel) 90%,transparent)}:host ::ng-deep .screenshots .p-image img{width:100%;height:100%;object-fit:contain;object-position:center;border:0}:host ::ng-deep .screenshots .p-image .p-image-preview-mask,:host ::ng-deep .screenshots .p-image .p-image-mask{inset:0;width:100%;height:100%}.missing{border:1px dashed var(--border);border-radius:12px;padding:36px 10px;text-align:center;color:var(--muted)}:host ::ng-deep .p-image-mask,:host ::ng-deep .p-image-preview-mask{background:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .p-image-toolbar{background:#0c1220!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;filter:none!important;border:1px solid rgba(255,255,255,.14)!important;box-shadow:0 8px 24px #00000059!important}:host ::ng-deep .p-image-action{color:#eaf1ff!important;opacity:1!important;text-shadow:none!important}:host ::ng-deep .p-image-preview{background:#7eb2ff2e!important}::ng-deep .p-image-mask.p-component-overlay,::ng-deep .p-image-preview-mask.p-component-overlay{background:#0c121efa!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .screenshots .p-image .p-image-mask,:host ::ng-deep .screenshots .p-image .p-image-preview-indicator{background:#0c121e42!important}.themes-catalog{margin-top:14px;padding:6px 0 16px;border-top:1px dashed var(--border)}.themes-catalog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}.themes-catalog-item{display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;padding:.45rem .65rem;border:1px solid var(--border);border-radius:10px;background:color-mix(in srgb,var(--panel) 92%,transparent)}.theme-dot{width:.8rem;height:.8rem;border-radius:999px;border:1px solid color-mix(in srgb,var(--text) 25%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,#ffffff 55%,transparent) inset;flex:0 0 auto}.toc{position:sticky;top:20px;max-height:calc(100vh - 120px);overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px}.toc h4{margin:0 0 8px}.toc ul{margin:0;padding-left:18px;list-style-position:outside}.toc a{display:block;color:var(--text);text-decoration:none;line-height:1.25;overflow-wrap:anywhere;word-break:break-word}.toc li{margin:0 0 6px}.toc li:last-child{margin-bottom:0}@media(max-width:1360px)and (min-width:1101px){.body-grid{grid-template-columns:minmax(0,1fr) minmax(280px,320px)}}@media(max-width:1100px){.fw-docs{grid-template-columns:1fr;height:auto;overflow:visible}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--border)}.content{height:auto;overflow:visible}.body-grid{grid-template-columns:1fr}.toc{position:static}}\n"] }]
|
|
26415
27504
|
}], ctorParameters: () => [{ type: i1.Router }, { type: i1.ActivatedRoute }], propDecorators: { contentScroller: [{
|
|
@@ -26418,4 +27507,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
26418
27507
|
}] } });
|
|
26419
27508
|
|
|
26420
27509
|
export { FrameworkDocsComponent };
|
|
26421
|
-
//# sourceMappingURL=wuic-framework-lib-framework-docs.component-
|
|
27510
|
+
//# sourceMappingURL=wuic-framework-lib-framework-docs.component-CxckBsIX.mjs.map
|