mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
feat(n8n Form Node): Add Hidden Fields (#12803)
Some checks failed
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Has been cancelled
Some checks failed
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Has been cancelled
This commit is contained in:
parent
0f345681d9
commit
0da1114981
|
@ -377,6 +377,10 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if isHidden}}
|
||||||
|
<input type="hidden" id="{{id}}" name="{{id}}" value="{{hiddenValue}}" />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if isTextarea}}
|
{{#if isTextarea}}
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label class='form-label {{inputRequired}}' for='{{id}}'>{{label}}</label>
|
<label class='form-label {{inputRequired}}' for='{{id}}'>{{label}}</label>
|
||||||
|
|
|
@ -266,7 +266,15 @@ export class Form extends Node {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fields = context.getNodeParameter('formFields.values', []) as FormFieldsParameter;
|
fields = (context.getNodeParameter('formFields.values', []) as FormFieldsParameter).map(
|
||||||
|
(field) => {
|
||||||
|
if (field.fieldType === 'hiddenField') {
|
||||||
|
field.fieldLabel = field.fieldName as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return field;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const method = context.getRequestObject().method;
|
const method = context.getRequestObject().method;
|
||||||
|
|
|
@ -64,6 +64,11 @@ export const formFields: INodeProperties = {
|
||||||
placeholder: 'e.g. What is your name?',
|
placeholder: 'e.g. What is your name?',
|
||||||
description: 'Label that appears above the input field',
|
description: 'Label that appears above the input field',
|
||||||
required: true,
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
fieldType: ['hiddenField'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Element Type',
|
displayName: 'Element Type',
|
||||||
|
@ -92,6 +97,10 @@ export const formFields: INodeProperties = {
|
||||||
name: 'File',
|
name: 'File',
|
||||||
value: 'file',
|
value: 'file',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Hidden Field',
|
||||||
|
value: 'hiddenField',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Number',
|
name: 'Number',
|
||||||
value: 'number',
|
value: 'number',
|
||||||
|
@ -119,7 +128,33 @@ export const formFields: INodeProperties = {
|
||||||
default: '',
|
default: '',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
fieldType: ['dropdown', 'date', 'file', 'html'],
|
fieldType: ['dropdown', 'date', 'file', 'html', 'hiddenField'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Field Name',
|
||||||
|
name: 'fieldName',
|
||||||
|
description:
|
||||||
|
'The name of the field, used in input attributes and referenced by the workflow',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
fieldType: ['hiddenField'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Field Value',
|
||||||
|
name: 'fieldValue',
|
||||||
|
description:
|
||||||
|
'Input value can be set here or will be passed as a query parameter via Field Name if no value is set',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
fieldType: ['hiddenField'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -242,7 +277,7 @@ export const formFields: INodeProperties = {
|
||||||
'Whether to require the user to enter a value for this field before submitting the form',
|
'Whether to require the user to enter a value for this field before submitting the form',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
fieldType: ['html'],
|
fieldType: ['html', 'hiddenField'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -106,7 +106,16 @@ describe('Form Node', () => {
|
||||||
mockWebhookFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
mockWebhookFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||||
if (paramName === 'operation') return 'page';
|
if (paramName === 'operation') return 'page';
|
||||||
if (paramName === 'useJson') return false;
|
if (paramName === 'useJson') return false;
|
||||||
if (paramName === 'formFields.values') return [{ fieldLabel: 'test' }];
|
if (paramName === 'formFields.values')
|
||||||
|
return [
|
||||||
|
{ fieldLabel: 'test' },
|
||||||
|
{
|
||||||
|
fieldName: 'Powerpuff Girl',
|
||||||
|
fieldValue: 'Blossom',
|
||||||
|
fieldType: 'hiddenField',
|
||||||
|
fieldLabel: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
if (paramName === 'options') {
|
if (paramName === 'options') {
|
||||||
return {
|
return {
|
||||||
formTitle: 'Form Title',
|
formTitle: 'Form Title',
|
||||||
|
@ -121,7 +130,42 @@ describe('Form Node', () => {
|
||||||
|
|
||||||
await form.webhook(mockWebhookFunctions);
|
await form.webhook(mockWebhookFunctions);
|
||||||
|
|
||||||
expect(mockResponseObject.render).toHaveBeenCalledWith('form-trigger', expect.any(Object));
|
expect(mockResponseObject.render).toHaveBeenCalledWith('form-trigger', {
|
||||||
|
appendAttribution: 'test',
|
||||||
|
buttonLabel: 'Form Button',
|
||||||
|
formDescription: 'Form Description',
|
||||||
|
formDescriptionMetadata: 'Form Description',
|
||||||
|
formFields: [
|
||||||
|
{
|
||||||
|
id: 'field-0',
|
||||||
|
errorId: 'error-field-0',
|
||||||
|
label: 'test',
|
||||||
|
inputRequired: '',
|
||||||
|
defaultValue: '',
|
||||||
|
isInput: true,
|
||||||
|
placeholder: undefined,
|
||||||
|
type: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'field-1',
|
||||||
|
errorId: 'error-field-1',
|
||||||
|
label: 'Powerpuff Girl',
|
||||||
|
inputRequired: '',
|
||||||
|
defaultValue: '',
|
||||||
|
placeholder: undefined,
|
||||||
|
hiddenName: 'Powerpuff Girl',
|
||||||
|
hiddenValue: 'Blossom',
|
||||||
|
isHidden: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
formSubmittedText: 'Your response has been recorded',
|
||||||
|
formTitle: 'Form Title',
|
||||||
|
n8nWebsiteLink: 'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger',
|
||||||
|
testRun: true,
|
||||||
|
useResponseData: false,
|
||||||
|
validForm: true,
|
||||||
|
formSubmittedHeader: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return form data for POST request', async () => {
|
it('should return form data for POST request', async () => {
|
||||||
|
@ -182,6 +226,7 @@ describe('Form Node', () => {
|
||||||
if (paramName === 'completionTitle') return 'Test Title';
|
if (paramName === 'completionTitle') return 'Test Title';
|
||||||
if (paramName === 'completionMessage') return 'Test Message';
|
if (paramName === 'completionMessage') return 'Test Message';
|
||||||
if (paramName === 'redirectUrl') return '';
|
if (paramName === 'redirectUrl') return '';
|
||||||
|
if (paramName === 'formFields.values') return [];
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
mockWebhookFunctions.getParentNodes.mockReturnValue([
|
mockWebhookFunctions.getParentNodes.mockReturnValue([
|
||||||
|
@ -225,6 +270,8 @@ describe('Form Node', () => {
|
||||||
if (paramName === 'completionTitle') return 'Test Title';
|
if (paramName === 'completionTitle') return 'Test Title';
|
||||||
if (paramName === 'completionMessage') return 'Test Message';
|
if (paramName === 'completionMessage') return 'Test Message';
|
||||||
if (paramName === 'redirectUrl') return 'https://n8n.io';
|
if (paramName === 'redirectUrl') return 'https://n8n.io';
|
||||||
|
if (paramName === 'formFields.values') return [];
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
mockWebhookFunctions.getParentNodes.mockReturnValue([
|
mockWebhookFunctions.getParentNodes.mockReturnValue([
|
||||||
|
|
|
@ -110,6 +110,12 @@ describe('FormTrigger, formWebhook', () => {
|
||||||
html: '<div>Test HTML</div>',
|
html: '<div>Test HTML</div>',
|
||||||
requiredField: false,
|
requiredField: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'Powerpuff Girl',
|
||||||
|
fieldValue: 'Blossom',
|
||||||
|
fieldType: 'hiddenField',
|
||||||
|
fieldLabel: '',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
|
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
|
||||||
|
@ -174,6 +180,17 @@ describe('FormTrigger, formWebhook', () => {
|
||||||
html: '<div>Test HTML</div>',
|
html: '<div>Test HTML</div>',
|
||||||
isHtml: true,
|
isHtml: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'field-5',
|
||||||
|
errorId: 'error-field-5',
|
||||||
|
hiddenName: 'Powerpuff Girl',
|
||||||
|
hiddenValue: 'Blossom',
|
||||||
|
label: 'Powerpuff Girl',
|
||||||
|
isHidden: true,
|
||||||
|
inputRequired: '',
|
||||||
|
defaultValue: '',
|
||||||
|
placeholder: undefined,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
formSubmittedText: 'Your response has been recorded',
|
formSubmittedText: 'Your response has been recorded',
|
||||||
formTitle: 'Test Form',
|
formTitle: 'Test Form',
|
||||||
|
@ -300,9 +317,21 @@ describe('FormTrigger, prepareFormData', () => {
|
||||||
acceptFileTypes: '.jpg,.png',
|
acceptFileTypes: '.jpg,.png',
|
||||||
multipleFiles: true,
|
multipleFiles: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldLabel: 'username',
|
||||||
|
fieldName: 'username',
|
||||||
|
fieldValue: 'powerpuffgirl125',
|
||||||
|
fieldType: 'hiddenField',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldLabel: 'villain',
|
||||||
|
fieldName: 'villain',
|
||||||
|
fieldValue: 'Mojo Dojo',
|
||||||
|
fieldType: 'hiddenField',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const query = { Name: 'John Doe', Email: 'john@example.com' };
|
const query = { Name: 'John Doe', Email: 'john@example.com', villain: 'princess morbucks' };
|
||||||
|
|
||||||
const result = prepareFormData({
|
const result = prepareFormData({
|
||||||
formTitle: 'Test Form',
|
formTitle: 'Test Form',
|
||||||
|
@ -368,6 +397,28 @@ describe('FormTrigger, prepareFormData', () => {
|
||||||
acceptFileTypes: '.jpg,.png',
|
acceptFileTypes: '.jpg,.png',
|
||||||
multipleFiles: 'multiple',
|
multipleFiles: 'multiple',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'field-4',
|
||||||
|
errorId: 'error-field-4',
|
||||||
|
label: 'username',
|
||||||
|
inputRequired: '',
|
||||||
|
defaultValue: '',
|
||||||
|
placeholder: undefined,
|
||||||
|
hiddenName: 'username',
|
||||||
|
hiddenValue: 'powerpuffgirl125',
|
||||||
|
isHidden: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'field-5',
|
||||||
|
errorId: 'error-field-5',
|
||||||
|
label: 'villain',
|
||||||
|
inputRequired: '',
|
||||||
|
defaultValue: 'princess morbucks',
|
||||||
|
placeholder: undefined,
|
||||||
|
hiddenName: 'villain',
|
||||||
|
isHidden: true,
|
||||||
|
hiddenValue: 'princess morbucks',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
useResponseData: true,
|
useResponseData: true,
|
||||||
appendAttribution: true,
|
appendAttribution: true,
|
||||||
|
|
|
@ -167,6 +167,11 @@ export function prepareFormData({
|
||||||
} else if (fieldType === 'html') {
|
} else if (fieldType === 'html') {
|
||||||
input.isHtml = true;
|
input.isHtml = true;
|
||||||
input.html = field.html as string;
|
input.html = field.html as string;
|
||||||
|
} else if (fieldType === 'hiddenField') {
|
||||||
|
input.isHidden = true;
|
||||||
|
input.hiddenName = field.fieldName as string;
|
||||||
|
input.hiddenValue =
|
||||||
|
input.defaultValue === '' ? (field.fieldValue as string) : input.defaultValue;
|
||||||
} else {
|
} else {
|
||||||
input.isInput = true;
|
input.isInput = true;
|
||||||
input.type = fieldType as 'text' | 'number' | 'date' | 'email';
|
input.type = fieldType as 'text' | 'number' | 'date' | 'email';
|
||||||
|
@ -432,6 +437,9 @@ export async function formWebhook(
|
||||||
if (field.fieldType === 'html') {
|
if (field.fieldType === 'html') {
|
||||||
field.html = sanitizeHtml(field.html as string);
|
field.html = sanitizeHtml(field.html as string);
|
||||||
}
|
}
|
||||||
|
if (field.fieldType === 'hiddenField') {
|
||||||
|
field.fieldLabel = field.fieldName as string;
|
||||||
|
}
|
||||||
return field;
|
return field;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -2693,6 +2693,8 @@ export type FormFieldsParameter = Array<{
|
||||||
formatDate?: string;
|
formatDate?: string;
|
||||||
html?: string;
|
html?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
fieldName?: string;
|
||||||
|
fieldValue?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type FieldTypeMap = {
|
export type FieldTypeMap = {
|
||||||
|
|
Loading…
Reference in a new issue