Le pagine ShadowUI
Documentazione storica, non aggiornata.
Il template ShadowUI permette di suddividere l’interfaccia utente dell’applicazione in pagine diverse, in cui ogni pagina corrisponde ad una videata. A differenza di IonicUI, ShadowUI offre due varianti di page controller, ottimizzate per diversi scenari d’uso.
ShaMainView vs ShaMainViewSticky
Vediamo le differenze tra i due page controller di ShadowUI confrontando le loro caratteristiche.
ShaMainView
Page controller base per applicazioni con sidebar standard.
Adatto per:
- Applicazioni con menu laterale sempre visibile
- Layout classico sidebar + contenuto
- Navigazione semplice senza breadcrumb permanente.
ShaMainViewSticky
Page controller avanzato con header sticky e breadcrumb.
Adatto per:
- Applicazioni professionali con navigazione complessa
- Header fisso in alto con breadcrumb navigation
- Sidebar collassabile con layout moderno
- Gestione automatica della cronologia di navigazione.
Nota importante: in questo manuale ci concentreremo su ShaMainViewSticky essendo la variante più completa e moderna.
Struttura del Page Controller
La struttura base di un page controller ShadowUI è costituita dai seguenti livelli.
Livello 1 – ShaSidebarContainer
- Contenitore principale con layout verticale
- Gestisce la divisione tra header (fisso in alto) e contenuto principale
- Visibile su tutti i dispositivi
Livello 2a – ShaHeaderBar
- Header fisso in alto
- Contiene:
- Pulsante menu: Per aprire/chiudere la sidebar su mobile
- ShaBreadcrumb: Navigazione automatica che mostra il percorso
- ShaDropdownMenu: Menu utente con profilo, impostazioni, logout
- Sempre visibile durante lo scroll del contenuto
Livello 2b – mainCtn (Container)
- Contenitore orizzontale
- Divide lo spazio tra sidebar (a sinistra) e contenuto pagine (a destra)
- Si adatta automaticamente alla larghezza disponibile
Livello 3 – Sidebar e Contenuto
- ShaSidebar: Menu laterale collassabile
- ShaSidebarHeader: Logo, titolo progetto, badge
- ShaSidebarContent: Voci di menu (ShaSidebarGroup + ShaSidebarItem)
- ShaSidebarFooter: Impostazioni, info utente
- ShaNavController: Stack che contiene le pagine aperte, gestisce le transizioni tra pagine, mantiene lo stack di navigazione, supporta animazioni tra pagine
Struttura gerarchica completa
ShaMainViewSticky
└── ShaSidebarContainer
├── ShaHeaderBar (STICKY - fisso in alto)
│ └── ShaBreadcrumb
│
└── mainCtn
├── ShaSidebar (laterale)
│ ├── ShaSidebarHeader
│ ├── ShaSidebarContent
│ └── ShaSidebarFooter
│
└── ShaSidebarPageContainer
└── ShaNavController (gestisce stack pagine)
L’Intestazione (ShaHeaderBar)
L’header sticky contiene gli elementi fondamentali per la navigazione:
- Pulsante menu: Apre/chiude la sidebar (visibile su mobile)
- Breadcrumb: Mostra il percorso di navigazione corrente
- Menu utente: Dropdown con profilo, impostazioni, logout
Il breadcrumb viene aggiornato automaticamente dal page controller ogni volta che si naviga tra le pagine. Mostra il percorso completo di navigazione e permette di tornare a qualsiasi pagina precedente cliccando su di essa.
Il breadcrumb si popola in base al parametro title passato al metodo push():
view.push(App.DettaglioProgetto, {
title: "Dettaglio Progetto", // ← Questo appare nel breadcrumb
root: false
});
La Sidebar (ShaSidebar)
ShaSidebarHeader
L’header contiene tipicamente:
- Logo dell’applicazione
- Nome del progetto corrente
- Badge informativi (es. chiave progetto, stato)
ShaSidebarContent
Nel content vanno inseriti i menu di navigazione:
- ShaSidebarGroup: gruppi di menu con etichetta (es. “Navigation”, “Admin”)
- ShaSidebarItem: singole voci di menu cliccabili
ShaSidebarFooter
Il footer contiene la sezione in fondo alla sidebar per elementi secondari:
- Impostazioni
- Help/Documentazione
- Info utente
Nell’immagine seguente un esempio di utilizzo di ShaMainView.

Il contenitore pagine (ShaNavController)
Il ShaNavController è il componente che gestisce lo stack di pagine aperte. Quando si chiamano i metodi push() o pop() del page controller, le videate vengono aggiunte o rimosse da questo stack.
Il nav controller gestisce automaticamente:
- Animazioni di transizione tra pagine
- Stack di navigazione (history)
- Evento onBack quando si torna indietro
- Rimozione delle pagine dallo stack
Navigazione: proprietà e metodi del Page Controller
Sia ShaMainView che ShaMainViewSticky ereditano una serie di proprietà e metodi per gestire la navigazione tra le pagine dell’applicazione.
Questi metodi permettono di:
- Aprire nuove pagine (push)
- Tornare indietro (pop)
- Rimuovere pagine dallo stack (remove)
- Controllare la sidebar (toggleMenu)
- Aggiornare breadcrumb e titoli (setTitle, setBreadcrumb).
Proprietà principali
| Proprietà | Tipo | Descrizione |
|---|---|---|
| navStack | array | Stack delle view navigate (cronologia) |
| removing | boolean | Flag per evitare operazioni concorrenti durante rimozione |
| actualActivePageIndex | number | Indice della pagina attualmente attiva |
| menuDisabled | boolean | Stato disabilitato del menu sidebar |
| animateView | boolean | Abilita/disabilita animazioni di transizione |
Il navStack contiene oggetti con questa struttura:
{
view: viewInstance, // Istanza della view
options: { ... }, // Opzioni passate a push()
index: 0 // Posizione nel ShaNavController
}
Metodo push()
Naviga a una nuova view aggiungendola allo stack, questa la sua sintassi:
view.push(viewclass, options)
Parametri options:
| Parametro | Tipo | Descrizione |
|---|---|---|
| root | boolean | Se true, sostituisce tutto lo stack (pagina principale) |
| popup | boolean | Apre come popup invece che nel contenitore |
| animate | boolean | Abilita animazione di transizione (default: true) |
| wait | boolean | Attende 10ms prima di eseguire la transizione |
| remove | boolean | Rimuove le view precedenti dallo stack |
| title | string | Titolo da mostrare nel breadcrumb |
Esempi:
// 1. Navigazione standard
function* dashboardItem_onClick() {
yield view.push(App.DashboardView, {
title: "Dashboard"
});
}
// 2. Apertura dettaglio da lista
function* ticketCard_onClick() {
let ticket = this.sender.row;
yield view.push(App.TicketDetailView, {
title: `Ticket #${ticket.id}`,
ticketId: ticket.id
});
}
// 3. Pagina principale (root) - sostituisce tutto lo stack
function* projectsItem_onClick() {
yield view.push(App.ProjectsView, {
root: true,
title: "Progetti"
});
}
// 4. Apertura come popup
function* settingsButton_onClick() {
yield view.push(App.SettingsView, {
popup: true,
title: "Impostazioni"
});
}
Comportamento con root impostato a true:
- Elimina tutte le view precedenti dallo stack
- Disabilita l’animazione di default
- La nuova view diventa la “radice” della navigazione
- Non mostra pulsante “indietro” nel breadcrumb
Metodo pop()
Torna indietro nello stack, chiudendo le view successive.
Sintassi:
view.pop(viewOrCount, info)
Parametri:
- viewOrCount: Numero di view da chiudere oppure classe della view target
- info: Oggetto con dati da passare alla view precedente (opzionale)
Esempi:
// 1. Torna indietro di una view
function* backButton_onClick() {
yield view.pop();
}
// 2. Torna indietro di 3 view
function* closeAllButton_onClick() {
yield view.pop(3);
}
// 3. Torna a una view specifica
function* backToDashboard_onClick() {
yield view.pop(App.DashboardView);
}
// 4. Torna indietro passando dati
function* saveButton_onClick() {
yield this.view.ticketDM.save();
yield view.pop(1, {
saved: true,
ticketId: this.ticket.id
});
}
Per ricevere i dati con l’evento onBack:
La view precedente può intercettare il ritorno implementando l’evento onBack:
// In TicketListView
function* onBack(closedView, info) {
if (info.saved) {
// Ricarica la lista
yield this.view.ticketsDM.load();
yield app.toast(`Ticket ${info.ticketId} salvato`, {
variant: "success"
});
}
}
Metodo remove()
Rimuove view dallo stack senza navigare indietro. Utile per pulire lo stack mantenendo la view corrente.
Sintassi:
view.remove(viewOrCount, info)
Parametri:
- viewOrCount: Numero di view da rimuovere oppure classe della view oppure true per rimuovere tutto
- info: Oggetto con informazioni (opzionale)
Esempi:
// 1. Rimuovi la view precedente (quella prima della corrente)
yield view.remove(1);
// 2. Rimuovi tutte le view tranne la corrente
yield view.remove(true);
// 3. Flusso: Dashboard → List → Detail → Edit
// Dopo save, vuoi: Dashboard → Detail (salta List)
function* saveButton_onClick() {
yieldthis.saveProject();
// Rimuovi List dallo stack
yield view.remove(2);
// Torna a Detail
yield view.pop(1, { saved: true });
}
Metodo toggleMenu()
Apre o chiude la sidebar.
Sintassi:
view.toggleMenu(show)
Parametri:
- show (boolean): true per aprire, false per chiudere, undefined per toggle
Esempi:
// 1. Toggle (apri se chiusa, chiudi se aperta)
function* menuButton_onClick() {
yield view.toggleMenu();
}
// 2. Chiudi menu dopo selezione su mobile
function* menuItem_onClick() {
yield view.push(App.TargetView, { title: "Pagina" });
if (app.device.isMobile) {
yield view.toggleMenu(false);
}
}
Metodo setTitle() – Solo ShaMainViewSticky
Aggiorna il titolo dell’ultima view nello stack e rigenera il breadcrumb.
Sintassi:
view.setTitle(newTitle)
Esempi:
// 1. Aggiorna titolo dopo caricamento dati
function* onLoad() {
yield this.view.projectDM.load();
let project = this.view.projectDM.getData();
view.setTitle(`Progetto: ${project.name}`);
}
// 2. Aggiorna titolo durante modifica
function* titleInput_onChange() {
let title = this.view.titleInput.value;
view.setTitle(`Modifica: ${title}`);
}
Metodo setBreadcrumb()
Rigenera il breadcrumb basandosi sullo stack corrente. Viene chiamato automaticamente da push(), pop(), e setTitle().
Il breadcrumb mostra tutte le view con un title nelle options (esclusi i popup).
Navigazione breadcrumb: Quando l’utente clicca su un elemento del breadcrumb, viene automaticamente chiamato pop() per tornare a quella view.
Metodo getActivePage()
Ottiene la view attualmente attiva (l’ultima nello stack).
Sintassi:
let currentView = view.getActivePage();
Esempio:
function* refreshButton_onClick() {
let activeView = view.getActivePage();
if (activeView.refreshData) {
yield activeView.refreshData();
}
}
Metodo postMessage()
Invia un messaggio alle view nello stack. Di default invia solo all’ultima view, con bc: true invia a tutte (broadcast).
Sintassi:
view.postMessage(message)
Esempi:
// 1. Notifica aggiornamento dati alla view corrente
yield view.postMessage({
type: "data-updated",
entity: "ticket",
id: 123
});
// In TicketListView - Ricevi messaggio
function* onMessage(message) {
if (message.type === "data-updated") {
yield this.view.ticketsDM.load();
}
}
// 2. Broadcast a tutte le view
yield view.postMessage({
bc: true,
type: "theme-changed",
theme: "dark"
});
Pattern comuni
Flusso Master-Detail.
// Lista → Dettaglio → Modifica → Torna a Dettaglio
// ProjectsView
function* projectCard_onClick() {
let project = this.sender.row;
yield view.push(App.ProjectDetailView, {
title: project.name,
projectId: project.id
});
}
// ProjectDetailView
function* editButton_onClick() {
yield view.push(App.ProjectEditView, {
title: "Modifica Progetto",
projectId: this.options.projectId
});
}
// ProjectEditView
function* saveButton_onClick() {
yield this.view.projectDM.save();
yield view.pop(1, { saved: true });
}
// ProjectDetailView - onBack
function* onBack(closedView, info) {
if (info.saved) {
yield this.view.projectDM.load();
yield app.toast("Salvato", { variant: "success" });
}
}
Logout con pulizia stack.
function* logoutButton_onClick() {
let confirm = yield app.popup(
"Conferma",
"Sei sicuro di voler uscire?",
["Annulla", "Esci"]
);
if (confirm === 1) {
yield app.logout();
// Torna a login pulendo tutto
yield view.push(App.LoginView, {
root: true,
remove: true,
animate: false
});
}
Menu dinamico con DataMap.
// MainPage - onLoad
function* onLoad() {
yield this.view.projectsDM.load();
}
// MainPage - projectsDM_onRowComposition
function* projectsDM_onRowComposition(row) {
let item = this.view.addChild(
ShaSidebarItem,
this.view.projectsGroup
);
item.label = row.name;
item.icon = "shaicons shaicons-folder";
item.row = row;
}
// MainPage - projectItem_onClick
function* projectItem_onClick() {
let project = this.sender.row;
yield view.push(App.ProjectDetailView, {
root: true,
title: project.name,
projectId: project.id
});
}
Differenze ShaMainView vs ShaMainViewSticky.
| Caratteristica | ShaMainView | ShaMainViewSticky |
|---|---|---|
| Header | Scrolla con contenuto | Fisso in alto (sticky) |
| Breadcrumb | Dentro mainCtn | Nel livello top (sempre visibile) |
| Metodo setTitle | Non disponibile | Disponibile |
| Sidebar padding | Standard | padding-top: 4rem |
| Z-index header | Standard | 11 (sempre sopra) |
| Caso d’uso | App semplici | App professionali con navigazione complessa |
Best Practices
Titoli descrittivi
Usa sempre titoli significativi per il breadcrumb.
// ❌ Male
yield view.push(App.DetailView, { title: "Dettagli" });
// ✅ Bene
yield view.push(App.TicketDetailView, {
title: `Ticket #${ticket.id} - ${ticket.title}`
});
Root per pagine principali
Usa root: true solo per view di primo livello.
// Dashboard, Projects, Settings = root
yield view.push(App.DashboardView, { root: true, title: "Dashboard" });
// Detail, Edit, etc = NO root
yield view.push(App.TicketDetailView, { title: "Ticket #123" });
Pulizia stack
Evita stack troppo profondi.
// Se lo stack diventa: A → B → C → D → E
// E non serve tornare a B, C, D:
yield view.remove(3); // Rimuovi B, C, D
yield view.pop(); // Torna ad A
Feedback visivo
Disabilita pulsanti durante la navigazione.
function* openButton_onClick() {
this.view.openButton.disabled = true;
yield view.push(App.TargetView, { title: "..." });
this.view.openButton.disabled = false;
}
Creare un Page Controller nella propria applicazione
Per utilizzare il page controller nella propria applicazione, seguire questi passaggi:
- Creare una videata, ad esempio di nome MainPage
- Cambiare la classe della videata da Application.View a Shadow.ShaMainViewSticky
- La struttura base apparirà automaticamente nella videata
Subito dopo aver impostato l’estensione, all’interno della videata che prima era vuota appariranno le strutture personalizzabili del page controller.
Il page controller è quindi costituito da: – Una sezione visuale per il menu (da personalizzare in ShaSidebarContent) – Una serie di metodi ereditati dalla classe base per gestire la navigazione (push, pop, toggleMenu, ecc.)
Definire le voci di menu
Per definire le voci di menu, inserire gli elementi all’interno di ShaSidebarContent:
- Aprire la videata MainPage nell’IDE
- Navigare a: shadcnSidebarContainer > mainCtn > shadcnSidebar > shadcnSidebarContent
- Aggiungere ShaSidebarGroup per raggruppare le voci
- All’interno di ogni gruppo, aggiungere ShaSidebarItem
- Per ogni ShaSidebarItem, impostare:
- label: Testo della voce
- icon: Icona (es. “shaicons shaicons-home”)
- iconPosition: Posizione icona (left/right)
- Gestire l’evento onClick di ogni item per aprire la videata corrispondente
Esempio implementazione:
// MainPage - dashboardItem onClick
function* dashboardItem_onClick() {
yield view.push(App.DashboardView, {
root: true,
title: "Dashboard"
});
// Chiudi menu su mobile
if (app.device.isMobile) {
yield view.toggleMenu(false);
}
}