PageSourceSearch

https://onthinktanks.org/wp-content/plugins/ott-popup/assets/js/popup.js?ver=1.5.0

js onthinktanks.org collected 2026-10-02 05:07:20 UTC 6,259 bytes, 217 lines download raw bytes

1/**
2 * OTT Popup — front-end behavior: view/conversion tracking, modal/bar close &
3 * dismiss, and bottom-bar height sync.
4 *
5 * A view is counted once per session (sessionStorage) and only when the popup
6 * is actually visible (so device-hidden or dismissed popups are not counted).
7 * A conversion is counted when the CTA is clicked. The tallest bottom-bar
8 * height is published to --ottpopup-bar-h so themes can offset their footer.
9 */
10( function () {
11	'use strict';
12
13	if ( typeof window.ottPopupConfig === 'undefined' || ! window.ottPopupConfig.restUrl ) {
14		return;
15	}
16
17	var config = window.ottPopupConfig;
18
19	/**
20	 * Sends an event to the REST API.
21	 *
22	 * @param {number} id     Popup ID.
23	 * @param {string} type   'view' or 'conversion'.
24	 * @param {string} button Optional CTA button: 'primary' or 'secondary'.
25	 */
26	function track( id, type, button ) {
27		id = parseInt( id, 10 );
28		if ( ! id || ( type !== 'view' && type !== 'conversion' ) ) {
29			return;
30		}
31
32		var data = { id: id, type: type, nonce: config.nonce || '', token: config.token || '' };
33		if ( button === 'primary' || button === 'secondary' ) {
34			data.button = button;
35		}
36
37		var payload = JSON.stringify( data );
38
39		// sendBeacon survives navigation (clicking the CTA link).
40		if ( navigator.sendBeacon ) {
41			try {
42				var blob = new Blob( [ payload ], { type: 'application/json' } );
43				if ( navigator.sendBeacon( config.restUrl, blob ) ) {
44					return;
45				}
46			} catch ( e ) {
47				// Fall through to fetch below.
48			}
49		}
50
51		fetch( config.restUrl, {
52			method: 'POST',
53			headers: {
54				'Content-Type': 'application/json',
55				'X-WP-Nonce': config.nonce || '',
56				'X-OTT-Token': config.token || ''
57			},
58			body: payload,
59			keepalive: true,
60			credentials: 'same-origin'
61		} ).catch( function () {} );
62	}
63
64	/**
65	 * Counts a view once per session for the given ID.
66	 *
67	 * @param {string} id Popup ID.
68	 */
69	function trackViewOnce( id ) {
70		var key = 'ottpopup_viewed_' + id;
71		try {
72			if ( window.sessionStorage && sessionStorage.getItem( key ) ) {
73				return;
74			}
75			if ( window.sessionStorage ) {
76				sessionStorage.setItem( key, '1' );
77			}
78		} catch ( e ) {
79			// No sessionStorage — count anyway.
80		}
81		track( id, 'view' );
82	}
83
84	function dismissKey( id ) {
85		return 'ottpopup_closed_' + id;
86	}
87
88	function isDismissed( id ) {
89		try {
90			return !! ( window.sessionStorage && sessionStorage.getItem( dismissKey( id ) ) );
91		} catch ( e ) {
92			return false;
93		}
94	}
95
96	function setDismissed( id ) {
97		try {
98			if ( window.sessionStorage ) {
99				sessionStorage.setItem( dismissKey( id ), '1' );
100			}
101		} catch ( e ) {}
102	}
103
104	/**
105	 * Whether the element is currently rendered (not display:none / hidden).
106	 * Works for position:fixed elements (offsetParent is null for those).
107	 *
108	 * @param {Element} el Element.
109	 * @return {boolean}
110	 */
111	function isVisible( el ) {
112		var s = window.getComputedStyle( el );
113		return s.display !== 'none' && s.visibility !== 'hidden';
114	}
115
116	function closePopup( el ) {
117		var id = el.getAttribute( 'data-ottpopup-id' );
118		// Only remember the dismissal when the popup opted in; otherwise it
119		// reappears on every page view (attribute is "0").
120		if ( id && el.getAttribute( 'data-ottpopup-remember' ) !== '0' ) {
121			setDismissed( id );
122		}
123		el.classList.add( 'ott-popup-is-closed' );
124		syncBarHeight();
125	}
126
127	/**
128	 * Publishes the tallest visible bottom-bar height to --ottpopup-bar-h so
129	 * themes can offset their footer below the bar.
130	 */
131	function syncBarHeight() {
132		var bars   = document.querySelectorAll( '.ott-popup--bottom' );
133		var height = 0;
134
135		Array.prototype.forEach.call( bars, function ( el ) {
136			if ( isVisible( el ) && ! el.classList.contains( 'ott-popup-is-closed' ) ) {
137				height = Math.max( height, el.offsetHeight );
138			}
139		} );
140
141		document.documentElement.style.setProperty( '--ottpopup-bar-h', height + 'px' );
142	}
143
144	function init() {
145		var popups = document.querySelectorAll( '[data-ottpopup-id]' );
146
147		// Hide popups dismissed earlier this session.
148		Array.prototype.forEach.call( popups, function ( el ) {
149			var id = el.getAttribute( 'data-ottpopup-id' );
150			if ( isDismissed( id ) ) {
151				el.classList.add( 'ott-popup-is-closed' );
152			}
153		} );
154
155		syncBarHeight();
156
157		if ( window.addEventListener ) {
158			window.addEventListener( 'resize', syncBarHeight );
159			window.addEventListener( 'load', syncBarHeight );
160		}
161
162		// Count views — only for popups that are actually visible right now.
163		Array.prototype.forEach.call( popups, function ( el ) {
164			var id = el.getAttribute( 'data-ottpopup-id' );
165			if ( ! el.classList.contains( 'ott-popup-is-closed' ) && isVisible( el ) ) {
166				trackViewOnce( id );
167			}
168		} );
169
170		// Clicks: close button, overlay backdrop, CTA conversion.
171		document.addEventListener( 'click', function ( event ) {
172			if ( ! event.target.closest ) {
173				return;
174			}
175
176			var closeBtn = event.target.closest( '[data-ottpopup-close]' );
177			if ( closeBtn ) {
178				var pop = closeBtn.closest( '[data-ottpopup-id]' );
179				if ( pop ) {
180					closePopup( pop );
181				}
182				return;
183			}
184
185			// Click on the overlay backdrop itself (not its content) closes it.
186			if ( event.target.classList && event.target.classList.contains( 'ott-popup-overlay' ) ) {
187				closePopup( event.target );
188				return;
189			}
190
191			// CTA: data attribute carries the popup ID directly.
192			var cta = event.target.closest( '[data-ottpopup-cta]' );
193			if ( cta ) {
194				track( cta.getAttribute( 'data-ottpopup-cta' ), 'conversion', cta.getAttribute( 'data-ottpopup-button' ) );
195			}
196		} );
197
198		// Escape closes any open overlay.
199		document.addEventListener( 'keydown', function ( event ) {
200			if ( event.key !== 'Escape' ) {
201				return;
202			}
203			var overlays = document.querySelectorAll( '.ott-popup-overlay' );
204			Array.prototype.forEach.call( overlays, function ( el ) {
205				if ( ! el.classList.contains( 'ott-popup-is-closed' ) ) {
206					closePopup( el );
207				}
208			} );
209		} );
210	}
211
212	if ( document.readyState === 'loading' ) {
213		document.addEventListener( 'DOMContentLoaded', init );
214	} else {
215		init();
216	}
217} )();

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.