Skip to content

Commit 223a629

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth
2 parents 2ad38d0 + 742e879 commit 223a629

38 files changed

+458
-206
lines changed

Changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [v1.5.9] - next
99

10+
### Changed
11+
12+
- now you should use import adminforth from '@/adminforth' instead of window.adminforth. It has type hints and is more reliable
13+
14+
### Fixed
15+
- show hook is now called as it was when user edits the page
16+
1017
## [v1.5.8]
1118

1219
### Added

adminforth/documentation/docs/tutorial/03-Customization/02-customFieldRendering.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Here is how it looks:
6262
In very similar way you can render how cell is rendered in `'edit'` and `'create'` view.
6363
You can use it for creating custom editors for the fields. Check [component specs](/docs/api/types/Common/interfaces/AdminForthFieldComponents#create) to understand which props are passed to the component
6464

65-
## Parametrizing the custom components
65+
## Parametrize the custom components
6666

6767
Sometimes you need to render same component with different parameters.
6868
You can use [full component declaration](/docs/api/types/Common/interfaces/AdminForthComponentDeclarationFull)
@@ -178,6 +178,79 @@ And simply do `npm install` for the package you need:
178178
npm i <some package> -D
179179
```
180180

181+
## Editing values component
182+
183+
In same way as we define `show` and list component, we can create component for edit/create page.
184+
Let's create custom dropdown for `country` field which will show emoji flags of the countries.
185+
186+
```html title='./custom/CountryDropdown.vue'
187+
<template>
188+
<Select
189+
class="w-full"
190+
:options="column.enum"
191+
:model-value="record[column.name]"
192+
@update:model-value="emit('update:value', $event)"
193+
>
194+
<template #item="{option}">
195+
<span class="text-xl inline-flex">{{ getCountryFlag(option.value) }}</span> {{ option.label }}
196+
</template>
197+
198+
<template #selected-item="{option}">
199+
<span class="text-xl inline-flex">{{ getCountryFlag(option.value) }}</span> {{ option.label }}
200+
</template>
201+
</Select>
202+
</template>
203+
204+
<script setup lang="ts">
205+
import Select from "@/afcl/Select.vue";
206+
import type {
207+
AdminForthResourceColumnCommon,
208+
AdminForthResourceCommon,
209+
AdminUser,
210+
} from "@/types/Common";
211+
212+
const props = defineProps<{
213+
column: AdminForthResourceColumnCommon;
214+
record: any;
215+
meta: any;
216+
resource: AdminForthResourceCommon;
217+
adminUser: AdminUser;
218+
}>();
219+
220+
const emit = defineEmits(["update:value"]);
221+
222+
function getCountryFlag(countryCode: string) {
223+
return countryCode?.toUpperCase()
224+
.replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397));
225+
}
226+
227+
</script>
228+
```
229+
230+
Now you can use this component in the configuration of the resource:
231+
232+
```ts title='./resources/apartments.ts'
233+
{
234+
...
235+
resourceId: 'aparts',
236+
columns: [
237+
...
238+
{
239+
name: 'country',
240+
components: {
241+
//diff-add
242+
edit: '@@/CountryDropdown.vue',
243+
//diff-add
244+
create: '@@/CountryDropdown.vue',
245+
},
246+
...
247+
},
248+
...
249+
],
250+
...
251+
}
252+
```
253+
181254
182255
## Pre-made renderers
183256

adminforth/documentation/docs/tutorial/03-Customization/04-hooks.md

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,45 @@ Hooks are used to:
99
- modify the fetched data before it is displayed in the list and show
1010
- prevent the request to db depending on some condition (Better use [allowedActions](./05-limitingAccess.md) for this)
1111

12+
Every hook is executed when AdminForth frontend makes some internal API HTTP request to the backend.
1213

13-
## Modify the data before it is saved to the database
14+
Every hook must return one of two objects:
15+
1) If everything is fine and request flow should be continued hook should return `{ ok: true }`
16+
2) If for some reason you need to interrupt request flow in hook you should return `{ ok: false, error: 'some error message for user' }`. This is
17+
handy for access-related tasks, though most of such tasks should be solved with [allowedActions](./05-limitingAccess.md) and not hooks.
18+
19+
Every hook is array of async functions, so you can have multiple hooks for one event.
20+
For simplicity of course you can specify hook as scalar async function and not as array, but internally it will be anyway converted to array with single element just after app start. Plugins can push new own hooks in front of yours (using `unshift`) or after yours (using `push`). For example audit log plugin adds hooks for registration of all changes in the database.
21+
22+
Here we will consider possible flows one by one
23+
24+
## Initial data for edit page flow
25+
26+
When user opens edit page, AdminForth makes a request to the backend to get the initial data for the form.
27+
28+
![Initial data for edit page flow](image-28.png)
29+
30+
Practically you can use `show.afterDatasourceResponse` to modify or add some data before it is displayed on the edit page.
31+
32+
For example [upload plugin](/docs/tutorial/Plugins/upload/) uses this hook to generate signed preview URL so user can see existing uploaded file preview in form, and at the same time database stores only original file path which might be not accessible without presigned URL.
33+
34+
## Saving data on edit page
35+
36+
When user clicks the "Save" button on edit page, AdminForth makes a request to the backend to save the data.
37+
38+
![Saving data on edit page](image-27.png)
39+
40+
Practically you can use `edit.beforeSave` hook to modify the data or populate new fields before it is saved to the database.
41+
42+
> 👆 Note: according to diagram you should understand that interrupting flow from `edit.afterSave` does not prevent data modification in DB
43+
44+
## Saving data on create page
45+
46+
When user clicks the "Save" button from create page, AdminForth makes a request to the backend to create new record.
47+
48+
![Saving data on create page](image-26.png)
49+
50+
### Example: modify the created object before it is saved to the database
1451

1552
Let's add reference to `adminUser` when user creates a new apartment:
1653

@@ -38,9 +75,9 @@ import type { AdminUser } from 'adminforth';
3875
//diff-add
3976
create: {
4077
//diff-add
41-
beforeSave: async ({ adminUser, record }: { adminUser: AdminUser, record: any }) => {
78+
beforeSave: async ({ adminUser, record }: { adminUser: AdminUser, updates: any }) => {
4279
//diff-add
43-
record.realtor_id = adminUser.dbUser.id;
80+
updates.realtor_id = adminUser.dbUser.id;
4481
//diff-add
4582
return { ok: true, record };
4683
//diff-add
@@ -52,11 +89,15 @@ import type { AdminUser } from 'adminforth';
5289
}
5390
```
5491

92+
In this way user who creates the apartment will be assigned as a realtor. Also user can't set other realtor then himself, even if he will make request using curl/devtools because hook will override the value.
5593

56-
## Limiting access to user-related data
94+
## List page flow
5795

96+
When user opens the list page, AdminForth makes a request to the backend to get the list of items.
5897

59-
### List of entities
98+
![List page flow](image-31.png)
99+
100+
### Example: limit access in list to user-related records
60101

61102
For example we can prevent the user to see Apartments created by other users. Superadmin user still can see all:
62103

@@ -90,13 +131,17 @@ For example we can prevent the user to see Apartments created by other users. Su
90131
}
91132
```
92133

93-
This hook will prevent the user to see Apartments created by other users in list, however user if will be able to discover
94-
the apartment id, will be able to use show page to see the apartment details. Let's limit it as well:
134+
This hook will prevent the user to see Apartments created by other users in list, however if user will be able to discover
135+
the apartment id, he will be able to use show page to see the apartment details, that is why separate limiting for show page is required as well. Below we will discover how to limit access to show page.
95136

96137
### Dropdown list of foreignResource
97138

98139
By default if there is `foreignResource` like we use for demo on `realtor_id` column, the filter will suggest a
99-
select dropdown with list of all Realtors. This might be a leak to get id's of other users. Let's limit it:
140+
select dropdown with list of all Realtors.
141+
142+
This might bring us a leak where explorer will get id's of other users in the system which might be not desired
143+
144+
Let's limit it:
100145

101146
```ts title='./resources/apartments.ts'
102147
{
@@ -118,7 +163,22 @@ select dropdown with list of all Realtors. This might be a leak to get id's of o
118163

119164
In our case we limit the dropdown list to show only the current user, however you can use same sample to list only objects who are related to the current user in case if you will have relation configurations which require to show related objects which belongs to the current user.
120165

121-
### Show entity
166+
Flow diagram for dropdown list:
167+
168+
![Flow diagram for dropdown list](image-30.png)
169+
170+
## Show page flow
171+
172+
When user opens the show page, AdminForth makes a request to the backend to get the item. This request ia absolutely the same as one for edit initial data, because naturally for most of cases data for show page are the same as initial data for edit page.
173+
174+
However if you still need to distinguish between these two cases you can use `query.source` parameter in hook (we do not mentioned it in diagram for simplicity and rare demand).
175+
176+
Here is show request flow:
177+
178+
![Here is show request](image-29.png)
179+
180+
181+
### Example show limiting:
122182

123183
```ts title='./resources/apartments.ts'
124184
{
@@ -143,14 +203,14 @@ In our case we limit the dropdown list to show only the current user, however yo
143203
}
144204
```
145205

146-
> 👆 Please note that we use `response[0].realtor_id.pk` because this fiel has `foreignResource` in column option is set
206+
> 👆 Please note that we use `response[0].realtor_id.pk` because this field has `foreignResource` in column option is set
147207
> Otherwise you would use just `response[0].realtor_id`
148208
149-
Important notice: when using hook to filter out list of items for list page or list of items on dropdown makes a lot of sense (because gives ability to change filter of database request), using hook for show page is not reasonable:
209+
Important notice: Using hook to filter out list of items for list page or list of items for dropdown makes a lot of sense because gives ability to change filter of database request. However using hook for show page is not reasonable:
150210

151-
First of all it sematicaly better aligns with using allowedActions interface. For this particular case you must use [allowedActions.show](./05-limitingAccess.md#disable-showing-the-resource-based-on-owner)
211+
First of all it semantically better aligns with using `allowedActions` interface. For this particular case you must use [allowedActions.show](./05-limitingAccess.md#disable-showing-the-resource-based-on-owner)
152212

153-
Secondly limiting access from this hook will not prevent executing other hooks (e.g. beforeDatasourceRequest), when allowedActions check
213+
Secondly limiting access from this hook will not prevent executing other hooks (e.g. `beforeDatasourceRequest`), when allowedActions check
154214
always performed before any hooks and any database requests.
155215

156216
## All hooks

adminforth/documentation/docs/tutorial/03-Customization/05-limitingAccess.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11

22
# Limiting actions access
33

4+
As you might have noticed in diagrams from [adminforth hooks](./04-hooks.md) section of this tutorial, AdminForth checks `options.allowedActions` before executing any action. In this section we will show real-code examples of how to limit access to actions based on user role or record values.
5+
6+
Before we start it is worth to mention that callbacks or scalars defined in `allowedActions` are called/parsed not only before actual request but also before displaying buttons in the UI. So first time, when frontend loads any page of resource, it "calls" `allowedActions` to understand whether user has access to each function, and e.g. if it says that user can't delete record, AdminForth will not show delete icon in the UI:
7+
8+
![Resource any page request](image-21.png)
9+
10+
As you can see allowedAction callbacks are called in parallel in async manner. However it is important to keep them fast and not to make any slow operations in them, to keep UI responsive.
411

512
## Statically disable some action
613

314 KB
Loading
307 KB
Loading
331 KB
Loading
346 KB
Loading
387 KB
Loading
344 KB
Loading

0 commit comments

Comments
 (0)