Skip to content

Commit

Permalink
fix form data binding (#4714)
Browse files Browse the repository at this point in the history
feat: bind row data to form fields
  • Loading branch information
connoratrug authored Feb 20, 2025
1 parent ce62a58 commit 8701d15
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 79 deletions.
2 changes: 2 additions & 0 deletions apps/metadata-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export type columnValue =
| columnValueObject
| columnValue[];

export type recordValue = Record<string, columnValue>;

export interface columnValueObject {
[x: string]: columnValue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type {
columnValue,
IColumn,
ITableMetaData,
recordValue,
} from "../../../../../metadata-utils/src/types";
import type { IRow } from "../../../Interfaces/IRow";
import constants from "../../constants.js";
Expand All @@ -27,10 +29,10 @@ export function getRowErrors(

export function getColumnError(
column: IColumn,
rowData: Record<string, any>,
rowData: recordValue | undefined,
tableMetaData: ITableMetaData
): string | undefined {
const value = rowData[column.id];
const value = rowData?.[column.id];
const type = column.columnType;
const missesValue = isMissingValue(value);
// FIXME: this function should also check all array types
Expand Down Expand Up @@ -89,7 +91,10 @@ export function getColumnError(
}
if (type === "JSON") {
try {
if (!isJsonObjectOrArray(JSON.parse(value))) {
if (
typeof value === "string" &&
!isJsonObjectOrArray(JSON.parse(value))
) {
return `Root element must be an object or array`;
}
} catch {
Expand Down Expand Up @@ -123,17 +128,17 @@ export function isRequired(value: string | boolean): boolean {
}
}

function isInValidNumericValue(columnType: string, value: number) {
function isInValidNumericValue(columnType: string, value?: columnValue) {
if (["DECIMAL", "INT"].includes(columnType)) {
return isNaN(value);
return value === undefined || (typeof value === "number" && isNaN(value));
} else {
return false;
}
}

function getRequiredExpressionError(
expression: string,
values: Record<string, any>,
values: Record<string, any> | undefined,
tableMetaData: ITableMetaData
): string | undefined {
try {
Expand All @@ -151,7 +156,7 @@ function getRequiredExpressionError(

function getColumnValidationError(
validation: string,
values: Record<string, any>,
values: recordValue | undefined,
tableMetaData: ITableMetaData
) {
try {
Expand All @@ -170,7 +175,7 @@ function getColumnValidationError(

export function executeExpression(
expression: string,
values: Record<string, any>,
values: recordValue | undefined,
tableMetaData: ITableMetaData
) {
//make sure all columns have keys to prevent reference errors
Expand Down Expand Up @@ -267,7 +272,7 @@ export function filterVisibleColumns(

export function isColumnVisible(
column: IColumn,
values: Record<string, any>,
values: recordValue | undefined,
tableMetadata: ITableMetaData
): boolean {
const expression = column.visible;
Expand Down
14 changes: 1 addition & 13 deletions apps/tailwind-components/components/Input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
:disabled="disabled"
:describedBy="describedBy"
:placeholder="placeholder"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -22,7 +21,6 @@
:disabled="disabled"
:describedBy="describedBy"
placeholder="Input an email address"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -36,7 +34,6 @@
:disabled="disabled"
:describedBy="describedBy"
placeholder="Input a hyperlink"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -50,7 +47,6 @@
:describedBy="describedBy"
:trueLabel="trueLabel"
:falseLabel="falseLabel"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -63,7 +59,6 @@
:disabled="disabled"
:describedBy="describedBy"
:placeholder="placeholder"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -77,7 +72,6 @@
:describedBy="describedBy"
:placeholder="placeholder"
:options="options as IValueLabel[]"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -91,7 +85,6 @@
:describedBy="describedBy"
:placeholder="placeholder"
:options="options as IValueLabel[]"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -107,7 +100,6 @@
:refSchemaId="refSchemaId as string"
:refTableId="refTableId as string"
:refLabel="refLabel as string"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
:is-array="false"
Expand All @@ -124,7 +116,6 @@
:refSchemaId="refSchemaId as string"
:refTableId="refTableId as string"
:refLabel="refLabel as string"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
:is-array="true"
Expand All @@ -137,7 +128,6 @@
:invalid="invalid"
:disabled="disabled"
:describedBy="describedBy"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -151,7 +141,6 @@
:disabled="disabled"
:describedBy="describedBy"
placeholder="Input a date"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -165,7 +154,6 @@
:disabled="disabled"
:describedBy="describedBy"
placeholder="Input a date and time"
@update:modelValue="emit('update:modelValue', $event)"
@focus="emit('focus')"
@blur="emit('blur')"
/>
Expand All @@ -192,6 +180,6 @@ const props = defineProps<
falseLabel?: string;
}
>();
const emit = defineEmits(["focus", "blur", "update:modelValue"]);
const emit = defineEmits(["focus", "blur"]);
const typeUpperCase = computed(() => props.type.toUpperCase());
</script>
2 changes: 1 addition & 1 deletion apps/tailwind-components/components/form/Field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defineProps<
falseLabel?: string;
}
>();
const emit = defineEmits(["focus", "blur", "update:modelValue"]);
const emit = defineEmits(["focus", "blur"]);
</script>

<template>
Expand Down
51 changes: 27 additions & 24 deletions apps/tailwind-components/components/form/Fields.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
columnValue,
IColumn,
ITableMetaData,
recordValue,
} from "../../../metadata-utils/src/types";
import {
isColumnVisible,
Expand All @@ -13,25 +14,20 @@ import {
import type { IFormLegendSection } from "../../../metadata-utils/src/types";
import { scrollToElementInside } from "~/utils/scrollTools";
import logger from "@/utils/logger";
//todo: don't forget about default values for reflinks
import consola from "consola";
const props = defineProps<{
id: string;
schemaId: string;
metadata: ITableMetaData;
data: Record<columnId, columnValue>[];
}>();
const emit = defineEmits(["error", "update:modelValue"]);
const modelValue = defineModel<recordValue>("modelValue", {
required: true,
});
const emit = defineEmits(["error"]);
const dataMap = reactive<Record<columnId, columnValue>>(
Object.fromEntries(
props.metadata.columns
.filter((column) => column.columnType !== "HEADING")
.map((column) => [column.id, ""])
)
);
const visibleMap = reactive<Record<columnId, boolean>>({});
const errorMap = reactive<Record<columnId, string>>({});
const previousColumn = ref<IColumn>();
Expand All @@ -40,9 +36,10 @@ const previousColumn = ref<IColumn>();
props.metadata.columns
.filter((column) => column.columnType === "HEADING")
.forEach((column) => {
logger.debug(isColumnVisible(column, dataMap, props.metadata));
logger.debug(isColumnVisible(column, modelValue.value, props.metadata));
visibleMap[column.id] =
!column.visible || isColumnVisible(column, dataMap, props.metadata)
!column.visible ||
isColumnVisible(column, modelValue.value, props.metadata)
? true
: false;
logger.debug(
Expand Down Expand Up @@ -94,30 +91,38 @@ const numberOfRequiredFields = computed(
const numberOfRequiredFieldsWithData = computed(
() =>
props.metadata.columns.filter(
(column) => column.required && dataMap[column.id]
(column) =>
column.required && modelValue[column.id as keyof typeof modelValue]
).length
);
const recordLabel = computed(() => props.metadata.label);
function validateColumn(column: IColumn) {
logger.debug("validate " + column.id);
delete errorMap[column.id];
const error = getColumnError(column, dataMap, props.metadata);
if (error) errorMap[column.id] = error;
else {
const error = getColumnError(column, modelValue.value, props.metadata);
consola.info("error", error);
if (error) {
errorMap[column.id] = error;
} else {
errorMap[column.id] = props.metadata.columns
.filter((c) => c.validation?.includes(column.id))
.map((c) => {
const result = getColumnError(c, dataMap, props.metadata);
const result = getColumnError(c, modelValue.value, props.metadata);
return result;
})
.join("");
}
}
function checkVisibleExpression(column: IColumn) {
if (!column.visible || isColumnVisible(column, dataMap, props.metadata)) {
if (
!column.visible ||
isColumnVisible(column, modelValue.value, props.metadata)
) {
visibleMap[column.id] = true;
} else {
visibleMap[column.id] = false;
Expand All @@ -128,20 +133,18 @@ function checkVisibleExpression(column: IColumn) {
}
function onUpdate(column: IColumn, $event: columnValue) {
dataMap[column.id] = $event;
if (errorMap[column.id]) {
validateColumn(column);
}
props.metadata.columns
.filter((c) => c.visible?.includes(column.id))
.forEach((c) => {
visibleMap[c.id] = isColumnVisible(c, dataMap, props.metadata)
visibleMap[c.id] = isColumnVisible(c, modelValue.value, props.metadata)
? true
: false;
logger.debug("updating visibility for " + c.id + "=" + visibleMap[c.id]);
});
previousColumn.value = column;
emit("update:modelValue", dataMap);
}
function onFocus(column: IColumn) {
Expand Down Expand Up @@ -211,7 +214,7 @@ function goToSection(headerId: string) {
<FormField
class="pb-8"
v-else-if="visibleMap[column.id] === true"
v-model="dataMap[column.id]"
v-model="modelValue[column.id]"
:id="`${column.id}-form-field`"
:type="column.columnType"
:label="column.label"
Expand Down
5 changes: 4 additions & 1 deletion apps/tailwind-components/components/input/CheckboxGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
:checked="modelValue!.includes(option.value)"
:disabled="disabled"
@change="toggleSelect"
class="peer sr-only"
class="opacity-0 absolute ml-4 mt-2"
/>
<!-- don't use ssr only that make screen 'hop'. Also we want aria to be able to use it. It is just hidden behind the icon
todo remove this comment once stable.
-->
<InputCheckboxIcon
:checked="modelValue!.includes(option.value)"
:invalid="invalid"
Expand Down
5 changes: 3 additions & 2 deletions apps/tailwind-components/components/input/Ref.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { IQueryMetaData } from "../../../molgenis-components/src/client/IQu
import type {
ITableMetaData,
columnValueObject,
recordValue,
} from "../../../metadata-utils/src/types";
import { type IInputProps, type IValueLabel } from "~/types/types";
Expand All @@ -30,8 +31,8 @@ const modelValue = defineModel<columnValueObject[] | columnValueObject>();
const tableMetadata = ref<ITableMetaData>();
const emit = defineEmits(["focus", "blur", "error", "update:modelValue"]);
const optionMap: Ref<Record<string, columnValueObject>> = ref({});
const selectionMap: Ref<Record<string, columnValueObject>> = ref({});
const optionMap: Ref<recordValue> = ref({});
const selectionMap: Ref<recordValue> = ref({});
const initialCount = ref<number>(0);
const count = ref<number>(0);
const offset = ref<number>(0);
Expand Down
2 changes: 1 addition & 1 deletion apps/tailwind-components/components/input/String.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defineProps<
type?: string;
}
>();
const emit = defineEmits(["focus", "blur", "update:modelValue"]);
const emit = defineEmits(["focus", "blur"]);
</script>

<template>
Expand Down
Loading

0 comments on commit 8701d15

Please sign in to comment.