Personalizzazioni grafiche in FLUID

Vorrei condividere con voi le mie prove di personalizzazione grafica di applicazioni realizzate con il motore grafico FLUID.

Questo articolo è in modalità wiki così posso continuare a modificarlo e rimane tutto in un unico testo.
Naturalmente tutti i suggerimenti sono i benvenuti.

Vorrei affrontare diversi argomenti e li aggiungerò un poco alla volta a questo articolo.

  • Cambiare le icone di default
  • Aggiungere testo alle icone di default
  • Cambiare la posizione della toolbar

Cambiare le icone di default

Le icone utilizzate da FLUID sono quelle di Ionic nella versione 4.5.10 che trovate sul sito ionic.io.

Le icone di Ionic sono dei font e la struttura utilizzata è così composta: ho un oggetto button con la relativa classe che lo identifica e la sua icona con la relativa classe.
Prendiamo ad esempio il bottone di refresh della toolbar di pannello, qui sotto vediamo come vengono creati gli elementi html.

<button click-delay="40" id="37_" customid="pan_1_6_refresh" class="disable-hover button button-default button-icon-only generic-btn panel-toolbar-btn refresh-btn">
  <span class="button-inner">
    <ion-icon class="ion-md-refresh">
    </ion-icon>
  </span>
</button>

Ci interessano in particolare la classe refresh-btn sul button e la classe ion-md-refresh sull’oggetto ion-icon.
Per cambiare l’icona utilizzata possiamo semplicemente aggiungere al file customf.css (da aggiungere nella directory custom della nostra applicazione):

.refresh-btn .ion-md-refresh:before {
    content:"";
}

L’icona in formato font è quella specificata in content:“”;
Come trovo quella giusta?
Per prima cosa la cerco sul sito delle icone Ionic e recupero il suo nome e nel nostro caso per esempio voglio refresh-circle invece di refresh.
Quindi apriamo in un editor di testo il file ionicons4.css che si trova in:

%COMMONPROGRAMFILES(X86)%\INDE\CURRENT\Template\Fluid\objects\ionic\bundles

Cerco la classe dell’icona che voglio sovrascrivere (ion-md-refresh) e copio la specifica:

content:"";

Nel file customf.css quindi va aggiunto:

.refresh-btn .ion-md-refresh:before {
    content:"";
}

L’editor di testo non vi farà vedere il contenuto del carattere font copiato ma funziona.

Aggiungere testo alle icone della toolbar

Per aggiungere il testo ad un’icona di un bottone della toolbar non è sufficiente utilizzare un css personalizzato ma occorre modificare una funzione del motore grafico e precisamente createToolbarConfig che va copiata dal file idfpanel.js, che si trova in:

%COMMONPROGRAMFILES(X86)%\INDE\CURRENT\Template\Fluid\objects\fluid\widgets\panel\idfPanel.js

Quindi nella directory custom dell’applicazione si crea il file customf.js e qui copiamo la funzione createToolbarConfig.

/**
 * Create toolbar configuration
 */
Client.IdfPanel.prototype.createToolbarConfig = function ()
{
  Client.IdfFrame.prototype.createToolbarConfig.call(this);
  //
  // Create toolbar zones configuration
  this.toolbarZonesConfig = [];
  for (let i = 0; i < Client.IdfPanel.maxToolbarZones; i++) {
    if (Client.mainFrame.idfMobile)
      this.toolbarZonesConfig[i] = this.toolbarConf;
    else {
      // Create zone configuration (the first one is not visible)
      this.toolbarZonesConfig[i] = this.createElementConfig({c: "Container", className: "panel-toolbar-zone"});
      this.toolbarConf.children.push(this.toolbarZonesConfig[i]);
    }
  }
  //
  let zoneIdx, commandIdx;
  //
  // Move collapse button to collapse command zone
  commandIdx = this.toolbarConf.children.indexOf(this.collapseButtonConf);
  this.toolbarConf.children.splice(commandIdx, 1);
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_COLLAPSE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.collapseButtonConf);
  this.collapseButtonConf.className += " panel-toolbar-btn";
  //
  // Move menu button to collapse command zone
  commandIdx = this.toolbarConf.children.indexOf(this.menuButtonConf);
  this.toolbarConf.children.splice(commandIdx, 1);
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_COLLAPSE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.menuButtonConf);
  this.menuButtonConf.className += " panel-toolbar-btn";
  //
  // Move lock button to lock command zone
  commandIdx = this.toolbarConf.children.indexOf(this.lockButtonConf);
  this.toolbarConf.children.splice(commandIdx, 1);
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_LOCK);
  this.toolbarZonesConfig[zoneIdx].children.push(this.lockButtonConf);
  this.lockButtonConf.className += " panel-toolbar-btn";
  //
  // Move icon to status bar command zone
  commandIdx = this.toolbarConf.children.indexOf(this.iconButtonConf);
  this.toolbarConf.children.splice(commandIdx, 1);
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_STATUSBAR);
  this.toolbarZonesConfig[zoneIdx].children.push(this.iconButtonConf);
  this.iconButtonConf.className += " panel-toolbar-btn";
  //
  // Move title to status bar command zone
  commandIdx = this.toolbarConf.children.indexOf(this.titleConf);
  this.toolbarConf.children.splice(commandIdx, 1);
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_STATUSBAR);
  this.toolbarZonesConfig[zoneIdx].children.push(this.titleConf);
  //
  // Create status bar configuration
  this.statusbarConf = this.createElementConfig({c: "Span", className: "panel-statusbar"});
  this.titleConf.children.push(this.statusbarConf);
  //
  // Create qbe button configuration
  this.qbeButtonConf = this.createElementConfig({c: "IonButton", icon: "information-circle-outline", className: "generic-btn panel-toolbar-btn qbe-tip-btn"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_STATUSBAR);
  this.toolbarZonesConfig[zoneIdx].children.push(this.qbeButtonConf);
  //
  // Create navigation buttons
  //
  // Create top button configuration
  this.topButtonConf = this.createElementConfig({c: "IonButton", icon: "rewind", className: "generic-btn panel-toolbar-btn top-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_top"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_NAVIGATE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.topButtonConf);
  //
  // Create previous button configuration
  this.prevButtonConf = this.createElementConfig({c: "IonButton", icon: "play", className: "generic-btn panel-toolbar-btn prev-btn", events: ["onClick"]});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_NAVIGATE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.prevButtonConf);
  //
  // Create next button configuration
  this.nextButtonConf = this.createElementConfig({c: "IonButton", icon: "play", className: "generic-btn panel-toolbar-btn next-btn", events: ["onClick"]});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_NAVIGATE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.nextButtonConf);
  //
  // Create bottom button configuration
  this.bottomButtonConf = this.createElementConfig({c: "IonButton", icon: "fastforward", className: "generic-btn panel-toolbar-btn bottom-btn", events: ["onClick"]});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_NAVIGATE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.bottomButtonConf);
  //
  // Create search button configuration
  this.searchButtonConf = this.createElementConfig({c: "IonButton", icon: "search", className: "generic-btn panel-toolbar-btn search-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_search"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_SEARCH);
  this.toolbarZonesConfig[zoneIdx].children.push(this.searchButtonConf);
  //
  // Create find button configuration
  this.findButtonConf = this.createElementConfig({c: "IonButton", icon: "flash", className: "generic-btn panel-toolbar-btn find-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_find"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_FIND);
  this.toolbarZonesConfig[zoneIdx].children.push(this.findButtonConf);
  //
  // Create form/list button configuration
  this.formListButtonConf = this.createElementConfig({c: "IonButton", icon: Client.mainFrame.idfMobile ? "arrow-back" : "list", className: "generic-btn panel-toolbar-btn formlist-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_formlist"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_FORMLIST);
  this.toolbarZonesConfig[zoneIdx].children.push(this.formListButtonConf);
  //
  // Create cancel button configuration
  this.cancelButtonConf = this.createElementConfig({c: "IonButton", icon: "undo", className: "generic-btn panel-toolbar-btn undo-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_cancel"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_CANCEL);
  this.toolbarZonesConfig[zoneIdx].children.push(this.cancelButtonConf);
  //
  // Create refresh button configuration
    // PG - personalizzazione - inizio
  //this.refreshButtonConf = this.createElementConfig({c: "IonButton", icon: "refresh", className: "generic-btn panel-toolbar-btn refresh-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_refresh"});
  this.refreshButtonConf = this.createElementConfig({c: "IonButton", label: "Ricarica", className: "generic-btn panel-toolbar-btn panel-toolbar-btn-label refresh-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_refresh"});
  // PG - personalizzazione - fine
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_REQUERY);
  this.toolbarZonesConfig[zoneIdx].children.push(this.refreshButtonConf);
  //
  // Create delete button configuration
    // PG - personalizzazione - inizio
  //this.deleteButtonConf = this.createElementConfig({c: "IonButton", icon: "trash", className: "generic-btn panel-toolbar-btn delete-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_del"});
  this.deleteButtonConf = this.createElementConfig({c: "IonButton", icon: "trash", label : "- Elimina", className: "generic-btn panel-toolbar-btn panel-toolbar-btn-label delete-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_del"});
  // PG - personalizzazione - fine
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_DELETE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.deleteButtonConf);
  //
  // Create insert button configuration
  // PG - personalizzazione - inizio
  //this.insertButtonConf = this.createElementConfig({c: "IonButton", icon: "add", className: "generic-btn panel-toolbar-btn insert-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_new"});
  this.insertButtonConf = this.createElementConfig({c: "IonButton", label: "Nuovo", className: "generic-btn panel-toolbar-btn panel-toolbar-btn-label insert-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_new"});
  // PG - personalizzazione - fine
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_INSERT);
  this.toolbarZonesConfig[zoneIdx].children.push(this.insertButtonConf);
  //
  // Create duplicate button configuration
  this.duplicateButtonConf = this.createElementConfig({c: "IonButton", icon: "copy", className: "generic-btn panel-toolbar-btn duplicate-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_dupl"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_DUPLICATE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.duplicateButtonConf);
  //
  // Create save button configuration
  // PG - personalizzazione - inizio
  //this.saveButtonConf = this.createElementConfig({c: "IonButton", icon: "save", className: "generic-btn panel-toolbar-btn save-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_save"});
  this.saveButtonConf = this.createElementConfig({c: "IonButton", label: "Salva", className: "generic-btn panel-toolbar-btn panel-toolbar-btn-label save-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_save"});
  // PG - personalizzazione - fine
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_UPDATE);
  this.toolbarZonesConfig[zoneIdx].children.push(this.saveButtonConf);
  //
  // Create print button configuration
  this.printButtonConf = this.createElementConfig({c: "IonButton", icon: "print", className: "generic-btn panel-toolbar-btn print-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_print"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_PRINT);
  this.toolbarZonesConfig[zoneIdx].children.push(this.printButtonConf);
  //
  // Create csv button configuration
    // PG - personalizzazione - inizio
  //this.csvButtonConf = this.createElementConfig({c: "IonButton", icon: "open", className: "generic-btn panel-toolbar-btn csv-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_csv"});
  this.csvButtonConf = this.createElementConfig({c: "IonButton", label: "CSV", className: "generic-btn panel-toolbar-btn panel-toolbar-btn-label csv-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_csv"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_CSV);
  this.toolbarZonesConfig[zoneIdx].children.push(this.csvButtonConf);
  //
  // Create attach button configuration
  this.attachButtonConf = this.createElementConfig({c: "IonButton", icon: "attach", className: "generic-btn panel-toolbar-btn attach-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_attach"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_ATTACH);
  this.toolbarZonesConfig[zoneIdx].children.push(this.attachButtonConf);
  //
  // Create group button configuration
  this.groupButtonConf = this.createElementConfig({c: "IonButton", icon: "grid", className: "generic-btn panel-toolbar-btn group-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_group"});
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_GROUP);
  this.toolbarZonesConfig[zoneIdx].children.push(this.groupButtonConf);
  //
  // Create custom buttons configuration
  this.customButtonsConf = [];
  for (let i = 0; i < this.customCommands.length; i++) {
    // Positions for "1-8" custom commands are "18-25"
    let ofs = 18 + i;
    //
    // Positions for "9-16" custom commands are "37-45"
    if (i >= 8)
      ofs = 37 + i - 8;
    //
    this.customButtonsConf[i] = this.createElementConfig({c: "IonButton", className: "generic-btn panel-toolbar-btn custom-btn" + (i + 1), events: ["onClick"], customid: (this.id.replace(/:/g, "_") + "_custom" + i)});
    zoneIdx = this.getCommandZone(ofs);
    this.toolbarZonesConfig[zoneIdx].children.push(this.customButtonsConf[i]);
  }
  //
  // Remove empty zones
  // (not the commandsets, they can be created after this phase)
  zoneIdx = this.getCommandZone(Client.IdfPanel.commands.CZ_CMDSET);
  for (let i = 0; i < this.toolbarZonesConfig.length; i++) {
    if (!this.toolbarZonesConfig[i].children.length && i !== zoneIdx) {
      let idx = this.toolbarConf.children.indexOf(this.toolbarZonesConfig[i]);
      this.toolbarConf.children.splice(idx, 1);
    }
  }
};

A questo punto all’interno della funzione createToolbarConfig basta modificare il codice dei bottoni che ci interessano; qui vediamo il refresh e il delete.

this.refreshButtonConf = this.createElementConfig({c: "IonButton", label: "Ricarica", className: "generic-btn panel-toolbar-btn panel-toolbar-btn-label refresh-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_refresh"});
this.deleteButtonConf = this.createElementConfig({c: "IonButton", icon: "trash", label : "- Elimina", className: "generic-btn panel-toolbar-btn panel-toolbar-btn-label delete-btn", events: ["onClick"], customid: this.id.replace(/:/g, "_") + "_del"});

Alla prima riga di codice è stato tolto l’elemento icon e aggiunto quello label; nella seconda aggiunto l’elemento label lasciando invariato quello icon.
Essendo la dimensione fissata da css occorre aggiungere anche questo codice al file customf.css:

/* Colore del testo del bottone */
.panel-toolbar-btn {
  color: var(--col-fore-text-1) !important;
}
/* Larghezza del bottone che si adatta al testo e padding corretto */
.panel-toolbar-btn-label {
  width: fit-content !important;
  padding: 5px !important;    
}

Cambiare la posizione della toolbar

Se volessi avere la toolbat dei pannelli in basso invece che in alto occore solamente cambiare l’ordine di rendering della stessa a video con questa regola css nel file customf.css:

/* sposto la toolbar dei pannelli in basso */
.frame-toolbar {
  order: 2;
}

Continua…

3 Mi Piace

Grande, guida molto interessante!
Mi sa che però ho trovato degli errori ad esempio parli di

Ma poi nel codice c’è this.createElementConfig

Anche nella parte riguardante il cambiare icona sembra ripetuta 2 volte

Errore nel nome del file, manca la M di custom

Grazie correggo… la fretta :grimacing:

In realtà va bene come ho scritto (a parte ma M mancante) e ho specificato meglio che la funzione createToolbarConfig va copiata tutta in customf.js e poi modificata.

grazie, sicuramente utili però io il problema più grosso che vedo in Fluid è l’impossibilità (almeno io non saprei come fare) di ottenere un buon layout responsivo nella visualizzazione dettaglio. Quello che serve secondo me è poter usare tutto lo schermo e poi nel tablet o smartphone vedere spostarsi in verticale il contenuto, per farlo occorrerebbe poter inserire un contenitore flex. Non so se è fattibile agendo sulla classe del pannello, cosa che non ho ancora provato.

1 Mi Piace

Caro Giovanni FLUID funziona già in queto modo.
Di deufault il responsive è spento nei temi Seattle e Zen e acceso in Bootstrap e BootstrapZen.
Ma puoi decidere tu se accenderlo o meno dal customf.css.

Ti consiglio di leggere l’articolo della documentazione FLUID: Gestione responsive

Fammi sapere se è comprensibile.

Ciao Paolo,
dopo l’ultimo webinar su fluid sto facendo un paio di prove… da una videata gestita con multipagine per dividere i campi di un componente in più sezioni, lasciando alcuni campi visibili per tutte le pagine, non riesco a capire se devo gestire il posizionamento dei campi o è un errore che si viene a creare.
I campi nelle varie pagine in runtime vengono spostati (senza trovare ancora una logica) o capita che non parte proprio la videata…

hai esempi in merito o suggerimenti di lettura di qualche documentazione?

@f.liguori non saprei dirti così al volo ma le page dei pannelli devono funzionare come prima e quindi puoi segnalare il problema con un ticket di malfunzionamento allegando anche solo lo screen shot della videata che va male (a design time e run time).

Non so se sia il topic corretto in quanto non è realmente FLUID anche se utilizza lo stesso sistema.
In caso il buon @paolo.giannelli può spostarlo come ritiene opportuno :slight_smile:

Stiamo lavorando su tema ZEN non fluid, ma attivando le righe ad altezza variabile va ad usare il CSS di fluid, quindi tocca mettere a posto anche quello.

Altezza righe
Fluid usa una regola CSS per definire l’altezza o la larghezza dei campi, non usa il valore a design, queste sono le regole che ho applicato e sembra funzionare correttamente.

.list-fluid-row > .list-fluid-subrow > .panel-field-value-list, .list-fluid-header > .list-fluid-subheader > .panel-field-caption-list {
	min-height: 20px;
	word-break: keep-all;
	min-width: 20px;
}

Ho anche modificato il comportamento di default per spezzare le stringhe perché mi dava fastidio che mi andasse a capo tipo una lettera di una parola…

Altezza intestazioni lista
Anche le intestazioni le voglio più compatte

.list-fluid-subheader .panel-field-caption-list {
	height: 20px;
}
.list-fluid-row > .list-fluid-subrow > .panel-outer-row-sel, .list-fluid-row > .list-fluid-subrow > .list-rs-cap {
	min-height: 20px;
}

Attivatore dei campi (combo, date etc)
Riducendo l’altezza dei campi bisogna regolare anche quella degli attivatori, probabilmente nel caso dei numerici si vuole l’attivatore a sinistra ma a me non serviva e l’ho messo sempre a destra nella posizione corretta.

.fluid-activator.panel-value-activator {
    padding: 0px !important;
    border: none !important;
    float: right;
    height: 12px;
    position: relative;
}

Se poi qualcuno è a conoscenza di metodi migliori per personalizzare su fluid quanto si fa normalmente da design ben venga!

2 Mi Piace

Da quale versione di Foundation?

Sicuramente dalla 23.0, nelle precedenti ricordo anche io che non usava fluid perché le prime volte che avevo provato ad usarla ancora fluid non c’era

1 Mi Piace

Intervengo per chiarire la questione, il tema ZEN RD3 non usa Fluid, semplicemente il nome interno della lista ad altezza variabile è “lista fluida” e quindi le sue classi css iniziano con fluid.

Ma non c’entra niente con il motore grafico fluid, è semplicemente un conflitto di nomi.

2 Mi Piace

Grazie della precisazione @d.pierangeli volevo rispondere io ma mi hai preceduto.