Skip to content

Commit e02ba0c

Browse files
committed
fix: enhance action handling with optional payload support and improve type definitions
1 parent 195ff77 commit e02ba0c

File tree

5 files changed

+59
-9
lines changed

5 files changed

+59
-9
lines changed

adminforth/documentation/docs/tutorial/03-Customization/09-Actions.md

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,13 @@ The file points to your SFC in the custom folder (alias `@@/`), and `meta` lets
298298
```
299299

300300
Use this minimal wrapper component to add a border/rounding around the default action UI while keeping the action logic intact.
301-
Keep the `<slot />` (that's where AdminForth renders the default button) and emit `callAction` to trigger the handler when the wrapper is clicked.
301+
Keep the `<slot />` (that's where AdminForth renders the default button) and emit `callAction` (optionally with a payload) to trigger the handler when the wrapper is clicked.
302302

303303
```ts title="./custom/ActionBorder.vue"
304304
<template>
305305
<!-- Keep the slot: AdminForth renders the default action button/icon here -->
306-
<!-- Emit `callAction` to trigger the action when the wrapper is clicked -->
307-
<div :style="styleObj" @click="emit('callAction')">
306+
<!-- Emit `callAction` (optionally with a payload) to trigger the action when the wrapper is clicked -->
307+
<div :style="styleObj" @click="emit('callAction', {})">
308308
<slot />
309309
</div>
310310
</template>
@@ -313,7 +313,7 @@ Keep the `<slot />` (that's where AdminForth renders the default button) and emi
313313
import { computed } from 'vue';
314314

315315
const props = defineProps<{ meta?: { color?: string; radius?: number; padding?: number } }>();
316-
const emit = defineEmits<{ (e: 'callAction', payload?: unknown): void }>();
316+
const emit = defineEmits<{ (e: 'callAction', payload?: any): void }>();
317317

318318
const styleObj = computed(() => ({
319319
display: 'inline-block',
@@ -322,4 +322,55 @@ const styleObj = computed(() => ({
322322
padding: (props.meta?.padding ?? 2) + 'px',
323323
}));
324324
</script>
325-
```
325+
```
326+
327+
### Pass dynamic values to the action
328+
329+
You can pass arbitrary data from your custom UI wrapper to the backend action by emitting `callAction` with a payload. That payload will be available on the server under the `extra` argument of your action handler.
330+
331+
Frontend examples:
332+
333+
```vue title="./custom/ActionBorder.vue"
334+
<template>
335+
<!-- Two buttons that pass different flags to the action -->
336+
<button @click="emit('callAction', { asListed: true })" class="mr-2">Mark as listed</button>
337+
<button @click="emit('callAction', { asListed: false })">Mark as unlisted</button>
338+
339+
<!-- Or keep the default slot button and wrap it: -->
340+
<div :style="styleObj" @click="emit('callAction', { asListed: true })">
341+
<slot />
342+
</div>
343+
</template>
344+
345+
<script setup lang="ts">
346+
const emit = defineEmits<{ (e: 'callAction', payload?: any): void }>();
347+
</script>
348+
```
349+
350+
Backend handler: read the payload via `extra`.
351+
352+
```ts title="./resources/apartments.ts"
353+
{
354+
resourceId: 'aparts',
355+
options: {
356+
actions: [
357+
{
358+
name: 'Toggle listed',
359+
icon: 'flowbite:eye-solid',
360+
showIn: { list: true, showButton: true, showThreeDotsMenu: true },
361+
// The payload from emit('callAction', { asListed: true|false }) arrives here as `extra`
362+
action: async ({ resource, recordId, adminUser, extra }) => {
363+
const asListed = extra?.asListed === true;
364+
// Example update (use your own data layer):
365+
await admin.resource('aparts').update(recordId, { listed: asListed });
366+
return { ok: true, successMessage: `Set listed=${asListed}` };
367+
}
368+
}
369+
]
370+
}
371+
}
372+
```
373+
374+
Notes:
375+
- If you don’t emit a payload, the default behavior is used by the UI (e.g., in lists the current row context is used). When you do provide a payload, it will be forwarded to the backend as `extra` for your action handler.
376+
- You can combine default context with your own payload by merging before emitting, for example: `emit('callAction', { ...row, asListed: true })` if your component has access to the row object.

adminforth/modules/codeInjector.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,6 @@ class CodeInjector implements ICodeInjector {
482482
throw new Error('customComponent.file is missing for action: ' + JSON.stringify({ id: action.id, name: action.name }));
483483
}
484484
if (!customResourceComponents.includes(file)) {
485-
console.log('Found injection', file);
486485
customResourceComponents.push(file);
487486
}
488487
});

adminforth/spa/src/components/ThreeDotsMenu.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const props = defineProps({
107107
108108
const emit = defineEmits(['startBulkAction']);
109109
110-
async function handleActionClick(action: any, payload: any) {
110+
async function handleActionClick(action: AdminForthActionInput, payload: any) {
111111
adminforth.list.closeThreeDotsDropdown();
112112
113113
const actionId = action.id;

adminforth/spa/src/views/ShowView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ async function deleteRecord() {
243243
244244
}
245245
246-
async function startCustomAction(actionId, extra) {
246+
async function startCustomAction(actionId: string, extra: any) {
247247
actionLoadingStates.value[actionId] = true;
248248
249249
const data = await callAdminForthApi({

dev-demo/resources/users.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default {
5050
dataSource: "maindb",
5151
table: "users",
5252
resourceId: "users",
53-
label: "Users1",
53+
label: "Users",
5454

5555
recordLabel: (r: any) => `👤 ${r.email}`,
5656
plugins: [

0 commit comments

Comments
 (0)