mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-30 05:42:00 -08:00
feat(n8n Form Node): Add Hidden Fields (#12803)
Some checks are pending
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) Waiting to run
Some checks are pending
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) Waiting to run
This commit is contained in:
parent
0f345681d9
commit
0da1114981
|
@ -377,6 +377,10 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if isHidden}}
|
||||
<input type="hidden" id="{{id}}" name="{{id}}" value="{{hiddenValue}}" />
|
||||
{{/if}}
|
||||
|
||||
{{#if isTextarea}}
|
||||
<div class='form-group'>
|
||||
<label class='form-label {{inputRequired}}' for='{{id}}'>{{label}}</label>
|
||||
|
|
|
@ -266,7 +266,15 @@ export class Form extends Node {
|
|||
});
|
||||
}
|
||||
} 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;
|
||||
|
|
|
@ -64,6 +64,11 @@ export const formFields: INodeProperties = {
|
|||
placeholder: 'e.g. What is your name?',
|
||||
description: 'Label that appears above the input field',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
hide: {
|
||||
fieldType: ['hiddenField'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Element Type',
|
||||
|
@ -92,6 +97,10 @@ export const formFields: INodeProperties = {
|
|||
name: 'File',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
name: 'Hidden Field',
|
||||
value: 'hiddenField',
|
||||
},
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'number',
|
||||
|
@ -119,7 +128,33 @@ export const formFields: INodeProperties = {
|
|||
default: '',
|
||||
displayOptions: {
|
||||
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',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
fieldType: ['html'],
|
||||
fieldType: ['html', 'hiddenField'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -106,7 +106,16 @@ describe('Form Node', () => {
|
|||
mockWebhookFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
if (paramName === 'operation') return 'page';
|
||||
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') {
|
||||
return {
|
||||
formTitle: 'Form Title',
|
||||
|
@ -121,7 +130,42 @@ describe('Form Node', () => {
|
|||
|
||||
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 () => {
|
||||
|
@ -182,6 +226,7 @@ describe('Form Node', () => {
|
|||
if (paramName === 'completionTitle') return 'Test Title';
|
||||
if (paramName === 'completionMessage') return 'Test Message';
|
||||
if (paramName === 'redirectUrl') return '';
|
||||
if (paramName === 'formFields.values') return [];
|
||||
return {};
|
||||
});
|
||||
mockWebhookFunctions.getParentNodes.mockReturnValue([
|
||||
|
@ -225,6 +270,8 @@ describe('Form Node', () => {
|
|||
if (paramName === 'completionTitle') return 'Test Title';
|
||||
if (paramName === 'completionMessage') return 'Test Message';
|
||||
if (paramName === 'redirectUrl') return 'https://n8n.io';
|
||||
if (paramName === 'formFields.values') return [];
|
||||
|
||||
return {};
|
||||
});
|
||||
mockWebhookFunctions.getParentNodes.mockReturnValue([
|
||||
|
|
|
@ -110,6 +110,12 @@ describe('FormTrigger, formWebhook', () => {
|
|||
html: '<div>Test HTML</div>',
|
||||
requiredField: false,
|
||||
},
|
||||
{
|
||||
fieldName: 'Powerpuff Girl',
|
||||
fieldValue: 'Blossom',
|
||||
fieldType: 'hiddenField',
|
||||
fieldLabel: '',
|
||||
},
|
||||
];
|
||||
|
||||
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
|
||||
|
@ -174,6 +180,17 @@ describe('FormTrigger, formWebhook', () => {
|
|||
html: '<div>Test HTML</div>',
|
||||
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',
|
||||
formTitle: 'Test Form',
|
||||
|
@ -300,9 +317,21 @@ describe('FormTrigger, prepareFormData', () => {
|
|||
acceptFileTypes: '.jpg,.png',
|
||||
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({
|
||||
formTitle: 'Test Form',
|
||||
|
@ -368,6 +397,28 @@ describe('FormTrigger, prepareFormData', () => {
|
|||
acceptFileTypes: '.jpg,.png',
|
||||
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,
|
||||
appendAttribution: true,
|
||||
|
|
|
@ -167,6 +167,11 @@ export function prepareFormData({
|
|||
} else if (fieldType === 'html') {
|
||||
input.isHtml = true;
|
||||
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 {
|
||||
input.isInput = true;
|
||||
input.type = fieldType as 'text' | 'number' | 'date' | 'email';
|
||||
|
@ -432,6 +437,9 @@ export async function formWebhook(
|
|||
if (field.fieldType === 'html') {
|
||||
field.html = sanitizeHtml(field.html as string);
|
||||
}
|
||||
if (field.fieldType === 'hiddenField') {
|
||||
field.fieldLabel = field.fieldName as string;
|
||||
}
|
||||
return field;
|
||||
},
|
||||
);
|
||||
|
|
|
@ -2693,6 +2693,8 @@ export type FormFieldsParameter = Array<{
|
|||
formatDate?: string;
|
||||
html?: string;
|
||||
placeholder?: string;
|
||||
fieldName?: string;
|
||||
fieldValue?: string;
|
||||
}>;
|
||||
|
||||
export type FieldTypeMap = {
|
||||
|
|
Loading…
Reference in a new issue