Skip to content

Commit

Permalink
feat(n8n Form Trigger Node): Improvements (#10092)
Browse files Browse the repository at this point in the history
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <[email protected]>
Co-authored-by: Shireen Missi <[email protected]>
  • Loading branch information
3 people authored Jul 29, 2024
1 parent 7a30d84 commit 711b667
Show file tree
Hide file tree
Showing 12 changed files with 1,014 additions and 146 deletions.
231 changes: 216 additions & 15 deletions packages/cli/templates/form-trigger.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
margin-bottom: 16px;
}
.n8n-link {
padding-bottom: 24px;
}
.n8n-link a {
color: #7e8186;
font-weight: 600;
Expand Down Expand Up @@ -103,11 +106,12 @@
border-radius: 6px;
width: 100%;
font-size: 14px;
color: #909399;
color: #71747A;
font-weight: 400;
padding: 12px;
}
form textarea:focus,
form input:focus {
outline: none;
border-color: rgb(90, 76, 194);
Expand All @@ -128,7 +132,7 @@
border-radius: 6px;
width: 100%;
font-size: 14px;
color: #909399;
color: #71747A;
font-weight: 400;
background-color: white;
padding: 12px;
Expand All @@ -141,6 +145,10 @@
sans-serif;
}
::placeholder {
opacity: 0.5;
}
#submit-btn {
width: 100%;
height: 48px;
Expand Down Expand Up @@ -225,9 +233,77 @@
height: 18px;
cursor: pointer;
}
/* required field ----------------------------- */
.form-required {
}
label.form-required::after {
content: ' *';
color: #ff6d5a;
}
hr {
border: 0;
height: 1px;
border-top: 1px solid #dbdfe7;
margin-top: 24px;
margin-bottom: 24px;
display: none;
}
.file-input-wrapper {
position: relative;
display: inline-block;
width: 100%;
}
input[type="file"] {
}
.clear-button {
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-65%);
background-color: #7e8186;
border: none;
border-radius: 50%;
font-size: 14px;
font-weight: 600;
font-family:
Open Sans,
sans-serif;
color: white;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
cursor: pointer;
display: none;
}
input[type="file"]:not(:empty) + .clear-button {
display: inline-block;
}
@media only screen and (max-width: 400px) {
hr {
display: block;
}
.container {
width: 95%;
min-height: 100vh;
padding: 24px;
background-color: white;
border: 1px solid #dbdfe7;
border-radius: 8px;
box-shadow: 0px 4px 16px 0px #634dff0f;
}
.card {
padding: 0px;
background-color: white;
border: 0px solid #dbdfe7;
border-radius: 0px;
box-shadow: 0px 0px 10px 0px #634dff0f;
margin-bottom: 0px;
}
}
</style>
</head>

Expand All @@ -238,20 +314,23 @@
<div class='test-notice'>
<p>This is test version of your form. Use it only for testing your Form Trigger.</p>
</div>
<hr>
{{/if}}



{{#if validForm}}
<form class='card' action='#' method='POST' name='n8n-form' id='n8n-form' novalidate>
<div class='form-header'>
<h1>{{formTitle}}</h1>
<p>{{formDescription}} </p>
<p style="white-space: pre-line">{{formDescription}} </p>
</div>

<div class='inputs-wrapper'>
{{#each formFields}}
{{#if isMultiSelect}}
<div>
<label class='form-label'>{{label}}</label>
<label class='form-label {{inputRequired}}'>{{label}}</label>
<div class='multiselect {{inputRequired}}' id='{{id}}'>
{{#each multiSelectOptions}}
<div class='multiselect-option'>
Expand All @@ -268,7 +347,7 @@

{{#if isSelect}}
<div class='form-group'>
<label class='form-label' for='{{id}}'>{{label}}</label>
<label class='form-label {{inputRequired}}' for='{{id}}'>{{label}}</label>
<div class='select-input'>
<select id='{{id}}' name='{{id}}' class='{{inputRequired}}'>
<option value='' disabled selected>Select an option ...</option>
Expand All @@ -285,12 +364,32 @@

{{#if isTextarea}}
<div class='form-group'>
<label class='form-label' for='{{id}}'>{{label}}</label>
<label class='form-label {{inputRequired}}' for='{{id}}'>{{label}}</label>
<textarea
class='form-input {{inputRequired}}'
id='{{id}}'
name='{{id}}'
></textarea>
placeholder="{{placeholder}}"
>{{defaultValue}}</textarea>
<p class='{{errorId}} error-hidden'>
This field is required
</p>
</div>
{{/if}}

{{#if isFileInput}}
<div class='form-group file-input-wrapper'>
<label class='form-label {{inputRequired}}' for='{{id}}'>{{label}}</label>
<input
class='form-input {{inputRequired}}'
type='file'
id='{{id}}'
name='{{id}}'
accept='{{acceptFileTypes}}'
{{multipleFiles}}
placeholder="{{placeholder}}"
/>
<button class="clear-button">&times;</button>
<p class='{{errorId}} error-hidden'>
This field is required
</p>
Expand All @@ -299,12 +398,14 @@

{{#if isInput}}
<div class='form-group'>
<label class='form-label' for='{{id}}'>{{label}}</label>
<label class='form-label {{inputRequired}}' for='{{id}}'>{{label}}</label>
<input
class='form-input {{inputRequired}}'
type='{{type}}'
id='{{id}}'
name='{{id}}'
value="{{defaultValue}}"
placeholder="{{placeholder}}"
/>
<p class='{{errorId}} error-hidden'>
This field is required
Expand Down Expand Up @@ -355,9 +456,13 @@
</div>

{{#if appendAttribution}}
<hr>
<div class='n8n-link'>
<a href={{n8nWebsiteLink}} target='_blank'>
Form automated with
{{#if customAttribution}}
{{{customAttribution}}}
{{else}}
<svg
width='73'
height='20'
Expand All @@ -384,10 +489,13 @@
fill='#101330'
/>
</svg>
{{/if}}
</a>
</div>
{{/if}}



{{#if redirectUrl}}
<a id='redirectUrl' href='{{redirectUrl}}' style='display: none;'></a>
{{/if}}
Expand All @@ -396,21 +504,40 @@
</div>
<script>
function validateInput(input, errorElement) {
if (input.type === 'number' && input.value !== '') {
const value = input.value.trim();
const value = input.value.trim();
const type = input.type;
if (value === '' || isNaN(value)) {
if (type === 'email' && value !== '') {
return validateEmailInput(value, errorElement);
} else if (type === 'number' && value !== '') {
if (isNaN(value)) {
errorElement.textContent = 'Enter only numbers in this field';
errorElement.classList.add('error-show');
return false;
} else {
errorElement.classList.remove('error-show');
return true;
}
} else if (input.value === '') {
} else if (value === '') {
errorElement.textContent = 'This field is required';
errorElement.classList.add('error-show');
return false;
} else {
errorElement.classList.remove('error-show');
return true;
}
}
function validateEmailInput(value, errorElement) {
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const isValidEmail = regex.test(value);
if (!isValidEmail) {
errorElement.textContent = 'Enter a valid email address in this field';
errorElement.classList.add('error-show');
return false;
} else {
errorElement.textContent = 'This field is required';
errorElement.classList.remove('error-show');
return true;
}
Expand Down Expand Up @@ -444,7 +571,44 @@
const form = document.querySelector('#n8n-form');
const requiredInputs = document.querySelectorAll('.form-required');
document.querySelectorAll("input[type=number]").forEach(function (element) {
element.addEventListener("wheel", function(event) {
if (document.activeElement === event.target) {
event.preventDefault();
}
});
});
document.querySelectorAll('input[type="file"]').forEach(fileInput => {
const clearButton = fileInput.nextElementSibling;
let previousFiles = [];
fileInput.addEventListener('change', () => {
const files = fileInput.files;
if (files.length > 0) {
previousFiles = Array.from(files);
clearButton.style.display = 'inline-block';
} else {
if (previousFiles.length > 0) {
const dataTransfer = new DataTransfer();
previousFiles.forEach(file => dataTransfer.items.add(file));
fileInput.files = dataTransfer.files;
clearButton.style.display = 'inline-block';
}
}
});
clearButton.addEventListener('click', (event) => {
event.preventDefault();
fileInput.value = '';
previousFiles = [];
clearButton.style.display = 'none';
});
});
const requiredInputs = document.querySelectorAll('.form-required:not(label)');
const emailInputs = document.querySelectorAll("input[type=email]");
requiredInputs.forEach((input) => {
const errorSelector = `.error-${input.id}`;
Expand All @@ -464,10 +628,34 @@
}
});
emailInputs.forEach(function (input) {
const errorSelector = `.error-${input.id}`;
const error = document.querySelector(errorSelector);
input.addEventListener("input", function(event) {
const value = input.value.trim();
if (value === "") {
error.classList.remove('error-show');
} else {
validateEmailInput(value, error);
}
});
});
form.addEventListener('submit', (e) => {
const valid = [];
e.preventDefault();
emailInputs.forEach(function (input) {
const value = input.value.trim();
if(value === '') {
return;
}
const errorSelector = `.error-${input.id}`;
const error = document.querySelector(errorSelector);
valid.push(validateEmailInput(value, error));
});
requiredInputs.forEach((input) => {
const errorSelector = `.error-${input.id}`;
const error = document.querySelector(errorSelector);
Expand All @@ -480,7 +668,20 @@
});
if (valid.every((v) => v)) {
var formData = new FormData(form);
var formData = new FormData();
for (const filed of form.elements) {
if(filed.type !== 'file') {
formData.append(filed.name, filed.value);
} else {
for (const file of filed.files) {
if(file.size === 0) {
continue;
}
formData.append(filed.name, file);
}
}
}
document.querySelectorAll('.multiselect').forEach((multiselect) => {
const selectedValues = getSelectedValues(multiselect);
Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/composables/useRunWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
testUrl = `${rootStore.formWaitingUrl}/${runWorkflowApiResponse.executionId}${suffix}`;
}

if (testUrl) openPopUpWindow(testUrl);
if (testUrl && options.source !== 'RunData.ManualChatMessage') openPopUpWindow(testUrl);
}
}

Expand Down
Loading

0 comments on commit 711b667

Please sign in to comment.