Skill Tracer
Skills › Marketing Skills › conversion-tracking › gravity-forms.js

gravity-forms.js

232 lines · skills/conversion-tracking/assets/snippets/gravity-forms.js at 88c79f6

1/*! Gravity Forms conversion detection (MIT)
2 * Detects: form submission. Pushes dataLayer event: gravity_form_submitted
3 * From the conversion-tracking skill in Marketing Skills */
4/* Gravity Forms Submission Listener.
5 * Event-only adaptation of verified production detection helpers.
6 * Source SHA256 3ed591d48dc449e94fd48f634bb07ae8285f5d661a4b48fc5db867ca3e659b69.
7 * Reads no form field values. Sends only event and form_id to dataLayer.
8 * Native handoff stores only form ID, source path and time in sessionStorage.
9 */
10(function(){
11"use strict";
12if(window.__ctGalleryGravityForms)return;
13window.__ctGalleryGravityForms=true;
14// Preserve the original recipe guard for an old recipe loaded after this code.
15// Do not return on the legacy guard: an old-first listener is independently
16// active and remains an unsupported migration state, but must not suppress us.
17window.__ctDetectGravityForms=true;
18function isGravityForm(formEl) {
19 if (!formEl) return false;
20 // Check for gform_wrapper parent or gform_ ID prefix
21 try {
22 if (formEl.closest && formEl.closest('.gform_wrapper')) return true;
23 } catch (e) {
24 // .closest not available — fall back to ID check
25 }
26 // Fallback: GF forms have IDs like "gform_1", "gform_5"
27 var id = formEl.id || '';
28 return /^gform_\d+$/.test(id);
29 }
31function getFormId(formEl) {
32 if (!formEl) return null;
33 var fid = (formEl.getAttribute && formEl.getAttribute('data-formid')) || '';
34 if (fid) return String(fid);
35 var id = formEl.id || '';
36 var m = /gform_(\d+)/.exec(id);
37 return m ? m[1] : null;
38 }
40function isAjaxForm(formEl) {
41 if (!formEl) return false;
42 try {
43 var target = (formEl.getAttribute && formEl.getAttribute('target')) || '';
44 if (target.indexOf('gform_ajax_frame_') === 0) return true;
45 if (formEl.querySelector && formEl.querySelector('input[name="gform_ajax"]')) {
46 return true;
47 }
48 } catch (e) {
49 // DOM access failed — treat as non-AJAX; the reload path is the
50 // safer default (it still only fires on confirmed success).
51 }
52 return false;
53 }
55function isNodeHidden(el) {
56 try {
57 if (el.hidden) return true;
58 if (el.getAttribute && el.getAttribute('aria-hidden') === 'true') return true;
59 var view = el.ownerDocument && el.ownerDocument.defaultView;
60 if (view && view.getComputedStyle) {
61 var cs = view.getComputedStyle(el);
62 if (cs && (cs.display === 'none' || cs.visibility === 'hidden')) return true;
63 }
64 } catch (e) {
65 // fall through
66 }
67 return false;
68 }
70function hasVisibleValidationErrors(rootEl) {
71 if (!rootEl || !rootEl.querySelectorAll) return false;
72 try {
73 if (rootEl.className &&
74 (' ' + rootEl.className + ' ').indexOf(' gform_validation_error ') !== -1) {
75 return true;
76 }
77 var nodes = rootEl.querySelectorAll(
78 '.gform_validation_errors, .validation_error, .gfield_error, .validation_message, [aria-invalid="true"]'
79 );
80 for (var i = 0; i < nodes.length; i++) {
81 var node = nodes[i];
82 if (isNodeHidden(node)) continue;
83 if (node.getAttribute && node.getAttribute('aria-invalid') === 'true') return true;
84 // Error classes and ARIA state are structural signals. Never read
85 // rendered validation text: themes may include resolved field values.
86 var classes = typeof node.className === 'string' ? node.className : '';
87 if (/^(?:.*\s)?(?:gform_validation_errors|validation_error|gfield_error)(?:\s.*)?$/.test(classes)) return true;
88 }
89 } catch (e) {
90 // fall through
91 }
92 return false;
93 }
95function safeFormId(formId) {
96 if (formId == null) return null;
97 var s = String(formId);
98 return /^\d+$/.test(s) ? s : null;
99 }
101function hasServerRejectionMarkers(formId) {
102 var fid = safeFormId(formId);
103 try {
104 if (fid) {
105 var scope = document.querySelector('#gform_wrapper_' + fid) ||
106 document.querySelector('#gform_' + fid + ', form[data-formid="' + fid + '"]');
107 if (scope && scope.closest) {
108 scope = scope.closest('.gform_wrapper') || scope;
109 }
110 // The submitted form isn't on this page at all → no rejection
111 // evidence FOR THIS FORM, whatever other forms are showing.
112 return scope ? hasVisibleValidationErrors(scope) : false;
113 }
114 var wrappers = document.querySelectorAll('.gform_wrapper');
115 for (var i = 0; i < wrappers.length; i++) {
116 if (hasVisibleValidationErrors(wrappers[i])) return true;
117 }
118 } catch (e) {
119 // fall through
120 }
121 return false;
122 }
124function hasConfirmationMessage(formId) {
125 var fid = safeFormId(formId);
126 try {
127 var nodes = document.querySelectorAll(
128 '.gform_confirmation_message, [id^="gform_confirmation_message_"], [id^="gform_confirmation_wrapper_"]'
129 );
130 for (var i = 0; i < nodes.length; i++) {
131 var node = nodes[i];
132 if (isNodeHidden(node)) continue;
133 // Presence of a visible, form-attributed confirmation element is a
134 // structural legacy signal; its rendered contents are never inspected.
135 if (!fid) continue;
136 // Attribute the node to a form id if it carries one (own id or an
137 // ancestor confirmation-wrapper id).
138 var taggedId = null;
139 var m = /gform_confirmation_(?:message|wrapper)_(\d+)/.exec(node.id || '');
140 if (m) {
141 taggedId = m[1];
142 } else if (node.closest) {
143 try {
144 var anc = node.closest('[id^="gform_confirmation_wrapper_"], [id^="gform_confirmation_message_"]');
145 var am = anc && /_(\d+)$/.exec(anc.id || '');
146 if (am) taggedId = am[1];
147 } catch (e2) {
148 // .closest unavailable — treat as untagged
149 }
150 }
151 if (taggedId === fid) return true;
152 }
153 } catch (e) {
154 // fall through
155 }
156 return false;
157 }
158var pending={},complete={},TTL=120000,markerKey='__ct_gallery_gf_pending_v3';
159var modernHookInstalled=false,seenEntries={};
160window.dataLayer=window.dataLayer||[];
161function forget(id){var entry=pending[id];if(entry&&entry.timer)clearTimeout(entry.timer);delete pending[id];}
162function discardNotice(id,reason,form){try{document.dispatchEvent(new CustomEvent('ct:gform-attempt-discarded',{detail:{formId:String(id||''),reason:reason||'discarded',form:form||null}}));}catch(e){}}
163function emitConfirmed(id,form,uniqueAccepted){id=safeFormId(id);if(!id||(!uniqueAccepted&&complete[id]))return;complete[id]=true;var handled=false;try{if(typeof window.__ctGravityMethod2Emit==='function')handled=window.__ctGravityMethod2Emit(id,form)===true;}catch(e){}if(!handled)window.dataLayer.push({event:'gravity_form_submitted',form_id:id});}
164function fireLegacy(id){id=safeFormId(id);if(modernHookInstalled||!id||!pending[id]||complete[id])return;var form=pending[id].form;forget(id);emitConfirmed(id,form);}
165function attempt(form,method,type){
166 if(!isGravityForm(form))return;var id=safeFormId(getFormId(form));if(!id)return;
167 if(modernHookInstalled){return;}
168 if(type&&type!=='submit'){forget(id);discardNotice(id,'nonfinal');return;}
169 forget(id);complete[id]=false;
170 var entry={form:form,method:method||(isAjaxForm(form)?'iframe':'postback'),trusted:method==='postback',time:Date.now(),path:location.pathname,timer:null};
171 entry.timer=setTimeout(function(){if(pending[id]===entry){forget(id);discardNotice(id,'timeout');}},TTL);pending[id]=entry;
173function acceptedIframe(frame){
174 if(modernHookInstalled||!frame||frame.tagName!=='IFRAME'||!/^gform_ajax_frame_\d+$/.test(frame.id||''))return;
175 var id=frame.id.replace('gform_ajax_frame_','');if(!pending[id])return;
176 try{var doc=frame.contentDocument;if(!doc||!doc.querySelector('.GF_AJAX_POSTBACK'))return;if(doc.querySelector('.gform_validation_error, .gform_validation_errors, .gfield_error, [aria-invalid=\"true\"]')){forget(id);discardNotice(id,'rejected');return;}var confirmation=doc.querySelector('#gform_confirmation_wrapper_'+id+', #gform_confirmation_message_'+id);if(confirmation&&!isNodeHidden(confirmation))fireLegacy(id);}catch(e){}
178document.addEventListener('load',function(event){acceptedIframe(event.target);},true);
179var jqueryHooked=false;
180function hook(){
181 if(!jqueryHooked&&typeof window.jQuery==='function'){jqueryHooked=true;window.jQuery(document).on('gform_confirmation_loaded.ctGallery',function(event,id){fireLegacy(id);});}
182 if(modernHookInstalled||!window.gform||!gform.utils||!gform.utils.addAsyncFilter)return;
183 modernHookInstalled=true;
184 gform.utils.addAsyncFilter('gform/submission/pre_submission',function(data){
185 var form=data&&data.form;if(form&&!form.tagName&&form[0])form=form[0];var id=safeFormId(getFormId(form));
186 if(!id)return data;
187 if(data.submissionType!=='submit'){
188 discardNotice(id,'nonfinal',form);
189 return data;
190 }
191 if(data.abort===true)return data;
192 if(data.submissionMethod==='postback'||data.submissionMethod==='iframe')data.submissionMethod='ajax';
193 return data;
194 });
195 gform.utils.addAsyncFilter('gform/submission/submission_started',function(data){
196 var form=data&&data.form;if(form&&!form.tagName&&form[0])form=form[0];var id=safeFormId(getFormId(form));
197 if(id&&data.submissionType==='submit')complete[id]=false;
198 return data;
199 });
200 gform.utils.addAsyncFilter('gform/ajax/post_ajax_submission',function(data){
201 var form=data&&data.form;if(form&&!form.tagName&&form[0])form=form[0];var id=safeFormId(getFormId(form));if(!id)return data;
202 var result=data&&data.submissionResult,verdict=result&&result.data;
203 var rawEntryId=verdict&&verdict.entry_id,entryId=typeof rawEntryId==='number'?rawEntryId:(typeof rawEntryId==='string'&&/^\d+$/.test(rawEntryId)?Number(rawEntryId):NaN);
204 var key=id+':'+entryId;
205 if(result&&result.success===true&&verdict&&verdict.is_valid===true&&verdict.submission_type==='submit'&&isFinite(entryId)&&Math.floor(entryId)===entryId&&entryId>0&&entryId<=9007199254740991&&verdict.is_spam===false){
206 if(seenEntries[key]){discardNotice(id,'duplicate-entry',form);return data;}
207 seenEntries[key]=true;emitConfirmed(id,form,true);
208 }else if(result&&result.success===true)discardNotice(id,'rejected',form);
209 return data;
210 });
211 gform.utils.addAsyncFilter('gform/ajax/pre_display_error',function(data){
212 if(data&&data.formId){var errorForm=null;try{errorForm=document.querySelector('#gform_'+safeFormId(data.formId));}catch(e){}discardNotice(data.formId,'ajax-error',errorForm);}
213 return data;
214 });
215 document.addEventListener('gform/submission/submission_aborted',function(event){
216 var form=event&&event.detail&&event.detail.form,id=safeFormId(getFormId(form));
217 if(id)discardNotice(id,'abort',form);
218 });
220hook();document.addEventListener('gform/theme/scripts_loaded',hook);document.addEventListener('DOMContentLoaded',hook);window.addEventListener('load',hook);
221document.addEventListener('submit',function(event){attempt(event.target,null,'submit');},true);
222document.addEventListener('click',function(event){var el=event.target;if(!el||!el.closest)return;var control=el.closest('[id^="gform_submit_button_"]');if(control)attempt(control.closest('form'),null,'submit');},true);
223function confirmationNode(node){if(modernHookInstalled||!node||node.nodeType!==1)return;var nodes=[];if(node.matches&&node.matches('[id^="gform_confirmation_message_"], [id^="gform_confirmation_wrapper_"]'))nodes.push(node);if(node.querySelectorAll){var list=node.querySelectorAll('[id^="gform_confirmation_message_"], [id^="gform_confirmation_wrapper_"]');for(var i=0;i<list.length;i++)nodes.push(list[i]);}for(var j=0;j<nodes.length;j++){var match=/gform_confirmation_(?:message|wrapper)_(\d+)/.exec(nodes[j].id||'');if(match&&!isNodeHidden(nodes[j]))fireLegacy(match[1]);}}
224if(typeof MutationObserver==='function')new MutationObserver(function(mutations){for(var i=0;i<mutations.length;i++)for(var j=0;j<mutations[i].addedNodes.length;j++)confirmationNode(mutations[i].addedNodes[j]);}).observe(document.body||document.documentElement,{childList:true,subtree:true});
225window.addEventListener('pagehide',function(){
226 var markers=[];for(var id2 in pending)if(Object.prototype.hasOwnProperty.call(pending,id2)){var entry=pending[id2];if(entry.method!=='postback'||Date.now()-entry.time>10000||(!entry.trusted&&hasVisibleValidationErrors(entry.form)))continue;markers.push({id:id2,time:entry.time,path:entry.path});}
227 if(markers.length)try{sessionStorage.setItem(markerKey,JSON.stringify(markers));}catch(e){}
228});
229function deferred(){if(modernHookInstalled)return;var markers;try{var raw=sessionStorage.getItem(markerKey);sessionStorage.removeItem(markerKey);markers=JSON.parse(raw||'[]');}catch(e){return;}if(!Array.isArray(markers))return;for(var i=0;i<markers.length;i++){var marker=markers[i],id=safeFormId(marker&&marker.id);if(!id||!marker.time||Date.now()-marker.time>60000)continue;if(hasServerRejectionMarkers(id))continue;if(hasConfirmationMessage(id)){pending[id]={};fireLegacy(id);}}}
230if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',deferred);else deferred();
232})();