pgkiln 0.85.0 → 0.87.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/apex-feature-parity.md +3 -3
- package/docs/guide/04-pages-and-regions.md +24 -5
- package/docs/guide/05-items.md +20 -1
- package/docs/guide/06-processing.md +7 -3
- package/docs/guide/07-dynamic-actions.md +3 -3
- package/docs/guide/08-security.md +6 -2
- package/docs/guide/09-reference.md +1 -0
- package/docs/guide/11-from-apex.md +3 -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 +240 -9
- package/examples/gallery/samples/sample-grids.sql +16 -4
- 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 +10 -1
- package/public/app.js +30 -9
- package/src/builder/components.ts +1 -1
- package/src/builder/region-help.ts +2 -2
- package/src/runtime/authz.ts +7 -0
- package/src/runtime/engine.ts +6 -3
- package/src/runtime/grid.ts +69 -9
- package/src/runtime/items.ts +9 -1
- package/src/runtime/lists.ts +2 -2
- package/src/runtime/push.ts +7 -1
- package/src/runtime/regions.ts +25 -8
- package/src/runtime/signin.ts +5 -5
|
@@ -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, new rows' default values, and dynamic actions on a
|
|
7
|
+
-- new row and after a 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 shows a message; the row''s role, start date and hours already have values, from the columns'' <code>default</code> (text, may use <code>&ITEM.</code>) and <code>default_sql</code> (a SELECT) in <code>columns</code>. A new row left at its defaults isn''t saved; <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',
|
|
@@ -118,7 +118,9 @@ select pg_temp.page_id('sample-grids', r.page), r.seq, r.title, r.type, 12, r.te
|
|
|
118
118
|
"headings": {"team_id": "Team", "starts": "Started", "hours": "Hours a week", "rate": "Hourly rate"},
|
|
119
119
|
"columns": {"name": {"required": true},
|
|
120
120
|
"team_id": {"lov": "select name, id from sample_grids.team order by name", "required": true},
|
|
121
|
-
"role": {"lov": "STATIC:Lead,Developer,Designer,Tester,Analyst", "required": true}
|
|
121
|
+
"role": {"lov": "STATIC:Lead,Developer,Designer,Tester,Analyst", "required": true, "default": "Developer"},
|
|
122
|
+
"starts": {"default_sql": "select current_date"},
|
|
123
|
+
"hours": {"default": "40"}},
|
|
122
124
|
"formats": {"rate": "999G990D00"},
|
|
123
125
|
"aggregates": {"hours": ["sum", "avg"], "rate": "avg", "id": "count"},
|
|
124
126
|
"frozen": 1,
|
|
@@ -149,3 +151,13 @@ select pg_temp.page_id('sample-grids', 3), 10, 'Save the members', 'grid_dml', r
|
|
|
149
151
|
-- the detail grid of page 4: the role as a select list
|
|
150
152
|
update meta.region set config = jsonb_set(config, '{columns,role}', '{"lov": "STATIC:Lead,Developer,Designer,Tester,Analyst", "required": true}')
|
|
151
153
|
where page_id = pg_temp.page_id('sample-grids', 4) and seq = 20;
|
|
154
|
+
|
|
155
|
+
-- page 3: dynamic actions on the grid's own events (trigger: the grid's static id).
|
|
156
|
+
-- grid_row_init: a new row (Add row, Duplicate); its defaults come from the grid's columns (default, default_sql);
|
|
157
|
+
-- grid_save: a message on the page that comes back after a save.
|
|
158
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, action, code, message)
|
|
159
|
+
values
|
|
160
|
+
(pg_temp.page_id('sample-grids', 3), 10, 'A new member', 'grid_row_init', 'members', 'show_success', null,
|
|
161
|
+
'A grid_row_init dynamic action: a new row. Its role, start date and hours start with the columns'' defaults; fill in the name and the team.'),
|
|
162
|
+
(pg_temp.page_id('sample-grids', 3), 20, 'Saved', 'grid_save', 'members', 'show_success', null,
|
|
163
|
+
'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);
|