diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts
index c662c8209..36eadbbe9 100644
--- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts
+++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts
@@ -230,9 +230,12 @@ export class DbTableWidgetsComponent implements OnInit {
// isCurrency, isBtcAddress, isISO8601, isISO31661Alpha2, isISO31661Alpha3, isISO4217,
// isDataURI, isMagnetURI, isMimeType, isLatLong, isSlug, isStrongPassword, isTaxID, isVAT
// OR use "regex" with a regex parameter for custom pattern matching
+// force_send_empty_string: when true, always send "" to the backend on clear,
+// even if the column is nullable. Default false (cleared input becomes NULL on nullable columns).
{
"validate": null,
- "regex": null
+ "regex": null,
+ "force_send_empty_string": false
}`,
Textarea: `// provide number of strings to show.
{
diff --git a/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.html b/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.html
index b46bc7023..89c0dc055 100644
--- a/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.html
+++ b/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.html
@@ -8,8 +8,8 @@
[validateType]="validateType"
[regexPattern]="regexPattern"
attr.data-testid="record-{{label()}}-text"
- [(ngModel)]="value" (ngModelChange)="onFieldChange.emit($event)">
- @if (maxLength && maxLength > 0 && value() && (maxLength - value().length) < 100) {
+ [(ngModel)]="value" (ngModelChange)="handleValueChange($event)">
+ @if (maxLength && maxLength > 0 && value() && 100 > (maxLength - value().length)) {
{{value().length}} / {{maxLength}}
}
@if (textField.errors?.['required']) {
diff --git a/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.spec.ts b/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.spec.ts
index 5f3dd5d3d..89483fdc8 100644
--- a/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.spec.ts
+++ b/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.spec.ts
@@ -46,7 +46,9 @@ describe('TextEditComponent', () => {
});
it('should parse regexPattern from widget params', () => {
- fixture.componentRef.setInput('widgetStructure', { widget_params: { validate: 'regex', regex: '^[a-z]+$' } } as any);
+ fixture.componentRef.setInput('widgetStructure', {
+ widget_params: { validate: 'regex', regex: '^[a-z]+$' },
+ } as any);
component.ngOnInit();
expect(component.regexPattern).toBe('^[a-z]+$');
});
@@ -80,4 +82,49 @@ describe('TextEditComponent', () => {
component.validateType = 'customValidator';
expect(component.getValidationErrorMessage()).toBe('Invalid customValidator');
});
+
+ it('should default forceSendEmptyString to false when widget_params omits the key', () => {
+ fixture.componentRef.setInput('widgetStructure', { widget_params: { validate: null, regex: null } } as any);
+ component.ngOnInit();
+ expect(component.forceSendEmptyString).toBe(false);
+ });
+
+ it('should emit null on empty input when column is nullable and force_send_empty_string is not set', () => {
+ fixture.componentRef.setInput('structure', { allow_null: true } as any);
+ component.ngOnInit();
+ const emitted: any[] = [];
+ component.onFieldChange.subscribe((v) => emitted.push(v));
+ component.handleValueChange('');
+ expect(emitted).toEqual([null]);
+ });
+
+ it('should emit empty string on empty input when column is not nullable', () => {
+ fixture.componentRef.setInput('structure', { allow_null: false } as any);
+ component.ngOnInit();
+ const emitted: any[] = [];
+ component.onFieldChange.subscribe((v) => emitted.push(v));
+ component.handleValueChange('');
+ expect(emitted).toEqual(['']);
+ });
+
+ it('should emit empty string on empty input when force_send_empty_string is true even on nullable column', () => {
+ fixture.componentRef.setInput('structure', { allow_null: true } as any);
+ fixture.componentRef.setInput('widgetStructure', {
+ widget_params: { force_send_empty_string: true },
+ } as any);
+ component.ngOnInit();
+ const emitted: any[] = [];
+ component.onFieldChange.subscribe((v) => emitted.push(v));
+ component.handleValueChange('');
+ expect(emitted).toEqual(['']);
+ });
+
+ it('should emit non-empty value unchanged regardless of nullability', () => {
+ fixture.componentRef.setInput('structure', { allow_null: true } as any);
+ component.ngOnInit();
+ const emitted: any[] = [];
+ component.onFieldChange.subscribe((v) => emitted.push(v));
+ component.handleValueChange('hello');
+ expect(emitted).toEqual(['hello']);
+ });
});
diff --git a/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.ts b/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.ts
index 052f867d6..6063f88d1 100644
--- a/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.ts
+++ b/frontend/src/app/components/ui-components/record-edit-fields/text/text.component.ts
@@ -20,6 +20,7 @@ export class TextEditComponent extends BaseEditFieldComponent implements OnInit
maxLength: number | null = null;
validateType: string | null = null;
regexPattern: string | null = null;
+ forceSendEmptyString: boolean = false;
override ngOnInit(): void {
super.ngOnInit();
@@ -35,9 +36,18 @@ export class TextEditComponent extends BaseEditFieldComponent implements OnInit
this.validateType = params.validate || null;
this.regexPattern = params.regex || null;
+ this.forceSendEmptyString = !!params.force_send_empty_string;
}
}
+ handleValueChange(v: string | null): void {
+ if (v === '' && !this.forceSendEmptyString && this.structure()?.allow_null === true) {
+ this.onFieldChange.emit(null);
+ return;
+ }
+ this.onFieldChange.emit(v);
+ }
+
getValidationErrorMessage(): string {
if (!this.validateType) {
return '';