Search code examples
javascripthtmlgoogle-apps-scriptfile-uploadform-submit

I have a working html contact us form that works great, but it just wont send emails if there is no attachment. I'm stumped and need advice


I have a working Contact Us html form and a working google Apps Script that handles the emailing of the forms data. The form is shown below:

<!DOCTYPE html>
<html>
<head>

</head>
<style>
    * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
    }

    body {
        background-color: #F0F0F0;
    }

    .container {
        margin: 50px auto;
        width: 100%;
        max-width: 800px;
        border-radius: 20px;
        background-color: #5C7887;
        padding: 20px;
        text-align: center;
    }

    .row {
        display: flex;
        margin-bottom: 10px;
    }

        .row:nth-child(6) {
            width: 100%;
        }

        .row:last-child {
            display: block;
        }

    .col {
        flex-basis: 50%;
        padding: 5px;
    }

        .col:first-child {
            padding-right: 10px;
        }

        .col:last-child {
            padding-left: 10px;
        }

    label {
        display: block;
        font-weight: bold;
        color: #FFFFFF;
        margin-bottom: 5px;
    }

    input[type="text"], select, textarea {
        width: 100%;
        border-radius: 5px;
        padding: 5px;
        border: none;
        background-color: #FFFFFF;
        text-align: center;
    }

        textarea#description {
            text-align: left;
        }

    select {
        appearance: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 10"><path d="M0 0 L15 10 L30 0"></path></svg>');
        background-position: calc(100% - 10px) center;
        background-repeat: no-repeat;
        background-size: 10px 10px;
        padding-right: 20px;
        text-align: center;
    }

    .file-upload {
        position: relative;
        overflow: hidden;
        display: inline-block;
        background-color: white;
        color: #5C7887;
        border-radius: 5px;
        padding: 10px;
        cursor: pointer;
        font-weight: bold;
        border: none;
        font-family: sans-serif;
    }

        .file-upload input[type=file] {
            position: absolute;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 100%;
            opacity: 0;
            cursor: pointer;
        }

    .file-counter {
        margin-left: 0px;
        color: white;
    }

    button[type=submit], .file-upload {
        display: block;
        margin: auto;
        padding: 3px;
        border-radius: 5px;
        background-color: white;
        color: #5C7887;
        font-weight: bold;
        border: none;
        cursor: pointer;
    }

    button[type="submit"] {
        background-color: #fff;
        color: #5C7887;
        border: none;
        border-radius: 5px;
        padding: 10px 20px;
        font-size: 16px;
        font-weight: bold;
        cursor: pointer;
        transition: all 0.3s ease;
    }

        button[type="submit"]:hover, .file-upload:hover {
            background-color: #67C2F3;
            color: #fff;
        }

    @media screen and (max-width: 600px) {
        .row {
            flex-direction: column;
        }

        .col {
            flex-basis: 100%;
            padding: 5px 0;
        }

            .col:first-child {
                padding-right: 0;
            }

            .col:last-child {
                padding-left: 0;
            }

        .file-upload, button[type=submit] {
            width: 100%;
        }
    }
</style>
<body>
    <div class="container">
        <form action="https://script.google.com/macros/s/mycustomURL" method="post" enctype="multipart/form-data">
            <div class="row">
                <div class="col">
                    <label for="first-name">First Name:</label>
                    <input type="text" id="first-name" name="first-name">
                </div>
                <div class="col">
                    <label for="last-name">Last Name:</label>
                    <input type="text" id="last-name" name="last-name">
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <label for="phone">Phone Number:</label>
                    <input type="text" id="phone" name="phone" placeholder="(xxx) xxx-xxxx" maxlength="14">
                </div>
                <div class="col">
                    <label for="email">Email:</label>
                    <input type="text" id="email" name="email">
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <label for="street">Street:</label>
                    <input type="text" id="street" name="street">
                </div>
                <div class="col">
                    <label for="city">City:</label>
                    <input type="text" id="city" name="city">
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <label for="state">State:</label>
                    <select id="state" name="state">
                    </select>
                </div>
                <div class="col">
                    <label for="zip">Zip Code:</label>
                    <input type="text" id="zip" name="zip" oninput="this.value = this.value.replace(/[^0-9]/g, '').slice(0, 5)">
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <label for="Service-Needed">Service Needed:</label>
                    <select id="Service-Needed" name="Service-Needed" required>
                        <option value="" selected>Service Needed</option>
                        <option value="A">A Enclosures</option>
                        <option value="B">B</option>
                        <option value="C">C</option>
                        <option value="D">D</option>
                    </select>
                </div>
                <div class="col">
                    <label for="files">Files:</label>
                    <div class="file-upload">
                        Upload a File
                        <input type="file" id="files" name="files[]" multiple>
                    </div>
                    <span class="file-counter">0 files selected</span>
                </div>
            </div>
            <div class="row" style="text-align: center; flex-direction: column;">
                <label for="description">Description:</label>
                <textarea id="description" name="description" style="height: 200px;"></textarea>

            </div>
            <div class="row">
                <div class="col">
          <button type="submit" id="submit-btn">Submit</button>
                </div>
            </div>
        </form>
    <p id="message" style="display: none;"></p>
    </div>
    <script>
        // Get the input field and the counter element
        const input = document.getElementById('files');
        const counter = document.querySelector('.file-counter');

        // Add an event listener to the input field to update the counter when files are selected
        input.addEventListener('change', updateCounter);

        function updateCounter() {
            // Get the number of files selected
            const count = input.files.length;

            // Update the counter text
            if (count === 0) {
                counter.textContent = 'No files selected';
            } else if (count === 1) {
                counter.textContent = '1 file selected';
            } else {
                counter.textContent = count + ' files selected';
            }
        }
    </script>

    <script>
        // Get the phone input field
        const phoneInput = document.getElementById('phone');

        // Add event listener for input changes
        phoneInput.addEventListener('input', function (event) {
            // Remove all non-numeric characters from input value
            let phoneValue = event.target.value.replace(/\D/g, '');

            // Limit input value to 10 characters
            phoneValue = phoneValue.slice(0, 10);

            // Format input value to (xxx) xxx-xxxx
            if (phoneValue.length >= 3) {
                phoneValue = `(${phoneValue.slice(0, 3)}) ${phoneValue.slice(3)}`;
            }

            if (phoneValue.length >= 9) {
                phoneValue = `${phoneValue.slice(0, 9)}-${phoneValue.slice(9)}`;
            }

            // Set input value to formatted value
            event.target.value = phoneValue;
        });
    </script>
  
<script>
  async function getBase64(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result.split(',')[1]);
      reader.onerror = (error) => reject(error);
    });
  }

  const form = document.querySelector('form');
  const messageElement = document.getElementById('message');
  const submitButton = document.getElementById('submit-btn');

  form.addEventListener('submit', async (event) => {
    event.preventDefault();

    // Disable the submit button and change its text to "Sending".
    submitButton.disabled = true;
    submitButton.textContent = 'Sending...';

    const formData = new FormData(form);
    const files = formData.getAll('files[]');
    const filesData = [];

    for (const file of files) {
      const base64 = await getBase64(file);
      filesData.push({
        name: file.name,
        data: base64,
        mimeType: file.type,
      });
    }
    
    formData.set('files', JSON.stringify(filesData));

    // Show "Please wait" message while sending email
    messageElement.textContent = 'Please wait a moment.....';
    messageElement.style.display = 'block';
    messageElement.style.color = 'white';

    const response = await fetch('https://script.google.com/macros/s/mycustomURL', {
      method: 'POST',
      body: formData,
    });

    const result = await response.text();

    // Re-enable the submit button and change its text back to "Submit".
    submitButton.disabled = false;
    submitButton.textContent = 'Submit';

    if (response.ok) {
      messageElement.textContent = 'Thank you. Your email has been sent.';
      messageElement.style.display = 'block';
      messageElement.style.color = 'white';
    } else {
      messageElement.textContent = 'Hmmm. Something went wrong.';
      messageElement.style.display = 'block';
      messageElement.style.color = 'gold';
    }
  });
</script>

</body>
</html>

The Google Apps Script is shown below:

function doPost(e) {
  var firstName = e.parameter['first-name'];
  var lastName = e.parameter['last-name'];
  var phone = e.parameter['phone'];
  var email = e.parameter['email'];
  var street = e.parameter['street'];
  var city = e.parameter['city'];
  var state = e.parameter['state'];
  var zip = e.parameter['zip'];
  var serviceNeeded = e.parameter['Service-Needed'];
  var description = e.parameter['description'];
  var files = JSON.parse(e.parameter['files']);
  var attachments = [];

  for (var i = 0; i < files.length; i++) {
    attachments.push({
      fileName: files[i].name,
      mimeType: files[i].mimeType,
      content: Utilities.base64Decode(files[i].data)
    });
  }

  var body = "New message from " + firstName + " " + lastName + " via website:\n\n";
  body += "Name: " + firstName + " " + lastName + "\n";
  body += "Phone: " + phone + "\n";
  body += "Email: " + email + "\n";
  body += "Address: " + street + ", " + city + ", " + state + " " + zip + "\n";
  body += "Service Needed: " + serviceNeeded + "\n";
  body += "Description: " + description + "\n";

  try {
    // Add the attachments if files were uploaded
    var options = attachments.length > 0 ? {attachments: attachments} : {};

    GmailApp.sendEmail('recipient@test.com', 'New message from ' + firstName + ' ' + lastName + ' via website', body, options);
    return ContentService.createTextOutput("success");
  } catch (error) {
    Logger.log("Error sending email: " + error);
    return ContentService.createTextOutput("error");
  }
}

for the life of me, i can't figure out what is holding up a submission when there is no attachments involved. It works GREAT when attachments are sent, but i get nothing when there isnt attachments. The form even completes the a run and displays the text that the email has been sent, but its not true.

i have tried adding an IF function to my google app script to check if there is an attachment, but that did not work. I tried putting it on my last where it handles file uploads as well, but it did nothing. The only way it works is if the google apps script doesn't handle attachments, but that would be against my end goal. I need the form and script to send user data with or without attachments.

Google Apps Script If function:

  for (var i = 0; i < files.length; i++) {
    attachments.push({
      fileName: files[i].name,
      mimeType: files[i].mimeType,
      content: Utilities.base64Decode(files[i].data)
    });
  }

...

  try {
    // Add the attachments if files were uploaded
    var options = attachments.length > 0 ? {attachments: attachments} : {};

html contact us form if function:

  const formData = new FormData(form);
  const files = formData.getAll('files[]');
  const filesData = [];

  if (files.length > 0) {
    for (const file of files) {
      const base64 = await getBase64(file);
      filesData.push({
        name: file.name,
        data: base64,
        mimeType: file.type,
      });
    }
  
formData.delete('files[]');
    formData.set('files', JSON.stringify(filesData));
  }

Solution

  • When I saw your showing script, I thought that the reason for your current issue might be due to const files = formData.getAll('files[]');. Because, in this case, the length of files is 1 even when the file is not selected. In this case, the attachment file with no content is used. I thought that this might be the reason for your current issue.

    In order to remove this issue, how about the following modification?

    From:

    const files = formData.getAll('files[]');
    

    To:

    const files = formData.getAll('files[]').filter(e => e.size > 0);
    
    • By this modification, when the file is not selected in the input tag, the length of files is 0. So, I thought that the script can be worked without the attachment file.

    Note:

    • If your HTML is included in the Google Apps Script project, when the script is modified, please reflect the latest script to the Web Apps. Please be careful about this.

    Reference: