# 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
```json
{
"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.mobile` is optional. Omit if there is no mobile form.
- `allowedGroups` and `allowedUsers` are additive — a user in any allowed group or listed directly gets access.
---
## Desktop Form — File Structure
```xml
```
- `formKey` must be unique within the panel. By convention it matches the panel key.
- `` runs once when the panel is opened. The `run()` function is the entry point.
- Everything in `` is the Vaadin component tree.
---
## Mobile Form — File Structure
Mobile forms use a different namespace and XSD, and support a `` block for server-executed bridge functions.
```xml
```
### Calling bridges from mobile client scripts
```javascript
vCommunicationService.executeOnServer('myBridge', params).then(function(result) {
// handle result
}).catch(function(error) {
console.log('Error: ' + error);
});
```
### Mobile timers
```javascript
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
```javascript
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
```javascript
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
```javascript
var user = engine.getLoggedUser();
user.getLogin(); // username string
user.getNome(); // display name (desktop)
```
### Global variables — sharing state across components
```javascript
// 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
```javascript
if (engine.isValid()) {
// all required fields are filled
}
```
### Repeating timers (desktop polling)
```javascript
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.
```xml
```
### Button click
```xml
```
### Field value change
```xml
```
`immediate="true"` is required for `valueChange` to fire on every keystroke.
---
## Layout Components
### VerticalLayout / HorizontalLayout
```xml
```
**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.
```xml
```
### Panel
A styled container with an optional caption and background color.
```xml
...
```
### 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`, or `AbsoluteLayout` as the root component inside ``.
```xml
Page Title
Subtitle text
```
### TabLayout
```xml
```
### ScrollPanel
```xml
```
### WindowLayout (modal)
```xml
```
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
```xml
```
### Label (HTML content)
```xml
```
### ButtonWidget
```xml
```
`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.
```xml
— 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";
}
]]>
ID
```
**Datasource variants:**
- `` — dynamic SQL built in JavaScript; `engine` is available inside
- `` — static SQL with `${paramName}` tokens bound via ``
- `` — reference a `` definition (mobile)
**Parameter syntax — important distinction:**
- `sqlBuilderDataSource` and `freeQuery`: use `${paramName}` in SQL, bound via `params.put()` or ``
- `db` library scripts (inline SQL): use `:paramName` syntax 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)
```javascript
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:
```javascript
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:
```javascript
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`:
```javascript
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)
```javascript
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)
```javascript
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:
```javascript
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
```javascript
// 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:
```javascript
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:
```javascript
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:
1. **List view** (`layoutHome`) — DBTable + search field + "Add" button
2. **Form view** (`layoutAddItem`) — fields + Save + Back buttons, initially `visible="false"`
3. **initScript** exposes a `limparCampos` global that clears all form fields
4. **Edit** button in DBTable populates fields and switches to form view
5. **Save** checks `engine.isValid()`, upserts based on whether a key field has a value, then returns to list
6. **Back** clears fields and returns to list
### Reading system config
```javascript
var valor = vConfigService.getSystemConfigAsString('CONFIG_KEY');
```
### Getting the logged user in a script
```javascript
engine.getLoggedUser().getLogin() // desktop
engine.getLoggedUser().getNome() // desktop display name
engine.getLoggedUser().getName() // mobile display name
```