12 KiB
name, description
| name | description |
|---|---|
| vitruvio-criar-form-mobile | Use when the user wants to create or edit the MOBILE form of a Vitruvio panel or process (the React-Native form rendered in the mobile app). Triggers: "create mobile form", "criar formulário mobile", "mobile panel", "painel mobile", "criar painel mobile", "process mobile form", "formulário mobile do processo", "add mobile form", "mobile form xml", "bridge mobile". This is the single home for mobile form knowledge; vitruvio-criar-painel and vitruvio-criar-processo call it for their mobile part. For the desktop form use vitruvio-criar-form-desktop. |
Create Vitruvio Mobile Form
All messages shown to the user must be written in Portuguese.
Mobile forms are not desktop forms with a different skin. They use a different schema, a smaller component set, a different JavaScript runtime, and an explicit server↔client data contract. Read this whole skill before scaffolding — the mistakes here are not the desktop mistakes.
The four things that make mobile different
- Different schema. Root is
<mobile-forms>, namespacehttp://www.davinti.com.br/vitruvio/mobile-form, XSDvitruvio-mobile-form.xsd. - Only 18 components (vs 78 desktop). Do not assume a desktop widget exists on
mobile. The full list:
CheckBox, ComboBox, GoogleMapsField, HorizontalLayout, ImageLibraryByFieldValue, ImageWidget, Label, MaskedField, MoneyField, NumericField, OptionGroup, ProgressBarWidget, RatingStars, SignaturePadField, TabLayout, TextField, Toggle, VerticalLayout(plus structuralSubForm/ItemList). Read~/.local/share/vitruvio-platform/docs/components/mobile/<Component>.mdbefore using one. - Two JavaScript runtimes.
- Client-side (
initScript,discoveryScript, validators, component event scripts): modern React-Native JS — arrow functions, Promises,.then()/.catch()are fine and expected. Most APIs are async and return Promises. - Server-side (
<ServerSide><Bridge>execute(...)bodies): Rhino ES5, same rules as scripts/desktop. This is the only place with access to platform libs and services (libService,runtimeService, db, etc.).
- Client-side (
- Data is explicit. Nothing is auto-injected the way desktop process variables are. Every
piece of server data the form needs must be declared — via an
<Autoload>variable, a<QueryDataSource>(synced to the device, works offline), or fetched on demand from a named<Bridge>withvCommunicationService.executeOnServer(...).
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 gather the data contract
Ask (only what is missing):
- Panel or process mobile form? (see Differences: panel vs process below)
- Key — the panel or process key (folder name).
- For a process: which
formKey(s) — they must match theactiviti:formKeyinprocesses/<key>/<key>.bpmn. - Which variables does the form need? Because nothing is auto-injected, ask explicitly:
- process variables to read (each becomes an
<Autoload><variable>and/or a Bridge call) - reference/lookup data (each becomes a
<QueryDataSource>backed by aqueries/*.sql) - any server-only logic / libs needed (each becomes a
<Bridge>)
- process variables to read (each becomes an
- Offline? Whether the data sources must be available without connectivity (affects
autoSyncOnInit/autoSyncOnDiscovery).
Differences: panel vs process
The schema, component set, ServerSide/Bridge mechanics and JS runtimes below are identical for both. Only these differ:
| Panel mobile form | Process mobile form | |
|---|---|---|
| File | panels/<key>/<key>-mobile.xml |
processes/<key>/<key>-mobile.xml |
| Root attribute | <mobile-forms> (no processKey) |
<mobile-forms processKey="<key>"> |
| Forms per file | one <form> |
one <form formKey> per BPMN activiti:formKey (must match) |
| Manifest | set forms.mobile and showInMobileList: true on the panel entry |
set forms.mobile (and optionally forms.mobileAlternative) on the process entry |
| Variables | use engine.getGlobalVariable / Autoload |
process variables are fetched server-side via a Bridge (runtimeService.getVariable) and/or declared in <Autoload> |
Filename: name files by the artifact key + suffix —
<key>-mobile.xml(and<key>-desktop.xml,<key>.bpmn). This keeps every form searchable by its key instead of dozens of identicalform-mobile.xmltabs. Older content usedform-mobile.xml/form_web_mobile.xml; the real path is whateverforms.mobilepoints to, so legacy files still work — but new scaffolds use<key>-mobile.xml.
Step 3 — Scaffold the file
Skeleton (process variant shown; for a panel drop processKey and use a single <form>)
<?xml version="1.0" encoding="UTF-8"?>
<mobile-forms processKey="<key>"
xmlns="http://www.davinti.com.br/vitruvio/mobile-form"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/mobile-form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-form.xsd">
<form formKey="formColeta">
<name>Coleta</name>
<description>Etapa de coleta no app</description>
<!-- Runs when the form opens. CLIENT-SIDE: modern JS, async APIs return Promises. -->
<initScript language="JavaScript">
<![CDATA[
function run() {
// Autoloaded variables land in the global scope:
engine.getField('nomeLoja').setValue(engine.getGlobalVariable('nomeLoja'));
}
]]>
</initScript>
<!-- Optional: pull server data when the task is discovered (before the user opens it). -->
<discoveryScript language="JavaScript">
<![CDATA[
function run() {
var params = { id: execution.getProcessInstanceId() };
vCommunicationService.executeOnServer('bridgeNumeroCarga', params).then(result => {
var n = parseInt(result, 10);
execution.setVariable('numeroCarga', n).then(ok => {}).catch(err => {
console.log('Erro ao setar numeroCarga localmente', err);
});
}).catch(err => {
console.log('Erro ao coletar numeroCarga no server', err);
});
}
]]>
</discoveryScript>
<!-- Block completing the task when a rule is not met. -->
<validators>
<ScriptValidator execution="COMPLETE" language="JavaScript" id="validadorComplete">
<![CDATA[
function Validator() {
var msg;
this.getMessage = function() { return msg; };
this.isValid = function() {
if (engine.getField('confirma').getValue() == 'Sim') { return true; }
msg = 'Confirme a execução antes de finalizar.';
return false;
};
}
var validator = new Validator();
]]>
</ScriptValidator>
</validators>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="nomeLoja" type="string" caption="Loja" readOnly="true" />
<OptionGroup id="confirma" type="string" caption="Executou?" required="true">
<entry key="Sim" value="Sim" />
<entry key="Nao" value="Não" />
</OptionGroup>
<SignaturePadField id="assinatura" caption="Assinatura" />
</VerticalLayout>
</components>
<!-- Explicitly declared variables auto-injected into the form's scope on load. -->
<Autoload>
<variables autoInjectionScope="ENGINE_GLOBAL_SCOPE" autoPersist="true">
<variable>nomeLoja</variable>
<variable>numeroCarga</variable>
</variables>
</Autoload>
<!-- Everything the server provides to this form. -->
<ServerSide>
<!-- Named queries (queries/*.sql) synced to the device for offline lookups. -->
<DataSources>
<QueryDataSource key="qry_produtos_carga" autoSyncOnInit="true" autoSyncOnDiscovery="true" refreshInSeconds="60" />
</DataSources>
<!-- Server-side functions. Rhino ES5. Full access to libService / runtimeService / db.
Called from the client via vCommunicationService.executeOnServer('id', params). -->
<Bridges>
<Bridge language="JavaScript" id="bridgeNumeroCarga">
<![CDATA[
function execute(params) {
var numeroCarga = runtimeService.getVariable(params.id, 'numeroCarga');
return numeroCarga ? numeroCarga : -1;
}
]]>
</Bridge>
</Bridges>
</ServerSide>
</form>
</mobile-forms>
How libs and process variables reach the mobile form
The mobile app cannot call libService.loadScript(...) or read process variables directly —
those live on the server. The pattern is always declare a Bridge, call it from the client:
<!-- SERVER-SIDE (Rhino ES5): a lib used to build a barcode image -->
<Bridge language="JavaScript" id="imagemCodBarras">
<![CDATA[
function execute(codbarras) {
var generator = libService.loadScript('barcode-gen');
return ',' + generator.generateEAN13BarcodeImageAsBase64({ value: codbarras });
}
]]>
</Bridge>
// CLIENT-SIDE (modern JS): call the bridge, use the Promise result
vCommunicationService.executeOnServer('imagemCodBarras', codigoBarras).then(base64 => {
engine.getField('codigoImagem').setValue(base64);
}).catch(err => console.log('Erro no bridge imagemCodBarras', err));
Rules of thumb:
- Anything needing a platform lib, the database, or a platform service → put it in a
Bridge (server, ES5) and call it with
vCommunicationService.executeOnServer. - Reference/lookup tables the form reads repeatedly → a
QueryDataSourcebacked by aqueries/*.sql(works offline once synced). - Process variables the form needs → either declare them in
<Autoload>or fetch them in a Bridge viaruntimeService.getVariable(processInstanceId, 'varName')and store locally withexecution.setVariable(...). - Client-side
execution.getVariable(...)/execution.setVariable(...)are async and return Promises — use.then().
List-based entry: SubForm + ItemList
For "add many items" screens (collect a list of rows), use a SubForm with an <ItemList>:
<SubForm formKey="executarAcao">
<name>Executar AÇÃO</name>
<initScript language="JavaScript">
<![CDATA[ function run(apply) { if (apply) { apply(); } } ]]>
</initScript>
<ItemList addItemButtonCaption="Gravar na Lista" caption="Itens">
<property id="OBSERVACAO" caption="Observação" />
</ItemList>
<components>
<VerticalLayout width="100%" spacing="true" margin="true">
<!-- fields captured per item -->
</VerticalLayout>
</components>
</SubForm>
Validate list completeness with engine.getSubFormListSizeByStatus(function(size){ ... }, "all")
inside a COMPLETE validator.
Step 4 — Manifest
If run standalone, update the matching entry in vitruvio.json:
- Panel: set
forms.mobile = "panels/<key>/<key>-mobile.xml"andshowInMobileList: true. - Process: set
forms.mobile = "processes/<key>/<key>-mobile.xml".
Full entry creation is handled by vitruvio-criar-painel / vitruvio-criar-processo.
Step 5 — Report
Tell the user:
- File created/updated and variant (panel/process).
- Which variables/queries/bridges were declared, and that only declared data is available on the device — anything else must be added as an Autoload variable, QueryDataSource, or Bridge.
- Reminder: client scripts are modern JS (Promises); Bridge bodies are server-side Rhino ES5.
- For processes: each
<form formKey>must match anactiviti:formKeyin the BPMN. - Suggest reading
docs/components/mobile/and an example (~/.local/share/vitruvio-platform/examples/processes/*/form_web_mobile.xml) for richer screens.