20 KiB
Panels
Panels are UI screens defined entirely in XML. Vitruvio parses the XML against its XSD schema and renders Vaadin 7 components through a Java presenter layer — no Java UI code lives here.
vitruvio.json Registration
{
"key": "my-panel",
"name": "My Panel",
"description": "Short description.",
"category": "Category/Subcategory",
"displayOrder": 10,
"showInPresentation": false,
"openInNewWindow": false,
"showInMobileList": false,
"displayTimeInSeconds": 0,
"allowedGroups": ["group-key"],
"allowedUsers": [],
"forms": {
"desktop": "panels/my-panel/my-panel-desktop.xml",
"mobile": "panels/my-panel/my-panel-mobile.xml"
}
}
displayOrder— use gaps (10, 20, 30…) to allow future insertions.forms.mobileis optional. Omit if there is no mobile form.allowedGroupsandallowedUsersare additive — a user in any allowed group or listed directly gets access.
Desktop Form — File Structure
<?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="uniqueFormKey" width="100%" height="100%">
<name>Human-readable title</name>
<description>Short description.</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called when the panel loads
}
]]>
</initScript>
<components>
<!-- root layout component -->
</components>
</form>
</panel-form>
formKeymust be unique within the panel. By convention it matches the panel key.<initScript>runs once when the panel is opened. Therun()function is the entry point.- Everything in
<components>is the Vaadin component tree.
Mobile Form — File Structure
Mobile forms use a different namespace and XSD, and support a <ServerSide> block for server-executed bridge functions.
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
<form formKey="uniqueFormKey">
<name>Title</name>
<description></description>
<initScript language="JavaScript">
<![CDATA[
function run() { ... }
]]>
</initScript>
<ServerSide>
<DataSources>
<SQLBuilderScriptDataSource id="dsName" connection-key="datasource-key"
language="JavaScript" workOnline="true">
<![CDATA[
function buildSQL(requestParams, sql_params) {
return "SELECT ...";
}
]]>
</SQLBuilderScriptDataSource>
</DataSources>
<Bridges>
<Bridge id="myBridge" language="JavaScript">
<![CDATA[
function execute(params) {
// runs on the server — full access to libService, db, etc.
return { result: ... };
}
]]>
</Bridge>
</Bridges>
</ServerSide>
<components>
<!-- mobile component tree -->
</components>
</form>
</panel-form>
Calling bridges from mobile client scripts
vCommunicationService.executeOnServer('myBridge', params).then(function(result) {
// handle result
}).catch(function(error) {
console.log('Error: ' + error);
});
Mobile timers
engine.startTimer('timerName', function() {
// runs every intervalMs
}, intervalMs);
engine.stopTimer('timerName');
The engine API (Desktop)
engine is injected into all script contexts inside a panel.
Fields
var field = engine.getField('fieldId');
field.getValue(); // returns null when empty; for DB-backed fields returns a Java type — use == not === when comparing
field.setValue(value);
field.clear();
field.setEnabled(true/false);
field.setVisible(true/false);
field.setRequired(true/false);
field.focus();
field.refresh(); // DB components — re-runs the datasource query
Layouts
var layout = engine.getLayout('layoutId');
layout.getRootComposition().setVisible(true/false);
layout.getRootComposition().setCaption('New caption');
layout.getRootComposition().removeAllComponents();
layout.getRootComposition().addComponent(component);
layout.getRootComposition().setScrollTop(100000); // scroll to bottom
// WindowLayout only
layout.showWindow();
layout.closeWindow();
User & Session
var user = engine.getLoggedUser();
user.getLogin(); // username string
user.getNome(); // display name (desktop)
Global variables — sharing state across components
// Set in initScript or any handler
engine.setGlobalVariable('myHelper', function() { ... });
engine.setGlobalVariable('myData', { key: 'value' });
// Read anywhere in the form
engine.getGlobalVariable('myHelper')();
var data = engine.getGlobalVariable('myData');
// Clean up
engine.unsetGlobalVariable('myVar');
Use global variables to expose reusable functions (e.g. limparCampos, carregarDados) and shared state across components and events.
Form validation
if (engine.isValid()) {
// all required fields are filled
}
Repeating timers (desktop polling)
var timer = engine.registerRepeatingTimer(3000, function() {
// do something every 3s
// return true to keep running, false/undefined to stop
});
engine.setGlobalVariable('myTimer', timer);
// Later, to stop it:
engine.getGlobalVariable('myTimer').setEnabled(false);
engine.unsetGlobalVariable('myTimer');
Script Blocks and Events
initScript
Runs when the panel loads. Define helper functions here and expose them via setGlobalVariable so other components can call them.
<initScript language="JavaScript">
<![CDATA[
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
function carregarDados() { ... }
function run() {
engine.setGlobalVariable('carregarDados', carregarDados);
carregarDados();
}
]]>
</initScript>
Button click
<ButtonWidget id="btnSalvar" caption="Salvar" defaultIcon="SAVE" style="GREEN">
<onClickScript language="JavaScript">
<![CDATA[
function run() {
// handle click
}
]]>
</onClickScript>
</ButtonWidget>
Field value change
<TextField id="txfPesquisa" type="string" caption="Pesquisar" immediate="true">
<events>
<valueChange>
<script language="JavaScript">
<![CDATA[
function run() {
engine.getField('myTable').refresh();
}
]]>
</script>
</valueChange>
</events>
</TextField>
immediate="true" is required for valueChange to fire on every keystroke.
Layout Components
VerticalLayout / HorizontalLayout
<VerticalLayout width="100%" height="100%" spacing="true" margin="true"
id="myLayout" align="TOP_LEFT" expandRatio="1" visible="true"
backgroundColor="#f8b916">
Sizing fields inside layouts: always prefer width="100%" combined with expandRatio instead of fixed pixel widths. expandRatio controls the proportional share of the available space each child receives.
<HorizontalLayout width="100%" spacing="true">
<NumericField id="nmfId" width="100%" expandRatio="1" ... />
<TextField id="txfNome" width="100%" expandRatio="3" ... />
<ButtonWidget id="btnAcao" width="100%" expandRatio="1" ... />
</HorizontalLayout>
Panel
A styled container with an optional caption and background color.
<Panel width="100%" height="100%" caption="Section title" backgroundColor="#f0f0f0" margin="true">
<VerticalLayout>...</VerticalLayout>
</Panel>
CrudPanel
The standard page chrome: header bar + section content area.
Validators will reject it as a root element. Always wrap it in a Layout element like
VerticalLayout,HorizontalLayout,TabLayout, orAbsoluteLayoutas the root component inside<components>.
<VerticalLayout width="100%" height="100%">
<CrudPanel width="100%" height="100%" expandRatio="1">
<Header>
<Caption>Page Title</Caption>
<SubCaption>Subtitle text</SubCaption>
</Header>
<Section caption="Tab label" subCaption="..." showHeader="false" width="100%" height="100%">
<VerticalLayout width="100%" height="100%">
<!-- content — Section requires a layout component (VerticalLayout, HorizontalLayout, etc.) as its direct child, not bare components -->
</VerticalLayout>
</Section>
</CrudPanel>
</VerticalLayout>
TabLayout
<TabLayout width="100%">
<Tab caption="Tab 1">
<!-- content -->
</Tab>
<Tab caption="Tab 2">
<!-- content -->
</Tab>
</TabLayout>
ScrollPanel
<ScrollPanel id="scrollArea" width="100%" height="100%">
<VerticalLayout id="content" width="100%">
<!-- scrollable content -->
</VerticalLayout>
</ScrollPanel>
WindowLayout (modal)
<WindowLayout id="myModal" windowHeight="40%" windowWidth="40%"
windowResizable="false" windowClosable="false" windowModal="true">
<VerticalLayout width="100%" height="100%" margin="true">
<!-- modal content -->
</VerticalLayout>
</WindowLayout>
Open/close via engine.getLayout('myModal').showWindow() / .closeWindow().
Input Components
Common attributes
| Attribute | Purpose |
|---|---|
id |
Required for engine.getField() access |
caption |
Label above the component |
width |
CSS width (100%, 200px) |
expandRatio |
Flex grow ratio within parent layout |
visible |
true/false |
required |
Marks field as required for engine.isValid() |
enabled |
true/false |
description |
Tooltip text |
immediate |
true to fire events on every change |
maxLength |
Max character count (TextField, TextArea) |
TextField / TextArea / NumericField / DateField
<TextField id="txfNome" type="string" caption="Nome" width="100%" required="true" maxLength="100" />
<TextArea id="txaDescricao" type="string" caption="Descrição" width="100%" />
<NumericField id="nmfCodigo" type="number" caption="Código" width="100%" enabled="false" />
<DateField id="dtInicio" type="date" caption="Data Início" resolution="DAY" width="100%" />
Label (HTML content)
<Label id="lblInfo" contentMode="HTML" align="MIDDLE_CENTER">
<value>
<![CDATA[
<b style="font-size: 16px;">HTML content here</b>
]]>
</value>
</Label>
ButtonWidget
<ButtonWidget id="btnAcao" caption="Label" defaultIcon="SAVE"
style="GREEN" width="100%" height="30px" expandRatio="1" align="MIDDLE_RIGHT"
keyCode="ENTER">
style values: BLUE, RED, GREEN, GRAY
defaultIcon values: SAVE, ADD, EDIT, REMOVE, TRASH, SEARCH, BACK, SEND, ARROW_UP, ARROW_DOWN
DB Components
DBTable
The primary data grid. Fetches data from a SQL datasource.
<DBTable id="dbtItems" type="number" immediate="true" width="100%"
height="100%" expandRatio="1" caption="Items"
showRowCount="true" drawRefreshButton="true" selectable="false"
rows="10" multivalue="false" exportXLS="true" useGridComponent="true">
<datasource>
<sqlBuilderDataSource connection-key="vitruvio" language="JavaScript">
<![CDATA[
function buildSQL(params) {
// params is a Map<String, Object> — put values here and reference with ${name} in SQL.
// Read form fields via engine (guard with if(engine) — datasource may run before form init).
var search = engine ? engine.getField('txfPesquisa').getValue() : null;
var searchValue = (search && String(search) != '') ? '%' + String(search) + '%' : '%';
params.put('search', searchValue);
return "SELECT * FROM MY_TABLE WHERE upper(NAME) LIKE upper(${search}) ORDER BY NAME";
}
]]>
</sqlBuilderDataSource>
</datasource>
<key-field>ID</key-field>
<columns>
<column name="ID" caption="Código" expand-ratio=".1" />
<column name="NAME" caption="Nome" expand-ratio=".6" />
<generated name="edit" caption="Editar" expand-ratio=".1">
<!-- see Generated Columns below -->
</generated>
</columns>
<bind>
<parameter value-type="string" defaultValue="" parameterName="parFilter" field-ref="txfFilter" />
</bind>
<events>
<valueChange>
<script language="JavaScript">
<![CDATA[
function run() {
var id = engine.getField('dbtItems').getValue();
// react to row selection
}
]]>
</script>
</valueChange>
</events>
<styleGenerator>
<scriptGenerator language="JavaScript">
<![CDATA[
function getStyle() {
return 'background-light-gray';
}
]]>
</scriptGenerator>
</styleGenerator>
</DBTable>
Datasource variants:
<sqlBuilderDataSource>— dynamic SQL built in JavaScript;engineis available inside<freeQuery connection-key="...">— static SQL with${paramName}tokens bound via<bind><form-datasource ref="dsId" />— reference a<DataSources>definition (mobile)
Parameter syntax — important distinction:
sqlBuilderDataSourceandfreeQuery: use${paramName}in SQL, bound viaparams.put()or<bind>dblibrary scripts (inline SQL): use:paramNamesyntax instead
Note: In sqlBuilderDataSource, always guard with if (engine) before calling engine.getField(...) because the datasource may be evaluated before the form is fully initialised.
Generated Columns (action buttons in table rows)
var vc = libService.loadScript('vaadinComponents');
function Generator() {
this.generate = function(itemId, columnId, item, container) {
var btn = vc.buttonIcon('Editar', function() {
var nome = item.getItemProperty('NAME').getValue();
engine.getField('txfNome').setValue(nome);
}, 'edit');
return btn;
}
}
var script = new Generator();
For delete with confirmation dialog:
var vc = libService.loadScript('vaadinComponents');
importClass(Packages.br.com.davinti.base.vaadin.components.layout.ConfirmationBox);
function Generator() {
this.generate = function(itemId, columnId, item, container) {
var btn = vc.buttonIcon('Deletar', function() {
var listener = new ConfirmationBox.ConfirmationBoxListener() {
dialogEnd: function(context, action) {
if (action == ConfirmationBox.Action.YES) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
banco.update("DELETE FROM MY_TABLE WHERE ID = :id", { id: Number(itemId) });
engine.getField('dbtItems').refresh();
}
}
};
ConfirmationBox.show(
ConfirmationBox.DialogIcon.WARNING,
'Confirmar exclusão',
'Deseja excluir o item ' + item.getItemProperty('NAME').getValue() + '?',
listener,
ConfirmationBox.ACTION_YES_CANCEL
);
}, 'trash');
return btn;
}
}
var script = new Generator();
DBTwinColSelect (multi-select from DB)
Multi-select widget populated from a query.
Reading selected values: getValue() on a multivalue="true" DBTwinColSelect returns a Java Collection, not a JS array. Use the Java iterator API:
var selected = engine.getField('dbtwItems').getValue();
if (selected != null) {
var iter = selected.iterator();
while (iter.hasNext()) {
var item = String(iter.next());
// process item
}
}
Setting values programmatically: use a Java ArrayList:
var itens = new java.util.ArrayList();
banco.query(sql, params).each(function(row) {
itens.add(java.lang.Long.valueOf(row.ID));
});
engine.getField('dbtwItems').setValue(itens);
DBComboBox
Single-select dropdown from a query. Use .getValue() / .setValue(val) / .clear().
Feedback to the User
MessageBox (desktop)
importClass(Packages.br.com.davinti.base.vaadin.components.layout.MessageBox);
MessageBox.show(MessageBox.BoxType.SUCESS, 'Sucesso', 'Saved.');
MessageBox.show(MessageBox.BoxType.ERROR, 'Erro', 'Something failed.');
MessageBox.show(MessageBox.BoxType.WARNING, 'Atenção', 'Please check...');
Note: BoxType.SUCESS has no second S — that's the platform's spelling.
Tray notification (non-blocking)
var messages = libService.loadScript('messages');
var n = messages.notification;
n.show({
caption: 'Saved',
msg: 'Data saved successfully.',
delay: 3,
type: n.type.tray
});
MessageBox (mobile)
The mobile API is different:
MessageBox.show('Title', 'Message text');
MessageBox.showLoading('Loading...');
MessageBox.hideLoading();
MessageBox.confirm('Message', 'Title', [
{ text: 'Sim', handler: function() { ... } },
{ text: 'Não', handler: function() { ... } }
]);
Loading Scripts and Libraries
// In initScript (runs once, available to all handlers via globalVariable)
var libIA = libService.loadScript('IA_lib');
engine.setGlobalVariable('libIA', libIA);
// Inside a button or event handler
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
The vaadinComponents lib is used heavily in generated columns to create dynamic Vaadin components programmatically (buttons, layouts, labels, etc.).
Database in Panels
Same as in endpoints — see the root CLAUDE.md for db usage notes. Key extras for panels:
banco.getSequenceNextVal('MY_SEQUENCE'); // get next ID before insert
banco.transaction(function() {
this.update(sql1, params1);
this.update(sql2, params2);
});
banco.isOracle(); // true if Oracle — use for DB-dialect differences (SYSDATE vs CURRENT_TIMESTAMP, etc.)
Field ID Naming Conventions
Prefix field IDs by component type for readability:
| Prefix | Component |
|---|---|
txf |
TextField |
txa |
TextArea |
ncf / nmf |
NumericField |
dbt |
DBTable |
dbb |
DBComboBox |
dbtw |
DBTwinColSelect |
btn |
ButtonWidget |
chk |
CheckBox |
lbl |
Label |
dt |
DateField |
cmb |
ComboBox |
Common Patterns
Show/hide navigation between views
Use multiple layouts within the same form and toggle visibility:
engine.getLayout('layoutForm').getRootComposition().setVisible(true);
engine.getLayout('layoutList').getRootComposition().setVisible(false);
engine.getLayout('sectionHeader').getRootComposition().setCaption('New Caption');
CRUD in a single form
The recurring pattern across panels:
- List view (
layoutHome) — DBTable + search field + "Add" button - Form view (
layoutAddItem) — fields + Save + Back buttons, initiallyvisible="false" - initScript exposes a
limparCamposglobal that clears all form fields - Edit button in DBTable populates fields and switches to form view
- Save checks
engine.isValid(), upserts based on whether a key field has a value, then returns to list - Back clears fields and returns to list
Reading system config
var valor = vConfigService.getSystemConfigAsString('CONFIG_KEY');
Getting the logged user in a script
engine.getLoggedUser().getLogin() // desktop
engine.getLoggedUser().getNome() // desktop display name
engine.getLoggedUser().getName() // mobile display name