pgkiln 0.85.0 → 0.86.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/155_mail_available.sql +23 -0
- package/docs/guide/04-pages-and-regions.md +1 -1
- package/docs/guide/06-processing.md +7 -3
- package/docs/guide/07-dynamic-actions.md +3 -3
- package/docs/guide/09-reference.md +1 -0
- package/docs/guide/12-development.md +4 -0
- package/examples/gallery/README.md +23 -6
- package/examples/gallery/gallery.json +4 -0
- package/examples/gallery/samples/sample-approvals.sql +102 -11
- package/examples/gallery/samples/sample-calendar.sql +40 -5
- package/examples/gallery/samples/sample-dynamic-actions.sql +237 -9
- package/examples/gallery/samples/sample-grids.sql +35 -3
- package/examples/gallery/samples/sample-maps.sql +65 -7
- package/examples/gallery/samples/sample-processes.sql +466 -0
- package/examples/gallery/samples/sample-validations.sql +407 -0
- package/examples/gallery/samples/ux-patterns.sql +154 -14
- package/examples/hr/README.md +2 -0
- package/examples/hr/hr_80_notifications.sql +125 -0
- package/examples/hr/hr_81_vector_search_type.sql +33 -0
- package/package.json +1 -1
- package/public/app.css +8 -1
- package/public/app.js +28 -9
- package/src/runtime/engine.ts +6 -3
- package/src/runtime/lists.ts +2 -2
- package/src/runtime/push.ts +7 -1
- package/src/runtime/regions.ts +9 -4
|
@@ -6,7 +6,19 @@
|
|
|
6
6
|
-- page 3 values from the server (set value, execute SQL) and a report
|
|
7
7
|
-- refreshed when an item changes;
|
|
8
8
|
-- page 4 messages, confirmations, a wait popup, classes, item errors,
|
|
9
|
-
-- an inline dialog and actions on page load and key release
|
|
9
|
+
-- an inline dialog and actions on page load and key release;
|
|
10
|
+
-- page 5 keyboard and mouse: key down / key press (with Cancel Event),
|
|
11
|
+
-- focus, blur, select, double click, mouse enter / leave and
|
|
12
|
+
-- Set Style with greater / less conditions;
|
|
13
|
+
-- page 6 a form with rules: Before Submit, Submit, Refresh Item, Disable,
|
|
14
|
+
-- Hide, list and empty conditions, Page Unload, a dialog page
|
|
15
|
+
-- (page 10) closed or cancelled by a dynamic action;
|
|
16
|
+
-- page 7 regions and the window: before / after refresh, resize, scroll,
|
|
17
|
+
-- Close Region, Print, Share, Download, Push Subscribe;
|
|
18
|
+
-- page 8 a tree: Selection Change, Expand Tree, Collapse Tree;
|
|
19
|
+
-- page 9 faceted search: Facets Change and the report's dialogs;
|
|
20
|
+
-- page 11 Show AI Assistant (asking needs an AI service set up by an
|
|
21
|
+
-- administrator; the page works without one).
|
|
10
22
|
--
|
|
11
23
|
-- Installed by an administrator from the App Gallery, or with
|
|
12
24
|
-- psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-dynamic-actions.sql
|
|
@@ -21,6 +33,17 @@ create or replace function pg_temp.region_id(p_alias text, p_page int, p_static
|
|
|
21
33
|
-- 1. Schema and data ---------------------------------------------------------
|
|
22
34
|
create schema sample_da;
|
|
23
35
|
|
|
36
|
+
create table sample_da.city (
|
|
37
|
+
country text not null,
|
|
38
|
+
name text not null,
|
|
39
|
+
primary key (country, name)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
insert into sample_da.city (country, name) values
|
|
43
|
+
('NL', 'Amsterdam'), ('NL', 'Rotterdam'), ('NL', 'Utrecht'), ('BE', 'Antwerp'), ('BE', 'Brussels'), ('BE', 'Ghent'),
|
|
44
|
+
('DE', 'Berlin'), ('DE', 'Hamburg'), ('DE', 'Munich'), ('FR', 'Lyon'), ('FR', 'Paris'), ('FR', 'Toulouse'),
|
|
45
|
+
('US', 'Boston'), ('US', 'Chicago'), ('US', 'Seattle'), ('JP', 'Osaka'), ('JP', 'Sapporo'), ('JP', 'Tokyo');
|
|
46
|
+
|
|
24
47
|
create table sample_da.product (
|
|
25
48
|
id int generated always as identity primary key,
|
|
26
49
|
name text not null unique,
|
|
@@ -48,17 +71,25 @@ values ('sample-dynamic-actions', 'Sample Dynamic Actions', 1, 'app_users', 'app
|
|
|
48
71
|
insert into meta.app_user (app_id, username, password_hash, roles)
|
|
49
72
|
select id, 'demo', meta.hash_password('demo'), '{}' from meta.app where alias = 'sample-dynamic-actions';
|
|
50
73
|
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
-- push notifications on, for the dynamic action push_subscribe (page 7)
|
|
75
|
+
update meta.app set pwa = true, pwa_push = true where alias = 'sample-dynamic-actions';
|
|
76
|
+
|
|
77
|
+
insert into meta.page (app_id, page_no, name, title, parent_page, mode)
|
|
78
|
+
select a.id, p.no, p.name, p.name, p.parent, p.mode
|
|
53
79
|
from meta.app a, (values
|
|
54
|
-
(1, 'Sample Dynamic Actions', null), (2, 'Show, hide, enable', 1), (3, 'Values from the server', 1
|
|
55
|
-
)
|
|
80
|
+
(1, 'Sample Dynamic Actions', null, 'normal'), (2, 'Show, hide, enable', 1, 'normal'), (3, 'Values from the server', 1, 'normal'),
|
|
81
|
+
(4, 'Messages and effects', 1, 'normal'), (5, 'Keyboard and mouse', 1, 'normal'), (6, 'A form with rules', 1, 'normal'),
|
|
82
|
+
(7, 'Regions and the window', 1, 'normal'), (8, 'A tree', 1, 'normal'), (9, 'Faceted search', 1, 'normal'),
|
|
83
|
+
(10, 'A dialog page', 6, 'modal'), (11, 'AI assistant', 1, 'normal')
|
|
84
|
+
) as p (no, name, parent, mode)
|
|
56
85
|
where a.alias = 'sample-dynamic-actions';
|
|
57
86
|
|
|
58
87
|
insert into meta.nav_entry (app_id, seq, label, icon, target_page)
|
|
59
88
|
select a.id, n.seq, n.label, n.icon, n.page
|
|
60
89
|
from meta.app a, (values
|
|
61
|
-
(10, 'Home', 'home', 1), (20, 'Show, hide, enable', 'eye', 2), (30, 'Values from the server', 'refresh', 3), (40, 'Messages and effects', 'bell', 4)
|
|
90
|
+
(10, 'Home', 'home', 1), (20, 'Show, hide, enable', 'eye', 2), (30, 'Values from the server', 'refresh', 3), (40, 'Messages and effects', 'bell', 4),
|
|
91
|
+
(50, 'Keyboard and mouse', 'keyboard', 5), (60, 'A form with rules', 'clipboard', 6), (70, 'Regions and the window', 'layers', 7),
|
|
92
|
+
(80, 'A tree', 'folder', 8), (90, 'Faceted search', 'filter', 9), (100, 'AI assistant', 'chat', 11)
|
|
62
93
|
) as n (seq, label, icon, page)
|
|
63
94
|
where a.alias = 'sample-dynamic-actions';
|
|
64
95
|
|
|
@@ -70,7 +101,13 @@ select a.id, 'DA_TOPICS', e.seq, e.label, e.icon, e.page, e.description
|
|
|
70
101
|
from meta.app a, (values
|
|
71
102
|
(10, 'Show, hide, enable', 'eye', 2, 'Items that appear, disappear or switch on depending on another item.'),
|
|
72
103
|
(20, 'Values from the server', 'refresh', 3, 'SQL fills in items and refreshes a report, without reloading the page.'),
|
|
73
|
-
(30, 'Messages and effects', 'bell', 4, 'Alerts, confirmations, a wait popup, classes, item errors and an inline dialog.')
|
|
104
|
+
(30, 'Messages and effects', 'bell', 4, 'Alerts, confirmations, a wait popup, classes, item errors and an inline dialog.'),
|
|
105
|
+
(40, 'Keyboard and mouse', 'keyboard', 5, 'Keys counted or refused, focus, selected text, double clicks, hovering and Set Style.'),
|
|
106
|
+
(50, 'A form with rules', 'clipboard', 6, 'Before submit, submit, a list refreshed by another item, disable and hide, a dialog page.'),
|
|
107
|
+
(60, 'Regions and the window', 'layers', 7, 'Before and after a refresh, resize and scroll, print, share, download, notifications.'),
|
|
108
|
+
(70, 'A tree', 'folder', 8, 'A node chosen in a tree shows its products; expand and collapse every node.'),
|
|
109
|
+
(80, 'Faceted search', 'filter', 9, 'Facets that changed, and the interactive report''s dialogs opened by buttons.'),
|
|
110
|
+
(90, 'AI assistant', 'chat', 11, 'A button that opens an AI assistant and puts the cursor in its question.')
|
|
74
111
|
) as e (seq, label, icon, page, description)
|
|
75
112
|
where a.alias = 'sample-dynamic-actions';
|
|
76
113
|
|
|
@@ -111,9 +148,88 @@ select pg_temp.page_id('sample-dynamic-actions', r.page), r.seq, r.title, r.type
|
|
|
111
148
|
'{}', 'target'),
|
|
112
149
|
(4, 40, 'An inline dialog', 'static', 12, 'inline_dialog',
|
|
113
150
|
'<p>This region has the template <em>Inline dialog</em>: hidden until a dynamic action (or a link to its id) opens it. Escape, the close button or a click beside it closes it.</p>',
|
|
114
|
-
'{}', 'dialog')
|
|
151
|
+
'{}', 'dialog'),
|
|
152
|
+
|
|
153
|
+
-- 5 Keyboard and mouse
|
|
154
|
+
(5, 5, 'About this page', 'static', 12, 'plain',
|
|
155
|
+
'<p class="lead">Events of the keyboard and the mouse. <strong>Key down</strong> counts every key you press in <em>Type here</em> (a <em>Set value</em> on the server). <strong>Key press</strong> with the condition <em>greater or equal</em> 100 and <strong>Cancel Event</strong> keeps a fourth digit out of <em>Quantity</em>. <strong>Focus</strong> and <strong>Blur</strong> highlight the note''s region while you are in it, <strong>Select</strong> notices selected text, a <strong>Double click</strong> runs <em>Clear</em>. <strong>Mouse enter</strong> and <strong>Mouse leave</strong> highlight a region under the pointer, and <strong>Set Style</strong> marks the score with a coloured bar by conditions <em>greater</em>, <em>greater or equal</em> and <em>less</em>.</p>',
|
|
156
|
+
'{}', null),
|
|
157
|
+
(5, 10, 'Keys', 'static', 6, 'standard', null, '{}', 'keys'),
|
|
158
|
+
(5, 20, 'A note', 'static', 6, 'standard', null, '{}', 'note-box'),
|
|
159
|
+
(5, 30, 'Score', 'static', 6, 'standard', null, '{}', 'score'),
|
|
160
|
+
(5, 40, 'Point at this region', 'static', 6, 'standard',
|
|
161
|
+
'<p>Move the pointer onto this region: <em>Mouse enter</em> adds the class <code>is-highlight</code>, <em>Mouse leave</em> removes it again. Each runs once per visit, not on every movement inside it.</p>',
|
|
162
|
+
'{}', 'hover'),
|
|
163
|
+
|
|
164
|
+
-- 6 A form with rules
|
|
165
|
+
(6, 5, 'About this page', 'static', 12, 'plain',
|
|
166
|
+
'<p class="lead"><strong>Disable</strong> with the condition <em>is empty</em> keeps the buttons off until you have typed a name and left the field. A city list is drawn again with <strong>Refresh item</strong> when the country changes (its SQL uses the country). <strong>Hide</strong> with <em>not in list</em> hides the VAT number outside the EU, <strong>Show</strong> with <em>in list</em> shows the free delivery note and the customs number; <em>less or equal</em> 0 copies shows an error. Every submit runs <strong>Before submit</strong> first: a confirmation that can stop it. <em>Send</em> is a submit button (its process shows a message); <em>Keep as a draft</em> runs the action <strong>Submit</strong>, a submit without a button: the values are kept in session state and no process runs. Leaving the page (<strong>Page unload</strong>) shows a wait popup until the next page is there. <em>Open the dialog page</em> opens page 10, whose buttons run <strong>Close dialog</strong> and <strong>Cancel dialog</strong>.</p>',
|
|
167
|
+
'{}', null),
|
|
168
|
+
(6, 10, 'Order', 'static', 6, 'standard', null, '{}', 'order'),
|
|
169
|
+
(6, 20, 'Delivery', 'static', 6, 'standard', null, '{}', 'delivery'),
|
|
170
|
+
(6, 30, 'Free delivery', 'static', 6, 'standard',
|
|
171
|
+
'<p>Orders to the Netherlands and Belgium are delivered for free. This region is shown by <em>Show</em> with the condition <em>in list</em> <code>NL,BE</code>.</p>',
|
|
172
|
+
'{}', 'free-delivery'),
|
|
173
|
+
(6, 40, 'Send', 'static', 12, 'buttons_container', null, '{}', 'send'),
|
|
174
|
+
|
|
175
|
+
-- 7 Regions and the window
|
|
176
|
+
(7, 5, 'About this page', 'static', 12, 'plain',
|
|
177
|
+
'<p class="lead"><em>Shuffle</em> refreshes the report: <strong>Before refresh</strong> dims it, <strong>After refresh</strong> notes the time. Changing the window''s size runs <strong>Resize</strong>; scrolling runs <strong>Scroll</strong>, which closes the collapsible <em>Tips</em> with <strong>Close region</strong>. The other buttons run <strong>Print</strong>, <strong>Share</strong> (the device''s share sheet, else the address is copied), <strong>Download</strong> (the file of a download process of the page) and <strong>Push subscribe</strong> (turns on notifications on this device; the server needs <code>PGKILN_SECRET_KEY</code> for them).</p>',
|
|
178
|
+
'{}', null),
|
|
179
|
+
(7, 10, 'Tips', 'static', 12, 'collapsible',
|
|
180
|
+
'<p>This region is collapsible. Scroll the page and a <em>Scroll</em> dynamic action closes it with <em>Close region</em>; open it again with its heading.</p><p>A dynamic action on <em>Resize</em> or <em>Scroll</em> runs at most once per frame, so it can''t slow the page down.</p>',
|
|
181
|
+
'{}', 'tips'),
|
|
182
|
+
(7, 20, 'Try the actions', 'static', 12, 'buttons_container', null, '{}', 'tools'),
|
|
183
|
+
(7, 30, 'Products in random order', 'report', 12, 'standard',
|
|
184
|
+
'select name, category, price from sample_da.product order by random()',
|
|
185
|
+
'{"formats": {"price": "999G990D00"}, "sortable": false, "page_size": 50}', 'shuffled'),
|
|
186
|
+
(7, 40, 'Pick products', 'report', 12, 'standard',
|
|
187
|
+
'select name, category, price from sample_da.product order by name',
|
|
188
|
+
'{"formats": {"price": "999G990D00"}, "page_size": 10, "selection": {"column": "name", "item": "P7_PICKED"}}', 'picks'),
|
|
189
|
+
|
|
190
|
+
-- 8 A tree
|
|
191
|
+
(8, 5, 'About this page', 'static', 12, 'plain',
|
|
192
|
+
'<p class="lead">Clicking a node of the tree runs <strong>Selection change [Tree]</strong>: a <em>Set value</em> without SQL puts the node''s id into an item, and <em>Refresh region</em> draws the report with it. <strong>Expand tree</strong> and <strong>Collapse tree</strong> open or close every node.</p>',
|
|
193
|
+
'{}', null),
|
|
194
|
+
(8, 10, 'Categories and products', 'tree', 5, 'standard',
|
|
195
|
+
E'select ''c-'' || lower(category) as id, null::text as parent_id, category as label from sample_da.product group by category\nunion all\nselect id::text, ''c-'' || lower(category), name from sample_da.product',
|
|
196
|
+
'{"expanded": 1}', 'catalogue-tree'),
|
|
197
|
+
(8, 20, 'Chosen', 'report', 7, 'standard',
|
|
198
|
+
E'select name, category, price from sample_da.product\n where :P8_NODE is not null and (id::text = :P8_NODE or ''c-'' || lower(category) = :P8_NODE)\n order by name',
|
|
199
|
+
'{"formats": {"price": "999G990D00"}, "searchable": false, "empty": "Choose a category or a product in the tree."}', 'chosen'),
|
|
200
|
+
|
|
201
|
+
-- 9 Faceted search
|
|
202
|
+
(9, 5, 'About this page', 'static', 12, 'plain',
|
|
203
|
+
'<p class="lead">Choosing a facet runs <strong>Facets change</strong> on the page that comes back with the new filters: here a success message. The buttons of the report run <strong>Open report dialog</strong> with the interactive report''s <em>Filter</em>, <em>Columns</em> and <em>Chart</em> dialogs.</p>',
|
|
204
|
+
'{}', null),
|
|
205
|
+
(9, 10, 'Filters', 'facets', 3, 'collapsible', null, '{}', 'filters'),
|
|
206
|
+
(9, 30, 'Catalogue', 'report', 9, 'standard',
|
|
207
|
+
'select name, category, price from sample_da.product',
|
|
208
|
+
'{"formats": {"price": "999G990D00"}}', 'catalogue'),
|
|
209
|
+
|
|
210
|
+
-- 10 A dialog page
|
|
211
|
+
(10, 5, 'About this page', 'static', 12, 'plain',
|
|
212
|
+
'<p class="lead">This page is a <strong>modal dialog</strong>. <em>Close</em> runs <strong>Close dialog</strong>: the page below runs its <em>Dialog closed</em> actions (a message). <em>Cancel</em> runs <strong>Cancel dialog</strong>: the dialog closes and nothing else happens.</p>',
|
|
213
|
+
'{}', null),
|
|
214
|
+
(10, 10, 'Close or cancel', 'static', 12, 'buttons_container', null, '{}', 'dialog-buttons'),
|
|
215
|
+
|
|
216
|
+
-- 11 AI assistant
|
|
217
|
+
(11, 5, 'About this page', 'static', 12, 'plain',
|
|
218
|
+
'<p class="lead">The button runs <strong>Show AI Assistant</strong>: it opens the AI assistant region (here in an inline dialog) and puts the cursor in its question. Answers need an AI service that an administrator set up and allowed for this application; without one the assistant says so.</p>',
|
|
219
|
+
'{}', null),
|
|
220
|
+
(11, 10, 'Ask', 'static', 12, 'buttons_container', null, '{}', 'ask'),
|
|
221
|
+
(11, 20, 'Office supplies assistant', 'ai_assistant', 12, 'inline_dialog', null,
|
|
222
|
+
'{"service": "CLAUDE", "system": "You answer questions about the office supplies in the data, briefly.", "welcome": "Ask me about the office supplies: prices, categories, what is cheapest.", "placeholder": "Which pens are there?", "context": [{"name": "products", "sql": "select name, category, price from sample_da.product order by category, name", "max_rows": 50}], "error_message": "No AI service is set up for this application: an administrator adds one (Administration, AI services) and allows it for the application."}',
|
|
223
|
+
'assistant')
|
|
115
224
|
) as r (page, seq, title, type, cols, template, source, config, sid);
|
|
116
225
|
|
|
226
|
+
-- the facets filter the catalogue report (its id)
|
|
227
|
+
update meta.region set config = jsonb_build_object(
|
|
228
|
+
'report', pg_temp.region_id('sample-dynamic-actions', 9, 'catalogue'),
|
|
229
|
+
'facets', '[{"column": "category", "label": "Category"},
|
|
230
|
+
{"column": "price", "label": "Price", "type": "range", "ranges": [{"to": 5, "label": "Below 5"}, {"from": 5, "to": 15, "label": "5 to 15"}, {"from": 15, "label": "15 and more"}]}]'::jsonb)
|
|
231
|
+
where id = pg_temp.region_id('sample-dynamic-actions', 9, 'filters');
|
|
232
|
+
|
|
117
233
|
insert into meta.item (page_id, region_id, seq, name, label, type, lov, default_value, help, config)
|
|
118
234
|
select pg_temp.page_id('sample-dynamic-actions', i.page), pg_temp.region_id('sample-dynamic-actions', i.page, i.region),
|
|
119
235
|
i.seq, i.name, i.label, i.type, i.lov, i.def, i.help, i.config::jsonb
|
|
@@ -131,7 +247,21 @@ select pg_temp.page_id('sample-dynamic-actions', i.page), pg_temp.region_id('sam
|
|
|
131
247
|
(3, 'filter', 10, 'P3_CATEGORY', 'Category', 'select', 'select distinct category as d, category as r from sample_da.product order by 1', null,
|
|
132
248
|
'The report below is refreshed when this changes.', '{"warn_unsaved": false, "null_label": "All categories"}'),
|
|
133
249
|
(4, 'greeting', 10, 'P4_NAME', 'Your name', 'text', null, null, 'Type: the greeting follows each key you release.', '{"warn_unsaved": false}'),
|
|
134
|
-
(4, 'greeting', 20, 'P4_GREETING', 'Greeting', 'text', null, null, null, '{"warn_unsaved": false}')
|
|
250
|
+
(4, 'greeting', 20, 'P4_GREETING', 'Greeting', 'text', null, null, null, '{"warn_unsaved": false}'),
|
|
251
|
+
(5, 'keys', 10, 'P5_TYPE', 'Type here', 'text', null, null, 'Key down: every key counts, also arrows and Backspace.', '{"warn_unsaved": false}'),
|
|
252
|
+
(5, 'keys', 20, 'P5_KEYS', 'Keys pressed', 'display', null, '0', null, '{}'),
|
|
253
|
+
(5, 'keys', 30, 'P5_QTY', 'Quantity (up to 999)', 'text', null, null, 'Key press: a fourth digit is refused.', '{"warn_unsaved": false}'),
|
|
254
|
+
(5, 'note-box', 10, 'P5_NOTE', 'Note', 'textarea', null, 'Select a word of this text, or double-click to empty the note.', 'Focus highlights the region; a double click empties the note.', '{"warn_unsaved": false}'),
|
|
255
|
+
(5, 'score', 10, 'P5_SCORE', 'Score (0 to 100)', 'number', null, null, 'Set Style marks a score above 80 green, below 50 red.', '{"warn_unsaved": false}'),
|
|
256
|
+
(6, 'order', 10, 'P6_NAME', 'Your name', 'text', null, null, 'The buttons stay off while this is empty.', '{}'),
|
|
257
|
+
(6, 'order', 20, 'P6_COPIES', 'Copies', 'number', null, '1', 'Zero or fewer shows an error.', '{}'),
|
|
258
|
+
(6, 'delivery', 10, 'P6_COUNTRY', 'Country', 'select', 'STATIC:Netherlands;NL,Belgium;BE,Germany;DE,France;FR,United States;US,Japan;JP', null, null, '{"null_label": "Choose a country"}'),
|
|
259
|
+
(6, 'delivery', 20, 'P6_CITY', 'City', 'select', 'select name as d, name as r from sample_da.city where country = :P6_COUNTRY order by 1', null, 'Refresh item draws this list again when the country changes.', '{"null_label": "Choose a city"}'),
|
|
260
|
+
(6, 'delivery', 30, 'P6_VAT', 'VAT number', 'text', null, null, 'Only in the EU (hidden with not in list).', '{}'),
|
|
261
|
+
(6, 'delivery', 40, 'P6_CUSTOMS', 'Customs number', 'text', null, null, 'Only outside the EU (shown with in list).', '{}'),
|
|
262
|
+
(7, 'tools', 10, 'P7_REFRESHED', 'Last shuffled', 'display', null, 'not yet', null, '{}'),
|
|
263
|
+
(7, 'tools', 20, 'P7_PICKED', 'Picked', 'text', null, null, 'Selection change: the rows checked in Pick products, colon separated.', '{"warn_unsaved": false}'),
|
|
264
|
+
(8, 'chosen', 10, 'P8_NODE', 'Chosen node', 'text', null, null, 'Set by the tree: a category (c-…) or a product id.', '{"warn_unsaved": false}')
|
|
135
265
|
) as i (page, region, seq, name, label, type, lov, def, help, config);
|
|
136
266
|
|
|
137
267
|
insert into meta.button (page_id, region_id, seq, name, label, action, hot)
|
|
@@ -142,6 +272,31 @@ select pg_temp.page_id('sample-dynamic-actions', 4), pg_temp.region_id('sample-d
|
|
|
142
272
|
(70, 'ERROR', 'Show an item error', false), (80, 'CLEAR', 'Clear the errors', false), (90, 'DIALOG', 'Open the inline dialog', false)
|
|
143
273
|
) as b (seq, name, label, hot);
|
|
144
274
|
|
|
275
|
+
insert into meta.button (page_id, region_id, seq, name, label, action, hot, target_page)
|
|
276
|
+
select pg_temp.page_id('sample-dynamic-actions', b.page), pg_temp.region_id('sample-dynamic-actions', b.page, b.region), b.seq, b.name, b.label, b.action, b.hot, b.target
|
|
277
|
+
from (values
|
|
278
|
+
(6, 'send', 10, 'SEND', 'Send', 'submit', true, null::int), (6, 'send', 20, 'DRAFT', 'Keep as a draft', 'da', false, null),
|
|
279
|
+
(6, 'send', 30, 'OPEN_DIALOG', 'Open the dialog page', 'redirect', false, 10),
|
|
280
|
+
(7, 'tools', 10, 'SHUFFLE', 'Shuffle', 'da', true, null), (7, 'tools', 20, 'PRINT', 'Print', 'da', false, null),
|
|
281
|
+
(7, 'tools', 30, 'SHARE', 'Share', 'da', false, null), (7, 'tools', 40, 'DOWNLOAD', 'Download as CSV', 'da', false, null),
|
|
282
|
+
(7, 'tools', 50, 'NOTIFY', 'Turn on notifications', 'da', false, null),
|
|
283
|
+
(8, 'catalogue-tree', 10, 'EXPAND', 'Expand all', 'da', false, null), (8, 'catalogue-tree', 20, 'COLLAPSE', 'Collapse all', 'da', false, null),
|
|
284
|
+
(9, 'catalogue', 10, 'FILTER', 'Filter…', 'da', false, null), (9, 'catalogue', 20, 'COLUMNS', 'Columns…', 'da', false, null),
|
|
285
|
+
(9, 'catalogue', 30, 'CHART', 'Chart…', 'da', false, null),
|
|
286
|
+
(10, 'dialog-buttons', 10, 'CANCEL', 'Cancel', 'da', false, null), (10, 'dialog-buttons', 20, 'CLOSE', 'Close', 'da', true, null),
|
|
287
|
+
(11, 'ask', 10, 'ASK', 'Ask the assistant', 'da', true, null)
|
|
288
|
+
) as b (page, region, seq, name, label, action, hot, target);
|
|
289
|
+
|
|
290
|
+
-- page 6: what Send submits to (the sample stores nothing); page 7: the file of the Download button
|
|
291
|
+
insert into meta.process (page_id, seq, name, type, code, point, when_button, success_message, config)
|
|
292
|
+
select pg_temp.page_id('sample-dynamic-actions', p.page), p.seq, p.name, p.type, p.code, p.point, p.button, p.message, p.config::jsonb
|
|
293
|
+
from (values
|
|
294
|
+
(6, 10, 'Send the order', 'sql', 'select null', 'submit', 'SEND', 'Sent (this sample stores nothing): Before submit asked first.', '{}'),
|
|
295
|
+
(7, 10, 'PRODUCTS_CSV', 'download',
|
|
296
|
+
E'select ''name;category;price'' || E''\\n'' || string_agg(name || '';'' || category || '';'' || price, E''\\n'' order by name) as content,\n ''products.csv'' as filename, ''text/csv'' as mime_type\n from sample_da.product',
|
|
297
|
+
'submit', 'DOWNLOAD', null, '{}')
|
|
298
|
+
) as p (page, seq, name, type, code, point, button, message, config);
|
|
299
|
+
|
|
145
300
|
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, condition_type, condition_value,
|
|
146
301
|
action, affected_items, affected_region_id, code, items_to_submit, message, css_classes)
|
|
147
302
|
select pg_temp.page_id('sample-dynamic-actions', d.page), d.seq, d.name, d.event, d.trig, d.ctype, d.cvalue,
|
|
@@ -175,3 +330,76 @@ select pg_temp.page_id('sample-dynamic-actions', d.page), d.seq, d.name, d.event
|
|
|
175
330
|
(4, 130, 'Clear the errors', 'click', 'CLEAR', null, null, 'clear_errors', null, null, null, null, null, null),
|
|
176
331
|
(4, 140, 'Open the dialog', 'click', 'DIALOG', null, null, 'open_region', null, 'dialog', null, null, null, null)
|
|
177
332
|
) as d (page, seq, name, event, trig, ctype, cvalue, action, items, region, code, submit, message, classes);
|
|
333
|
+
|
|
334
|
+
-- pages 5 to 11: more events, actions and client-side conditions (a region trigger is its static id)
|
|
335
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, condition_type, condition_value,
|
|
336
|
+
action, affected_items, affected_region_id, code, items_to_submit, message, css_classes, config)
|
|
337
|
+
select pg_temp.page_id('sample-dynamic-actions', d.page), d.seq, d.name, d.event, d.trig, d.ctype, d.cvalue,
|
|
338
|
+
d.action, d.items, pg_temp.region_id('sample-dynamic-actions', d.page, d.region), d.code, d.submit, d.message, d.classes, d.config::jsonb
|
|
339
|
+
from (values
|
|
340
|
+
-- page 5 Keyboard and mouse
|
|
341
|
+
(5, 10, 'Count the keys', 'key_down', 'P5_TYPE', null, null, 'set_value', 'P5_KEYS', null,
|
|
342
|
+
'select coalesce(nullif(:P5_KEYS, '''')::int, 0) + 1', null, null, null, null),
|
|
343
|
+
(5, 20, 'Say why a key is refused', 'key_press', 'P5_QTY', 'greater_equal', '100', 'show_error', 'P5_QTY', null, null, null,
|
|
344
|
+
'Three digits at most: that key was refused.', null, null),
|
|
345
|
+
(5, 30, 'Refuse a fourth digit', 'key_press', 'P5_QTY', 'greater_equal', '100', 'cancel_event', null, null, null, null, null, null, null),
|
|
346
|
+
(5, 40, 'No error below 100', 'change', 'P5_QTY', 'less', '100', 'clear_errors', 'P5_QTY', null, null, null, null, null, null),
|
|
347
|
+
(5, 50, 'Highlight the note while in it', 'focus', 'P5_NOTE', null, null, 'add_class', null, 'note-box', null, null, null, 'is-highlight', null),
|
|
348
|
+
(5, 60, 'Stop highlighting the note', 'blur', 'P5_NOTE', null, null, 'remove_class', null, 'note-box', null, null, null, 'is-highlight', null),
|
|
349
|
+
(5, 70, 'Text selected', 'select', 'P5_NOTE', null, null, 'show_success', null, null, null, null,
|
|
350
|
+
'You selected text in the note (Select). Copy it with Ctrl+C.', null, null),
|
|
351
|
+
(5, 80, 'Empty the note', 'double_click', 'P5_NOTE', null, null, 'clear', 'P5_NOTE', null, null, null, null, null, null),
|
|
352
|
+
(5, 90, 'Highlight under the pointer', 'mouse_enter', 'hover', null, null, 'add_class', null, 'hover', null, null, null, 'is-highlight', null),
|
|
353
|
+
(5, 100, 'Pointer gone', 'mouse_leave', 'hover', null, null, 'remove_class', null, 'hover', null, null, null, 'is-highlight', null),
|
|
354
|
+
(5, 110, 'A plain score', 'change', 'P5_SCORE', 'greater_equal', '50', 'set_style', 'P5_SCORE', null, null, null, null, null,
|
|
355
|
+
'{"styles": {"border-width": "0", "padding": "0"}}'),
|
|
356
|
+
(5, 120, 'A high score in green', 'change', 'P5_SCORE', 'greater', '80', 'set_style', 'P5_SCORE', null, null, null, null, null,
|
|
357
|
+
'{"styles": {"border-style": "solid", "border-width": "0 0 0 4px", "border-color": "#067647", "padding": "0 0 0 8px"}}'),
|
|
358
|
+
(5, 130, 'A low score in red', 'change', 'P5_SCORE', 'less', '50', 'set_style', 'P5_SCORE', null, null, null, null, null,
|
|
359
|
+
'{"styles": {"border-style": "solid", "border-width": "0 0 0 4px", "border-color": "#b42318", "padding": "0 0 0 8px"}}'),
|
|
360
|
+
-- page 6 A form with rules
|
|
361
|
+
(6, 10, 'Send only with a name', 'change', 'P6_NAME', 'is_null', null, 'disable', null, 'send', null, null, null, null, null),
|
|
362
|
+
(6, 20, 'Cities of the country', 'change', 'P6_COUNTRY', null, null, 'refresh_item', 'P6_CITY', null, null, 'P6_COUNTRY', null, null, null),
|
|
363
|
+
(6, 30, 'No VAT number outside the EU', 'change', 'P6_COUNTRY', 'not_in_list', 'NL,BE,DE,FR', 'hide', 'P6_VAT', null, null, null, null, null, null),
|
|
364
|
+
(6, 40, 'Customs outside the EU', 'change', 'P6_COUNTRY', 'in_list', 'US,JP', 'show', 'P6_CUSTOMS', null, null, null, null, null, null),
|
|
365
|
+
(6, 50, 'Free delivery in the Benelux', 'change', 'P6_COUNTRY', 'in_list', 'NL,BE', 'show', null, 'free-delivery', null, null, null, null, null),
|
|
366
|
+
(6, 60, 'At least one copy', 'change', 'P6_COPIES', 'less_equal', '0', 'show_error', 'P6_COPIES', null, null, null,
|
|
367
|
+
'At least one copy, please.', null, null),
|
|
368
|
+
(6, 70, 'Copies are fine', 'change', 'P6_COPIES', 'greater', '0', 'clear_errors', 'P6_COPIES', null, null, null, null, null, null),
|
|
369
|
+
(6, 80, 'A draft submits the page', 'click', 'DRAFT', null, null, 'submit', null, null, null, null, null, null, null),
|
|
370
|
+
(6, 90, 'Ask before sending', 'before_submit', null, null, null, 'confirm', null, null, null, null, 'Submit the order form?', null, null),
|
|
371
|
+
(6, 100, 'A popup while leaving', 'page_unload', null, null, null, 'wait_popup', null, null, null, null, 'Loading the next page…', null, null),
|
|
372
|
+
(6, 110, 'The dialog page was closed', 'dialog_closed', '10', null, null, 'show_success', null, null, null, null,
|
|
373
|
+
'The dialog page ran Close dialog: this page ran its Dialog closed action instead of reloading.', null, null),
|
|
374
|
+
-- page 7 Regions and the window
|
|
375
|
+
(7, 10, 'Shuffle the report', 'click', 'SHUFFLE', null, null, 'refresh_region', null, 'shuffled', null, null, null, null, null),
|
|
376
|
+
(7, 20, 'Dim it while it loads', 'before_refresh', 'shuffled', null, null, 'add_class', null, 'shuffled', null, null, null, 'is-muted', null),
|
|
377
|
+
(7, 30, 'Note the time', 'after_refresh', 'shuffled', null, null, 'set_value', 'P7_REFRESHED', null,
|
|
378
|
+
'select to_char(clock_timestamp(), ''HH24:MI:SS'')', null, null, null, null),
|
|
379
|
+
(7, 40, 'The window changed size', 'resize', null, null, null, 'show_success', null, null, null, null,
|
|
380
|
+
'The window changed size (Resize): the regions flowed into the new width.', null, null),
|
|
381
|
+
(7, 50, 'Close the tips when scrolling', 'scroll', null, null, null, 'close_region', null, 'tips', null, null, null, null, null),
|
|
382
|
+
(7, 55, 'Note the picked rows', 'selection_change', 'picks', null, null, 'show_success', null, null, null, null,
|
|
383
|
+
'Selection change: the checked rows go into Picked with the next submit.', null, null),
|
|
384
|
+
(7, 60, 'Print the page', 'click', 'PRINT', null, null, 'print', null, null, null, null, null, null, null),
|
|
385
|
+
(7, 70, 'Share the page', 'click', 'SHARE', null, null, 'share', null, null, null, null, 'Dynamic actions in pgkiln, without JavaScript of your own.', null, null),
|
|
386
|
+
(7, 80, 'Download the products', 'click', 'DOWNLOAD', null, null, 'download', null, null, 'PRODUCTS_CSV', null, null, null, null),
|
|
387
|
+
(7, 90, 'Turn on notifications', 'click', 'NOTIFY', null, null, 'push_subscribe', null, null, null, null,
|
|
388
|
+
'Notifications are on for this device.', null, null),
|
|
389
|
+
-- page 8 A tree
|
|
390
|
+
(8, 10, 'Remember the node', 'tree_selection_change', 'catalogue-tree', null, null, 'set_value', 'P8_NODE', null, null, null, null, null, null),
|
|
391
|
+
(8, 20, 'Show its products', 'tree_selection_change', 'catalogue-tree', null, null, 'refresh_region', null, 'chosen', null, 'P8_NODE', null, null, null),
|
|
392
|
+
(8, 30, 'Open every node', 'click', 'EXPAND', null, null, 'expand_tree', null, 'catalogue-tree', null, null, null, null, null),
|
|
393
|
+
(8, 40, 'Close every node', 'click', 'COLLAPSE', null, null, 'collapse_tree', null, 'catalogue-tree', null, null, null, null, null),
|
|
394
|
+
-- page 9 Faceted search
|
|
395
|
+
(9, 10, 'The facets changed', 'facets_change', 'filters', null, null, 'show_success', null, null, null, null,
|
|
396
|
+
'The facets changed (Facets change): the catalogue shows the matching products.', null, null),
|
|
397
|
+
(9, 20, 'The filter dialog', 'click', 'FILTER', null, null, 'open_report_dialog', null, 'catalogue', 'filter', null, null, null, null),
|
|
398
|
+
(9, 30, 'The columns dialog', 'click', 'COLUMNS', null, null, 'open_report_dialog', null, 'catalogue', 'columns', null, null, null, null),
|
|
399
|
+
(9, 40, 'The chart dialog', 'click', 'CHART', null, null, 'open_report_dialog', null, 'catalogue', 'chart', null, null, null, null),
|
|
400
|
+
-- page 10 A dialog page
|
|
401
|
+
(10, 10, 'Cancel the dialog', 'click', 'CANCEL', null, null, 'cancel_dialog', null, null, null, null, null, null, null),
|
|
402
|
+
(10, 20, 'Close the dialog', 'click', 'CLOSE', null, null, 'close_dialog', null, null, null, null, null, null, null),
|
|
403
|
+
-- page 11 AI assistant
|
|
404
|
+
(11, 10, 'Open the assistant', 'click', 'ASK', null, null, 'show_ai_assistant', null, 'assistant', null, null, null, null, null)
|
|
405
|
+
) as d (page, seq, name, event, trig, ctype, cvalue, action, items, region, code, submit, message, classes, config);
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
--
|
|
4
4
|
-- The interactive grid: an editable grid made by the wizard; a grid with
|
|
5
5
|
-- select lists, required columns, totals, a frozen column, a row menu and a
|
|
6
|
-
-- default column layout
|
|
7
|
-
-- read-only grid with its single row, group by and chart views.
|
|
6
|
+
-- default column layout, and dynamic actions on a new row and after a
|
|
7
|
+
-- save; a master grid with its detail grid; and a read-only grid with its single row, group by and chart views.
|
|
8
8
|
--
|
|
9
9
|
-- Installed by an administrator from the App Gallery, or with
|
|
10
10
|
-- psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-grids.sql
|
|
@@ -109,7 +109,7 @@ select pg_temp.page_id('sample-grids', r.page), r.seq, r.title, r.type, 12, r.te
|
|
|
109
109
|
|
|
110
110
|
-- 3 Grid features
|
|
111
111
|
(3, 5, 'About this page', 'static', 'plain',
|
|
112
|
-
'<p class="lead">The grid''s attributes: <code>columns</code> gives the team column a list of values and makes the name required; <code>aggregates</code> adds totals over every row of the search; <code>frozen</code> keeps the name in view while the grid scrolls sideways; <code>row_actions</code> adds a menu per row (duplicate, delete); <code>layout</code> sets the default order and widths, and every user can rearrange it (Actions → Columns).</p>',
|
|
112
|
+
'<p class="lead">The grid''s attributes: <code>columns</code> gives the team column a list of values and makes the name required; <code>aggregates</code> adds totals over every row of the search; <code>frozen</code> keeps the name in view while the grid scrolls sideways; <code>row_actions</code> adds a menu per row (duplicate, delete); <code>layout</code> sets the default order and widths, and every user can rearrange it (Actions → Columns).</p><p>Two dynamic actions listen to the grid itself (the trigger is its static id <code>members</code>): <strong>Row initialization</strong> (<code>grid_row_init</code>) runs when <strong>Add row</strong> or Duplicate adds a row, and an <em>Execute JavaScript</em> action fills its empty role, start date and hours with defaults (the function <code>grids.memberDefaults</code> in the static file <code>sample-grids.js</code>); <strong>Save</strong> (<code>grid_save</code>) shows a message on the page that comes back after the grid was saved.</p>',
|
|
113
113
|
null, null, '{}', null),
|
|
114
114
|
(3, 10, 'Members', 'grid', 'standard',
|
|
115
115
|
E'select id, name, team_id, role, starts, hours, rate, remote\n from sample_grids.member order by team_id, name',
|
|
@@ -149,3 +149,35 @@ select pg_temp.page_id('sample-grids', 3), 10, 'Save the members', 'grid_dml', r
|
|
|
149
149
|
-- the detail grid of page 4: the role as a select list
|
|
150
150
|
update meta.region set config = jsonb_set(config, '{columns,role}', '{"lov": "STATIC:Lead,Developer,Designer,Tester,Analyst", "required": true}')
|
|
151
151
|
where page_id = pg_temp.page_id('sample-grids', 4) and seq = 20;
|
|
152
|
+
|
|
153
|
+
-- page 3: dynamic actions on the grid's own events (trigger: the grid's static id).
|
|
154
|
+
-- grid_row_init: a new row (Add row, Duplicate) gets defaults from a JavaScript function of a static file;
|
|
155
|
+
-- grid_save: a message on the page that comes back after a save.
|
|
156
|
+
insert into meta.static_file (app_id, name, mime, content)
|
|
157
|
+
select a.id, 'sample-grids.js', 'text/javascript', convert_to($js$// Sample Interactive Grids: functions for "Execute JavaScript" dynamic actions.
|
|
158
|
+
// A new member row: its empty role, start date and hours get defaults (a duplicated row keeps its copied values).
|
|
159
|
+
pgkiln.actions.register('grids.memberDefaults', (da) => {
|
|
160
|
+
const row = da.element;
|
|
161
|
+
if (!row) return;
|
|
162
|
+
const now = new Date();
|
|
163
|
+
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
164
|
+
const defaults = { role: 'Developer', starts: today, hours: '40' };
|
|
165
|
+
for (const th of row.closest('table').querySelectorAll('thead th[data-col-name]')) {
|
|
166
|
+
const value = defaults[th.dataset.colName];
|
|
167
|
+
const control = value && row.querySelector(`td[data-col="${th.dataset.col}"] :is(input:not([type=hidden]), select)`);
|
|
168
|
+
if (!control || control.value) continue;
|
|
169
|
+
control.value = value;
|
|
170
|
+
control.dispatchEvent(new Event('input', { bubbles: true }));
|
|
171
|
+
control.dispatchEvent(new Event('change', { bubbles: true }));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
$js$, 'utf8')
|
|
175
|
+
from meta.app a where a.alias = 'sample-grids';
|
|
176
|
+
|
|
177
|
+
update meta.app set static_includes = '{sample-grids.js}' where alias = 'sample-grids';
|
|
178
|
+
|
|
179
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, action, code, message)
|
|
180
|
+
values
|
|
181
|
+
(pg_temp.page_id('sample-grids', 3), 10, 'Defaults for a new member', 'grid_row_init', 'members', 'execute_javascript', 'grids.memberDefaults', null),
|
|
182
|
+
(pg_temp.page_id('sample-grids', 3), 20, 'Saved', 'grid_save', 'members', 'show_success', null,
|
|
183
|
+
'A grid_save dynamic action: the members are saved, and the totals include your changes.');
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
-- Map regions on Dutch museums, without PostGIS: markers with popups and
|
|
5
5
|
-- clustering; several layers (markers, a route and an area as GeoJSON, a
|
|
6
6
|
-- heat map that starts hidden); a map that filters the report beside it by
|
|
7
|
-
-- distance;
|
|
8
|
-
-- places show on a map
|
|
7
|
+
-- distance; a form with a location item ("Use my location"), a geocoded
|
|
8
|
+
-- address and a display map, whose places show on a map; and dynamic
|
|
9
|
+
-- actions on a map's own events and on the device's position.
|
|
9
10
|
--
|
|
10
11
|
-- Installed by an administrator from the App Gallery, or with
|
|
11
12
|
-- psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-maps.sql
|
|
@@ -94,7 +95,7 @@ select id, 'demo', meta.hash_password('demo'), '{}' from meta.app where alias =
|
|
|
94
95
|
|
|
95
96
|
insert into meta.page (app_id, page_no, name, title, parent_page)
|
|
96
97
|
select a.id, p.no, p.name, p.name, p.parent
|
|
97
|
-
from meta.app a, (values (1, 'Sample Maps', null), (2, 'Museums', 1), (3, 'Layers', 1), (4, 'Map and list', 1)) as p (no, name, parent)
|
|
98
|
+
from meta.app a, (values (1, 'Sample Maps', null), (2, 'Museums', 1), (3, 'Layers', 1), (4, 'Map and list', 1), (7, 'Map events', 1)) as p (no, name, parent)
|
|
98
99
|
where a.alias = 'sample-maps';
|
|
99
100
|
|
|
100
101
|
-- page 5 and its form, page 6: the Report and form wizard on the places
|
|
@@ -107,7 +108,7 @@ insert into meta.nav_entry (app_id, seq, label, icon, target_page)
|
|
|
107
108
|
select a.id, n.seq, n.label, n.icon, n.page
|
|
108
109
|
from meta.app a, (values
|
|
109
110
|
(10, 'Home', 'home', 1), (20, 'Museums', 'map', 2), (30, 'Layers', 'layers', 3),
|
|
110
|
-
(40, 'Map and list', 'list', 4), (50, 'My places', 'map', 5)
|
|
111
|
+
(40, 'Map and list', 'list', 4), (50, 'My places', 'map', 5), (60, 'Map events', 'bolt', 7)
|
|
111
112
|
) as n (seq, label, icon, page)
|
|
112
113
|
where a.alias = 'sample-maps';
|
|
113
114
|
|
|
@@ -120,7 +121,8 @@ select a.id, 'MAP_TOPICS', e.seq, e.label, e.icon, e.page, e.description
|
|
|
120
121
|
(10, 'Museums', 'map', 2, 'Markers from latitude and longitude columns, popups, and clusters that open as you zoom in.'),
|
|
121
122
|
(20, 'Layers', 'layers', 3, 'Several queries on one map: markers, a route and an area as GeoJSON, a heat map you switch on.'),
|
|
122
123
|
(30, 'Map and list', 'list', 4, 'Move the map, then show only the museums within that distance in the list beside it.'),
|
|
123
|
-
(40, 'My places', 'map', 5, 'A form with a location item: Use my location stores latitude,longitude, the map shows them.')
|
|
124
|
+
(40, 'My places', 'map', 5, 'A form with a location item: Use my location stores latitude,longitude, the map shows them.'),
|
|
125
|
+
(50, 'Map events', 'bolt', 7, 'Dynamic actions on a marker clicked and the map moved, and the museums nearest to you.')
|
|
124
126
|
) as e (seq, label, icon, page, description)
|
|
125
127
|
where a.alias = 'sample-maps';
|
|
126
128
|
|
|
@@ -164,12 +166,68 @@ select pg_temp.page_id('sample-maps', r.page), r.seq, r.title, r.type, r.cols, r
|
|
|
164
166
|
|
|
165
167
|
-- 5 My places (the wizard's report follows at seq 10)
|
|
166
168
|
(5, 5, 'About this page', 'static', 12, 'plain',
|
|
167
|
-
'<p class="lead">The form''s <strong>Location</strong> is a location item: <em>Use my location</em> asks the browser for the device''s position and stores it as <code>latitude,longitude</code> text, which a check constraint on the column keeps in shape. The map reads the same column.</p>',
|
|
169
|
+
'<p class="lead">The form''s <strong>Location</strong> is a location item: <em>Use my location</em> asks the browser for the device''s position and stores it as <code>latitude,longitude</code> text, which a check constraint on the column keeps in shape. The map reads the same column.</p><p>The form also has an <strong>Address</strong> (an <code>address</code> item, not stored): its <em>Check address</em> button offers the matching addresses, and the one chosen puts its position into Location (<code>coordinates_item</code>). For a new place, naming it runs two dynamic actions: a <em>Set Value</em> copies the name into Address and <code>trigger_geocoding</code> looks it up, as if you had pressed Check address. It needs the installation''s geocoder (<code>GEOCODER_URL</code>); without one it is a plain text field. Below it, <strong>On the map</strong> is a <code>display_map</code> item: a small map of the stored location with a pin.</p>',
|
|
168
170
|
'{}', null),
|
|
169
171
|
(5, 20, 'On the map', 'map', 12, 'standard',
|
|
170
172
|
'select id, name as title, note as body, location from sample_maps.place where location is not null',
|
|
171
|
-
'{"link": {"page": 6, "items": {"P6_ID": "#id#"}}, "empty": "No place has a location yet."}', null)
|
|
173
|
+
'{"link": {"page": 6, "items": {"P6_ID": "#id#"}}, "empty": "No place has a location yet."}', null),
|
|
174
|
+
|
|
175
|
+
-- 7 Map events
|
|
176
|
+
(7, 5, 'About this page', 'static', 12, 'plain',
|
|
177
|
+
'<p class="lead">Dynamic actions can listen to the map itself (the trigger is its static id <code>events-map</code>). <strong>Map Object Clicked</strong> (<code>map_object_click</code>) sees the marker''s <code>id</code> column: a <em>Set Value</em> without SQL puts it into <code>P7_MUSEUM</code> and the region <em>Clicked museum</em> is refreshed with it. <strong>Map Changed</strong> (<code>map_change</code>) runs when you move or zoom the map and sees the zoom level, shown in <code>P7_ZOOM</code>. The button <strong>Museums near me</strong> runs <code>current_position</code>, which asks the browser for the device''s position and puts it into <code>P7_HERE</code> as <code>latitude,longitude</code>; then the nearest museums are listed.</p>',
|
|
178
|
+
'{}', null),
|
|
179
|
+
(7, 10, 'Museums', 'map', 8, 'standard',
|
|
180
|
+
E'select id, name as title, city || '' · '' || kind as body, lat, lng\n from sample_maps.museum',
|
|
181
|
+
'{"zoom": 7, "height": "large", "name": "Museums"}', 'events-map'),
|
|
182
|
+
(7, 20, 'Map state', 'static', 4, 'standard', null, '{}', 'map-state'),
|
|
183
|
+
(7, 30, 'Clicked museum', 'classic_report', 4, 'standard',
|
|
184
|
+
E'select name as "Museum", city as "City", kind as "Kind", to_char(visitors, ''FM9G999G999'') as "Visitors"\n from sample_maps.museum where id::text = :P7_MUSEUM',
|
|
185
|
+
'{"empty": "Click a marker on the map."}', 'clicked'),
|
|
186
|
+
(7, 40, 'Nearest museums', 'classic_report', 4, 'standard',
|
|
187
|
+
E'with here as (\n select substring(:P7_HERE from ''^(-?\\d+(?:\\.\\d+)?),'')::numeric as lat,\n substring(:P7_HERE from '',(-?\\d+(?:\\.\\d+)?)$'')::numeric as lng)\nselect m.name as "Museum", m.city as "City",\n round(111.2 * sqrt(power(m.lat - h.lat, 2) + power((m.lng - h.lng) * cos(radians(h.lat)), 2))) || '' km'' as "Distance"\n from sample_maps.museum m, here h\n where h.lat is not null and h.lng is not null\n order by power(m.lat - h.lat, 2) + power((m.lng - h.lng) * cos(radians(h.lat)), 2)\n limit 5',
|
|
188
|
+
'{"empty": "Press Museums near me."}', 'nearest')
|
|
172
189
|
) as r (page, seq, title, type, cols, template, source, config, sid);
|
|
173
190
|
|
|
174
191
|
update meta.region set config = config || jsonb_build_object('report', pg_temp.region_id('sample-maps', 4, 'filter-list'))
|
|
175
192
|
where id = pg_temp.region_id('sample-maps', 4, 'filter-map');
|
|
193
|
+
|
|
194
|
+
-- page 6: a geocoded address (not stored), looked up when a new place gets its name, and a display map of the location
|
|
195
|
+
insert into meta.item (page_id, region_id, seq, name, label, type, help, config)
|
|
196
|
+
select i.page_id, i.region_id, x.seq, x.name, x.label, x.type, x.help, x.config::jsonb
|
|
197
|
+
from meta.item i, (values
|
|
198
|
+
(35, 'P6_ADDRESS', 'Address', 'address', 'Check address offers the matching addresses; the one you choose fills in Location.',
|
|
199
|
+
'{"coordinates_item": "P6_LOCATION", "country": "nl"}'),
|
|
200
|
+
(50, 'P6_MAP', 'On the map', 'display_map', null, '{"coordinates_item": "P6_LOCATION", "zoom": 15}')
|
|
201
|
+
) as x (seq, name, label, type, help, config)
|
|
202
|
+
where i.page_id = pg_temp.page_id('sample-maps', 6) and i.name = 'P6_LOCATION';
|
|
203
|
+
|
|
204
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, condition_type, action, affected_items, code,
|
|
205
|
+
items_to_submit, server_condition_type, server_condition_expr)
|
|
206
|
+
values
|
|
207
|
+
(pg_temp.page_id('sample-maps', 6), 10, 'The name as the address', 'change', 'P6_NAME', 'is_not_null', 'set_value', 'P6_ADDRESS',
|
|
208
|
+
'select :P6_NAME as "P6_ADDRESS"', 'P6_NAME', 'item_null', 'P6_ID'),
|
|
209
|
+
(pg_temp.page_id('sample-maps', 6), 20, 'Look the address up', 'change', 'P6_NAME', 'is_not_null', 'trigger_geocoding', 'P6_ADDRESS',
|
|
210
|
+
null, null, 'item_null', 'P6_ID');
|
|
211
|
+
|
|
212
|
+
-- page 7: dynamic actions on the map's own events (trigger: its static id) and the device's position
|
|
213
|
+
insert into meta.item (page_id, region_id, seq, name, label, type, help, config)
|
|
214
|
+
select pg_temp.page_id('sample-maps', 7), pg_temp.region_id('sample-maps', 7, 'map-state'), x.seq, x.name, x.label, x.type, x.help, '{"warn_unsaved": false}'
|
|
215
|
+
from (values
|
|
216
|
+
(10, 'P7_MUSEUM', 'Museum clicked', 'text', 'Set by the map_object_click dynamic action: the marker''s id column.'),
|
|
217
|
+
(20, 'P7_ZOOM', 'Zoom level', 'display', 'Set by the map_change dynamic action when you move or zoom the map.'),
|
|
218
|
+
(30, 'P7_HERE', 'Your position', 'text', 'Set by the current_position action: latitude,longitude. You can also type one, e.g. 52.09,5.12.')
|
|
219
|
+
) as x (seq, name, label, type, help);
|
|
220
|
+
|
|
221
|
+
insert into meta.button (page_id, region_id, seq, name, label, action)
|
|
222
|
+
values (pg_temp.page_id('sample-maps', 7), pg_temp.region_id('sample-maps', 7, 'map-state'), 10, 'NEAR_ME', 'Museums near me', 'da');
|
|
223
|
+
|
|
224
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, action, affected_items, affected_region_id, items_to_submit)
|
|
225
|
+
select pg_temp.page_id('sample-maps', 7), d.seq, d.name, d.event, d.trig, d.action, d.items, pg_temp.region_id('sample-maps', 7, d.region), d.submit
|
|
226
|
+
from (values
|
|
227
|
+
(10, 'The museum into P7_MUSEUM', 'map_object_click', 'events-map', 'set_value', 'P7_MUSEUM', null, null),
|
|
228
|
+
(20, 'Show the museum', 'map_object_click', 'events-map', 'refresh_region', null, 'clicked', 'P7_MUSEUM'),
|
|
229
|
+
(30, 'The zoom level into P7_ZOOM', 'map_change', 'events-map', 'set_value', 'P7_ZOOM', null, null),
|
|
230
|
+
(40, 'Where am I', 'click', 'NEAR_ME', 'current_position', 'P7_HERE', null, null),
|
|
231
|
+
(50, 'The nearest museums', 'click', 'NEAR_ME', 'refresh_region', null, 'nearest', 'P7_HERE'),
|
|
232
|
+
(60, 'The nearest museums (typed)', 'change', 'P7_HERE', 'refresh_region', null, 'nearest', 'P7_HERE')
|
|
233
|
+
) as d (seq, name, event, trig, action, items, region, submit);
|