This commit is contained in:
Matheus
2026-09-23 12:29:08 -03:00
commit f5d231ab8f
59 changed files with 16658 additions and 0 deletions
+673
View File
@@ -0,0 +1,673 @@
# 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
<?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>
```
- `formKey` must be unique within the panel. By convention it matches the panel key.
- `<initScript>` runs once when the panel is opened. The `run()` 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
<?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
```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
<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
```xml
<ButtonWidget id="btnSalvar" caption="Salvar" defaultIcon="SAVE" style="GREEN">
<onClickScript language="JavaScript">
<![CDATA[
function run() {
// handle click
}
]]>
</onClickScript>
</ButtonWidget>
```
### Field value change
```xml
<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
```xml
<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.
```xml
<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.
```xml
<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`, or `AbsoluteLayout` as the root component inside `<components>`.
```xml
<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
```xml
<TabLayout width="100%">
<Tab caption="Tab 1">
<!-- content -->
</Tab>
<Tab caption="Tab 2">
<!-- content -->
</Tab>
</TabLayout>
```
### ScrollPanel
```xml
<ScrollPanel id="scrollArea" width="100%" height="100%">
<VerticalLayout id="content" width="100%">
<!-- scrollable content -->
</VerticalLayout>
</ScrollPanel>
```
### WindowLayout (modal)
```xml
<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
```xml
<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)
```xml
<Label id="lblInfo" contentMode="HTML" align="MIDDLE_CENTER">
<value>
<![CDATA[
<b style="font-size: 16px;">HTML content here</b>
]]>
</value>
</Label>
```
### ButtonWidget
```xml
<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.
```xml
<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; `engine` is 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:**
- `sqlBuilderDataSource` and `freeQuery`: use `${paramName}` in SQL, bound via `params.put()` or `<bind>`
- `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
```