Passing variable from app.js to handlebars template

671 Views Asked by At

So I am creating a quote request contact form on a website and I need to make a confirmation or error message pop up under the form after it has been submitted. The issue I am faced with is how can I set a variable on the express side based on whether there was an error or not with the email sending and then use that variable within my handlebars template in order to display the proper message. I'm thinking I would use a helper to achieve this but I keep hitting a wall on attempting it. The logic should begin withing the transporter.sendMail as that is where the error will be determined. I put comments in to help identify. Here is the backend of my contact form:

    // ==== Contact Form ====
//Create Mailer options
const options = {
    viewEngine: {
        extname: '.hbs',
        layoutsDir: __dirname + '/views/email/',
        defaultLayout: 'template',
        partialsDir: 'views/partials/'
    },
    viewPath: 'views/email/',
    extName: '.hbs'
};
// Create Transporter
const transporter = nodemailer.createTransport({
    host: 'smtp-mail.outlook.com',
    port: 587,
    auth: {
        user: process.env.USER,
        pass: process.env.PASS
    }
});

// verify connection configuration
transporter.verify(function(error, success) {
    if (error) {
        console.log('Error with transporter verification:' + `\n${error}`);
    }
});

//attach the plugin to the nodemailer transporter
transporter.use('compile', hbs(options));

app.post('/send', (req, res) => {
    // Accepts the form data submitted and parse it
    let form = new multiparty.Form();
    let data = {};
    form.parse(req, function(err, fields) {
        Object.keys(fields).forEach(function(property) {
            data[property] = fields[property].toString();
        });
        // Create Mail object with options
        const mail = {
            from: `"********" <${process.env.USER}>`,
            to: '************', // receiver email,
            subject: 'Quote Request',
            template: 'email.body',
            // Import variables into email for use with handlebars
            context: {
                name: data.name,
                email: data.email,
                number: data.number,
                message: data.message
            }
        };
        // Send email
        transporter.sendMail(mail, (err, data) => {
            if (err) {
                console.log(err);
                // if error return mailError = true;
            }
            else {
                console.log('Email successfully sent to recipient!');
                // if sent return mailSent = true;
            }
        });
    });
});

Here is my script.js:

    // Contact Form Client Functions
//get the form by its id
const form = document.getElementById('contact-form');
//add event listener (when clicking the submit button, do the following)
const formEvent = form.addEventListener('submit', (event) => {
    // Prevent page from refreshing when submit button clicked
    event.preventDefault();

    //parse data to formData variable
    let mail = new FormData(form);
    //send mail
    sendMail(mail);
    // Determine if sendMail returned an error or not
    console.log(typeof mailError);
    // reset form feilds to empty
    form.reset();
});
const sendMail = (mail) => {
    console.log('step 1');
    fetch('/send', {
        method: 'post',
        body: mail
    }).then((response) => {
        return response.json();
    });
};

and here is the section within my template.hbs file that I need dynamically updated:

<div>
    {{#if mailSent}}
    <h4 style="color: lightgreen">Your message has been sent successfully!</h4>
    {{else if mailError}}
    <h4 style="color: red">ERROR: There was an issue sending your message, please
        try again.</h4>
    {{/if}}
</div>
1

There are 1 best solutions below

0
Mauro Aguilar On

I think you are mixing Server Side Rendering vs Client Side Rendering strategies (I suggest you to read this to understand the difference). Typically you'd want to use one or the other.

Server Side Rendering Approach: Here is a quick StackBlitz example I did based on your code using server side rendering that you can play with. The basic idea with this strategy is to let your express route render the response (using Handlebars):

app.post('/send-email', (req, res) => {
  // proceed to send email
  sendEmail((err, data) => {
    // render view based on response:
    res.render('form', {
      sent: !err,
      message: err?.message,
    });
  });
});

Notice how res.render is used in this case, we are not sending a JSON response but the direct view result instead, which would look something like this:

<form action="/send-email" method="POST">
  <h1>Send Email</h1>
  <p>Click send to get a random response!</p>
  <input type="email" placeholder="Enter your email" value="[email protected]" required />
  <input type="submit" value="Send" />
</form>

<div class="msg">
  {{#if sent}}
  <h4 style="color: lightgreen">Your message has been sent successfully!</h4>
  {{else if message}}
  <h4 style="color: red">
    ERROR: There was an issue sending your message, please try again.
    <br />
    Original server error: {{message}}
  </h4>
  {{/if}}
</div>

<script>
  document.querySelector('form').addEventListener('submit', () => {
    document.querySelector('.msg').style.display = 'none';
  });
</script>

Notice also how we don't use Javascript here to send the request, just the default behavior of <form> to make the request. This will cause the page to reload.

Client Side Rendering Approach: Here is the same example slightly modified to use AJAX and fetch API.

Now our endpoint must return a JSON response that the client can use to react accordingly:

app.post('/send-email', (req, res) => {
  sendEmail((err, data) => {
    res.status(!err ? 200 : 500).json({
      sent: !err,
      message: err?.message,
    });
  });
});

Then we let the client side Javascript handle the request and subsequent update of the DOM:

<form>
  <h1>Send Email</h1>
  <p>Click send to get a random response!</p>
  <input type="email" name="email" placeholder="Enter your email" value="[email protected]" required />
  <input type="submit" value="Send" />
</form>

<div class="msg">
  <h4 class="feedback"></h4>
</div>

<script>
  function sendMail(mail) {
    return fetch('/send-email', {
      method: 'post',
      body: mail,
    }).then(function (response) {
      return response.json();
    });
  }

  var msgContainer = document.querySelector('div.msg');
  msgContainer.style.display = 'none';
  document.querySelector('form').addEventListener('submit', function (e) {
    e.preventDefault();
    msgContainer.style.display = 'none';
    var mail = new FormData(e.target);
    sendMail(mail).then(function (res) {
      var message = res.sent
        ? 'Your message has been sent successfully!'
        : 'ERROR: There was an issue sending your message, please try again.';
      var feedback = document.querySelector('h4.feedback');
      feedback.textContent = message;
      feedback.style.color = res.sent ? 'lightgreen' : 'red';
      msgContainer.style.display = 'block';
    });
  });
</script>

This will NOT cause the page to reload.