2023-10-16 21:09:30 -07:00
import type {
IDataObject ,
INodeType ,
INodeTypeDescription ,
IWebhookResponseData ,
IWebhookFunctions ,
} from 'n8n-workflow' ;
import { FORM_TRIGGER_PATH_IDENTIFIER , jsonParse } from 'n8n-workflow' ;
import type { FormField } from './interfaces' ;
import { prepareFormData } from './utils' ;
export class FormTrigger implements INodeType {
description : INodeTypeDescription = {
displayName : 'n8n Form Trigger' ,
name : 'formTrigger' ,
icon : 'file:form.svg' ,
group : [ 'trigger' ] ,
version : 1 ,
description : 'Runs the flow when an n8n generated webform is submitted' ,
defaults : {
name : 'n8n Form Trigger' ,
} ,
inputs : [ ] ,
outputs : [ 'main' ] ,
webhooks : [
{
name : 'setup' ,
httpMethod : 'GET' ,
responseMode : 'onReceived' ,
path : FORM_TRIGGER_PATH_IDENTIFIER ,
ndvHideUrl : true ,
} ,
{
name : 'default' ,
httpMethod : 'POST' ,
responseMode : '={{$parameter["responseMode"]}}' ,
path : FORM_TRIGGER_PATH_IDENTIFIER ,
ndvHideMethod : true ,
} ,
] ,
eventTriggerDescription : 'Waiting for you to submit the form' ,
activationMessage : 'You can now make calls to your production Form URL.' ,
triggerPanel : {
header : 'Pull in a test form submission' ,
executionsHelp : {
inactive :
2023-10-20 01:49:58 -07:00
"Form Trigger has two modes: test and production. <br /> <br /> <b>Use test mode while you build your workflow</b>. Click the 'Test Step' button, then fill out the test form that opens in a popup tab. The executions will show up in the editor.<br /> <br /> <b>Use production mode to run your workflow automatically</b>. <a data-key=\"activate\">Activate</a> the workflow, then make requests to the production URL. Then every time there's a form submission via the Production Form URL, the workflow will execute. These executions will show up in the executions list, but not in the editor." ,
2023-10-16 21:09:30 -07:00
active :
2023-10-20 01:49:58 -07:00
"Form Trigger has two modes: test and production. <br /> <br /> <b>Use test mode while you build your workflow</b>. Click the 'Test Step' button, then fill out the test form that opens in a popup tab. The executions will show up in the editor.<br /> <br /> <b>Use production mode to run your workflow automatically</b>. <a data-key=\"activate\">Activate</a> the workflow, then make requests to the production URL. Then every time there's a form submission via the Production Form URL, the workflow will execute. These executions will show up in the executions list, but not in the editor." ,
2023-10-16 21:09:30 -07:00
} ,
activationHint : {
active :
"This node will also trigger automatically on new form submissions (but those executions won't show up here)." ,
inactive :
'<a data-key="activate">Activate</a> this workflow to have it also run automatically for new form submissions created via the Production URL.' ,
} ,
} ,
properties : [
{
displayName : 'Form Title' ,
name : 'formTitle' ,
type : 'string' ,
default : '' ,
placeholder : 'e.g. Contact us' ,
required : true ,
description : 'Shown at the top of the form' ,
} ,
{
displayName : 'Form Description' ,
name : 'formDescription' ,
type : 'string' ,
default : '' ,
placeholder : "e.g. We'll get back to you soon" ,
description :
'Shown underneath the Form Title. Can be used to prompt the user on how to complete the form.' ,
} ,
{
displayName : 'Form Fields' ,
name : 'formFields' ,
placeholder : 'Add Form Field' ,
type : 'fixedCollection' ,
default : { values : [ { label : '' , fieldType : 'text' } ] } ,
typeOptions : {
multipleValues : true ,
sortable : true ,
} ,
options : [
{
displayName : 'Values' ,
name : 'values' ,
values : [
{
displayName : 'Field Label' ,
name : 'fieldLabel' ,
type : 'string' ,
default : '' ,
placeholder : 'e.g. What is your name?' ,
description : 'Label appears above the input field' ,
required : true ,
} ,
{
displayName : 'Field Type' ,
name : 'fieldType' ,
type : 'options' ,
default : 'text' ,
description : 'The type of field to add to the form' ,
options : [
{
name : 'Text' ,
value : 'text' ,
} ,
{
name : 'Number' ,
value : 'number' ,
} ,
{
name : 'Date' ,
value : 'date' ,
} ,
{
name : 'Dropdown List' ,
value : 'dropdown' ,
} ,
] ,
required : true ,
} ,
{
displayName : 'Field Options' ,
name : 'fieldOptions' ,
placeholder : 'Add Field Option' ,
description : 'List of options that can be selected from the dropdown' ,
type : 'fixedCollection' ,
default : { values : [ { option : '' } ] } ,
required : true ,
displayOptions : {
show : {
fieldType : [ 'dropdown' ] ,
} ,
} ,
typeOptions : {
multipleValues : true ,
sortable : true ,
} ,
options : [
{
displayName : 'Values' ,
name : 'values' ,
values : [
{
displayName : 'Option' ,
name : 'option' ,
type : 'string' ,
default : '' ,
} ,
] ,
} ,
] ,
} ,
{
displayName : 'Multiple Choice' ,
name : 'multiselect' ,
type : 'boolean' ,
default : false ,
description :
'Whether to allow the user to select multiple options from the dropdown list' ,
displayOptions : {
show : {
fieldType : [ 'dropdown' ] ,
} ,
} ,
} ,
{
displayName : 'Required Field' ,
name : 'requiredField' ,
type : 'boolean' ,
default : false ,
description :
'Whether to require the user to enter a value for this field before submitting the form' ,
} ,
] ,
} ,
] ,
} ,
{
displayName : 'Respond When' ,
name : 'responseMode' ,
type : 'options' ,
options : [
{
name : 'Form Is Submitted' ,
value : 'onReceived' ,
description : 'As soon as this node receives the form submission' ,
} ,
{
name : 'Workflow Finishes' ,
value : 'lastNode' ,
description : 'When the last node of the workflow is executed' ,
} ,
] ,
default : 'onReceived' ,
description : 'When to respond to the form submission' ,
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
options : [
{
displayName : 'Form Submitted Text' ,
name : 'formSubmittedText' ,
description : 'The text displayed to users after they filled the form' ,
type : 'string' ,
default : 'Your response has been recorded' ,
} ,
] ,
} ,
] ,
} ;
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
const webhookName = this . getWebhookName ( ) ;
const mode = this . getMode ( ) === 'manual' ? 'test' : 'production' ;
const formFields = this . getNodeParameter ( 'formFields.values' , [ ] ) as FormField [ ] ;
//Show the form on GET request
if ( webhookName === 'setup' ) {
const formTitle = this . getNodeParameter ( 'formTitle' , '' ) as string ;
const formDescription = this . getNodeParameter ( 'formDescription' , '' ) as string ;
const instanceId = await this . getInstanceId ( ) ;
const { formSubmittedText } = this . getNodeParameter ( 'options' , { } ) as IDataObject ;
const data = prepareFormData (
formTitle ,
formDescription ,
formSubmittedText as string ,
formFields ,
mode === 'test' ,
instanceId ,
) ;
const res = this . getResponseObject ( ) ;
res . render ( 'form-trigger' , data ) ;
return {
noWebhookResponse : true ,
} ;
}
const bodyData = ( this . getBodyData ( ) . data as IDataObject ) ? ? { } ;
const returnData : IDataObject = { } ;
for ( const [ index , field ] of formFields . entries ( ) ) {
const key = ` field- ${ index } ` ;
let value = bodyData [ key ] ? ? null ;
if ( value === null ) returnData [ field . fieldLabel ] = null ;
if ( field . fieldType === 'number' ) {
value = Number ( value ) ;
}
if ( field . fieldType === 'text' ) {
value = String ( value ) . trim ( ) ;
}
if ( field . multiselect && typeof value === 'string' ) {
value = jsonParse ( value ) ;
}
returnData [ field . fieldLabel ] = value ;
}
returnData . submittedAt = new Date ( ) . toISOString ( ) ;
returnData . formMode = mode ;
const webhookResponse : IDataObject = { status : 200 } ;
return {
webhookResponse ,
workflowData : [ this . helpers . returnJsonArray ( returnData ) ] ,
} ;
}
}