1/** 2 * on_demand.js â Scheduler Setup Plugin 3 * 4 * window.SchedulerConfig is injected by PHP (wp_add_inline_script) BEFORE 5 * this file loads, so every value is available immediately. 6 * 7 * Usage anywhere on the site: 8 * 9 * // Raw values 10 * const key = SchedulerSetup.apiKey; 11 * const id = SchedulerSetup.schedulerId; 12 * 13 * // Trigger on-demand 14 * SchedulerSetup.run(); 15 * 16 * // Listen for completion 17 * document.addEventListener('schedulerSetup:done', (e) => { 18 * console.log('Scheduler finished', e.detail); 19 * }); 20 */ 21 22( function ( window ) { 23 'use strict'; 24 25 /* ------------------------------------------------------------------ */ 26 /* 1. Read the config object injected by PHP */ 27 /* ------------------------------------------------------------------ */ 28 var cfg = window.SchedulerConfig || {}; 29 30 var API_KEY = cfg.apiKey || ''; 31 var SCHEDULER_ID = cfg.schedulerId || ''; 32 33 /** 34 * Validate that required config values are present. 35 * @returns {boolean} 36 */ 37 function isConfigValid() { 38 if ( ! API_KEY ) { 39 console.warn( '[SchedulerSetup] _api_key is empty. Check ACF site-settings.' ); 40 return false; 41 } 42 if ( ! SCHEDULER_ID ) { 43 console.warn( '[SchedulerSetup] _scheduler_id is empty. Check ACF site-settings.' ); 44 return false; 45 } 46 return true; 47 } 48 49 50 /* ------------------------------------------------------------------ */ 51 /*2. Public API â attached to window so every file can use it */ 52 /* ------------------------------------------------------------------ */ 53 54 window.SchedulerSetup = { 55 /** The API key read from ACF site-settings._api_key */ 56 apiKey: API_KEY, 57 58 /** The scheduler ID read from ACF site-settings._scheduler_id */ 59 schedulerId: SCHEDULER_ID, 60 61 /** True when both values are present */ 62 isReady: isConfigValid(), 63 64 }; 65 66 67/* On demand script to run scheduler on page based on click */ 68 69/* IM Banshi - 03-20-2026 Resolve 70 * button click and load service 71 * titan script after click */ 72document.addEventListener('touchstart', handleSchedulerClick, { passive: false }); 73document.addEventListener('click', handleSchedulerClick); 74var schedulerListener = null; 75var fallbackTimer = null; 76var schedulerUsed = false; 77var loadInProgress = false; // true while a load is in flight but not yet resolved 78 79function handleSchedulerClick(e) { 80 81 // This is used when there is a mix of schedule-online hrefs 82 var target = e.target.closest('a[href="/schedule-online"], a[href="/schedule-online/"], a[href$="/schedule-online"], a[href$="/schedule-online/"]'); 83 84 if (!target) return; 85 86 e.preventDefault(); 87 88 console.log('Captured early interaction' + target.href); 89 90 // Now load your delayed scripts manually 91 loadSchedulerScript(target.href); 92} 93 94function loadSchedulerScript(targeLink) { 95 var link = targeLink; 96 var isLoaded = document.querySelector('#se-widget-embed'); 97 console.log(isLoaded); 98 99 if (isLoaded === null) { 100 console.log('Script not loaded'); 101 loadInProgress = true; // mark load as started 102 103 var script = document.createElement('script'); 104 script.src = "https://embed.scheduler.servicetitan.com/scheduler-v1.js"; 105 script.defer = true; 106 script.id = "se-widget-embed"; 107 108 script.setAttribute('data-api-key', SchedulerSetup.apiKey); 109 script.setAttribute('data-schedulerid', SchedulerSetup.schedulerId); 110 var maxTime = 2500; 111 var intervalTime = 100; 112 var elapsed = 0; 113 var runCount = 0; 114 115 script.onerror = function () { // recover if the widget script fails to load at all 116 console.warn('Scheduler script failed to load'); 117 loadInProgress = false; 118 if (link && link != '') { 119 window.location.href = link; 120 } 121 }; 122 123 script.onload = function () { 124 console.log('Script loaded by plugin'); 125 126 var checkInterval = setInterval(function () { 127 runCount++; 128 console.log('Interval run count:', runCount); 129 130 var iframeExists = document.querySelector('iframe[src*="scheduler"]') !== null; 131 var schedulerReady = (typeof _scheduler !== "undefined") && iframeExists; 132 133 if (schedulerReady) { 134 clearInterval(checkInterval); 135 console.log('Scheduler + iframe detected'); 136 137 function onReady(event) { 138 if (event.origin !== "https://scheduler.servicetitan.com") return; 139 140 if (event.data && event.data.type === "se-app-ready") { 141 console.log("Scheduler ready"); 142 143 window.removeEventListener("message", onReady); 144 145 _scheduler.show({ 146 schedulerId: SchedulerSetup.schedulerId 147 }); 148
149 schedulerUsed = true; 150 loadInProgress = false; // load resolved successfully 151 } 152 } 153 154 window.addEventListener("message", onReady); 155 } else { 156 elapsed += intervalTime; 157 158 if (elapsed >= maxTime) { 159 clearInterval(checkInterval); 160 161 console.log('Max time reached'); 162 console.log('Total interval runs:', runCount); 163 164 loadInProgress = false; // give up, unlock future clicks 165 166 if (link && link != '') { 167 window.location.href = link; 168 return; 169 } 170 } 171 } 172 173 }, intervalTime); 174 }; 175 176 document.head.appendChild(script); 177 178 return; // â STOP further execution 179 } 180 181 console.log('Schedule script found on page') 182 var eventReceived = false; 183 184 // Block multiple listeners 185 if (schedulerListener) { 186 window.removeEventListener("message", schedulerListener); 187 schedulerListener = null; 188 } 189 190 if (fallbackTimer) { 191 clearTimeout(fallbackTimer); 192 } 193 194 // If scheduler already used once â assume success (reopen) 195 if (schedulerUsed) { 196 console.log("Scheduler already used, skipping listener"); 197 _scheduler.show({ schedulerId: SchedulerSetup.schedulerId }); 198 return; 199 } 200 201 // A load from an earlier click is still in flight â ignore this click instead 202 // of falling through to the strict checks below 203 if (loadInProgress) { 204 console.log('Load already in progress, ignoring extra click'); 205 return; 206 } 207 208 // If scheduler not loaded â fallback 209 if (typeof _scheduler === "undefined") { 210 window.location.href = link; 211 return; 212 } 213 214 // Open widget 215 _scheduler.show({ schedulerId: SchedulerSetup.schedulerId }); 216 217 schedulerListener = function (event) { 218 219 if (event.origin !== "https://scheduler.servicetitan.com") return; 220 if (!event.data || event.data.type !== "se-event") return; 221 222 console.log("Scheduler Event:", event.data.eventName); 223 eventReceived = true; 224 schedulerUsed = true; 225 226 clearTimeout(fallbackTimer); 227 window.removeEventListener("message", schedulerListener); 228 schedulerListener = null; 229 }; 230 231 window.addEventListener("message", schedulerListener); 232 233 fallbackTimer = setTimeout(function () { 234 if (!eventReceived) { 235 console.warn("Scheduler silent â fallback triggered"); 236 window.location.href = link; 237 } 238 }, 2000); 239} 240 241 242 243}( window ) );
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.