pgkiln 0.81.0 → 0.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/db/migrations/147_condition_types.sql +80 -0
- package/db/migrations/148_validation_types.sql +44 -0
- package/db/migrations/149_dynamic_action_events.sql +45 -0
- package/db/migrations/150_process_types.sql +23 -0
- package/db/migrations/151_item_types.sql +16 -0
- package/db/migrations/152_workflow_diagram_region.sql +10 -0
- package/db/migrations/153_component_events.sql +21 -0
- package/docs/apex-feature-parity.md +11 -9
- package/docs/guide/02-concepts.md +3 -3
- package/docs/guide/03-builder.md +8 -1
- package/docs/guide/04-pages-and-regions.md +19 -4
- package/docs/guide/05-items.md +5 -0
- package/docs/guide/06-processing.md +104 -17
- package/docs/guide/07-dynamic-actions.md +43 -1
- package/docs/guide/11-from-apex.md +8 -0
- package/docs/guide/12-development.md +9 -1
- package/examples/hr/hr_79_condition_types.sql +17 -0
- package/package.json +1 -1
- package/public/app.css +14 -0
- package/public/app.js +242 -13
- package/public/builder.css +25 -2
- package/public/data-modeler.js +8 -5
- package/public/sql-monitor.js +5 -2
- package/src/appfiles.ts +4 -4
- package/src/builder/advisor.ts +43 -7
- package/src/builder/ai-builder.ts +50 -46
- package/src/builder/api.ts +49 -49
- package/src/builder/autorest.ts +92 -87
- package/src/builder/changelog.ts +39 -37
- package/src/builder/code-editor.ts +14 -10
- package/src/builder/components.ts +54 -23
- package/src/builder/conditions.ts +64 -0
- package/src/builder/data-modeler.ts +89 -84
- package/src/builder/data-security.ts +39 -37
- package/src/builder/dataload.ts +69 -65
- package/src/builder/datapump.ts +108 -97
- package/src/builder/ddl-tools.ts +38 -34
- package/src/builder/designer.ts +9 -1
- package/src/builder/document-view.ts +11 -11
- package/src/builder/i18n/de.ts +1662 -0
- package/src/builder/i18n/en.ts +1662 -0
- package/src/builder/i18n/es.ts +1662 -0
- package/src/builder/i18n/fr.ts +1662 -0
- package/src/builder/i18n/it.ts +1662 -0
- package/src/builder/i18n/ja.ts +1662 -0
- package/src/builder/i18n/ko.ts +1662 -0
- package/src/builder/i18n/nl.ts +1662 -0
- package/src/builder/i18n/pt.ts +1662 -0
- package/src/builder/i18n/zh-cn.ts +1662 -0
- package/src/builder/i18n/zh-tw.ts +1662 -0
- package/src/builder/i18n.ts +18 -0
- package/src/builder/object-actions.ts +18 -16
- package/src/builder/object-browser.ts +2 -2
- package/src/builder/querybuilder.ts +42 -36
- package/src/builder/quicksql.ts +16 -18
- package/src/builder/region-help.ts +3 -1
- package/src/builder/region-settings.ts +225 -198
- package/src/builder/report-settings.ts +64 -60
- package/src/builder/rest-modules.ts +145 -143
- package/src/builder/sampledata.ts +59 -52
- package/src/builder/scheduling.ts +180 -159
- package/src/builder/scripts.ts +71 -65
- package/src/builder/sql-charts.ts +36 -30
- package/src/builder/sql-monitor.ts +95 -89
- package/src/builder/sql-utilities.ts +47 -40
- package/src/builder/top-sql.ts +21 -17
- package/src/builder/unload.ts +48 -44
- package/src/builder/worksheet.ts +3 -3
- package/src/cron-schedule.ts +47 -17
- package/src/i18n/ar.ts +5 -0
- package/src/i18n/cs.ts +5 -0
- package/src/i18n/da.ts +5 -0
- package/src/i18n/de.ts +5 -0
- package/src/i18n/el.ts +5 -0
- package/src/i18n/es.ts +5 -0
- package/src/i18n/fi.ts +5 -0
- package/src/i18n/fr.ts +5 -0
- package/src/i18n/he.ts +5 -0
- package/src/i18n/it.ts +5 -0
- package/src/i18n/ja.ts +5 -0
- package/src/i18n/ko.ts +5 -0
- package/src/i18n/nb.ts +5 -0
- package/src/i18n/pl.ts +5 -0
- package/src/i18n/pt.ts +5 -0
- package/src/i18n/ru.ts +5 -0
- package/src/i18n/sv.ts +5 -0
- package/src/i18n/tr.ts +5 -0
- package/src/i18n/uk.ts +5 -0
- package/src/i18n/zh.ts +5 -0
- package/src/i18n.ts +10 -0
- package/src/metadata.ts +74 -9
- package/src/runtime/authz.ts +9 -5
- package/src/runtime/calendar.ts +3 -2
- package/src/runtime/context.ts +6 -0
- package/src/runtime/da-styles.ts +37 -0
- package/src/runtime/documents.ts +3 -2
- package/src/runtime/engine.ts +95 -13
- package/src/runtime/grid.ts +1 -0
- package/src/runtime/items.ts +33 -0
- package/src/runtime/logic.ts +79 -8
- package/src/runtime/maps.ts +56 -7
- package/src/runtime/process-types.ts +139 -0
- package/src/runtime/processes.ts +2 -0
- package/src/runtime/regions.ts +4 -0
- package/src/runtime/render.ts +19 -3
- package/src/runtime/routes.ts +6 -2
- package/src/runtime/tree.ts +4 -3
- package/src/runtime/workflow-diagram.ts +45 -0
- package/src/workflow.ts +10 -2
|
@@ -79,11 +79,32 @@ process fails, earlier inserts and updates are rolled back too.
|
|
|
79
79
|
|
|
80
80
|
## Validations
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
| `
|
|
85
|
-
|
|
86
|
-
| `
|
|
82
|
+
APEX's validation types (sprint 154–155; APEX: *Validation → Type*):
|
|
83
|
+
|
|
84
|
+
| `type` | APEX | Passes when |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `sql` | Expression | `expression` (a SQL boolean expression, binds allowed) is true |
|
|
87
|
+
| `exists` / `not_exists` | Rows returned / No rows returned | the query in `expression` returns a row / no row |
|
|
88
|
+
| `function_body` | Function Body (returning Boolean) | the PL/pgSQL body in `expression` returns true |
|
|
89
|
+
| `function_error` | Function Body (returning Error Text) | the body returns null or empty; otherwise its text is the error message |
|
|
90
|
+
| `not_null` | Item is NOT NULL | the associated item has a value |
|
|
91
|
+
| `not_null_or_zero` | Item is NOT NULL or zero | the item has a value that is not zero |
|
|
92
|
+
| `not_zero` | Item is NOT zero | the item is empty or not zero |
|
|
93
|
+
| `no_spaces` | Item contains no spaces | the item has no white space |
|
|
94
|
+
| `alphanumeric` | Item is alphanumeric | the item has only letters and digits (any script) |
|
|
95
|
+
| `numeric` | Item is numeric | the item is a number (a number item's format mask is read back first) |
|
|
96
|
+
| `valid_date` | Item is a valid date | the item is a date in the application's date format, or ISO |
|
|
97
|
+
| `valid_timestamp` | Item is a valid timestamp | the item is `YYYY-MM-DD HH:MI[:SS]` (or with a `T`) |
|
|
98
|
+
| `equals` / `not_equals` | Item = Value / Item != Value | the item's value is / isn't `expression` |
|
|
99
|
+
| `in_list` / `not_in_list` | Item is (NOT) contained in Value | the item's value is / isn't one of `expression`'s values, colon separated (`CLERK:ANALYST`) |
|
|
100
|
+
| `chars_only` / `chars_any` / `chars_none` | Item contains only / at least one / none of the characters in Value | checked against the characters of `expression` |
|
|
101
|
+
| `regex` | Item matches Regular Expression | the item's value matches `expression` (a POSIX regular expression, checked in Postgres) |
|
|
102
|
+
|
|
103
|
+
The format checks (`no_spaces` … `valid_timestamp`, `chars_*`, `regex`, `not_zero`, `not_in_list`,
|
|
104
|
+
`not_equals`) pass an empty item, as APEX's do: add `not_null` (or *Value required*) when it must
|
|
105
|
+
have one. The database refuses a type without the associated item or expression it needs. The SQL
|
|
106
|
+
types run as the application's role; the [Advisor](03-builder.md#advisor) names SQL validations a type
|
|
107
|
+
says exactly (`:P3_SAL is not null` → `not_null`).
|
|
87
108
|
|
|
88
109
|
Plus: every **required** editable item must have a value.
|
|
89
110
|
|
|
@@ -92,6 +113,10 @@ Plus: every **required** editable item must have a value.
|
|
|
92
113
|
| `item_name` | The message is shown next to this item (otherwise at the top of the page) |
|
|
93
114
|
| `message` | The error message |
|
|
94
115
|
| `when_button` | Only validate for this request. A `DELETE` request runs only the validations with `when_button` `DELETE` (e.g. "approved requests can't be deleted"), not the item checks |
|
|
116
|
+
| `condition_type`, `condition_expr`, `condition_value` | Only validate when the [server-side condition](#server-side-conditions) holds, e.g. `request_in` `SAVE,CREATE` (sprint 154) |
|
|
117
|
+
| `authz` | Only validate for users the authorization scheme lets through (sprint 155) |
|
|
118
|
+
| `always_execute` | Also validate for a `DELETE` request (APEX: *Always Execute*) |
|
|
119
|
+
| `display_location` | Where the error shows: `inline_and_notification` (next to the item and at the top), `inline` (empty: next to the item, with "correct the errors below" at the top) or `notification` (at the top only). Without an associated item it is at the top |
|
|
95
120
|
|
|
96
121
|
Example: commission only for salesmen.
|
|
97
122
|
|
|
@@ -122,7 +147,7 @@ two combine well; see error handling below.
|
|
|
122
147
|
|---|---|
|
|
123
148
|
| `point` | `submit` (after validations) or `load` (when the page is shown, after form fetch) |
|
|
124
149
|
| `when_button` | Only for this request (empty = every submit with a button) |
|
|
125
|
-
| `condition_type`, `condition_expr`, `condition_value` | Only when the [condition](#
|
|
150
|
+
| `condition_type`, `condition_expr`, `condition_value` | Only when the [condition](#server-side-conditions) holds (server-side condition, as for computations and branches) |
|
|
126
151
|
| `parent_process` | The name of a `chain` process on the page: this process then runs only inside that chain |
|
|
127
152
|
| `region_id` | The form or grid region, for `form_dml` / `grid_dml` |
|
|
128
153
|
| `config` | Settings of a `data_load`, `invoke_api`, `download`, `chain`, `workflow`, `send_push` or `plugin` process (JSON) |
|
|
@@ -393,6 +418,23 @@ queued in the page's transaction and sent after the submit commits; a user witho
|
|
|
393
418
|
nothing (no error). The application needs push notifications on (Settings → Progressive Web App).
|
|
394
419
|
From SQL the same is `meta.send_push(...)` ([chapter 9](09-reference.md#push-notifications-from-sql)).
|
|
395
420
|
|
|
421
|
+
### Declarative process types
|
|
422
|
+
|
|
423
|
+
Sprint 157 (APEX: page process types). Each type calls what pgkiln already has; its settings go in
|
|
424
|
+
the process's attributes (`config`), checked when it is saved and again when it runs.
|
|
425
|
+
|
|
426
|
+
| `type` | APEX | Settings | What it does |
|
|
427
|
+
|---|---|---|---|
|
|
428
|
+
| `close_dialog` | Close Dialog | `{"items": ["P5_ID"]}` | Closes the modal page after the submit (a modal page closes after a submit anyway; this one also hands the items' values to the page below, where items of the same name take them before its [Dialog Closed](07-dynamic-actions.md#dialog-closed) actions run) |
|
|
429
|
+
| `clear_session_state` | Clear Session State | `{"items": ["P3_NAME"], "pages": [3, 4], "all": false}` | Empties the items, every `P<n>_` item of the pages, or all page and application items |
|
|
430
|
+
| `reset_pagination` | Reset Pagination | – | Reports and grids start on their first page after the submit |
|
|
431
|
+
| `user_preferences` | User Preferences | `{"action": "set", "preference": "DEFAULT_DEPT", "item": "P1_DEPT"}`, or `"reset"` | Stores (or removes) a [user preference](#user-preferences); nothing for the public user |
|
|
432
|
+
| `server_geocoding` | Server Side Geocoding | `{"address_item": "P3_ADDRESS", "coordinates_item": "P3_LOCATION", "country": "nl"}` | The installation's geocoder (`GEOCODER_URL`); the first place's `lat,lng` goes into the coordinates item |
|
|
433
|
+
| `print_report` | Print Report | `{"document": "EMPLOYEE_SHEET"}` | A [document template](16-files.md#document-templates) or report query, sent as the answer to the submit |
|
|
434
|
+
| `human_task_create` | Human Task – Create | `{"definition": "LEAVE_APPROVAL", "detail_pk": "&P7_ID.", "parameters": {"DAYS": "&P7_DAYS."}, "id_item": "P7_TASK_ID"}` | Creates a task ([approvals](#approvals-and-the-task-list)) and puts its id in the item |
|
|
435
|
+
| `human_task_manage` | Human Task – Manage | `{"action": "approve", "task": "&P8_TASK_ID.", "comment": "&P8_COMMENT."}` | `approve`, `reject`, `complete` (with `"outcome"`), `claim`, `release`, `delegate` (with `"to"`) or `cancel`; the task functions decide who may |
|
|
436
|
+
| `form_init` | Form – Initialization | – | Nothing: a form region fetches its row by itself. Kept so imported pages show it |
|
|
437
|
+
|
|
396
438
|
### Generate text with AI
|
|
397
439
|
|
|
398
440
|
An `ai_generate` process (APEX: *Generate Text with AI*) sends a prompt to a large language model
|
|
@@ -489,7 +531,7 @@ looked up by SQL, a derived value.
|
|
|
489
531
|
| `point` | `before_header`: when the page is shown (after the form fetch, before the `load` processes). `after_submit`: after the posted values are stored, before the validations |
|
|
490
532
|
| `type` | How `expression` is read, see below |
|
|
491
533
|
| `expression` | The value, item, query, expression or function body |
|
|
492
|
-
| `condition_type`, `condition_expr`, `condition_value` | Only when the [condition](#
|
|
534
|
+
| `condition_type`, `condition_expr`, `condition_value` | Only when the [condition](#server-side-conditions) holds |
|
|
493
535
|
| `authz` | Only for users who pass this authorization scheme |
|
|
494
536
|
| `seq` | Order; later computations see the values of earlier ones |
|
|
495
537
|
|
|
@@ -545,7 +587,7 @@ dialog closes instead, and the calling page reloads.
|
|
|
545
587
|
| `target_url` | A path inside the application (after `/a/<alias>/`), e.g. `12?view=month` or `account`; `&ITEM.` values are URL-encoded. Other sites are refused (no scheme, `//`, `\` or `..`) |
|
|
546
588
|
| `target_function` | `function`: a PL/pgSQL function body that returns such a path (APEX: *Function returning a URL*), run as the application's role with binds; checked like `target_url` (`meta.branch_path_ok`). An empty result: the branch doesn't apply; a refused one shows a message and is logged |
|
|
547
589
|
| `target_app` | `app`: the alias of another application of this installation (APEX: *Branch to page in another application*). It must exist with that page (else the branch doesn't apply and a message is shown); the items are signed for that application, page and user. That application's own sign-in and authorization apply when the browser gets there |
|
|
548
|
-
| `condition_type`, `condition_expr`, `condition_value` | Only when the [condition](#
|
|
590
|
+
| `condition_type`, `condition_expr`, `condition_value` | Only when the [condition](#server-side-conditions) holds |
|
|
549
591
|
| `authz`, `seq`, `build_option` | As for other components |
|
|
550
592
|
|
|
551
593
|
A `before_header` branch to the page itself is skipped (it would loop).
|
|
@@ -560,18 +602,47 @@ begin
|
|
|
560
602
|
end
|
|
561
603
|
```
|
|
562
604
|
|
|
563
|
-
###
|
|
605
|
+
### Server-side conditions
|
|
606
|
+
|
|
607
|
+
Every component has the same server-side condition (sprint 154; APEX: *Server-side Condition*):
|
|
608
|
+
regions, items, buttons, dynamic actions, validations, processes, computations, branches and
|
|
609
|
+
application computations. In the Page Designer it is the **Server-side condition** group:
|
|
610
|
+
*Condition type*, *Expression / item* and *Value*.
|
|
564
611
|
|
|
565
612
|
| `condition_type` | Holds when |
|
|
566
613
|
|---|---|
|
|
567
|
-
| (empty) | always |
|
|
568
|
-
| `sql` |
|
|
569
|
-
| `exists` / `not_exists` | the query in
|
|
570
|
-
| `
|
|
571
|
-
| `
|
|
572
|
-
| `
|
|
573
|
-
|
|
574
|
-
|
|
614
|
+
| (empty) | always (a region's or button's `condition` without a type: the SQL below) |
|
|
615
|
+
| `sql` | the expression, a boolean SQL expression, is true |
|
|
616
|
+
| `exists` / `not_exists` | the query in the expression returns a row / no row |
|
|
617
|
+
| `function_body` | the PL/pgSQL body in the expression returns true (`return :P3_QTY::int > 0;`) |
|
|
618
|
+
| `request_in` / `request_not_in` | the request (the button pressed) is / is not one of the value's names, comma separated (`SAVE,CREATE`) |
|
|
619
|
+
| `item_null` / `item_not_null` | the item named in the expression is empty / has a value |
|
|
620
|
+
| `item_zero` / `item_not_zero` | that item is / is not zero (an empty item is not zero) |
|
|
621
|
+
| `item_null_or_zero` / `item_not_null_or_zero` | that item is empty or zero / neither |
|
|
622
|
+
| `item_equals` / `item_not_equals` | that item's value is / is not the value |
|
|
623
|
+
| `item_equals_item` / `item_not_equals_item` | that item's value is / is not the value of the item named in the value |
|
|
624
|
+
| `item_in_list` / `item_not_in_list` | that item's value is / is not one of the value's entries, separated by colons (`CLERK:ANALYST`) |
|
|
625
|
+
| `page_in` / `page_not_in` | the current page is / is not one of the value's page numbers (`1,3`) |
|
|
626
|
+
| `page_is_dialog` / `page_is_not_dialog` | the page is / is not a modal dialog |
|
|
627
|
+
| `user_signed_in` / `user_public` | the user is signed in / is the public user |
|
|
628
|
+
| `language_in` / `language_not_in` | the application's language for this request is / is not one of the value's codes (`de,pt-br`; `pt` also matches `pt-br`) |
|
|
629
|
+
| `always` / `never` | always / never (for a component you want to keep but switch off: a build option does it for a release) |
|
|
630
|
+
|
|
631
|
+
Only `sql`, `exists`, `not_exists` and `function_body` run SQL, as the application's database
|
|
632
|
+
role, in the request's transaction; the others are decided by the server without a query. The
|
|
633
|
+
database refuses a condition whose expression or value doesn't fit its type (`meta.condition_ok`).
|
|
634
|
+
|
|
635
|
+
A condition whose SQL fails counts as false and shows the error. Regions and buttons keep their
|
|
636
|
+
`condition` column as the expression: without a type it is SQL, as before, so older exports keep
|
|
637
|
+
working. Dynamic actions name theirs `server_condition_type`, `server_condition_expr` and
|
|
638
|
+
`server_condition_value` (their `condition_type` is the client-side condition): when it doesn't
|
|
639
|
+
hold, the dynamic action isn't on the page.
|
|
640
|
+
|
|
641
|
+
The Page Designer's tree shows the condition in words on the component's filter badge (*When
|
|
642
|
+
P3_JOB is one of CLERK, ANALYST*), in the builder's language. The
|
|
643
|
+
[Advisor](03-builder.md#advisor) names SQL conditions a type can say exactly (`:P3_ID is not null`,
|
|
644
|
+
`:REQUEST in ('SAVE', 'CREATE')`, `:P3_JOB = 'CLERK'`) as information; `<>` and `not in` stay SQL,
|
|
645
|
+
because in SQL an empty item makes them false.
|
|
575
646
|
|
|
576
647
|
**HR example, page 22 (Leave planner).** Computations fill the employee (yours, when none is
|
|
577
648
|
chosen: `sql_query` with condition `item_null`), the name (`function_body`) and the pending
|
|
@@ -1099,6 +1170,22 @@ background runs with the tenant of the session that queued it. Filter your own t
|
|
|
1099
1170
|
(`where tenant_id = meta.tenant_id()`, or a row level security policy). Use row level security in
|
|
1100
1171
|
the tenant's tables as well: the tenant is a value the application sets, not a database boundary.
|
|
1101
1172
|
|
|
1173
|
+
### Workflow diagram region
|
|
1174
|
+
|
|
1175
|
+
Sprint 160 (APEX 24.2+: Workflow Diagram region). A region of type `workflow_diagram` draws one
|
|
1176
|
+
workflow instance in a running application: its version's steps, the ones it went through (green),
|
|
1177
|
+
where it is now (blue, several with parallel branches) and a failed step (red, with the error above
|
|
1178
|
+
the diagram). Each reached step's tooltip lists when it was reached and what happened. The instance
|
|
1179
|
+
id comes from an item:
|
|
1180
|
+
|
|
1181
|
+
```json
|
|
1182
|
+
{"instance_item": "P9_WORKFLOW_ID"}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
It is read through `meta.workflows` as the application's role, so a user sees only workflows they
|
|
1186
|
+
started or administer (the Workflow Console's rule); others see the empty text. The diagram is SVG
|
|
1187
|
+
drawn on the server: it needs no JavaScript.
|
|
1188
|
+
|
|
1102
1189
|
### Send e-mail steps
|
|
1103
1190
|
|
|
1104
1191
|
A `send_mail` step (APEX 26.1: the *Send E-Mail* activity) queues an e-mail through the application's mail service,
|
|
@@ -12,7 +12,8 @@ your own from a static application file.
|
|
|
12
12
|
|---|---|
|
|
13
13
|
| `event` | When it runs, see [Events](#events) |
|
|
14
14
|
| `trigger_element` | What it listens to: item names (`change`, `focus`, `blur`, `key_release`), a button name (`click`), region ids or static ids (`after_refresh`, `selection_change`), dialog page numbers (`dialog_closed`; empty: any dialog) or event names (`custom`), comma separated. Empty for `load` and `before_submit` |
|
|
15
|
-
| `condition_type`, `condition_value` | Optional client-side condition on the (first) trigger item's value (for `dialog_closed`: on the dialog's page number): `equals`, `not_equals`, `in_list` (comma-separated values), `is_null`, `is_not_null` |
|
|
15
|
+
| `condition_type`, `condition_value` | Optional client-side condition on the (first) trigger item's value (for `dialog_closed`: on the dialog's page number): `equals`, `not_equals`, `greater`, `greater_equal`, `less`, `less_equal` (numbers as numbers, other values such as ISO dates as text; an empty value fails them), `in_list`, `not_in_list` (comma-separated values), `is_null`, `is_not_null`. APEX's *JavaScript expression* isn't offered: the Content-Security-Policy runs no code from strings; use `execute_javascript` with a registered function |
|
|
16
|
+
| `server_condition_type`, `server_condition_expr`, `server_condition_value` | A [server-side condition](06-processing.md#server-side-conditions) (sprint 154): when it doesn't hold for this request, the dynamic action isn't on the page at all, e.g. `user_signed_in` or `page_is_dialog` |
|
|
16
17
|
| `action` | What to do, see below |
|
|
17
18
|
| `affected_items`, `affected_region_id` | What it acts on |
|
|
18
19
|
| `code` | SQL for server-side actions; the function's name for `execute_javascript` |
|
|
@@ -38,6 +39,21 @@ your own from a static application file.
|
|
|
38
39
|
| `before_submit` | The page is about to be submitted; a declined `confirm` keeps the page | – |
|
|
39
40
|
| `custom` | JavaScript raised the event: `pgkiln.trigger('ORDER_SAVED', value)` (APEX: custom events, `apex.event.trigger`) | Event names (case-insensitive) |
|
|
40
41
|
| `menu_entry` | An entry `{"label": …, "action": "NAME"}` of a [menu button](06-processing.md#menu-buttons-and-badges) was chosen (APEX: Menu entry chosen); the condition sees the entry's name ([below](#menu-entry-chosen)) | Entry names, or menu button names (any of their entries) |
|
|
42
|
+
| `double_click` | An item, button or region was double-clicked (sprint 156) | Item names, button names, region ids or static ids |
|
|
43
|
+
| `key_down` / `key_press` | A key goes down in an item / a key that types a character (APEX: Key Down, Key Press); a `cancel_event` keeps the key out of the field | Item names |
|
|
44
|
+
| `mouse_enter` / `mouse_leave` | The pointer enters / leaves an item, button or region (once per element, not while moving inside it) | Item names, button names, region ids or static ids |
|
|
45
|
+
| `select` | Text is selected in an item | Item names |
|
|
46
|
+
| `resize` / `scroll` | The window is resized / scrolled (at most once per frame) | – |
|
|
47
|
+
| `page_unload` | The page is left (APEX: Page Unload); only quick actions in the browser finish | – |
|
|
48
|
+
| `before_refresh` | A region is about to be refreshed by a `refresh_region` action (APEX: Before Refresh); a `cancel_event` or declined `confirm` keeps it as it is | Region ids or static ids |
|
|
49
|
+
| `facets_change` | A faceted search or smart filters region's facets were applied (APEX: Facets Change); runs on the page that comes back with the new filters | The facets region's id or static id |
|
|
50
|
+
| `grid_row_init` | A row was added to an editable grid by Add row or Duplicate (APEX: Row Initialization [Interactive Grid]); the condition sees the row's number among the new rows | The grid's id or static id |
|
|
51
|
+
| `grid_save` | A grid's changes were saved (APEX: Save [Interactive Grid]); runs on the page that comes back | The grid's id or static id |
|
|
52
|
+
| `calendar_date_select` | An empty day or hour of a calendar was clicked (APEX: Date Select); the condition sees `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM`; a `cancel_event` keeps the Create link closed | The calendar's id or static id |
|
|
53
|
+
| `calendar_view_change` | A calendar showed another view or period (APEX: View Change); the condition sees `month`, `week`, `day` or `list` | The calendar's id or static id |
|
|
54
|
+
| `map_object_click` | A marker or shape of a map was clicked (APEX: Map Object Clicked); the condition sees the row's `id` column, else its title | The map's id or static id |
|
|
55
|
+
| `map_change` | A map was moved or zoomed by the user (APEX: Map Changed); the condition sees the zoom level | The map's id or static id |
|
|
56
|
+
| `tree_selection_change` | A tree node was clicked (APEX: Selection Change [Tree]); the condition sees its `id`, the node is marked; a `cancel_event` keeps its link closed | The tree's id or static id |
|
|
41
57
|
|
|
42
58
|
**The actions of one event and trigger run one after the other, in sequence order** (APEX: the true
|
|
43
59
|
actions of a dynamic action), each waiting for the one before (a server action for its answer). A
|
|
@@ -49,6 +65,25 @@ actions of a dynamic action), each waiting for the one before (a server action f
|
|
|
49
65
|
| 20 | `click` on `DELETE_ALL` | `execute_sql` | `delete from sales.order_line where id = any(string_to_array(:P5_SELECTED, ':')::int[])` |
|
|
50
66
|
| 30 | `click` on `DELETE_ALL` | `refresh_region` | the order lines |
|
|
51
67
|
|
|
68
|
+
### Component events
|
|
69
|
+
|
|
70
|
+
The last seven events above belong to a component region (sprint 161): the trigger is that region's
|
|
71
|
+
id or static id. Their value (the date, view, node id, map object id or zoom level) is what the
|
|
72
|
+
client-side condition compares, and an [Execute JavaScript](#execute-javascript) function gets it as
|
|
73
|
+
`value`, with the component's element (the grid row, calendar cell, map or tree node) as `element`.
|
|
74
|
+
Two of them can be cancelled: a `cancel_event` whose condition holds keeps the calendar's Create
|
|
75
|
+
link or the tree node's link closed, wherever it stands in the sequence; like every `cancel_event`
|
|
76
|
+
it also stops the actions after it, so put it last.
|
|
77
|
+
|
|
78
|
+
A **`set_value` without SQL** on a component event puts the event's value into its affected
|
|
79
|
+
items, in the browser. A tree of departments that filters an employee report, without JavaScript:
|
|
80
|
+
|
|
81
|
+
| Sequence | Event | Action | Affected / items to submit |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| 10 | `tree_selection_change` on the departments tree | `set_value` (no SQL) | `P5_DEPTNO` |
|
|
84
|
+
| 20 | `tree_selection_change` on the departments tree | `refresh_region` | the employees report; items to submit `P5_DEPTNO` |
|
|
85
|
+
| 30 | `tree_selection_change` on the departments tree | `cancel_event` | – (last, as it stops the actions after it; the node's link stays closed) |
|
|
86
|
+
|
|
52
87
|
## Actions
|
|
53
88
|
|
|
54
89
|
| Action | Runs in | Effect |
|
|
@@ -82,6 +117,12 @@ actions of a dynamic action), each waiting for the one before (a server action f
|
|
|
82
117
|
| `share` | browser | The device's share sheet with the page's title, `message` and address; where there is none, the address is copied |
|
|
83
118
|
| `wait_popup` | browser | Shows a modal "please wait" popup with `message` (default *Please wait…*) until `remove_wait_popup` or the next page (APEX: Show Wait Popup); [below](#wait-popup) |
|
|
84
119
|
| `remove_wait_popup` | browser | Removes the wait popup (APEX: Remove Wait Popup) |
|
|
120
|
+
| `cancel_event` | browser | Stops the actions after it, and the event's own effect: a button doesn't submit or follow its link, a typed key (`key_down`, `key_press`) stays out of the field, a refresh doesn't happen (APEX: Cancel Event). Sprint 156 |
|
|
121
|
+
| `set_style` | browser | Sets CSS properties on the affected items or region: `{"styles": {"color": "#b42318", "font-weight": "bold"}}` in the attributes (APEX: Set Style). Properties from a fixed list (colours, font, text, border, opacity, visibility, padding, margin, width); values without `url()` or `var()`, checked when saved and again before they reach the page |
|
|
122
|
+
| `collapse_tree` / `expand_tree` | browser | Closes / opens every node of the affected tree region (APEX: Collapse Tree, Expand Tree) |
|
|
123
|
+
| `show_ai_assistant` | browser | Opens the affected AI assistant region (or the inline dialog it sits in) and puts the cursor in its question (APEX: Show AI Assistant) |
|
|
124
|
+
| `trigger_geocoding` | browser | The affected geocoded address item looks its text up, as its *Check address* button does (APEX: Trigger Geocoding) |
|
|
125
|
+
| `open_report_dialog` | browser | Opens a dialog of the affected interactive report: `code` is `filter`, `columns`, `sort`, `highlight`, `compute`, `aggregate`, `chart`, `group`, `pivot`, `break`, `rows` or `saved` (APEX: Invoke Interactive Report Dialog) |
|
|
85
126
|
|
|
86
127
|
Server-side actions first store `items_to_submit` in session state, run as the application's
|
|
87
128
|
database role like everything else, and check the page's and the dynamic action's authorization.
|
|
@@ -170,6 +211,7 @@ The function gets one argument with the action's context:
|
|
|
170
211
|
| `elements` | The affected items' fields and the affected region, as DOM elements |
|
|
171
212
|
| `region` | The affected region's element, or `null` |
|
|
172
213
|
| `message` | The action's message |
|
|
214
|
+
| `element` | A [component event](#component-events)'s element (grid row, calendar cell, map, tree node), else `null` |
|
|
173
215
|
|
|
174
216
|
It may return a promise; an exception is shown as an error message. Besides `actions.register`,
|
|
175
217
|
`window.pgkiln` offers `getValue(item)`, `setValue(item, value)`, `showSuccess(message)`,
|
|
@@ -38,6 +38,14 @@ What's missing is listed in the [feature parity matrix](../apex-feature-parity.m
|
|
|
38
38
|
| Before-header processes | Process `point = 'load'` |
|
|
39
39
|
| Page computations (static, item, SQL query, SQL expression, PL/SQL function body) | Computations `before_header` / `after_submit`; function bodies are PL/pgSQL ([chapter 6](06-processing.md#computations)) |
|
|
40
40
|
| Branches (page or URL, *When button pressed*, server-side condition), before header and after processing | Branches with `when_button` and a condition; URLs stay inside the application ([chapter 6](06-processing.md#branches)) |
|
|
41
|
+
| Validation types (Rows returned, Expression, Function Body returning Boolean / Error Text, Item is NOT NULL / numeric / a valid date …, Item = Value, Item in Value, character checks, Regular Expression), Always Execute, Display Location | The same types as `type` (`exists`, `sql`, `function_body`, `function_error`, `not_null`, `numeric`, `valid_date`, `equals`, `in_list`, `chars_only`, `regex` …), `always_execute`, `display_location` ([chapter 6](06-processing.md#validations)). Interactive grid column validations aren't there yet: use a validation on the grid's process or a check constraint |
|
|
42
|
+
| Dynamic action events (Double Click, Key Down / Press, Mouse Enter / Leave, Select, Resize, Scroll, Page Unload, Before Refresh, Facets Change), conditions (greater / less than, not in list) and actions (Cancel Event, Set Style, Collapse / Expand Tree, Show AI Assistant, Trigger Geocoding, Invoke Interactive Report Dialog) | The same events, conditions and actions ([chapter 7](07-dynamic-actions.md)). JavaScript expression conditions: use a registered function |
|
|
43
|
+
| Component events: Row Initialization and Save [Interactive Grid], Date Select and View Change [Calendar], Map Object Clicked and Map Changed, Selection Change [Tree] | `grid_row_init`, `grid_save`, `calendar_date_select`, `calendar_view_change`, `map_object_click`, `map_change`, `tree_selection_change`, triggered by the region ([chapter 7](07-dynamic-actions.md#component-events)) |
|
|
44
|
+
| Workflow Diagram region | `workflow_diagram` with `{"instance_item": …}` ([chapter 6](06-processing.md#workflow-diagram-region)) |
|
|
45
|
+
| Map layers Points, Lines, Polygons, Heat Map, Extruded Polygons | `"layer"`: `markers`, `lines`, `polygons`, `heat`, `extruded` (drawn flat, shaded by height), with fill and line colours, width and opacity per layer or per row ([chapter 4](04-pages-and-regions.md#map)) |
|
|
46
|
+
| Items Image Upload, Text Field with autocomplete, Percent Graph, Display Map | `image_upload`, `autocomplete`, `percent_graph`, `display_map` ([chapter 5](05-items.md#item-types)) |
|
|
47
|
+
| Process types Close Dialog, Clear Session State, Reset Pagination, User Preferences, Server Side Geocoding, Print Report, Human Task – Create / – Manage, Form – Initialization | The same as `type` ([chapter 6](06-processing.md#declarative-process-types)), settings in the attributes |
|
|
48
|
+
| Server-side Condition (Rows returned, Expression, Function Body, Request = / != / is contained in, Item is NULL / NOT NULL / zero / = Value / in colon list, Current page in, Current page is a modal dialog, User is authenticated / public user, Current language in, Always / Never …) | The same list as `condition_type`: `exists`, `sql`, `function_body`, `request_in` / `request_not_in`, `item_null` / `item_not_null` / `item_zero` / `item_equals` / `item_in_list`, `page_in`, `page_is_dialog`, `user_signed_in` / `user_public`, `language_in`, `always` / `never` ([chapter 6](06-processing.md#server-side-conditions)). APEX's colon lists stay colon lists; *Request is contained in* takes commas |
|
|
41
49
|
| Build options (include / exclude) | Build options; `!NAME` for APEX's "exclude when the option is included" ([chapter 6](06-processing.md#build-options)) |
|
|
42
50
|
| Menu button, button badge | Button action `menu`; `badge` / `badge_query` |
|
|
43
51
|
| Dynamic actions *Set Focus*, *Add / Remove Class*, *Show Success / Error Message*, *Clear Errors* | `set_focus`, `add_class` / `remove_class`, `show_success` / `show_error`, `clear_errors` |
|
|
@@ -338,7 +338,8 @@ src/
|
|
|
338
338
|
components.ts property spec of every component (drives the property editor)
|
|
339
339
|
ui.ts IDE shell (icon rail, toolbar, breadcrumb, status bar), builder theme, form helpers, CSRF check, app tabs
|
|
340
340
|
i18n.ts (sprint 121) the builder's own texts: BUILDER_LANGUAGES, tr(s) / translatorFor(lang) → tx(key, params),
|
|
341
|
-
tx.rich (markup in a sentence), tx.dyn (keys made at run time), tx.label (known breadcrumb labels)
|
|
341
|
+
tx.rich (markup in a sentence), tx.dyn (keys made at run time), tx.label (known breadcrumb labels),
|
|
342
|
+
(sprint 162) tx.code (`backticks` become <code>), withTranslator / scopedTx (a request-scoped translator)
|
|
342
343
|
i18n/ en.ts defines the builder's keys; de, es, fr, it, nl, pt, zh-cn, zh-tw, ja, ko (sprint 134) are Records of the same keys
|
|
343
344
|
language.ts (migration 127) the status bar's language menu (POST /builder/language), the sign-in page's
|
|
344
345
|
language links (?lang=, a cookie), meta.developer.language
|
|
@@ -704,8 +705,15 @@ const tx = tr(s); // the developer's language (
|
|
|
704
705
|
html`<h2>${tx('wh.summary')}</h2><p>${tx('ab.count', { n: 3 })}</p>`; // plain text: html`` escapes it
|
|
705
706
|
tx.rich('ab.none_yet', { create: html`<a href="…">${tx('ab.create_one')}</a>` }); // markup inside a sentence
|
|
706
707
|
tx.dyn(`gallery.region.${type}`, REGION_LABELS[type][0]); // a key made at run time, with a fallback
|
|
708
|
+
tx.code('cr.schedule_help'); // (sprint 162) `backticked` parts become <code>: syntax help
|
|
707
709
|
```
|
|
708
710
|
|
|
711
|
+
(sprint 162) A long form built by many helpers (the region and report settings, RESTful Services' pages) doesn't
|
|
712
|
+
pass `tx` to each of them: its entry point runs inside `withTranslator(tr(s), () => …)`, and the helpers import
|
|
713
|
+
`scopedTx as tx`, which takes the translator of the request being rendered from an `AsyncLocalStorage` (so two
|
|
714
|
+
requests in different languages never mix; `test/builder-i18n.test.ts` renders German and French concurrently).
|
|
715
|
+
Outside `withTranslator` it is English.
|
|
716
|
+
|
|
709
717
|
A missing text falls back to English, never to the raw key. Shared helpers that render chrome take the session as
|
|
710
718
|
an optional last argument (`appHeader(a, 'pages', s)`, `workshopTabs('sql', s)`); `shell()` translates known English
|
|
711
719
|
breadcrumb labels (`tx.label`) so pages that aren't moved yet still get a translated trail.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 79: declarative server-side conditions (sprint 154, CD-2)
|
|
3
|
+
--
|
|
4
|
+
-- The Employee page's buttons showed with SQL conditions such as
|
|
5
|
+
-- ":P3_EMPNO is not null". The same tests as condition types: no query,
|
|
6
|
+
-- and the Page Designer's tree says "When P3_EMPNO is not empty".
|
|
7
|
+
-- Department (page 5) gets the same; the leave request's Approve and
|
|
8
|
+
-- Reject (page 7) stay SQL: they call hr.current_empno().
|
|
9
|
+
-- =====================================================================
|
|
10
|
+
|
|
11
|
+
update meta.button b
|
|
12
|
+
set condition_type = case when b.condition ~* ' is not null$' then 'item_not_null' else 'item_null' end,
|
|
13
|
+
condition = substring(b.condition from '^:([A-Z][A-Z0-9_]*) ')
|
|
14
|
+
from meta.page p join meta.app a on a.id = p.app_id
|
|
15
|
+
where b.page_id = p.id and a.alias = 'hr' and p.page_no in (3, 5)
|
|
16
|
+
and b.condition_type is null
|
|
17
|
+
and b.condition ~ '^:P[35]_(EMPNO|DEPTNO) is (not )?null$';
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -1468,6 +1468,8 @@ path.map-near { stroke: #1f2733; stroke-dasharray: 6 4; fill: #1f2733; fill-opac
|
|
|
1468
1468
|
.tree-view a, .tree-view summary > span, .tree-leaf > span { display: inline-flex; align-items: center; gap: 0.35rem; }
|
|
1469
1469
|
.tree-leaf { padding-left: 1rem; }
|
|
1470
1470
|
.tree-count { font-size: 0.75rem; color: var(--muted); }
|
|
1471
|
+
/* (161) the node a tree_selection_change dynamic action chose */
|
|
1472
|
+
.tree-view li.tree-selected > :is(a, span:not(.tree-count), .tree-row), .tree-view li.tree-selected > details > summary > span { background: var(--accent-soft); border-radius: 3px; outline: 1px solid var(--accent); outline-offset: 1px; }
|
|
1471
1473
|
/* a linked branch: the disclosure triangle sits in front of the link's row */
|
|
1472
1474
|
.tree-branch { position: relative; }
|
|
1473
1475
|
.tree-branch > .tree-row { display: inline-flex; align-items: center; gap: 0.35rem; }
|
|
@@ -1497,6 +1499,8 @@ path.map-near { stroke: #1f2733; stroke-dasharray: 6 4; fill: #1f2733; fill-opac
|
|
|
1497
1499
|
.wf-invoke_api rect { stroke: #1f7a8c; stroke-width: 2; stroke-dasharray: 2 2; }
|
|
1498
1500
|
.wf-active rect { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 3; }
|
|
1499
1501
|
.wf-active .wf-name { fill: var(--text); }
|
|
1502
|
+
.wf-done rect { fill: var(--success-soft, #e7f5ed); stroke: var(--success, #17794a); }
|
|
1503
|
+
.wf-failed rect { fill: var(--danger-soft, #fde7e4); stroke: var(--danger, #b42318); stroke-width: 3; }
|
|
1500
1504
|
.wf-warnings { background: var(--warning-soft); color: var(--text); border-color: var(--border); }
|
|
1501
1505
|
.wf-version-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0; }
|
|
1502
1506
|
.wf-version-diagram summary { cursor: pointer; }
|
|
@@ -2251,3 +2255,13 @@ body.nav-pillar-dark .t-nav details:has(> summary.in-trail) > ul { border-inline
|
|
|
2251
2255
|
.mfa-factors { list-style: none; padding: 0; margin: 0 0 .75rem; }
|
|
2252
2256
|
.mfa-factors li { padding: .4rem 0; border-bottom: 1px solid var(--border); }
|
|
2253
2257
|
@media (max-width: 480px) { .mfa-codes { columns: 1; } }
|
|
2258
|
+
|
|
2259
|
+
/* (sprint 158) Percent Graph and Display Map items */
|
|
2260
|
+
.percent-graph { display: flex; align-items: center; gap: .5rem; }
|
|
2261
|
+
.percent-graph progress { flex: 1; max-width: 16rem; height: .75rem; accent-color: var(--t-accent, #2563eb); }
|
|
2262
|
+
.percent-graph-value { font-variant-numeric: tabular-nums; min-width: 3.5ch; text-align: right; }
|
|
2263
|
+
.display-map { margin: 0; display: inline-block; max-width: 100%; }
|
|
2264
|
+
.display-map-tile { position: relative; width: 256px; max-width: 100%; aspect-ratio: 1; overflow: hidden; border-radius: 6px; border: 1px solid var(--t-border, #d0d5dd); }
|
|
2265
|
+
.display-map-tile img { display: block; width: 100%; height: 100%; }
|
|
2266
|
+
.display-map-pin { position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%; background: #d92d20; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.35); }
|
|
2267
|
+
.display-map figcaption { font-size: .8rem; margin-top: .25rem; }
|