Ciao a tutti!
Tutti sapete che la tabella Layout
contiene le pagine che vengono generate dal portale; forse non tutti però conoscete i typeSettings
, un campo della tabella Layout
all'interno del quale, tipicamente, il portale scrive l'associazione tra gli spazi dei singoli layouttpl
e le relative portlet.
Ma come possiamo usarli "a nostro piacimento"?
Questa domanda, come sempre, mi è stata fatta da un gruppo di ragazzi che sta facendo un ottimo lavoro mentre fanno il porting dalla 6.2 EE alle 7.2 EE di una applicazione che non hanno scritta loro ma che hanno ereditato.
(N.d.J: Della serie: doppio carpiato rovesciato, ma bendati e legati dopo aver girato in tondo per 5 minuti e poi fatti saltare da 35 metri d'altezza... Per darvi un'idea! :))
E la domanda, più che lecita, mi è stata rivolta perché chi ha codificato prima di loro l'applicativo, ha pensato bene di utilizzare i typeSettings
anche per salvare caratteristiche delle pagine.
Posto che noi in D'vel è una vita che facciamo queste robe (ma ce la siamo sempre spicciata facile usando i Custom Attribute :)), m'intrigava la soluzione che avevano realizzato e quindi ho lavorato con loro per riuscire a fare il porting del codice sulla 7.2.
Il caso funzionale
Il caso funzionale che avevano mappato era semplice: in alcune pagine è presente una portlet (scusate: widget, siamo sulla 7.. :D); questo widget però deve ereditare alcuni parametri per essere configurato, così chi ha codificato l'applicativo ha pensato bene di salvare nei typeSettings
questi parametri.
(N.d.J: faccio notare che la portlet poteva essere semplicemente configurata sulle singole pagine, senza stare tanto ad impazzire; ma non chiedetemi perché è stata scelta questa strada: se la vita fosse semplice a noi non ci cercherebbe nessuno, quindi.. ;D).
La vecchia implementazione
Per riuscire a fare questa implementazione, i vecchi developer avevano proceduto in questo modo:
- avevano fatto un bell'hook sulla portlet che gestiva il back end della gestione delle pagine;
- avevano fatto una bella JSP che si agganciava al
form-navigator
della gestione delle pagine; - accedendo alla nuova voce all'interno del
form-navigator
avevano messo la loro bella JSP; - al submit i dati venivano salvati trasparentemente nella
Layout
, così che il gioco fosse fatto!
Analizzando nel dettaglio la loro implementazione, in effetti posso anche riconoscere che è sicuramente più elegante e figa della nostra:
- il cliente non accede ai "Campi personalizzati" ma al back end standard di prodotto;
- all'interno delle voci di configurazione della pagina c'era la loro voce;
- accedendo alla loro voce c'era una form "impaginata a modo" (e non autogenerata come quella dei Custom Fields) che non era in effetti niente male..
Cavolo, allora la sfida si faceva interessante! :)
La nuova implementazione
La prima cosa che c'era da fare, quindi, era sostituire l'hook che, sulla 6.2, si agganciava con una JSP deployata sul portale, al form-navigator
presente all'interno della gestione delle pagine.
Nella 7, il form-navigator
ovviamente si è evoluto ed è quindi diventato necessario sviluppare due Components
per poterlo utilizzare / per potercisi collegare in maniera trasparente.
Il primo serve per creare la categoria all'interno del menù di navigazione del portale; il secondo per creare le singole sezioni che ci sono all'interno di questo menu.
Quindi abbiamo proceduto in questo modo; prima abbiamo creato la categoria:
package it.dvel.playground.web.layout;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.servlet.taglib.ui.FormNavigatorCategory;
import com.liferay.portal.kernel.servlet.taglib.ui.FormNavigatorConstants;
import java.util.Locale;
import org.osgi.service.component.annotations.Component;
@Component(
immediate = true,
property = "form.navigator.category.order:Integer=10",
service = FormNavigatorCategory.class)
public class CustomLayoutFormNavigatorCategory implements FormNavigatorCategory {
@Override
public String getFormNavigatorId() {
return FormNavigatorConstants.FORM_NAVIGATOR_ID_LAYOUT;
}
@Override
public String getKey() {
// Io ho fatto una PoC; voi fate i bravi e usate una COSTANTE!! :)
return "custom-category";
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "custom-category");
}
}
Fatto questo, abbiamo creato il pezzo di pagina che ci interessava.. O meglio: la entry che si sarebbe agganciata alla nostra category custom:
package it.dvel.playground.web.layout;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.model.Layout;
import com.liferay.portal.kernel.servlet.taglib.ui.BaseJSPFormNavigatorEntry;
import com.liferay.portal.kernel.servlet.taglib.ui.FormNavigatorConstants;
import com.liferay.portal.kernel.servlet.taglib.ui.FormNavigatorEntry;
import java.util.Locale;
import javax.servlet.ServletContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
property = "form.navigator.entry.order:Integer=100",
service = FormNavigatorEntry.class)
public class CustomLayoutFormNavigatorEntry extends BaseJSPFormNavigatorEntry
implements FormNavigatorEntry {
@Override
protected String getJspPath() {
return "/html/admin_layout/my_custom_fields_to_manage.jsp";
}
@Override
public String getCategoryKey() {
// Le costanti ragazzi: voi usate le costanti! ;)
return "custom-category";
}
@Override
public String getFormNavigatorId() {
return FormNavigatorConstants.FORM_NAVIGATOR_ID_LAYOUT;
}
@Override
public String getKey() {
// Non mi stancherò mai di ripetermi: usate le costanti! ;)
return "custom-entry";
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, getKey());
}
@Override
@Reference(
target = "(osgi.web.symbolicname=my-poc-module-web)",
unbind = "-")
public void setServletContext(ServletContext servletContext) {
// Questo setter è strategico: permette al container di recuperare
// la JSP che abbiamo indicato sopra direttamente dal nostro bundle
// e non a raglio da chissà quale pacchetto!
// Ricordatevi che questa roba sarà eseguita dal bundle standard della
// gestione delle pagine!
super.setServletContext(servletContext);
}
}
E anche questa è fatta!
Ora non rimaneva che sistemare la nostra JSP, all'interno del nostro bundle, e far gestire tutto al componente standard di portale.
Ed ecco qui la JSP:
<%@ include file="/META-INF/resources/html/init.jsp"%>
<%
// Recupero il plid del Layout visualizzato dalla request
Long selPlid = ParamUtil.getLong(renderRequest,"selPlid");
// Recupero il Layout usando il plid
Layout selLayout = LayoutLocalServiceUtil.getLayout(selPlid);
UnicodeProperties layoutTypeSettings = null;
if (selLayout != null) {
// Se il Layout non è nullo, recupero le typeSettings
layoutTypeSettings = selLayout.getTypeSettingsProperties();
}
%>
<liferay-ui:error-marker key="error-section" value="my-custom-fields-to-manage-error-message" />
<aui:model-context bean="<%= selLayout %>" model="<%= Layout.class %>" />
<h3><liferay-ui:message key="my-custom-fields-to-manage-title" /></h3>
<aui:fieldset cssClass="lfr-portrait-editor">
<%
String riskType = GetterUtil.getString(layoutTypeSettings.getProperty("risk-type"));
%>
<aui:select label="risk-type"
name="TypeSettingsProperties--risk-type--" showemptyoption="<%= true %>">
<aui:option label="type-car"
selected="<%= "CAR".equals(riskType) %>" value="<%=Constants.CAR%>"/>
<aui:option label="type-motorbike"
selected="<%= "MOTORBIKE".equals(riskType)%>" value="<%=Constants.MOTORBIKE%>"/>
<aui:option label="type-easy"
selected="<%= "EASY".equals(riskType) %>" value="<%= Constants.EASY %>"/>
<aui:option label="type-quote"
selected="<%= "QUOTE".equals(riskType) %>" value="<%= Constants.VERTIQUOTE %>"/>
<aui:option label="type-home"
selected="<%= "HOME".equals(riskType) %>" value="<%= Constants.HOME %>"/>
<aui:option label="type-other-vehicle"
selected="<%= "OTHER_VEHICLE".equals(riskType) %>" value="<%=Constants.OTHER_VEHICLE%>"/>
<aui:option label="type-ivass"
selected="<%= "IVASS".equals(riskType) %>" value="<%= Constants.IVASS %>"/>
</aui:select>
</aui:fieldset>
Ed eccolo qui, l'utimo tassello del puzzle! :)
Una nota importante: come potete vedere, nel nome della select è stato inserito:
TypeSettingsProperties--risk-type--
che i più attenti di voi avranno già riconosciuto come un meccanismo "automatico" che viene usato dal portale per leggere e salvare arbitrari valori che arrivano dal web (di fatto è la stessa convenzione che si utilizza sul salvataggio delle configurazioni!).
Detto questo, ovviamente, deploy, navigazione, test e... Funziona! ;)
A questo punto, anche voi (come noi :D), adesso potete utilizzare le typeSettings di pagina come un modo più elegante e furbo per far configurare parametri al Cliente.
.. Sempre che non abbiate una fretta del diavolo e i Custom Attribute non vi sembrino molto più smart e semplici da utilizzare!!
Direi che anche per oggi è tutto: se avete dubbi o domande, come sempre, sono a vostra disposizione; scriveteci nei commenti e fateci sapere che ne pensate!
Buona giornata a tutti! ;)