- ShaMainView vs ShaMainViewSticky
- Struttura del Page Controller
- Struttura gerarchica completa
- L’Intestazione (ShaHeaderBar)
- La Sidebar (ShaSidebar)
- Il contenitore pagine (ShaNavController)
- Navigazione: proprietà e metodi del Page Controller
- Creare un Page Controller nella propria applicazione
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);
}
}