Frontend

Viste BXM renderizzate lato server, piccoli componenti Alpine.js e una pipeline SCSS/JS compilata da Vite.

In questa pagina

Frontend

Come si combina il tutto

Il frontend è un'applicazione ibrida server-rendered + Alpine.js - nessuna SPA, nessun router lato client:

1
I layout ColdBox forniscono la struttura

Admin.bxm, AuthSplit.bxm e simili in app/layouts/ renderizzano il frame HTML.

2
I template BXM si renderizzano lato server

Le viste in app/views/ si renderizzano con i dati rc/prc già risolti dall'handler.

3
Alpine.js aggiunge interattività

Piccoli componenti x-data gestiscono form, modali, drawer e toggle - senza bisogno di uno step di build per componente.

4
Vite compila gli asset

SCSS + JS da resources/assets/ compilano in public/includes/, serviti al prefisso ASSET_URL.

Architettura Alpine.js

App.js (entry)
  ├── Registers all Alpine stores + components
  ├── Imports Bootstrap JS + Phosphor icons + Tippy.js
  │
  ├── Stores ($store.*)
  │   ├── theme.js     → dark/light mode, syncs data-bs-theme + localStorage
  │   └── sidebar.js   → collapse/open, mobile overlay, localStorage persistence
  │
  └── Components (x-data)
      ├── auth/        → AuthForm, RegisterForm, ForgotPasswordForm, PasswordResetForm
    ├── security/     → AuditLogForm, PermissionsForm, RolesForm, UserDetailForm, UsersForm
    ├── profile/      → PasskeyOnboarding, PreferencesForm, ProfileForm
    ├── settings/     → SettingsForm, SettingsRegistryForm
    └── ui/           → Drawer, GlobalProgress, GlobalToast, Logo, MessageBox, PasswordMeter, PasswordStrength, Switch

Ogni componente è un modulo indipendente che restituisce un oggetto Alpine x-data:

export default () => ( {
    visible: true,
    init() {
        setTimeout( () => this.visible = false, 5000 );
    }
} );
<div x-data="messageBox" x-show="visible" x-transition>
    <!-- alert content -->
</div>

Struttura SCSS

app.scss
  ├── _variables.scss   Bootstrap variable overrides
  ├── bootstrap          Full Bootstrap 5.3 import
  ├── _base.scss         CSS custom properties (light/dark theme)
  ├── components/        9 component partials
  ├── layouts/            Admin + Auth layout partials
  └── views/              Page-specific styles

Configurazione Vite

vite.config.mjs usa il plugin coldbox() di coldbox-vite-plugin:

  • Entry point: resources/assets/scss/app.scss e resources/assets/js/App.js
  • refresh: appRefreshPaths — ricaricamento completo automatico su modifiche a handler/viste
  • publicDirectory: "public/includes" — dove atterrano gli asset compilati
  • Preprocessore SCSS con flag silenceDeprecations per le versioni più recenti di Dart Sass (import, global-builtin, color-functions, if-function)
npm run dev        # Server di sviluppo Vite con HMR
npm run build      # Build di produzione → public/includes/
npm run lint       # Controllo ESLint su resources/assets/js
npm run lint:fix   # Correzione automatica ESLint
npm run lint:scss  # Stylelint su resources/assets/scss
ASSET_URL

In produzione, gli URL degli asset compilati sono preceduti dalla variabile d'ambiente ASSET_URL (.env.example la imposta per default a /includes) - vedi Configurazione.

Componenti di vista renderizzati lato server

Questi partial BXM vivono sotto app/views/_components/ e vengono renderizzati con l'helper view() di ColdBox. Sono intenzionalmente focalizzati sulla presentazione: passa i valori tramite la struct args e mantieni la logica di business negli handler o nei servizi.

Shell dell'applicazione

PartialScopo e input
_components/app/includesMetadati del documento, prevenzione FOUC per tema/sidebar, script delle passkey, e CSS/JS di Vite. title opzionale. Includi una sola volta in <head>.
_components/app/sidebarNavigazione admin, link Users/Roles/Permissions/Audit Log sensibili ai permessi, sottomenu impostazioni e footer della sidebar. Legge prc.authUser; includi da Admin.bxm.
_components/app/sidebar-brandLink logo/nome dell'applicazione usato dalla sidebar.
_components/app/sidebar-footerRiepilogo dell'utente autenticato e azioni profilo/disconnessione usate dalla sidebar.
_components/app/topbarToggle sidebar, toggle tema, breadcrumb, menu utente e azione di disconnessione. Legge prc.authUser e prc.title.
_components/app/topbar-breadcrumbsBreadcrumb della dashboard renderizzato dentro la topbar. Estendi quando aggiungi navigazione più profonda.
_components/app/topbar-notificationsSlot/componente di notifica della topbar per le notifiche dell'applicazione.
_components/app/footerCopyright e link del footer. classes opzionale. Legge prc.settings.cbCopyrightNotice.

Partial di autenticazione

PartialScopo e input
_components/auth/footerFooter usato dai layout di autenticazione.
_components/auth/passwordInputCampo password riutilizzabile con toggle di visibilità e indicazioni sulla robustezza della password.

Partial UI

PartialScopo e input
_components/ui/modalDialog generico Alpine che renderizza una vista annidata opzionale. id richiesto dovrebbe essere univoco; supporta title, openExpression, closeExpression, contentView e contentArgs.
_components/ui/drawerDialog sul lato destro con focus-trap, chiusura tramite backdrop/Escape e contentView/contentArgs opzionali; inizializza anche drawer().
_components/ui/confirmDialog di conferma con messaggio statico o legato ad Alpine, espressioni conferma/annulla, etichette, icona, classe pulsante ed espressione disabled.
_components/ui/messageboxAlert informazione/successo/avviso/errore che può essere chiuso. Supporta message/title statici o messageExpression/typeExpression/dismissAction dinamici, più autoDismiss e classes.
_components/ui/globalProgressBarra di progresso globale accessibile. Includi una volta per layout; controllata da $progress.start()`, `$progress.set(), e $progress.stop().
_components/ui/globalToastStack di toast globale. Includi una volta per layout; accetta duration, position, e maxVisible, e riceve notifiche da $toast().
_components/ui/avatarRenderizza l'immagine avatar di un utente quando hasAvatar è true, altrimenti ricade sulle initials. Visualizzazione read-only usata dalla sidebar, dalla topbar, dall'elenco Users e dalla pagina di dettaglio Users — vedi Avatar e logo del branding.
_components/ui/logoPartial riutilizzabile del logo/branding dell'applicazione.
_components/ui/passwordMeterMisuratore della policy password usato accanto ai campi password.
_components/ui/progressbarPartial di barra di progresso inline per un valore numerico locale.
_components/ui/switchPartial di controllo switch accessibile per le impostazioni booleane.

Componenti e store Alpine

resources/assets/js/App.js registra globalmente i seguenti nomi con Alpine. Usali come x-data="name" o x-data="name(...)" nelle viste BXM. I componenti dei form fanno richieste remote verso le rotte handler corrispondenti e si aspettano il token CSRF fornito dalla loro vista, inviato tramite fetchWithCsrf() (vedi CSRF sulle richieste che modificano dati).

Shell dell'applicazione e autenticazione

Nome AlpineSorgenteResponsabilità
adminBodycomponents/app/AdminBody.jsComportamento della shell della pagina admin ed eventi di layout globali.
sidebarBrandcomponents/app/SidebarBrand.jsInterazioni del brand nella sidebar.
footercomponents/app/Footer.jsStato del footer e comportamento dell'anno corrente.
authFormcomponents/auth/AuthForm.jsInvio del login, validazione, remember-me ed errori.
registerFormcomponents/auth/RegisterForm.jsValidazione della registrazione, disponibilità email e invio.
forgotPasswordFormcomponents/auth/ForgotPasswordForm.jsStato e feedback della richiesta di password dimenticata.
passwordResetFormcomponents/auth/PasswordResetForm.jsInvio e validazione del token di reset password.

Form admin e profilo

Nome AlpineSorgenteResponsabilità
usersFormcomponents/security/UsersForm.jsElenco utenti, ricerca, paginazione, invito, stato e azioni admin.
userDetailFormcomponents/security/UserDetailForm.jsProfilo utente, ruolo, permesso, preferenza, token e azioni di verifica.
rolesFormcomponents/security/RolesForm.jsCRUD dei ruoli e assegnazione/rimozione di utenti e permessi.
permissionsFormcomponents/security/PermissionsForm.jsElenco dei permessi e operazioni CRUD.
auditLogFormcomponents/security/AuditLogForm.jsFiltro di audit, paginazione, drawer di dettaglio, esportazione CSV, purge e azioni di clear.
settingsFormcomponents/settings/SettingsForm.jsModifica delle impostazioni core dell'applicazione e feedback relativo alla cache.
logoUploadercomponents/settings/LogoUploader.jsCaricamento/rimozione del logo del branding per il campo "App Logo Path", accanto al suo input URL manuale esistente e all'anteprima live — vedi Avatar e logo del branding.
settingsRegistryFormcomponents/settings/SettingsRegistryForm.jsRicerca nel registro, paginazione, creazione/aggiornamento, abilitazione/disabilitazione e azioni di eliminazione.
profileFormcomponents/profile/ProfileForm.jsCampi del profilo, policy password, gestione dei token API, il sotto-form di richiesta/annullamento del cambio email, e caricamento/rimozione dell'avatar.
preferencesFormcomponents/profile/PreferencesForm.jsPersistenza delle preferenze utente.
passkeyOnboardingcomponents/profile/PasskeyOnboarding.jsRegistrazione delle passkey e onboarding obbligatorio delle passkey.

Componenti UI e API globali

Nome AlpineSorgenteResponsabilità
messageBoxcomponents/ui/MessageBox.jsVisibilità dell'alert e chiusura temporizzata opzionale.
passwordMetercomponents/ui/PasswordMeter.jsVisualizzazione dei requisiti e della robustezza della password.
passwordStrengthcomponents/ui/PasswordStrength.jsCalcolo ed etichette della robustezza della password.
switchComponentcomponents/ui/Switch.jsStato del toggle e gestione del cambiamento.
drawercomponents/ui/Drawer.jsCiclo di vita del drawer e comportamento del focus.
globalProgresscomponents/ui/GlobalProgress.jsEventi di progresso e valore di progresso corrente.
globalToastcomponents/ui/GlobalToast.jsCoda dei toast, chiusura, mappatura del tipo e limiti dello stack.

Il sorgente contiene anche Header.js, Sidebar.js, TopBarNotifications.js, e Logo.js. I loro export sono disponibili per import locali, ma non sono attualmente registrati da App.js; registrali con Alpine.data() prima di usarli come componenti x-data globali.

Store, utility e proprietà magiche

APISorgenteUtilizzo
$store.themestores/theme.jsModalità chiara/scura, data-bs-theme, e persistenza localStorage.
$store.sidebarstores/sidebar.jsCollasso desktop, apertura/chiusura mobile, e persistenza localStorage.
$formatDate`, `$formatDateTime, $relativeDateutils/dateFormat.jsVisualizzazione coerente delle date con fallback.
$countLabelutils/countLabel.jsEtichette di conteggio singolare/plurale.
$sortClass`, `$sortIconutils/sort.jsIntestazioni di tabella ordinabili e indicatori.
$passwordMeetsPolicyutils/passwordPolicy.jsVerifica i requisiti password configurati.
$isEmailApp.jsControllo leggero del formato email.
$toast` / `$progresscomponents/ui/GlobalToast.js, GlobalProgress.jsAPI globali di notifica e progresso.
$focus` / `$copyApp.jsMette a fuoco un discendente dopo gli aggiornamenti di Alpine; copia testo tramite l'API clipboard del browser.
createRemoteListing()utils/listing.jsStato condiviso per elenchi remoti, caricamento, paginazione e gestione errori.
fetchWithCsrf(), refreshCsrfToken()utils/csrf.jsInvia una richiesta che modifica dati con il token CSRF del componente, recuperando una volta da un token obsoleto.

AlpinePlugins.js installa Collapse, Focus, Mask e Persist. passkeys.js fornisce l'integrazione WebAuthn lato browser. Mantieni documentate qui le nuove API browser riutilizzabili e aggiungi la loro registrazione/importazione in App.js quando sono globali.

CSRF sulle richieste che modificano dati

Ogni azione di componente che invia una richiesta non-GET passa attraverso fetchWithCsrf() (utils/csrf.js) invece di chiamare direttamente fetch(). Questo è l'unico punto incapsulato in cui vengono costruite le richieste che modificano dati, quindi il comportamento di recupero del token - e qualsiasi cosa vi venga aggiunta in seguito (hook di richiesta/risposta, header globali, telemetria) - deve cambiare solo qui piuttosto che in ogni componente che modifica lo stato.

Perché deve recuperare affatto. Il csrfToken di un componente viene incorporato una sola volta, quando la sua vista viene renderizzata. Il server può invalidarlo mentre la pagina è ancora aperta, in due modi che la documentazione stessa di cbcsrf segnala: csrfField() (il mixin dietro ogni input nascosto csrf) forza la rotazione del token della sessione al suo primo utilizzo per richiesta, quindi qualsiasi pagina che lo renderizza - Settings, la pagina di passkey richiesta, le pagine di auth - invalida silenziosamente il token seduto in ogni altra scheda aperta; e un token scade un tempo fisso dopo essere stato creato, non dopo il caricamento della pagina, quindi una pagina renderizzata tardi nella vita di un token può essere servita con solo pochi secondi rimasti. In entrambi i casi, il token incorporato di un componente può diventare obsoleto prima che l'utente finisca di digitare.

Il contratto:

export async function fetchWithCsrf( component, url, method, buildRequest ) { /* ... */ }
export async function refreshCsrfToken( component ) { /* ... */ }
  • component è l'istanza del componente Alpine (passa this). Deve esporre una proprietà mutabile csrfToken - fetchWithCsrf() la legge per costruire la richiesta e, in un nuovo tentativo dopo token obsoleto, la sovrascrive con il token corrente della sessione tramite refreshCsrfToken().
  • buildRequest( csrfToken ) restituisce i campi RequestInit specifici del metodo (headers, body, credentials, ecc.) per il token dato. Viene chiamato di nuovo al nuovo tentativo, quindi deve costruire il body ogni volta da capo piuttosto che chiudere su un valore calcolato una sola volta - questo è ciò che permette allo stesso helper di coprire allo stesso modo body URLSearchParams, JSON.stringify(), e FormData.
  • Su un 403, fetchWithCsrf() chiama refreshCsrfToken() e, se ha ottenuto un token genuinamente nuovo, ripete la richiesta una volta con buildRequest() chiamato di nuovo. Un secondo 403 (ad es. un vero fallimento di autorizzazione, o una sessione scaduta del tutto) viene restituito così com'è - i chiamanti hanno comunque bisogno della loro normale gestione degli errori per quel caso.
const response = await fetchWithCsrf( this, "/permissions", "POST", ( csrf ) => ( {
	headers : { "Content-Type": "application/x-www-form-urlencoded" },
	body    : new URLSearchParams( { permission: this.form.permission, csrf } ),
} ) );
const response = await fetchWithCsrf( this, form.action, "POST", ( csrf ) => {
	const formData = new FormData( form );
	formData.set( "csrf", csrf );
	return { body: formData, credentials: "same-origin", headers: { Accept: "application/json" } };
} );

Solo le letture GET/HEAD saltano fetchWithCsrf() e chiamano direttamente fetch() - non trasportano alcun token CSRF e non possono ricevere un 403 per questo motivo. Un pugno di endpoint del modulo cbSecurity (le rotte della cerimonia passkey WebAuthn) vengono anch'essi chiamati con fetch() semplice: si autenticano tramite la cerimonia WebAuthn stessa, non tramite il token CSRF di questa app, quindi sono fuori dall'ambito di questo helper. Ogni altra mutazione in resources/assets/js/components/ passa attraverso fetchWithCsrf(); mantieni i nuovi componenti form coerenti con questo quando aggiungono una richiesta che cambia lo stato del server.

Avatar e logo del branding

Gli avatar degli utenti e il logo del branding dell'applicazione sono memorizzati sul disco privato cbfs assets (vedi Configurazione) e distribuiti in streaming da Assets.bx (vedi Handler e Routing) piuttosto che serviti come file statici.

The Profile page, showing the avatar upload and assigned role
La pagina Profilo, che mostra il caricamento dell'avatar e il ruolo assegnato.
  • La visualizzazione passa attraverso il partial _components/ui/avatar: renderizza <img src="/avatars/:userId/:size"> quando hasAvatar è true, e altrimenti ricade su uno <span> con le iniziali. È collegata alla sidebar, alla topbar, e alla tabella dell'elenco Users (campo hasAvatar proiettato lato server), e in linea nella pagina di dettaglio Users (toggle x-show/x-cloak su user.hasAvatar, poiché l'avatar di quella pagina si trova dentro una scheda riepilogativa guidata da Alpine piuttosto che in un partial statico).
  • Il caricamento/rimozione dell'avatar dell'utente corrente vive nella pagina Profilo, gestito da profileForm (ProfileForm.js): un input file nascosto legge l'immagine selezionata come URI dati base64 (readFileAsDataUrl()) e lo invia a POST /profile/avatar; DELETE /profile/avatar lo rimuove. Entrambi incrementano un contatore version usato come parametro di query per l'invalidamento della cache sull'URL in streaming, poiché il percorso del file stesso non cambia tra i caricamenti.
  • Il logo del branding riceve lo stesso trattamento di caricamento/rimozione nella pagina Impostazioni, tramite il componente logoUploader (LogoUploader.js) contro POST/DELETE /settings/logo. Sostituisce il valore dell'input di testo dell'impostazione cbAppLogo con il percorso in streaming (/branding/logo/lg) al caricamento, e ripristina il default configurato alla rimozione — l'input di testo URL manuale e l'anteprima <img> live continuano a funzionare esattamente come prima per chiunque voglia puntare cbAppLogo a un URL esterno.
  • Entrambi gli endpoint di caricamento accettano le stesse forme: le immagini sono decodificate lato server con BaseSecureHandler.decodeDataUri(), poi ridimensionate/ritagliate in varianti sm/lg JPEG (avatar) o PNG (logo) da ImageService (app/models/system/ImageService.bx).
Modifica questa pagina Scarica Markdown Ultimo aggiornamento Oct 1, 2026, 2:02:30 PM