Skip to main content

Standard pages tuning

Fields Grouping

In some cases, you may want to organize data fields into specific groups for better structure and clarity. For example, you could create a "Main Info" group to include columns like title, description, country, and apartment_image. Another group, "Characteristics," could hold attributes such as price, square_meter, number_of_rooms, and listed. Any values without a specified group will be categorized under "Other.

./resources/apartments.ts
export default {
...
options: {
...
fieldGroups: [
{
groupName: 'Main info',
columns: ['id','title', 'description', 'country', 'apartment_image']
},
{
groupName: 'Characteristics',
columns: ['price', 'square_meter', 'number_of_rooms', "listed"]
}
],
}
}

Here is how it looks: alt text

You can also specify on which page you want to create or delete groups. If you assign null, the groups will disappear from this page.

./resources/apartments.ts
export default {
...
options: {
createFieldGroups: [
{
groupName: 'Main info',
columns: ['id','title']
},
{
groupName: 'Characteristics',
columns: ['description', 'country', 'price', 'square_meter', 'number_of_rooms', "listed"]
}
],
editFieldGroups: null,
showFieldGroups: null,
}
}

List

Default Sorting

./resources/apartments.ts
import { AdminForthSortDirections } from 'adminforth';

...
export default {
resourceId: 'aparts',
options: {
defaultSort: {
columnName: 'created_at',
direction: AdminForthSortDirections.asc,
}
}
}

Page size

use options.listPageSize to define how many records will be shown on the page

./resources/apartments.ts
export default {
resourceId: 'aparts',
options: {
...
listPageSize: 10,
}
}
]

Custom row click action

By default, when you click on a record in the list view, the show view will be opened.

You can change this behavior by using options.listTableClickUrl.

To disable any action (don't open show) return null:

./resources/apartments.ts
export default {
resourceId: 'aparts',
options: {
...
listTableClickUrl: async (record, adminUser) => null,
}
}
]

To open a custom page, return URL to the custom page (can start with https://, or relative adminforth path):

./resources/apartments.ts
      options: {
...
listTableClickUrl: async (record, adminUser) => {
return `https://google.com/search?q=${record.name}`;
}
}

If you wish to open the page in a new tab, add target=_blank get param to the returned URL:

./resources/apartments.ts
      options: {
...
listTableClickUrl: async (record, adminUser) => {
return `https://google.com/search?q=${record.name}&target=_blank`;
}
}

Auto-refresh records

options.listRowsAutoRefreshSeconds might be used to silently refresh records that are loaded (no new records will be fetched if they appear)

./resources/apartments.ts
export default {
resourceId: 'aparts',
hooks: {
list: {
afterDatasourceResponse: async ({ response }: { response: any }) => {
response.forEach((r: any) => {
// substitute random country on any load
const countries = [ 'US', 'DE', 'FR', 'GB', 'NL', 'IT', 'ES', 'DK', 'PL', 'UA',
'CA', 'AU', 'BR', 'JP', 'CN', 'IN', 'KR', 'TR', 'MX', 'ID']
r.country = countries[Math.floor(Math.random() * countries.length)];
})
return { ok: true, error: "" }
}
}
},
options: {
...
listRowsAutoRefreshSeconds: 1,
}
}
]

alt text

Creating

Fill with default values

Sometimes you want to generate some field value without asking user to fill it. For example createdAt oftenly store time of creation of the record. You can do this by using fillOnCreate:

./resources/apartments.ts

export default {
name: 'apartments',
fields: [
...
{
name: 'created_at',
type: AdminForthDataTypes.DATETIME,
fillOnCreate: ({ initialRecord, adminUser }) => (new Date()).toISOString(),
},
],
},
...
],

Also you can assign adminUser ID by adminUser.dbUser.id:

./resources/apartments.ts
export default {
name: 'apartments',
fields: [
...
{
name: 'created_by',
type: AdminForthDataTypes.STRING,
fillOnCreate: ({ initialRecord, adminUser }) => adminUser.dbUser.id,
},
],
},
...
],

Same effect can be achieved by using hooks. But fillOnCreate might be shorter and more readable.

Sometimes you might need to create a link that will open the create form with some fields pre-filled. For example, you might want to create a link that will open the create form with the realtor_id field pre-filled with the current user's ID.

<template>
...
<LinkButton
:to="{
name: 'resource-create',
params: {
resourceId: 'aparts',
},
query: {
values: encodeURIComponent(JSON.stringify({
realtor_id: coreStore?.adminUser.dbUser.id
})),
},
}"
>
{{$t('Create new apartment')}}
</LinkButton>
...
</template>

<script setup lang="ts">
import { LinkButton } from '@afcl';
import { useCoreStore } from '@/stores/core';

const coreStore = useCoreStore();
</script>

Editing

You can set a column editReadonly so it will be shown in the edit form but will be disabled.
This might be useful to better identify the record during editing or to show some additional information that should not be changed but can help to edit the record.

./resources/apartments.ts
export default {
name: 'apartments',
fields: [
...
{
name: 'created_at',
type: AdminForthDataTypes.DATETIME,
editReadonly: true,
},
],
},
...
],

editReadonly is check enforced both on fronted and backend. So it is safe to use it to make sure that data will be never changes.

minValue and maxValue

You can add minValue and maxValue limits to columns, so it will show an error below an input when entered value is out of bounds.

./resources/apartments.ts
export default {
name: 'apartments',
columns: [
...
{
name: 'square_meter',
label: 'Square',
minValue: 3,
maxValue: 1000,
},
],
},
...
],

minValue and maxValue checks are enforced both on frontend and backend.

Validation

In cases when column values must follow certain format, you can add validation to it. validation is an array of rules, each containing regExp that defines a format for a value and message that will be displayed in case when entered value does not pass the check.

./resources/adminuser.ts
export default {
name: 'adminuser',
columns: [
...
{
name: 'email',
required: true,
isUnique: true,
validation: [
{
regExp: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
message: 'Email is not valid, must be in format example@test.com',
},
],
},
],
},
...
],

validation checks are enforced both on frontend and backend.

Input prefix and suffix

You can add prefix or suffix to inputs by adding inputPrefix or inputSuffix fields to a column.

./resources/users.ts
export default {
name: 'users',
columns: [
...
{
name: "price",
inputSuffix: "USD",
allowMinMaxQuery: true,
},
],
},
...
],

These fields can only be used with following AdminForthDataTypes: DECIMAL, FLOAT, INTEGER, STRING and JSON (only if JSON column is an array with appropriate itemType).

Editing note

You can add editingNote to a column to show a note below the input field.

./resources/adminuser.ts
export default {
name: 'adminuser',
columns: [
...
{
name: "password",
editingNote: { edit: "Leave empty to keep password unchanged" },
},
],
},
...
],

Filling an array of values

Whenever you want to have a column to store not a single value but an array of values you have to set column as AdminForthDataTypes.JSON. This way when you are creating or editing a record you can type in a JSON array into a textfield. To simplify this process and allow you to create and edit separate items you can add isArray to a column.

./resources/adminuser.ts
export default {
name: 'adminuser',
columns: [
...
{
name: "room_sizes",
type: AdminForthDataTypes.JSON,
isArray: {
enabled: true,
itemType: AdminForthDataTypes.FLOAT,
},
},
],
},
...
],

Doing so, will result in UI displaying each item of the array as a separate input corresponding to isArray.itemType on create and edit pages.

itemType value can be any of AdminForthDataTypes except JSON and RICHTEXT.

By default it is forbidden to store duplicate values in an array column. To change that you can add allowDuplicateItems: true to isArray, like so:

./resources/adminuser.ts
export default {
name: 'adminuser',
columns: [
...
{
name: "room_sizes",
type: AdminForthDataTypes.JSON,
isArray: {
enabled: true,
itemType: AdminForthDataTypes.FLOAT,
allowDuplicateItems: true,
},
},
],
},
...
],

All validation rules, such as minValue, maxValue, minLength, maxLength and validation will be applied not to array itself but instead to each item.

Note: array columns can not be marked as masked, be a primaryKey and at the time can not be linked to a foreign resource.

Foreign resources

When you want to create a connection between two resources, you need to add foreignResource to a column, like so:

./resources/adminuser.ts
export default {
name: 'adminuser',
columns: [
...
{
name: "realtor_id",
foreignResource: {
resourceId: 'adminuser',
},
},
],
},
...
],

This way, when creating or editing a record you will be able to choose value for this field from a dropdown selector and on list and show pages this field will be displayed as a link to a foreign resource.

Filtering

Filter Options

You can specify the delay between filtering requests and filtering operator for a column using filterOptions field.

./resources/adminuser.ts
export default {
name: 'adminuser',
columns: [
...
{
name: "title",
required: true,
maxLength: 255,
minLength: 3,
filterOptions: {
debounceTimeMs: 500,
substringSearch: false,
},
},
],
},
...
],

debounceTimeMs field dictates how long (in milliseconds) to wait between inputs to send updated data request. By increasing this value, you can reduce the amount of requests set to backend. Default value for this field is set to 10ms. substringSearch sets what comparison operator to use for text field. By default this field is set to true, which results in using case-insensitive ILIKE operator, that will look for records that have filter string anywhere inside field value. Setting this substringSearch to false will result in using more strict EQ operator, that will look for exact full-string matches.