Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,19 @@ A list of fields you want us to send as the body of the request. Each one is an
| multi | `boolean` | false | If true, select-multi dropdown will allow for multiple selections from a pre-defined list. Make sure defining the right input type first: `"type": "select-multi"`. |
| selectLimit | `number` | unlimited | An optional setting for limiting the multiple selections from a pre-defined list. |
| showFieldWhen | `function` | false | Only available when using dynamic (js) config.</br> A function that dynamically controls field visibility and inclusion in API requests. Only available when using dynamic (js) config. The function receives an array of all current form fields and must return a boolean value. When `true`, the field is displayed in the form and included in API requests. When `false`, the field is hidden and null value is sent with the API requests. Use the `originalName` property to reference other fields in your conditions. <br>Common use cases:<br>- Show additional fields based on a dropdown selection<br>- Show advanced options based on other field values<br>- Create dependent field relationships <br>Example:` "showFieldWhen": (fields) => { return fields.find(f => f.originalName === 'jobTitle')?.value === 'boss';}` |
| onChange | `function` | false | Only available when using dynamic (js) config.</br> A custom callback function that is triggered when the field's value changes. The function receives the new value of the changed field and the array of all current form fields. Use this to implement field dependencies, such as resetting another field when a field changes to a particular value.<br>Example:` "onChange": (newValue, fields) => { if (newValue === "B") { const dependentField = fields.find(f => f.originalName === "dependentField"); if (dependentField) { dependentField.value = null; } } }` |
| onChange | `function` | false | Only available when using dynamic (js) config.</br> A custom callback function that is triggered when the field's value changes. Fires on every keystroke for text inputs - for text fields, consider using `onBlur` instead. The function receives the new value and the array of all current form fields.<br>Example:` "onChange": (newValue, fields) => { if (newValue === "B") { const dependentField = fields.find(f => f.originalName === "dependentField"); if (dependentField) { dependentField.value = null; } } }` |
| onBlur | `function` | false | Only available when using dynamic (js) config.</br> A custom callback function that is triggered when the field loses focus (after editing completes). The function receives the current value of the field and the array of all current form fields. Useful for actions that should only run after the user finishes editing, like setting defaults based on completed input.<br>Example:` "onBlur": (value, fields) => { if (value) { const dependentField = fields.find(f => f.originalName === "dependentField"); if (dependentField) { dependentField.value = "default"; } } }` |

#### Field Callbacks: onChange vs onBlur

Both callbacks receive the field value and all form fields, but fire at different times:

| Callback | Fires when | Best for |
|----------|-----------|----------|
| `onChange` | Every change (each keystroke for text inputs) | Checkboxes, dropdowns, toggles - inputs where each change is a deliberate action |
| `onBlur` | Field loses focus (user clicks away or tabs out) | Text, number, email inputs - when you want to react after user finishes typing |

**Recommendation**: Use `onBlur` for text-based inputs to avoid running your callback on every keystroke. Use `onChange` for checkboxes and select dropdowns where each interaction is intentional.

<br />

Expand Down
5 changes: 4 additions & 1 deletion src/assets/schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,10 @@
"description": "A function that determines if the field should be visible. The function receives an array of all form fields as its argument and should return a boolean. Example: `(fields) => fields.find(f => f.originalName === 'status')?.value === 'active'`"
},
"onChange": {
"description": "A callback function that is triggered when the field's value changes. The function receives the new value and the array of all form fields as arguments. Example: `(value, fields) => { if (value === 'B') { const dependentField = fields.find(f => f.originalName === 'dependentField'); if (dependentField) { dependentField.value = null; } } }`"
"description": "A callback function that is triggered when the field's value changes. The function receives the new value and the array of all form fields as arguments. Best for checkboxes, dropdowns, and other inputs where each change is a deliberate action. Example: `(value, fields) => { if (value === 'B') { const dependentField = fields.find(f => f.originalName === 'dependentField'); if (dependentField) { dependentField.value = null; } } }`"
},
"onBlur": {
"description": "A callback function that is triggered when the field loses focus (after editing completes). The function receives the current value and the array of all form fields as arguments. Best for text, number, email inputs - when you want to react after the user finishes typing rather than on every keystroke. Example: `(value, fields) => { if (value) { const dependentField = fields.find(f => f.originalName === 'dependentField'); if (dependentField) { dependentField.value = 'default'; } } }`"
},
"label": {
"type": "string",
Expand Down
2 changes: 2 additions & 0 deletions src/common/models/config.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export type TConfigInputField =

type showFieldWhen = (fields: IConfigInputField[]) => boolean;
type onChangeCallback = (newValue: any, fields: IConfigInputField[]) => void;
type onBlurCallback = (value: any, fields: IConfigInputField[]) => void;

export interface IConfigInputField {
originalName?: string;
Expand All @@ -180,6 +181,7 @@ export interface IConfigInputField {
selectLimit?: number;
showFieldWhen?: showFieldWhen;
onChange?: onChangeCallback;
onBlur?: onBlurCallback;
}

export interface IConfigOptionSource {
Expand Down
10 changes: 10 additions & 0 deletions src/components/formPopup/formPopup.comp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@ export const FormPopup = withAppContext(({ context, title, type, successMessage,
setFormFields(updatedFormFields);
}

function fieldBlurred(fieldName: string) {
const field = formFields.find(f => f.name === fieldName);
if (field?.onBlur) {
field.onBlur(field.value, formFields);
// Trigger re-render to reflect any changes made by the callback
setFormFields([...formFields]);
}
}

// Check if field should be visible based on showFieldWhen condition
function shouldFieldBeVisible(field: IConfigInputField, fields: IConfigInputField[]): boolean {
if (!field.showFieldWhen) return true;
Expand Down Expand Up @@ -312,6 +321,7 @@ export const FormPopup = withAppContext(({ context, title, type, successMessage,
key={`field_${idx}`}
field={field}
onChange={formChanged}
onBlur={fieldBlurred}
showReset={!field.type || field.type === 'text'}
/>
);
Expand Down
4 changes: 3 additions & 1 deletion src/components/formRow/formRow.comp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface IProps {
value: any,
submitAfterChange?: boolean
) => void;
onBlur?: (fieldName: string) => void;
showReset?: boolean;
direction?: "row" | "column";
}
Expand All @@ -38,7 +39,7 @@ interface IOption {
}

export const FormRow = withAppContext(
({ context, field, direction, showReset, onChange }: IProps) => {
({ context, field, direction, showReset, onChange, onBlur }: IProps) => {
const [optionSources, setOptionSources] = useState<any>({});
const [originalOptions, setOriginalOptions] = useState<any>({});
const { httpService, activePage, config } = context;
Expand Down Expand Up @@ -229,6 +230,7 @@ export const FormRow = withAppContext(
disabled: field.readonly,
required: field.required,
onChange: (e: any) => changeCallback(field.name, e.target.value),
onBlur: () => onBlur?.(field.name),
...(helpText ? { "aria-describedby": helpTextId } : {}),
};
};
Expand Down