1/* 2 Purpose: Send event data for form submissions through Tealium so that they can be used in Google Analytics and other tools. 3 Reference: 4 - https://docs.tealium.com/platforms/javascript/track/ 5 - https://developers.marketo.com/javascript-api/forms/api-reference/ 6 Area of use: 7 - All pages with forms. 8 How it works: 9 Set up a mapping between the Marketo form ID and a key in the formID2Key array. The key should be the tealium_event value. 10 In the eventNames object, set up a mapping between the tealium_event and an array that contains (in this order): event_category, event_action, event_detail. 11 The event_detail attribute can use {} to indicate the value should be grabbed dynamically. How the data is grabbed is defined in the detailGetters object. 12*/ 13(function () { 14 'use strict'; 15 16 // For some reason, if the same Marketo form exists more than once on a page, it will be submitted 17 // that number of times every time one of instances is submitted. This causes duplicate analytics data. 18 // In order to prevent this, we set a global variable to remember that this form was submitted and prevent it from being submitted again during the same page load. 19 window.marketoFormsSubmitted = new Set(); 20 21 // The MktoForms2 object is the top-level publicly visible namespace for Marketo Forms2 functionality 22 MktoForms2.whenReady(function (form) { 23 let dataForTealium = ''; 24 25 var formID2Key = { 26 // Map Marketo form IDs to eventNames (AKA Event Label in Google Analytics) 27 // Production 28 // This mapping should include every form in /admin/content/marketo-forms 29 // Grouped by event name 30 // Keep IDs in sync with e3_marketo_forms.js 31 "3196": "chilipiper_assignment", // FO - ChiliPiper Booking 32 "2987": "customer_inquiry", // FO - Customer Inquiry 33 "1508": "customer_referral", // FO - Customer Referral 34 "2765": "customer_referral", // FO - Customer Referral - French 35 "3590": "customer_referral", // FO - Customer Referral - Italian 36 "2766": "customer_referral", // FO - Customer Referral - Spanish 37 "2195": "email_preferences_update", // FO - Preference Center 38 "1065": "gated_content_download", // FO - Gated Learning Assets 39 "2473": "partner_referral", // FO - Business Partner Self Serve (AKA Partner Referral Program) 40 "1528": "research_interest", // FO - Participate in Research 41 "2535": "sales_inquiry", // FO - ClassPass Guarantee 42 "1560": "sales_inquiry", // FO - Get Started Multi-Step Demo Request 43 "3703": "sales_inquiry", // FO - Get Started Multi-Step Demo Request - French 44 "3709": "sales_inquiry", // FO - Get Started Multi-Step Demo Request - Italian 45 "3705": "sales_inquiry", // FO - Get Started Multi-Step Demo Request - Spanish 46 "1068": "sales_inquiry", // FO - Professional Services 47 "1311": "sales_inquiry", // FO - Sales Inquiry 48 "1318": "sales_inquiry", // FO - Sales Inquiry - French 49 "1319": "sales_inquiry", // FO - Sales Inquiry - German 50 "2985": "sales_inquiry", // FO - Sales Inquiry - Italian 51 "1317": "sales_inquiry", // FO - Sales Inquiry - Spanish 52 "3307": "sales_qualification", // FO - Sales Inquiry Secondary Form 53 "3507": "sales_qualification", // FO - Sales Inquiry Secondary Form - French 54 "3510": "sales_qualification", // FO - Sales Inquiry Secondary Form - German 55 "3514": "sales_qualification", // FO - Sales Inquiry Secondary Form - Italian 56 "3515": "sales_qualification", // FO - Sales Inquiry Secondary Form - Spanish 57 "3561": "sales_qualification", // FO - Paid Social Secondary Form 58 "3712": "sales_qualification", // FO - Paid Social Secondary Form - French 59 "3713": "sales_qualification", // FO - Paid Social Secondary Form - Italian 60 "3711": "sales_qualification", // FO - Paid Social Secondary Form - Spanish 61 "1067": "subscribe_request", // FO - Learning Digest 62 "1943": "subscribe_request", // FO - Learning Digest - English GB 63 "3735": "subscribe_request", // FO - Playlist subscription form 64 "1987": "subprocessor_notifications", // FO - Subprocessor Notifications 65 "1070": "webinar_register", // FO - Webinar (Recorded) 66 "1288": "webinar_register", // FO - Webinar (Upcoming) 67 }; 68 69 // Make formID2Key globally accessible so that it can be used in e3_marketo_forms.js 70 window.formID2Key = formID2Key; 71 72 var eventNames = { 73 // Tealium Event Name is AKA Event Label in Google Analytics 74 // Map tealium_event to remaining event attributes 75 // "tealium_event" : ["event_category", "event_action", "event_detail", "business_email", "business_postal_code", "customer_vertical"] 76 /* Sales Conversion */ 77 "customer_inquiry": ["sales_conversion", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], // Existing customers raising their hand (not a net new lead)
78 "customer_referral": ["sales_conversion", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], // Can become a lead in Salesforce 79 "partner_referral": ["sales_conversion", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], // Can become a lead in Salesforce 80 "sales_inquiry": ["sales_conversion", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}","{customer_vertical}","{number_of_staff}"], // Can become a lead in Salesforce 81 /* Other */ 82 "chilipiper_assignment": ["chilipiper_assignment", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], // ChiliPiper Assignment 83 "sales_qualification": ["sales_qualification", "form_submit", "{page_name}", "{business_postal_code}"], // Held in Marketo. This is the secondary form for the two-step sales inquiry flow (not a standalone form) 84 "email_preferences_update": ["consent_management", "form_submit", "{content_title}", "{business_email}", "{business_postal_code}"], 85 "gated_content_download": ["download", "form_submit", "{content_title}", "{business_email}", "{business_postal_code}"], // Held in Marketo 86 "research_interest": ["research_interest", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], // Open to customers. Routed to Research team via Marketo 87 "subprocessor_notifications": ["legal_request", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], 88 "subscribe_request": ["subscribe", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], // Held in Marketo 89 "webinar_register": ["content_conversion", "form_submit", "{content_title}", "{business_email}", "{business_postal_code}"], // Held in Marketo 90 "email_collect": ["email_collect", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], 91 "unnamed_form_submit": ["uncategorized", "form_submit", "{page_name}", "{business_email}", "{business_postal_code}"], 92 }; 93 94 // For Google Analytics 4, there is a solution duct-taped together in Tealium 95 // See tag "GA4 (gtag.js) - www.mindbodyonline.com" 96 // It uses maps variables from this old GA event structure to the newer one 97 // For reference (last updated November 24, 2024): 98 // generate_lead = sales_inquiry, customer_referral, partner_referral 99 // generate_prospect = gated_content_download, webinar_register 100 // subscribe = subscribe_request 101 102 var buildTealiumData = function (form, context, key) { 103 104 var dataObj = { 105 "tealium_event": key, 106 "event_category": context[0], 107 "event_action": context[1], 108 "event_detail": context[2], 109 "business_email": context[3], 110 "business_postal_code": context[4], 111 "customer_vertical": context[5], 112 "number_of_staff": context[6], 113 }; 114 115 // Support dynamic values in event_detail 116 var isInBrackets = dataObj.event_detail.match(/\{(.*?)\}/); 117 if (isInBrackets) { 118 let funcName = "get_" + isInBrackets[1]; 119 if (functionExists(funcName)) { 120 dataObj.event_detail = detailGetters[funcName](); 121 } 122 } 123 124 // Replace {business_email} with dynamic value from form 125 dataObj.business_email = form.getValues()['Email']; 126 127 // Replace {business_postal_code} with dynamic value from geolocation cookie 128 dataObj.business_postal_code = ''; // Submit empty string if we fail to get a postal code for the user. 129 var geolocationCookie = document.Cookie.read("STYXKEY_mbGeolocation"); 130 if (geolocationCookie) { 131 var geolocationInfo = JSON.parse(geolocationCookie); 132 var postalCode = geolocationInfo.postalCode; 133 if (postalCode) { 134 dataObj.business_postal_code = postalCode; 135 } 136 } 137 138 // Added {customer_vertical} to the data object with dynamic value from form 139 dataObj.customer_vertical = form.getValues()['Secondary_Market__c']; 140 141 // Add order_id if it exists on the form 142 let orderIdValue = form.getValues()['tealiumOrderID']; 143 if (orderIdValue) { 144 dataObj.order_id = orderIdValue; 145 } 146 147 // Add business_phone if it exists on the form 148 let phoneValue = form.getValues()['basicPhone']; 149 if (phoneValue) { 150 dataObj.business_phone = phoneValue; 151 } 152 153 // Add number_of_staff if it exists on the form 154 let staffValue = form.getValues()['Number_of_Staff__c']; 155 if (staffValue) { 156 dataObj.number_of_staff = staffValue; 157 } 158 else { 159 dataObj.number_of_staff = ''; 160 } 161 162 // Add is_this_your_full_time_job if it exists on the form (custom15) 163 let fullTimeJobValue = form.getValues()['custom15']; 164 if (fullTimeJobValue) { 165 dataObj.is_this_your_full_time_job = fullTimeJobValue; 166 } 167 168 // Add is_your_business_open if it exists on the form (custom8) 169 let businessOpenValue = form.getValues()['custom8']; 170 if (businessOpenValue) { 171 dataObj.is_your_business_open = businessOpenValue; 172 } 173 174 // Store data to be submitted later (onSuccess) 175 dataForTealium = dataObj; 176 } 177 178 function functionExists(funcName) { 179 if (typeof detailGetters[funcName] === "function") { 180 return 1; 181 } 182 } 183 184 function findEventName(formNum) { 185 if (formID2Key[formNum]) { 186 return formID2Key[formNum]; 187 } else { 188 return "unnamed_form_submit"; 189 } 190 } 191 192 var detailGetters = { 193 get_content_title: function () { 194 if (window.utag_data) { 195 return utag_data.content_title || ""; 196 } 197 }, 198 199 get_page_name: function () { 200 if (window.utag_data) { 201 return utag_data.page_name || ""; 202 } 203 } 204 }; 205 206 form.onSubmit(function () { 207 var formID = form.getId(), 208 key = findEventName(formID); 209 210 if (eventNames[key]) { 211 buildTealiumData(form, eventNames[key], key); 212 } 213 }); 214 215 function onlyValuesSet(data) { 216 var out = {}; 217 for (var key in data) { 218 if (Object.prototype.hasOwnProperty.call(data, key)) { 219 var val = data[key]; 220 if (val !== undefined && val !== null && val !== '') { 221 out[key] = val; 222 } 223 } 224 } 225 return out; 226 } 227 228 // onSuccess handler for Marketo form 229 form.onSuccess(function () { 230 var formID = form.getId(); 231 // Prevent duplicate submissions to Tealium when the form exists more than once on the page. 232 if (window.marketoFormsSubmitted.has(formID)) { 233 return; 234 } 235 window.marketoFormsSubmitted.add(formID); 236 // Submit form event data to Tealium (only properties with a value set)
237 try { 238 utag.link(onlyValuesSet(dataForTealium)); 239 } catch (err) { 240 if (typeof newrelic != "undefined") { 241 newrelic.noticeError(err); 242 } else { 243 console.log('newrelic not found'); 244 console.error('Error sending form event data to Tealium: ', err); 245 } 246 } 247 }); 248 }); 249})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.