How to use Vue, Bootstrap and reCAPTCHA to build forms for Salesforce Marketing Cloud

How to use Vue, Bootstrap and reCAPTCHA to build forms for Salesforce Marketing Cloud

This articles explains how to create forms in Salesforce Marketing Cloud using the most popular frameworks and technologies, such as Vue, Bootstrap, Axios and Google reCAPTCHA.

Latest and greatest

Being a front-end developer is hard. But it gets a lot easier once we learn how to use the latest frameworks and technologies.

The issue is: there are a lot of them! Choosing the right framework is often a question of preference, popularity and how steep the learning curve is.

Marketing Cloud on the other hand is easy. It doesn’t matter which frameworks we choose, but considering that most of the Cloud pages are mainly focused on gathering data through a single form, we should keep it as simple as possible.

Before we start

Before diving in, let’s have a closer look at the frameworks and technologies we are going to use.

Bootstrap

Bootstrap is considered to be one of the most popular and complete HTML, CSS and JavaScript frameworks.

We are going to use it to give a visual style to our form and manage the validation messages.

Vue

Vue.js is an extremely popular JavaScript framework for building one-page user interfaces. It’s very easy to learn, manage and use, especially when it comes to small and simple applications.

We are going to use it for gathering and validating the data from the form.

Axios

Axios is a very practical JavaScript library for sending data asynchronously.

We are going to use it to send the data from one Marketing Cloud page to another without leaving the form page.

Google reCAPTCHA

Google reCAPTCHA is a must when it comes to securing the forms from abusive submissions and bot attacks.

We are going to use the invisible variant of reCAPTCHA to add a layer of security to our forms, without disrupting the user experience.

Prerequisites

Please create the following in Marketing Cloud:

  • Cloud page to store the Form.
  • Cloud page to store the Form Handler
  • Ressource page or Content block to store the Vue JavaScript file.
  • Data Extension to store the data with EmailAddress as one required field.

How it works

Here is how the flow and the application work:

  • Customer fills out the form and clicks on the submit button.
  • Application checks the validity of the form.
  • If there are errors, they are displayed.
  • If there are no errors, reCAPTCHA is triggered.
  • If reCAPTCHA detects suspicious behaviour, it will prompt the customer with a picture quizz.
  • If reCAPTCHA passes, the form is hidden and the loading spinner is displayed.
  • Form sends data to the Form Handler with a request.
  • If the Form Handler request succeeds, the loading spinner is hidden and a success message is displayed.
  • If the Form Handler request fails, the loading spinner is hidden and an error message is displayed.

Creating the Form

Now that everything is cleared out, let’s create the Form and the Vue application script.

Form page

As we can see in the code below, integrating the different libraries is as easy as copy-pasting a link.

But when it comes to Vue, you’ll notice some unusual tags and attributes in your HTML code.

Here are the new elements we can find in the HTML code of the form:

  • <template> tag is used to store a condition such as v-if and will not be visible once the Vue application renders the HTML.
  • v-if attribute allows to create conditions directly in the HTML based on the data variables of the Vue application.
  • v-bind attribute creates a 2-way binding between a data variable in the Vue application and an input field of the form.
  • ref attribute corresponds to getElementById() function in Vue, but shorter.
  • @click.prevent attribute corresponds to addEventListener(“click”) function with event.preventDefault() in JavaScript and references a function that will be executed in the Vue application.

Please analyse the code below but note the following:

1. The different parts of the form are hidden or shown based on the status variable in the Vue application:

  • status = 100: Display form.
  • status = 101: Hide form, display loading spinner.
  • status = 200: Hide form, display success message.
  • status = anything else: Hide form, display error message.

2. The form validation messages are displayed based on the class was-validated. This class is placed on the form tag by Vue when the customer submits the form.

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://www.google.com/recaptcha/api.js?dl=en"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link 
        rel="stylesheet" 
        href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" 
        integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" 
        crossorigin="anonymous"
    >
</head>
<body>
<div id="app" class="container">
    <div class="mx-auto my-4">
        <template v-if="status == 100">
            <form class="needs-validation" 
				  ref="form" 
				  action="{{ FORM HANDLER URL }}" 
				  method="post"
			>
                <div class="form-group">
                    <label>Email: </label>
                    <input 
                        class="form-control"
                        v-model="form.EmailAddress"
                        type="email" 
                        required
                    >
                    <div class="invalid-feedback">
                        Please enter a valid email address.
                    </div>
                    <div class="valid-feedback">
                        Looks good!
                    </div>
                </div>
                <div ref="recaptcha"></div>
                <button 
						@click.prevent="validateForm" 
						class="btn btn-primary"
				>Submit</button>
            </form>
        </template>
        <template v-if="status == 101">
            <div class="text-center">
                <div class="spinner-border" role="status">
                    <span class="sr-only">Loading...</span>
                </div>
                </div>
        </template>
        <template v-if="status == 200">
            <div class="text-center">
                <h2>Thank you for submitting this form.</h2>
            </div>
        </template>
        <template v-if="status > 101 && status != 200">
            <div class="text-center text-danger">
                <h2>Huston, we have a problem.</h2>
            </div>
        </template>
    </div>
</div>
<script src="{{ YOUR JS PATH }}/app.js"></script>
</body>
</html>

Vue application

Now, let’s have a look at the JavaScript application attached to the Form page.

Vue application is just a JavaScript object with 4 essential keys:

  • el contains the selector of the application (id or class in the HTML).
  • data contains the variables, binded or not to the fields of the form.
  • mounted is a lifecycle function that corresponds to the moment when the application is ready and the HTML page is fully loaded.
  • methods contains all the functions that the Vue application will use.

Please refer to the comments within the code to figure out how it works.

new Vue({
    el: '#app',
    data: {
        status: 100,
        form: {
            'EmailAddress': '',
            'g-recaptcha-response': ''
        },
        endpoint: ''
    },
    mounted: function() {
		// reCAPTCHA needs a second to load
        setTimeout(this.grecaptchaRender, 1000);
		// Get the URL of the Form Handler
        this.endpoint = this.$refs.form.getAttribute('action');
    },
    methods: {

        sendFormData: function() {

			// Status 101 will display the loading spinner and hide the form
            this.status = 101;
			
            var $this = this;

			// Send form data to the Form Handler with AJAX
            axios({
                method: 'POST',
                url: this.endpoint,
                data: this.form,
                validateStatus: function() { return true }
            }).then(function(result) {
                $this.status = result.status;
                if(result.data.Status != 'OK') {
					// Status 500 will hide the form and display an error message
                    $this.status = 500;
                    console.error(result.data.Message);
                }
            }).catch(function(error) {
                console.error(result);
            });
        
        },

        validateForm: function() {
			// Execute reCAPTCHA if the form is valid
            if (this.$refs.form.checkValidity() !== false) this.grecaptchaExecute();
            // Show form validation messages
			this.$refs.form.classList.add('was-validated');
        },

        grecaptchaRender: function() {
			// Display reCAPTCHA badge
            grecaptcha.render(this.$refs.recaptcha, {
                'sitekey'  : '{{ YOUR SITE KEY }}',
                'callback' : this.grecaptchaCallback,
                'size': 'invisible'
            });
        },

        grecaptchaExecute: function() {
			// Trigger reCAPTCHA validation
            grecaptcha.execute();
        },

        grecaptchaCallback: function() {
			
			// reCAPTCHA sends a request for a token
            var $this = this;
            return new Promise(function (resolve, reject) {
                if (grecaptcha.getResponse() !== "") {
					// If reCAPTCHA token is received, store it
                    $this.form['g-recaptcha-response'] = grecaptcha.getResponse();
                    // Send form data to the Form Handler
					$this.sendFormData();
                }
                grecaptcha.reset();
            });

        }
    }
})

It is important to notice that the reCAPTCHA token is sent to the Form Handler for server-side validation.

Creating the Form Handler

Please have a look at the Form Handler code, stored on another Cloud page where we are sending the data from the form.

This is just a classical Form Handler with origin verification, reCAPTCHA validation, Data Extension record insert and error handling.

<script runat='server'>

    Platform.Load('core', '1.1.1');

    var proxy = new Script.Util.WSProxy();

    try {

        var data = Platform.Request.GetPostData();

        var parsed = Platform.Function.ParseJSON(data);
		
        /////////////////////////////////////////////////////
        ////// VERIFY ORIGIN
        /////////////////////////////////////////////////////
    
        var referer = Platform.Request.ReferrerURL;
        var regex = /^(https:\/\/(.*\.)?(( {{ YOUR DOMAIN }} )\. {{ YOUR DOMAIN EXTENSION }} ))($|\/)/g;
        /* For example: /^(https:\/\/(.*\.)?((google\.com))($|\/)/g; */
		var match = referer.match(regex);
        var origin = (match.length > 0) ? match[1] : null;
    
        if (origin != null) {
    
            HTTPHeader.SetValue('Access-Control-Allow-Methods', 'POST');
            HTTPHeader.SetValue('Access-Control-Allow-Origin', origin);
    
            Platform.Response.SetResponseHeader('Strict-Transport-Security', 'max-age=200');
            Platform.Response.SetResponseHeader('X-XSS-Protection', '1; mode=block');
            Platform.Response.SetResponseHeader('X-Frame-Options', 'Deny');
            Platform.Response.SetResponseHeader('X-Content-Type-Options', 'nosniff');
            Platform.Response.SetResponseHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
            Platform.Response.SetResponseHeader('Content-Security-Policy', "default-src 'self'");
    
        } else {
            throw 'Wrong origin';
        }		

        /////////////////////////////////////////////////////
        ////// VERIFY RECAPTCHA
        /////////////////////////////////////////////////////

        var g_recaptcha_response = parsed['g-recaptcha-response']; 
        var secret = '{{ YOUR SECRET KEY }}';
        var payload = 'secret=' + secret + '&response=' + g_recaptcha_response;
		var contentType = 'application/x-www-form-urlencoded';
		var endpoint = 'https://www.google.com/recaptcha/api/siteverify';
		
        var req = HTTP.Post(endpoint, contentType, payload);

        if (req.StatusCode == 200) {

            var resp = Platform.Function.ParseJSON(String(req.Response));

            if (!resp.success) throw 'Wrong reCAPTCHA';

        } else {
            throw 'reCAPTCHA API error';
        }

        /////////////////////////////////////////////////////
        ////// INSERT RECORD
        /////////////////////////////////////////////////////

        var req = proxy.createItem('DataExtensionObject', {
            Name: '{{ YOUR DATA EXTENSION NAME }}',
            Properties: formatProps(parsed)
        });

        var res = req.Results[0],
            message = '';

        if(req.Status != 'OK') {
            message = res.ErrorMessage;
        } else {
            message = res.StatusMessage;
        }

        /////////////////////////////////////////////////////
        ////// CALLBACK
        /////////////////////////////////////////////////////

        Write(Stringify({ Status: req.Status, Message: message }));

    } catch(err) {

        Write(Stringify({ Status: 'Error', Message: err }));

    }

    function formatProps(data) {
        var arr = [];
        for (k in data) {
            arr.push({
                Name: k,
                Value: data[k]
            });
        }
        return arr;
    }

</script>

Try it yourself!

Remember to replace the following parts of the code by your personal information:

  • {{ FORM HANDLER URL }}
  • {{ YOUR JS PATH }}
  • {{ YOUR SITE KEY }}
  • {{ YOUR SECRET KEY }}
  • {{ YOUR DOMAIN }}
  • {{ YOUR DOMAIN EXTENSION }}
  • {{ YOUR DATA EXTENSION NAME }}

If you wish to discover how Vue.js works behind the scenes and get a better understanding, please install Vue DevTools Chrome extension.

Conclusion

As you can see, it’s not complicated to implement a Vue application in Salesforce Marketing Cloud.

The real power of Vue comes into play when you realize how easy it is to manage and scale.

Please let me know if you are interested in more complex Vue applications examples or articles about other web frameworks and technologies.

Have I missed anything?

Please poke me with a sharp comment below or use the contact form.

  1. Hello,

    shouldn’t the form handler be using “Platform.Response.Write” instead of “Write” in the callback?

  2. Hello

    I’m trying to have a go with this, but i cant figure out where to host the javascript file. Is it possible to have it live in the script tags instead of linking externally?

  3. You can put your JS in a script tag or you can create a Code Ressource page 😉

  4. Cool. I got another issue with the form handler i hope you can push me in the right direction.

    My vue form is hosted on http://cloud.email.domain.domainextension/form.

    in the formhandler you have the regex: /^(https:\/\/(.*\.)?((mydomain\.mydomainextension))($|\/)/g;

    to check Platform.Request.ReferrerURL

    This part of it is giving me an error: {“Status”:”Error”,”Message”:{“message”:”parsing \”^(https:\\/\\/(.*\\.)?((((mydomain\.mydomainextension))))(\\z|\\/)\” – Not enough )’s.”,”description”:”System.ArgumentException: parsing \”^(https:\\/\\/(.*\\.)?((((mydomain\.mydomainextension))}} ))(\\z|\\/)\” – Not enough )’s. – from System\r\n\r\n”}}

    any idea how i can solve this?

  5. I made a mistake in the article, there is a “)” missing. I fixed it. In your case it should be ^(https:\/\/(.*\.)?((mydomain)\.mydomainextension))($|\/)/g

  6. Hi Ivan, have you found a way to use vue components like vue-phone-number-input in cloud pages? These templates vue app components needs a folder / file structure and getting them to work in a single page web app seems difficult.

  7. Yes of course! You build you app locally, compile it into html and js files and then paste them in Marketing Cloud, just don’t forget to update the paths accordingly. It sure is more complicated but you can have a fully interactive app.

Comments are closed.

Up Next:

How to implement Google reCAPTCHA on Salesforce Marketing Cloud pages with forms

How to implement Google reCAPTCHA on Salesforce Marketing Cloud pages with forms