10 KiB
Create Vitruvio Desktop Form
All messages shown to the user must be written in Portuguese.
Desktop forms are Vaadin 8 forms defined in XML and rendered by the Vitruvio engine. This skill creates the desktop form. There are two variants that share almost all of their component vocabulary but differ in their root element and how variables flow — see Differences: panel vs process below.
Step 1 — Confirm you are inside a Vitruvio repo
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
If NOT_A_VITRUVIO_REPO, stop and tell the user to cd into the correct repo.
Step 2 — Determine variant and target
Ask (only what is missing):
- Panel or process form? (decides root element / file location — see below)
- Key — the panel or process key (the folder name).
- For a process, which
formKey(s) are needed — they must match theactiviti:formKeyvalues inprocesses/<key>/<key>.bpmn. - What should the form show/do? — fields and behaviour, so you can scaffold something useful.
Differences: panel vs process
| Panel | Process | |
|---|---|---|
| File | panels/<key>/<key>-desktop.xml |
processes/<key>/<key>-desktop.xml |
| Root element | <panel-form> |
<forms> (with processKey attribute optional) |
| Namespace | http://www.davinti.com.br/vitruvio/form/panel |
http://www.davinti.com.br/vitruvio/form |
| XSD | vitruvio-panel-form.xsd |
vitruvio-form.xsd |
| Forms per file | exactly one <form> |
one <form formKey> per BPMN activiti:formKey |
| Variables | none built-in; use engine.getGlobalVariable |
process variables: engine.getVariable/setVariable; submitted field id="X" in formKey="A" → variable A_X |
<library> |
not used | optional: shared <complex-component id> reused via <component-ref refId> |
Everything below (components, DBTable, engine API, ES5 rules) is identical for both.
Step 3 — Scaffold the file
Panel variant — panels/<key>/<key>-desktop.xml
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
<form formKey="<key>" width="100%" height="100%">
<name><name></name>
<description><description></description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called once when the panel opens
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%" height="100%">
<!-- add widgets here -->
</VerticalLayout>
</components>
</form>
</panel-form>
Process variant — processes/<key>/<key>-desktop.xml
<?xml version="1.0" encoding="UTF-8"?>
<forms xmlns="http://www.davinti.com.br/vitruvio/form"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-form.xsd">
<!-- Optional: shared components used across multiple forms -->
<library>
<!-- <complex-component id="libShared">...</complex-component> -->
</library>
<!-- One <form> per activiti:formKey in the BPMN -->
<form formKey="formAbertura" width="100%">
<name>Abertura</name>
<description>Abertura do processo</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called when the form opens
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="descricao" type="string" caption="Descrição" width="100%" required="true" />
</VerticalLayout>
</components>
</form>
<form formKey="formExecutar" width="100%">
<name>Executar</name>
<description>Etapa de execução</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// read a process variable set during a previous step
// var valor = engine.getVariable('formAbertura_descricao');
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="resultado" type="string" caption="Resultado" width="100%" required="true" />
<ComboBox id="aprovado" type="string" caption="Aprovado?" required="true" allowNullSelection="false">
<entry key="1" value="Sim"/>
<entry key="0" value="Não"/>
</ComboBox>
</VerticalLayout>
</components>
</form>
</forms>
Components (78 desktop components)
Before using a component you are unsure about, read its reference:
~/.local/share/vitruvio-platform/docs/components/desktop/<ComponentName>.md
(full list in docs/components/INDEX.md).
Layout
| Component | Common attributes |
|---|---|
VerticalLayout |
spacing, margin, width, height, expandRatio |
HorizontalLayout |
same as above |
Panel |
id, caption, width, height, margin |
TabLayout |
id, width, framed — contains <Tab caption="..."> children |
Widgets
| Component | Key attributes |
|---|---|
TextField |
id, type (string/number), caption, width, required |
NumericField |
id, type, caption, width, visible |
DateField |
id, type (date/datetime), caption, resolution (DAY/MINUTE) |
ComboBox |
id, type, caption, allowNullSelection — children: <entry key="..." value="..."/> |
Label |
id, width, contentMode (HTML/TEXT) — child: <value>...</value> |
ButtonWidget |
id, caption, style (GREEN/RED/DEFAULT), defaultIcon — child: <onClickScript> |
RichTextArea |
id, type, caption, width, height |
ImageWidget |
id, width, height — child: <image><base64 extension="png">...</base64></image> |
DBTable (data grid)
<DBTable id="tbDados" type="string" width="100%" rows="8"
exportXLS="true" showRowCount="true" selectable="true">
<datasource>
<!-- Option A: static query -->
<freeQuery connection-key="vitruvio_producao">
<![CDATA[
SELECT col1, col2
FROM my_table
WHERE param = ${myParam}
]]>
</freeQuery>
<!-- Option B: dynamic query built in JS -->
<sqlBuilderDataSource connection-key="vitruvio_producao" language="JavaScript">
<![CDATA[
function buildSQL(params) {
var sql = 'SELECT col1, col2 FROM my_table WHERE 1=1';
var val = engine.getField('myFilter').getValue();
if (val) {
sql += ' AND col1 = ${val}';
params.put('val', val);
}
return sql;
}
]]>
</sqlBuilderDataSource>
</datasource>
<key-field>CHAVE</key-field>
<columns>
<column name="COL1" caption="Column 1" expand-ratio="1"/>
<column name="COL2" caption="Column 2" expand-ratio="2"/>
<generated name="Action" expand-ratio="0.5">
<scriptColumnGenerator language="JavaScript">
<![CDATA[
function Generator() {
var com = libService.loadScript('vaadinComponents');
this.generate = function(itemId, columnId, item, container) {
var btn = com.buttonIcon('action', function() {
var id = item.getItemProperty('CHAVE').getValue();
// do something
}, 'edit');
return com.horizontalLayout([btn]);
}
}
var script = new Generator();
]]>
</scriptColumnGenerator>
</generated>
</columns>
<bind>
<parameter value-type="number" defaultValue="0" parameterName="myParam" field-ref="otherTable"/>
</bind>
</DBTable>
SQL in datasources: use ${paramName} for substitution — NOT :paramName. Named
params (:paramName) are only for queries/*.sql files.
engine API
// Fields
engine.getField('id').getValue()
engine.getField('id').getConvertedValue() // typed value (number, date, etc.)
engine.getField('id').setValue(value)
engine.getField('id').setEnabled(bool)
engine.getField('id').setVisible(bool)
engine.getField('id').setRequired(bool)
engine.getField('id').setCaption('new caption')
engine.getField('id').refresh() // DBTable — re-run its query
// Widgets / layouts
engine.getWidgetController('id').getButton()
engine.getLayout('id').getSelectedTab()
// User
engine.getLoggedUser().getLogin()
engine.getLoggedUser().getNome()
// Global variables (survive tab changes within a session)
engine.setGlobalVariable('key', value)
engine.getGlobalVariable('key')
// Process forms only — process variables:
engine.getVariable('varName')
engine.setVariable('varName', value)
engine.getProcessDefinitionId()
engine.formKey() // current form's formKey
engine.getFormName()
// Open another panel / load a library
var vUI = libService.loadScript('vUI');
vUI.showPanel('panelKey', { param1: value1 });
var lib = libService.loadScript('scriptKey');
Script rules (Rhino ES5)
Desktop form scripts run on Rhino ES5 — no let/const, arrow functions, template
literals, destructuring, class, import/export. Use var, string + concat,
JSON.parse/JSON.stringify, importClass(Packages.some.java.Class) for Java interop.
(See the repo CLAUDE.md "JavaScript — ES5 / Rhino Engine" section.)
Note: this ES5 rule is for desktop. Mobile forms use modern JS on the client — see vitruvio-criar-form-mobile.
Step 4 — Manifest
If run standalone, set "forms"."desktop" to the file path on the existing panel or
process entry in vitruvio.json. Full entry creation is handled by vitruvio-criar-painel
/ vitruvio-criar-processo.
Step 5 — Report
Tell the user:
- File created/updated and which variant (panel/process).
- For processes: each
<form formKey>must match anactiviti:formKeyin the BPMN, and submitted fieldid="X"informKey="A"becomes process variableA_X. run()in<initScript>is called every time the form opens.${paramName}for SQL substitution in datasources;:paramNameonly in named query files.