Inizia gratis

Documentazione · ShadowUI

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.

shadowui main view Instant Developer

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

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àTipoDescrizione
navStackarrayStack delle view navigate (cronologia)
removingbooleanFlag per evitare operazioni concorrenti durante rimozione
actualActivePageIndexnumberIndice della pagina attualmente attiva
menuDisabledbooleanStato disabilitato del menu sidebar
animateViewbooleanAbilita/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:

ParametroTipoDescrizione
rootbooleanSe true, sostituisce tutto lo stack (pagina principale)
popupbooleanApre come popup invece che nel contenitore
animatebooleanAbilita animazione di transizione (default: true)
waitbooleanAttende 10ms prima di eseguire la transizione
removebooleanRimuove le view precedenti dallo stack
titlestringTitolo 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
    });
}
// 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.

CaratteristicaShaMainViewShaMainViewSticky
HeaderScrolla con contenutoFisso in alto (sticky)
BreadcrumbDentro mainCtnNel livello top (sempre visibile)
Metodo setTitleNon disponibileDisponibile
Sidebar paddingStandardpadding-top: 4rem
Z-index headerStandard11 (sempre sopra)
Caso d’usoApp sempliciApp 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:

  1. Creare una videata, ad esempio di nome MainPage
  2. Cambiare la classe della videata da Application.View a Shadow.ShaMainViewSticky
  3. 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:

  1. Aprire la videata MainPage nell’IDE
  2. Navigare a: shadcnSidebarContainer > mainCtn > shadcnSidebar > shadcnSidebarContent
  3. Aggiungere ShaSidebarGroup per raggruppare le voci
  4. All’interno di ogni gruppo, aggiungere ShaSidebarItem
  5. Per ogni ShaSidebarItem, impostare:
  • label: Testo della voce
  • icon: Icona (es. “shaicons shaicons-home”)
  • iconPosition: Posizione icona (left/right)
  1. 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);
  }
}