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.
Files changed (109) hide show
  1. package/db/migrations/147_condition_types.sql +80 -0
  2. package/db/migrations/148_validation_types.sql +44 -0
  3. package/db/migrations/149_dynamic_action_events.sql +45 -0
  4. package/db/migrations/150_process_types.sql +23 -0
  5. package/db/migrations/151_item_types.sql +16 -0
  6. package/db/migrations/152_workflow_diagram_region.sql +10 -0
  7. package/db/migrations/153_component_events.sql +21 -0
  8. package/docs/apex-feature-parity.md +11 -9
  9. package/docs/guide/02-concepts.md +3 -3
  10. package/docs/guide/03-builder.md +8 -1
  11. package/docs/guide/04-pages-and-regions.md +19 -4
  12. package/docs/guide/05-items.md +5 -0
  13. package/docs/guide/06-processing.md +104 -17
  14. package/docs/guide/07-dynamic-actions.md +43 -1
  15. package/docs/guide/11-from-apex.md +8 -0
  16. package/docs/guide/12-development.md +9 -1
  17. package/examples/hr/hr_79_condition_types.sql +17 -0
  18. package/package.json +1 -1
  19. package/public/app.css +14 -0
  20. package/public/app.js +242 -13
  21. package/public/builder.css +25 -2
  22. package/public/data-modeler.js +8 -5
  23. package/public/sql-monitor.js +5 -2
  24. package/src/appfiles.ts +4 -4
  25. package/src/builder/advisor.ts +43 -7
  26. package/src/builder/ai-builder.ts +50 -46
  27. package/src/builder/api.ts +49 -49
  28. package/src/builder/autorest.ts +92 -87
  29. package/src/builder/changelog.ts +39 -37
  30. package/src/builder/code-editor.ts +14 -10
  31. package/src/builder/components.ts +54 -23
  32. package/src/builder/conditions.ts +64 -0
  33. package/src/builder/data-modeler.ts +89 -84
  34. package/src/builder/data-security.ts +39 -37
  35. package/src/builder/dataload.ts +69 -65
  36. package/src/builder/datapump.ts +108 -97
  37. package/src/builder/ddl-tools.ts +38 -34
  38. package/src/builder/designer.ts +9 -1
  39. package/src/builder/document-view.ts +11 -11
  40. package/src/builder/i18n/de.ts +1662 -0
  41. package/src/builder/i18n/en.ts +1662 -0
  42. package/src/builder/i18n/es.ts +1662 -0
  43. package/src/builder/i18n/fr.ts +1662 -0
  44. package/src/builder/i18n/it.ts +1662 -0
  45. package/src/builder/i18n/ja.ts +1662 -0
  46. package/src/builder/i18n/ko.ts +1662 -0
  47. package/src/builder/i18n/nl.ts +1662 -0
  48. package/src/builder/i18n/pt.ts +1662 -0
  49. package/src/builder/i18n/zh-cn.ts +1662 -0
  50. package/src/builder/i18n/zh-tw.ts +1662 -0
  51. package/src/builder/i18n.ts +18 -0
  52. package/src/builder/object-actions.ts +18 -16
  53. package/src/builder/object-browser.ts +2 -2
  54. package/src/builder/querybuilder.ts +42 -36
  55. package/src/builder/quicksql.ts +16 -18
  56. package/src/builder/region-help.ts +3 -1
  57. package/src/builder/region-settings.ts +225 -198
  58. package/src/builder/report-settings.ts +64 -60
  59. package/src/builder/rest-modules.ts +145 -143
  60. package/src/builder/sampledata.ts +59 -52
  61. package/src/builder/scheduling.ts +180 -159
  62. package/src/builder/scripts.ts +71 -65
  63. package/src/builder/sql-charts.ts +36 -30
  64. package/src/builder/sql-monitor.ts +95 -89
  65. package/src/builder/sql-utilities.ts +47 -40
  66. package/src/builder/top-sql.ts +21 -17
  67. package/src/builder/unload.ts +48 -44
  68. package/src/builder/worksheet.ts +3 -3
  69. package/src/cron-schedule.ts +47 -17
  70. package/src/i18n/ar.ts +5 -0
  71. package/src/i18n/cs.ts +5 -0
  72. package/src/i18n/da.ts +5 -0
  73. package/src/i18n/de.ts +5 -0
  74. package/src/i18n/el.ts +5 -0
  75. package/src/i18n/es.ts +5 -0
  76. package/src/i18n/fi.ts +5 -0
  77. package/src/i18n/fr.ts +5 -0
  78. package/src/i18n/he.ts +5 -0
  79. package/src/i18n/it.ts +5 -0
  80. package/src/i18n/ja.ts +5 -0
  81. package/src/i18n/ko.ts +5 -0
  82. package/src/i18n/nb.ts +5 -0
  83. package/src/i18n/pl.ts +5 -0
  84. package/src/i18n/pt.ts +5 -0
  85. package/src/i18n/ru.ts +5 -0
  86. package/src/i18n/sv.ts +5 -0
  87. package/src/i18n/tr.ts +5 -0
  88. package/src/i18n/uk.ts +5 -0
  89. package/src/i18n/zh.ts +5 -0
  90. package/src/i18n.ts +10 -0
  91. package/src/metadata.ts +74 -9
  92. package/src/runtime/authz.ts +9 -5
  93. package/src/runtime/calendar.ts +3 -2
  94. package/src/runtime/context.ts +6 -0
  95. package/src/runtime/da-styles.ts +37 -0
  96. package/src/runtime/documents.ts +3 -2
  97. package/src/runtime/engine.ts +95 -13
  98. package/src/runtime/grid.ts +1 -0
  99. package/src/runtime/items.ts +33 -0
  100. package/src/runtime/logic.ts +79 -8
  101. package/src/runtime/maps.ts +56 -7
  102. package/src/runtime/process-types.ts +139 -0
  103. package/src/runtime/processes.ts +2 -0
  104. package/src/runtime/regions.ts +4 -0
  105. package/src/runtime/render.ts +19 -3
  106. package/src/runtime/routes.ts +6 -2
  107. package/src/runtime/tree.ts +4 -3
  108. package/src/runtime/workflow-diagram.ts +45 -0
  109. 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
- | `type` | Passes when |
83
- |---|---|
84
- | `not_null` | the associated item has a value |
85
- | `sql` | `expression` (a SQL boolean expression, binds allowed) is true |
86
- | `regex` | the item's value matches `expression` (a POSIX regular expression, checked in Postgres) |
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](#conditions-of-computations-processes-and-branches) holds (server-side condition, as for computations and branches) |
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](#conditions-of-computations-processes-and-branches) holds |
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](#conditions-of-computations-processes-and-branches) holds |
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
- ### Conditions of computations, processes and branches
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` | `condition_expr`, a boolean SQL expression, is true |
569
- | `exists` / `not_exists` | the query in `condition_expr` returns a row / no row |
570
- | `item_null` / `item_not_null` | the item named in `condition_expr` is empty / has a value |
571
- | `item_equals` / `item_not_equals` | that item's value is / is not `condition_value` |
572
- | `request_in` | the request (the button pressed) is one of `condition_value`, comma separated |
573
-
574
- A condition whose SQL fails counts as false and shows the error.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgkiln",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "description": "Open source low-code, SQL-driven application builder for PostgreSQL, inspired by Oracle APEX",
5
5
  "keywords": [
6
6
  "postgresql",
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; }