initial
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
---
|
||||
name: vitruvio-criar-form-mobile
|
||||
description: >
|
||||
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
|
||||
|
||||
1. **Different schema.** Root is `<mobile-forms>`, namespace
|
||||
`http://www.davinti.com.br/vitruvio/mobile-form`, XSD `vitruvio-mobile-form.xsd`.
|
||||
2. **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 structural `SubForm`/`ItemList`). Read
|
||||
`~/.local/share/vitruvio-platform/docs/components/mobile/<Component>.md` before using one.
|
||||
3. **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.).
|
||||
4. **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>` with `vCommunicationService.executeOnServer(...)`.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
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 the `activiti:formKey` in
|
||||
`processes/<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 a `queries/*.sql`)
|
||||
- any server-only logic / libs needed (each becomes a `<Bridge>`)
|
||||
- **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 identical `form-mobile.xml` tabs. Older content used `form-mobile.xml` /
|
||||
> `form_web_mobile.xml`; the real path is whatever `forms.mobile` points 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
|
||||
<?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**:
|
||||
|
||||
```xml
|
||||
<!-- 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>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 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 **`QueryDataSource`** backed by a
|
||||
`queries/*.sql` (works offline once synced).
|
||||
- **Process variables** the form needs → either declare them in `<Autoload>` or fetch them
|
||||
in a Bridge via `runtimeService.getVariable(processInstanceId, 'varName')` and store locally
|
||||
with `execution.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>`:
|
||||
|
||||
```xml
|
||||
<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"` and `showInMobileList: 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 an `activiti:formKey` in the BPMN.
|
||||
- Suggest reading `docs/components/mobile/` and an example
|
||||
(`~/.local/share/vitruvio-platform/examples/processes/*/form_web_mobile.xml`) for richer screens.
|
||||
Reference in New Issue
Block a user