More consistent form listeners in Google Tag Manager


Sick of the Google Tag Manager built-in form listener not working? This custom form listener can help.

The problem

There are many ways to build forms on the web. This means that tracking successful submissions can be challenging, particularly in situations where:

  1. The default submit event is prevented, so the form can be submitted through JavaScript.
  2. The form redirects to a thank you page quickly.
  3. The form dispatches submit events when invalid.
  4. The site search form is on the same page as the form we want to track.
  5. The website is a single page app.

Need a hand?

If you need help with something similar to this blog post, then get in touch through my contact page.

Get in touch

The solution

This tag adds an indempotent event listener on form submit buttons, and checks the values of the form’s fields before firing either an invalid form submit event, or valid submit event.

There are some caveats to this which I’ll cover in the closing comments.

To use the following code, create a new Custom HTML tag with the custom HTML tag code in Google Tag Manager. Fire the tag on All DOM Ready events. You should also fire this on any route changes for single page apps.

You can change your dataLayer name by updating the dataLayerName variable, and track forms using the ‘GET’ method by changing postFormsOnly to false.

Trigger

DOM Ready Trigger for the following custom HTML tag to track forms.

Code for the custom HTML tag

"use strict";
(function() {
    //CONFIG///
    var dataLayerName = "dataLayer";
    var postFormsOnly = true;
    ///////////
    var submitSelect = postFormsOnly ? "form[method='POST'] [type='submit']" : "form [type='submit']";
    var formSubmitElements = document.querySelectorAll(submitSelect);
    for (var i = 0; i < formSubmitElements.length; i++) {
        var ele = formSubmitElements[i];
        var listener;
        listener = function(ele) {
            return (function(event) {
                if (ele.form) {
                    ele = ele.form
                } else {
                    var j = 0;
                    while (j < 12) {
                        if (ele.tagName !== 'FORM') {
                            ele = ele["parentNode"];
                            if (j === 11) {
                                throw "Parent form not found.";
                            }
                            j++
                        } else break
                    }
                }
                var fields = ele.querySelectorAll("input, textarea, select");
                var formState = {
                    valid: true,
                    missingRequiredField: false,
                    missingValidEmail: false,
                };
                for (var k = 0; k < fields.length; k++) {
                    var field = fields[k]; //check required
                    if (field.required === true || field.required === "") {
                        if (field.value.length === 0) {
                            formState.valid = false;
                            formState.missingRequiredField = true;
                        }
                        if (field.type === "checkbox" && !field.checked) {
                            formState.valid = false;
                            formState.missingRequiredField = true
                        }
                    } //check email
                    if (field.type === "email" || field.name === "email") {
                        //update later if needed
                        var isEmail = field.value.search("@") !== -1 && field.value.search(/\./) !== -1 && field.value.length > 3 ? true : false;
                        if (!isEmail) {
                            formState.valid = false;
                            formState.missingValidEmail = true;
                        }
                    }
                }
                var dataLayerEvent = {
                    form: ele,
                    formId: ele.id,
                    formClass: ele.className,
                    formAction: ele.action,
                    formMethod: ele.method,
                    formState: formState,
                }
                if (formState.valid === false) {
                    dataLayerEvent['event'] = "invalidFormSubmit";
                } else dataLayerEvent['event'] = "validFormSubmit"
                window[dataLayerName].push(dataLayerEvent);
            })
        };
        if (ele.customSubmitClickListener !== undefined) {
            if (ele.addEventListener) {
                ele.removeEventListener("click", ele.customSubmitClickListener, false);
            } else if (ele.attachEvent) {
                ele.detachEvent("onclick", ele.customSubmitClickListener);
            }
        }
        // add reference to new form listener so the trigger function can use it and we can remove if needed
        ele.customSubmitClickListener = listener(ele);
        // add new form listener
        if (ele.addEventListener) {
            ele.addEventListener("click", ele.customSubmitClickListener, false);
        } else if (ele.attachEvent) {
            // old browsers
            ele.attachEvent("onclick", ele.customSubmitClickListener);
        }
    }
})();

The custom HTML tag

Form listener custom HTML tag with the DOM Ready trigger attached.

Demo

dataLayer event


//Latest dataLayer event will go here when you submit the form.
//If you inject GTM into this page to test it, this will stop populating.


Need a hand?

If you need help with something similar to this blog post, then get in touch through my contact page.

Get in touch

Closing comments

Email validation is hard

Making sure you don’t accidentally exclude valid emails is important. You’re better off tracking some false positives than excluding some valid submissions.

With this in mind, my code does a crude check for a valid email address. I’m sure you could probably improve on this, but be careful not to accidentally exclude any valid emails.

Forms are weird

I’m certain there are forms out there that won’t work with this code. If they don’t, try editing the code yourself to get it working. Otherwise, post a comment below and I’ll take a look. Good luck!

Comments


No comments yet!

Add a comment