Skip to content

Commit 4eb374f

Browse files
authored
Merge pull request #605 from devforth/feature/AdminForth/1589/add-option-to-listpagesizeopt
Feature/admin forth/1589/add option to listpagesizeopt
2 parents a9eb195 + 3423a59 commit 4eb374f

6 files changed

Lines changed: 130 additions & 5 deletions

File tree

adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,48 @@ export default {
319319
]
320320
```
321321

322+
### List Page Size Options
323+
You can define available pagination sizes using options.listPageSizeOptions. This allows users to choose how many records they want to see per page in the list view.
324+
```typescript title="./resources/apartments.ts"
325+
export default {
326+
resourceId: 'aparts',
327+
options: {
328+
...
329+
listPageSize: 10,
330+
// listPageSizeOptions can be a static array
331+
//diff-add
332+
listPageSizeOptions: [10, 20, 50],
333+
// OR a function for dynamic options based on user role
334+
//diff-add
335+
listPageSizeOptions: ({ adminUser }) => {
336+
//diff-add
337+
if (adminUser?.dbUser?.role === 'superadmin') {
338+
//diff-add
339+
return [50, 100, 500];
340+
//diff-add
341+
}
342+
//diff-add
343+
return [10, 20, 50];
344+
//diff-add
345+
},
346+
}
347+
}
348+
]
349+
```
350+
#### How it works
351+
- listPageSize defines the default number of records per page when the list is opened.
352+
- listPageSizeOptions defines the available page size options shown to the user.
353+
354+
For example: listPageSizeOptions: [10, 20, 50] will allow switching between 10 / 20 / 50 records per page.
355+
356+
#### UI behavior
357+
Page size switching is implemented via a select dropdown (select input) in the table pagination controls.
358+
- User opens the select
359+
- Chooses a value (e.g. 20)
360+
- Table reloads with the new page size
361+
> ☝️Notes
362+
If `listPageSizeOptions` is not provided (or resolves to an empty array), the page size select is not shown. The selected value updates the table immediately and triggers a data refetch. Use `listPageSize` to define the initial number of records per page, and `listPageSizeOptions` to define which page sizes the user can switch between.
363+
322364
### Virtual scroll
323365

324366
Set `options.listVirtualScrollEnabled` to true to enable virtual scrolling in the table. The default value is false. Enable this option if you need to display a large number of records on a single page.

adminforth/modules/restApi.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,9 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
12031203
),
12041204
options: {
12051205
...resource.options,
1206+
listPageSizeOptions: typeof resource.options.listPageSizeOptions === 'function'
1207+
? await resource.options.listPageSizeOptions({ adminUser, adminforth: this.adminforth })
1208+
: resource.options.listPageSizeOptions,
12061209
fieldGroups: resource.options.fieldGroups?.map((group, i) => ({
12071210
...group,
12081211
noTitle: group.noTitle ?? false,

adminforth/spa/src/afcl/Select.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick,type PropType, t
119119
import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
120120
import { useElementSize } from '@vueuse/core'
121121
122-
type ISingleSelectModelValue = string | number | boolean;
122+
type ISingleSelectModelValue = string | number | boolean | null;
123123
124124
const props = defineProps({
125125
options: Array,

adminforth/spa/src/components/ResourceListTable.vue

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,22 @@
340340
</span>
341341
</template>
342342
</span>
343+
<div v-if="totalRows > 0 && pageSizeOptionsComputed?.length"
344+
class="flex items-center gap-2 ml-auto" >
345+
<span class="text-sm text-lightListTablePaginationHelpText dark:text-darkListTablePaginationHelpText whitespace-nowrap">
346+
{{ $t('Rows per page') }}
347+
</span>
348+
<Select
349+
v-model="pageSizeInternal"
350+
:options="pageSizeOptionsComputed"
351+
:searchDisabled="true"
352+
:disableTogleOfSelectedItem="true"
353+
:style="{ width: selectDynamicWidth }"
354+
:placeholder="pageSizeInternal?.toString()"
355+
class="text-sm min-w-20"
356+
classesForInput="h-[34px] min-h-0 py-1 pl-2 pr-6 text-sm rounded-md cursor-pointer af-button-shadow bg-lightDropdownButtonsBackground text-lightDropdownButtonsText border-lightDropdownButtonsBorder dark:bg-darkDropdownButtonsBackground dark:text-darkDropdownButtonsText dark:border-darkDropdownButtonsBorder"
357+
/>
358+
</div>
343359
</div>
344360
</template>
345361

@@ -368,6 +384,7 @@ import { useAdminforth } from '@/adminforth';
368384
import Checkbox from '@/afcl/Checkbox.vue';
369385
import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
370386
import CallActionWrapper from '@/components/CallActionWrapper.vue'
387+
import { Select } from '@/afcl';
371388
372389
const coreStore = useCoreStore();
373390
const { t } = useI18n();
@@ -378,6 +395,7 @@ const props = withDefaults(defineProps<{
378395
rows: any[] | null,
379396
totalRows: number,
380397
pageSize: number,
398+
pageSizeOptions: number[],
381399
checkboxes: any[],
382400
sort: any[],
383401
noRoundings?: boolean,
@@ -407,10 +425,45 @@ const emits = defineEmits([
407425
'update:page',
408426
'update:sort',
409427
'update:checkboxes',
410-
'update:records'
428+
'update:records',
429+
'update:pageSize'
411430
412431
]);
413432
433+
const pageSizeOptionsComputed = computed(() => {
434+
let combinedOptions = [...props.pageSizeOptions];
435+
436+
if (props.resource?.options?.listPageSize && !combinedOptions.includes(props.resource?.options?.listPageSize)) {
437+
combinedOptions.push(props.resource?.options?.listPageSize);
438+
combinedOptions.sort((a, b) => a - b);
439+
}
440+
441+
return combinedOptions.map(size => ({
442+
value: size,
443+
label: size.toString()
444+
}));
445+
});
446+
447+
const pageSizeInternal = ref<number | null>(null);
448+
449+
const selectDynamicWidth = computed(() => {
450+
const length = pageSizeInternal.value?.toString().length || 2;
451+
return `${length + 5}ch`;
452+
})
453+
454+
watch(() => props.pageSize, (newVal) => {
455+
pageSizeInternal.value = newVal;
456+
});
457+
458+
watch(() => pageSizeInternal.value, (newSize) => {
459+
if (newSize) {
460+
localStorage.setItem(`pageSize_${props.resource?.resourceId}`, newSize.toString());
461+
emits('update:pageSize', newSize);
462+
page.value = 1;
463+
}
464+
});
465+
466+
414467
const checkboxesInternal: Ref<any[]> = ref([]);
415468
const pageInput = ref('1');
416469
const page = ref(1);

adminforth/spa/src/views/ListView.vue

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@
178178
@update:sort="sort = $event"
179179
@update:checkboxes="checkboxes = $event"
180180
@update:records="getListInner"
181+
@update:pageSize="(newSize) => { pageSize = newSize; page = 1; }"
181182
:sort="sort"
183+
:pageSizeOptions="Array.isArray(coreStore.resource?.options?.listPageSizeOptions) ? coreStore.resource?.options?.listPageSizeOptions : []"
182184
:pageSize="pageSize"
183185
:totalRows="totalRows"
184186
:checkboxes="checkboxes"
@@ -270,7 +272,23 @@ const customActionLoadingStates = ref<{[key: string]: boolean}>({});
270272
const DEFAULT_PAGE_SIZE = 10;
271273
272274
273-
const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
275+
const pageSize = ref(DEFAULT_PAGE_SIZE);
276+
277+
const syncPageSize = () => {
278+
const resourceId = route.params.resourceId;
279+
const savedSize = localStorage.getItem(`pageSize_${resourceId}`);
280+
281+
if (savedSize) {
282+
pageSize.value = parseInt(savedSize);
283+
}
284+
else if (coreStore.resource?.options?.listPageSize) {
285+
pageSize.value = coreStore.resource.options.listPageSize;
286+
}
287+
else {
288+
pageSize.value = DEFAULT_PAGE_SIZE;
289+
}
290+
};
291+
274292
const isVirtualScrollEnabled = computed(() => coreStore.resource?.options?.listVirtualScrollEnabled || false);
275293
const listBufferSize = computed(() => coreStore.resource?.options?.listBufferSize || 30);
276294
@@ -405,6 +423,9 @@ async function init() {
405423
await coreStore.fetchResourceFull({
406424
resourceId: route.params.resourceId as string
407425
});
426+
427+
syncPageSize();
428+
408429
isPageLoaded.value = true;
409430
// !!! clear filters should be in same tick with sort assignment so that watch can catch it as one change
410431
@@ -455,7 +476,7 @@ async function init() {
455476
}
456477
}
457478
458-
watch([page, sort, () => filtersStore.filters], async () => {
479+
watch([page, sort, pageSize, () => filtersStore.filters], async () => {
459480
// console.log('🔄️ page/sort/filter change fired, page:', page.value);
460481
await getListInner();
461482
}, { deep: true });

adminforth/types/Common.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,13 @@ export interface AdminForthResourceInputCommon {
472472
* Page size for list view
473473
*/
474474
listPageSize?: number,
475-
475+
476+
/**
477+
* Available page size options for list view, provided as an array of page sizes
478+
* or a function returning them. When set together with `listPageSize`, the page
479+
* size should be one of the values returned here.
480+
*/
481+
listPageSizeOptions?: number[] | ((args: { adminUser: any, adminforth: any }) => number[] | Promise<number[]>);
476482
/**
477483
* Whether to use virtual scroll in list view.
478484
*/

0 commit comments

Comments
 (0)