Crea la tua form personalizzata

L’dea che sta dietro alle form personalizzate, chiamati anche plugin, è che tu puoi aggiungere un intero form associato a famiglie di task (o risorse, o anche genericamente presente nel menu di Twproject,a seconda del contesto e dei diritti dell’utente) semplicemente creando un singolo file jsp: non è necessario compilare classi, creare uno schema del database, o gestire transizioni, anche se definisci nuovi campi da salvare. Ovviamente, se vuoi anche creare nuovi classi di supporto, o aggiungere jars al classpath, sei libero di farlo.

screen981

Le form personalizzate sono normalmente visibili nella sezione documenti dell’editor del task e delle risorse: le forms sono usate per estendere le proprietà degli oggetti di Twproject. I plugins sono di solito intesi per azioni automattizate (e.g. wizards) o per estendere la capacità di report.

Uso delle form personalizzate

In realtà nell’installazione standard di Twproject avrai già alcuni esempi di form personalizzate su task e risorse. Questi di default non sono visibili se non sono soddifatte alcune condizioni:

Simple Custom Form: visibile quando il nome del task è TESTFORM.

Project Complexity: visibile solo quando il task è di root e la sua rilevanza è >= 80.

Project Value:visibile solo quando il task è di root e la sua rilevanza è >= 80.

Project Risk:visibile solo quando il task è di root e la sua rilevanza è >= 80 el tipo di task è PRODUCTION.

Sui task dove sono abilitati, vai nel tab “documenti” dell’editor del task/risorsa:

simpleCustomFormLink

Clicca su “simple custom form”:

screen976

Questa form caotica è solo un esempio dello spettro di campi che puoi aggiungere su una form.

Crea la tua form

Questa sezione richiede alcune capacità di programmazione Java.

Questa sezione non è per i deboli di cuore 🙂 solo chi conosce Java può trarre beneficio da questa lettura.

I forms/reports/plugins hanno senso solo se “personalizzati”. Quindi in questa sezione spiegheremo come lavorano e come modificare/creare il tuo.

Ci sono vari esempi di form forniti; per iniziare, usa simpleCustomForm.jsp: è ben commentato, e contiene esempi di campi diversi (stringhe, date, numeri…) che possono essere usati in una form. Copialo in nuovo file nella stessa cartella, e inizia a modificarlo.

Prima di tutto, ciò che rende le form personalizzate pratiche è che sono “senza problemi”. Puoi estendere un task con decine di nuove proprietà senza occuparti del  salvataggio/modifica/eliminazione dei dati, che è gestito dal framework. Il livello di persistenza è completamente nascosto da Twproject.

Dove sono le form personalizzate

Le form personalizzate di default sono pagine .jsp nella cartella:

[root]/applications/teamwork/plugins 

Devi mettere le tue form personalizzate in:

[root]/applications/teamwork/customers/ACME/plugins

DOve ACME è il nome/short code della tua azienda.

Per elencare i plugin ativi vai in strumenti -> admin page, poi clicca su “forms and plugin” nel box “Customization”.

Quando Twproject si avvia fa una verifica su questa cartella e inizializza ogni plugin. Puoi forzare una nuova verifica cliccando sul bottone “reload plugins”.

adminCustomForms

Come funziona

Caricamento: All’avvio, Twproject chiamerà il metodo inizializza sulle jsp trovate, e quelle che non lanciano un’eccezione vengono caricate in memoria tra i plguin disponibili.

Visibilità: un plugin può apparire nei seguenti luoghi: nel menu “tools” di Twproject, sull’editor del task e sull’editor delle risorse. Se verranno visualizzati è determinato dal risultato della chiamata “isVisibleInThisContext” sulla pagina jsp.

Persistenza: Dove vengono salvati i dati e in che modo? Dato che una form può cambiare in ogni momento i campi presenti su di essa, i suoi dati non sono soggetti a integrità referenziale. Tutti i dati sono salvati nella tabella olpl_des_data and olpl_des_data_value. Lo sviluppatore non deve fare niente per la persistenza dei dati: tutti i campi presenti sulla form saranno salvati, e automaticamente associati all’entità attraverso la quale sei giunto alla form. Quindi per esempio, se uno è su un task, i dati scritti sulle form per quel task saranno salvati in olpl_des_data_value, e collegati al task attraverso un record in olpl_des_data: referenceId sarà l’id del task, referenceClassName la classe del task, e designerName sarà una form normalizzata del nome del filejsp.

Analisi del plugin

Ok, adesso inizia la parte più tosta….

Quando un plugin è inizializzato, esso registra se stesso in un gruppo, e inietta una inner class che estende PagePlugin, usata per capire se il plugin dovrebbe essere visibile nel contesto corrente. Diamo uno sguardo al codice (l’esempio viene da simpleCustomForm.jsp):

<%@ page import="com.twproject.resource.Person,
… lots of import removed …

%><%@ page contentType="text/html; charset=utf-8" pageEncoding="UTF-8" %><%!

/**

* This inner class is used by Twproject to know if this form applies to current context.

* PagePlugin classes are loaded at startup (or by hand) in memory to be performant.

*

*/

public class PagePluginExt extends PagePlugin {

public boolean isVisibleInThisContext(PageState pagestate) {

boolean ret = false;

if (pagestate.mainObject != null && pagestate.mainObject.getClass().equals(Task.class)) {

Task task = (Task) pagestate.mainObject;

// ----- begin test condition on task -----------------

// this form will be visible only on root tasks

ret = task.getParent() == null;

// ----- end test condition on task -----------------

}

return ret;

}

}

%>

L’inner class jsp deve implementare il metodo isVisibleInThisContext().

Questo metodo basato sui dati ottenuti dall’stanza  PageState e principalmente dal campo “mainObject” verifica di essere nel contesto approoriato.

In questo caso stiamo controllando se il mainObject è un Task e se il task è uno di root. Se entrambe le condizioni sono vere il form sarà visibile in questo contesto.

Ogni form personalizzata è composta da due parti chiamate in diversi cicli dell’applicazione. La prima parte è l’inizializzazione. Questa parte è chiamata all’avvio e inietta l’stanza PagePlugin nel sistema.

Il meotodo PagePluginExt.isVisibleInThisContext è chiamato ogni volta che Twproject sta creando link per plugin per il gruppo “TASK_FORMS”.

<%
/*
*/
// ############################# BEGIN INITIALIZE ###############################################
if (JspIncluder.INITIALIZE.equals(request.getParameter(Commands.COMMAND))) {
PluginBricks.getPagePluginInstance("TASK_FORMS", new PagePluginExt(), request);
// ############################ END INITIALIZE ################################################

In realtà Twproject usa quattro gruppi: “REPORTS”, “RESOURCE_FORMS”, “TASK_FORMS”, “TASKLOG” che sono mostrati rispettivamente in task/risorse lista/editor, docuemnti della risorsa, docuemnti del task, log del task.

La seconda parte è la definizione della form.

La definizione è composta da due parti: definizione dei dati e del layout html.

} else if (Designer.DRAW_FORM.equals(request.getAttribute(JspIncluder.ACTION))) {
// ------- recover page model and objects ----- BEGIN DO NOT MOFIFY --------------
PageState pageState = PageState.getCurrentPageState(request);
Task task = (Task) PersistenceHome.findByPrimaryKey(Task.class, pageState.mainObjectId);
Designer designer = (Designer) JspIncluderSupport.getCurrentInstance(request);
task.bricks.buildPassport(pageState);
// ------- recover page model and objects ----- END DO NOT MOFIFY --------------
// check security and set read_only modality
designer.readOnly = !task.bricks.canWrite;
// ################################ BEGIN FORM DATA DEFINITION ##############################
if (designer.fieldsConfig) {

puoi avere un selettore come radio

CodeValueList cvl = new CodeValueList();
cvl.add("0", "list value 0");
cvl.add("1", "list value 1");
cvl.add("2", "list value 2");
cvl.add("3", "list value 3");
cvl.add("4", "list value 4");
DesignerField dfr = new DesignerField(CodeValue.class.getName(), "RADIO",
"Checklist Example as radio", false, false, null);
dfr.separator = "&nbsp;";
dfr.cvl = cvl;
dfr.displayAsCombo = false;
designer.add(dfr);
DesignerField dfl = new DesignerField(CodeValue.class.getName(), "COMBO",
"Checklist Example as list", false, false, null);
dfl.separator = "</td><td>";
dfl.cvl = cvl;
dfl.displayAsCombo = true;
designer.add(dfl);

o come lista

DesignerField dfStr = new DesignerField(String.class.getName(), "STRING",
"String example", false, false, "preloaded value");
dfStr.separator = "</td><td>";
dfStr.fieldSize = 20;
designer.add(dfStr);
standard text fields
DesignerField dfNote = new DesignerField(String.class.getName(), "NOTES",
"Text example (limited to 2000)", false, false, "");
dfNote.fieldSize = 80;
dfNote.rowsLength = 5;
dfNote.separator = "<br>";
designer.add(dfNote);
text area
DesignerField dfInt = new DesignerField(Double.class.getName(), "INTEGER",
"Integer example", false, false, "");
dfInt.separator = "</td><td>";
dfInt.fieldSize = 4;
designer.add(dfInt);
DesignerField dfdouble = new DesignerField(Double.class.getName(), "DOUBLE",
"Double example", false, false, "");
dfdouble.separator = "</td><td>";
dfdouble.fieldSize = 4;
designer.add(dfdouble);
numeric fields
DesignerField dfdate = new DesignerField(Date.class.getName(), "DATE",
"Date example", false, false, null);
dfdate.separator = "</td><td>";
designer.add(dfdate);
date
DesignerField dffile = new DesignerField(PersistentFile.class.getName(), "FILE",
"Upload example", false, false, null);
dffile.fieldSize = 40;
dffile.separator = "</td><td colspan=3>";
designer.add(dffile);
uploaded files
DesignerField dfperson = new DesignerField(Person.class.getName(), "PERSON",
"Any persistent (Identifiable) object example, here Person", false, false, null);
dfperson.separator = "</td><td>";
dfperson.fieldSize = 40;
designer.add(dfperson);
lookup on other Twproject’s entities
DesignerField dfbool = new DesignerField(Boolean.class.getName(), "BOOLEAN",
"Check if agree", false, false, "");
designer.add(dfbool);
boolean
// Master Detail example. You can add a detail to the form and then add field to detail.
Detail detail = designer.addDetail("DETAIL");
detail.label = "Master-Detail example";
DesignerField dfitem = new DesignerField(String.class.getName(), "ITEM",
"Item", false, false, "");
dfitem.fieldSize=55;
detail.add(dfitem);
DesignerField dfqty = new DesignerField(Integer.class.getName(), "QTY",
"Qty", false, false, "");
dfqty.fieldSize = 4;
detail.add(dfqty);
even master detail sections
// ########################### END FORM DATA DEFINITION #####################################
} else {

Una volta cjhe hai dichiarato il campo che vuoi usare, devi definirlo nel layout html della pagina.

// ########################### BEGIN FORM LAYOUT DEFINITION #################################
// create a container around the form
Container c = new Container(pageState);
c.title = "<big>Custom form DEMO</big> for task: " + task.getDisplayName();
c.start(pageContext);

creiamo un contenitore intorno alla form

// you can extract data to enrich your form using data from current task.
// In this case we will extract missing days from current task
String daysMissing = pageState.getI18n("UNSPECIFIED");
if (task.getSchedule() != null && task.getSchedule().getEndDate() != null) {
if (task.getSchedule().getValidityEndTime() > new Date().getTime()) {
long missing = task.getSchedule().getValidityEndTime() - new Date().getTime();
daysMissing = DateUtilities.getMillisInDaysHoursMinutes(missing);
} else
daysMissing = "<b>" + pageState.getI18n("OVERDUE") + "</b>";
}
%>
<%-- ---------------------- BEGIN TASK DATA ----------------------
---------------------- You can use the task recovered before to display cue data --%>
<br>
<table border="0">
<tr>
<th colspan="2"> Some data from current task:</th>
</tr>
<tr>
<td ><%=pageState.getI18n("RELEVANCE")%></td><td> <%=task.getRelevance()%></td>
</tr><tr>
<td> <%=pageState.getI18n("TASK_END")%></td>
<td> <%=task.getSchedule() != null &&
task.getSchedule().getEndDate() != null ?
JSP.w(task.getSchedule().getEndDate()) : "&nbsp;-&nbsp;"%></td>
</tr><tr>
<td> <%=pageState.getI18n("TASK_REMAINING")%></td>
<td> <%=daysMissing%></td>
</tr><tr>
<td> <%=pageState.getI18n("TASK_PROGRESS")%></td><td>
<%
PercentileDisplay pd = TaskBricks.getProgressBarForTask(task, pageState);
pd.toHtml(pageContext);
%>
</td>
</tr>
</table>
<%-- ------------------- END TASK DATA ----------------- --%>

Sappiamo che in questo contesto l’oggetto principale è un task quindi possiamo usarlo per estarrre alcuni dati per arricchire la form.

<%-- ------------------- BEGIN HTML GRID ----------------- --%>
<table border="0">
<tr>
<td colspan="4"><%designer.draw("RADIO", pageContext);%></td>
</tr>
<tr>
<td><%designer.draw("COMBO", pageContext);%></td>
<td><%designer.draw("STRING", pageContext);%></td>
</tr>
<tr>
<td colspan="4"><%designer.draw("NOTES", pageContext);%></td>
</tr>
<tr>
<td><%designer.draw("INTEGER", pageContext);%></td>
<td><%designer.draw("DOUBLE", pageContext);%></td>
</tr>
<tr>
<td><%designer.draw("DATE", pageContext);%></td>
<td><%designer.draw("PERSON", pageContext);%> &nbsp;
<%designer.draw("BOOLEAN", pageContext);%>
</td>
</tr>
<tr>
<td><%designer.draw("FILE", pageContext);%></td>
</tr>
</table>
We call designer.draw for every declared field
<table><tr><td><%designer.draw("DETAIL", pageContext);%></td></tr></table>
Then the master-detail
<%-- ------------------- END HTML GRID ----------------- --%>
And the html grid is closed
<%
double testUseValues = 0;
//sum of weights
testUseValues += designer.getEntry("INTEGER", pageState).intValueNoErrorCodeNoExc();
testUseValues += designer.getEntry("DOUBLE", pageState).doubleValueNoErrorNoCatchedExc();
%>
<hr>
<b><big>Test of sum of stored values:&nbsp;<%=JSP.w(testUseValues)%></big></b>

Possiamo aggiungere alcuni calcoli sui valori inseriti. Possiamo eventualmente mischiare dati dalla form e dal task.

<%
c.end(pageContext);
}
// ############################## END FORM LAYOUT DEFINITION ################################
}
%>

Questo è tutto. I bottoni “stampa” e “salva” vengono aggiunti automaticamente.