diff --git a/adminforth/index.ts b/adminforth/index.ts
index 05ede79c7..7acf79efa 100644
--- a/adminforth/index.ts
+++ b/adminforth/index.ts
@@ -230,6 +230,55 @@ class AdminForth implements IAdminForth {
});
}
+ validateRecordValues(resource: AdminForthResource, record: any): any {
+ // check if record with validation is valid
+ for (const column of resource.columns.filter((col) => col.name in record && col.validation)) {
+ let error = null;
+ if (column.isArray?.enabled) {
+ error = record[column.name].reduce((err, item) => {
+ return err || AdminForth.Utils.applyRegexValidation(item, column.validation);
+ }, null);
+ } else {
+ error = AdminForth.Utils.applyRegexValidation(record[column.name], column.validation);
+ }
+ if (error) {
+ return error;
+ }
+ }
+
+ // check if record with minValue or maxValue is within limits
+ for (const column of resource.columns.filter((col) => col.name in record
+ && ['integer', 'decimal', 'float'].includes(col.isArray?.enabled ? col.isArray.itemType : col.type)
+ && (col.minValue !== undefined || col.maxValue !== undefined))) {
+ if (column.isArray?.enabled) {
+ const error = record[column.name].reduce((err, item) => {
+ if (err) return err;
+
+ if (column.minValue !== undefined && item < column.minValue) {
+ return `Value in "${column.name}" must be greater than ${column.minValue}`;
+ }
+ if (column.maxValue !== undefined && item > column.maxValue) {
+ return `Value in "${column.name}" must be less than ${column.maxValue}`;
+ }
+
+ return null;
+ }, null);
+ if (error) {
+ return error;
+ }
+ } else {
+ if (column.minValue !== undefined && record[column.name] < column.minValue) {
+ return `Value in "${column.name}" must be greater than ${column.minValue}`;
+ }
+ if (column.maxValue !== undefined && record[column.name] > column.maxValue) {
+ return `Value in "${column.name}" must be less than ${column.maxValue}`;
+ }
+ }
+ }
+
+ return null;
+ }
+
async discoverDatabases() {
this.statuses.dbDiscover = 'running';
@@ -350,24 +399,9 @@ class AdminForth implements IAdminForth {
{ resource: AdminForthResource, record: any, adminUser: AdminUser, extra?: HttpExtra }
): Promise<{ error?: string, createdRecord?: any }> {
- // check if record with validation is valid
- for (const column of resource.columns.filter((col) => col.name in record && col.validation)) {
- const error = AdminForth.Utils.applyRegexValidation(record[column.name], column.validation);
- if (error) {
- return { error };
- }
- }
-
- // check if record with minValue or maxValue is within limits
- for (const column of resource.columns.filter((col) => col.name in record
- && ['integer', 'decimal', 'float'].includes(col.type)
- && (col.minValue !== undefined || col.maxValue !== undefined))) {
- if (column.minValue !== undefined && record[column.name] < column.minValue) {
- return { error: `Value in "${column.name}" must be greater than ${column.minValue}` };
- }
- if (column.maxValue !== undefined && record[column.name] > column.maxValue) {
- return { error: `Value in "${column.name}" must be less than ${column.maxValue}` };
- }
+ const err = this.validateRecordValues(resource, record);
+ if (err) {
+ return { error: err };
}
// execute hook if needed
@@ -435,24 +469,9 @@ class AdminForth implements IAdminForth {
{ resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser, extra?: HttpExtra }
): Promise<{ error?: string }> {
- // check if record with validation is valid
- for (const column of resource.columns.filter((col) => col.name in record && col.validation)) {
- const error = AdminForth.Utils.applyRegexValidation(record[column.name], column.validation);
- if (error) {
- return { error };
- }
- }
-
- // check if record with minValue or maxValue is within limits
- for (const column of resource.columns.filter((col) => col.name in record
- && ['integer', 'decimal', 'float'].includes(col.type)
- && (col.minValue !== undefined || col.maxValue !== undefined))) {
- if (column.minValue !== undefined && record[column.name] < column.minValue) {
- return { error: `Value in "${column.name}" must be greater than ${column.minValue}` };
- }
- if (column.maxValue !== undefined && record[column.name] > column.maxValue) {
- return { error: `Value in "${column.name}" must be less than ${column.maxValue}` };
- }
+ const err = this.validateRecordValues(resource, record);
+ if (err) {
+ return { error: err };
}
// remove editReadonly columns from record
diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts
index e13a2392c..e35ef6077 100644
--- a/adminforth/modules/configValidator.ts
+++ b/adminforth/modules/configValidator.ts
@@ -22,6 +22,7 @@ import {
AllowedActionsEnum,
AdminForthComponentDeclaration ,
AdminForthResourcePages,
+ AdminForthDataTypes,
} from "../types/Common.js";
import AdminForth from "adminforth";
import { AdminForthConfigMenuItem } from "adminforth";
@@ -400,6 +401,35 @@ export default class ConfigValidator implements IConfigValidator {
col.editingNote = typeof inCol.editingNote === 'string' ? { create: inCol.editingNote, edit: inCol.editingNote } : inCol.editingNote;
+ if (col.isArray !== undefined) {
+ if (typeof col.isArray !== 'object') {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray must be an object`);
+ } else if (col.isArray.enabled) {
+ if (col.primaryKey) {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray cannot be used for a primary key columns`);
+ }
+ if (col.masked) {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray cannot be used for a masked column`);
+ }
+ if (col.foreignResource) {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray cannot be used for a foreignResource column`);
+ }
+
+ if (!col.type || col.type !== AdminForthDataTypes.JSON) {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray can be used only with column type JSON`);
+ }
+
+ if (col.isArray.itemType === undefined) {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray must have itemType`);
+ }
+ if (col.isArray.itemType === AdminForthDataTypes.JSON) {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray itemType cannot be JSON`);
+ }
+ if (col.isArray.itemType === AdminForthDataTypes.RICHTEXT) {
+ errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray itemType cannot be RICHTEXT`);
+ }
+ }
+ }
if (col.foreignResource) {
if (!col.foreignResource.resourceId) {
diff --git a/adminforth/spa/src/components/ColumnValueInput.vue b/adminforth/spa/src/components/ColumnValueInput.vue
new file mode 100644
index 000000000..98f458b07
--- /dev/null
+++ b/adminforth/spa/src/components/ColumnValueInput.vue
@@ -0,0 +1,175 @@
+
+