1/* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 Guide Training Interest â shared modal runtime 3 Loaded by every page that surfaces the "Guide Training" trigger. 4 Paired with /assets/forms/guide-training.css (scoped styles). 5 6 What this file does: 7 1. Injects the modal HTML into <body> once, after DOMContentLoaded. 8 2. Auto-binds any element with class `.jf-guide-training-custom` 9 OR attribute [data-gti-open="guide-training"] as a trigger. 10 3. Handles open / close (overlay click, [data-gti-close], ESC). 11 4. Wires the multi-select pill widgets. 12 5. Submits to the JotForm REST API. 13 14 Adding a trigger to a new page: 15 - Make sure the page <head> (or before </body>) loads: 16 <link rel="stylesheet" href="/assets/forms/guide-training.css"> 17 <script src="/assets/forms/guide-training.js" defer></script> 18 - On the link that should open the form, set: 19 class="jf-guide-training-custom" 20 data-gti-open="guide-training" 21 style="cursor:pointer;" 22 (or just one of the two â either is enough.) Drop the existing 23 href/mailto: â the script preventDefaults the click anyway. 24 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 25 26(function () { 27 'use strict'; 28 29 // ââ JotForm REST API credentials ââââââââââââââââââââââââââââââââââââ 30 // To swap to a different form, update FORM_ID + API_KEY and the 31 // FIELD_MAP keys below to match the new form's field IDs. 32 const FORM_ID = '261398521066056'; 33 const API_KEY = 'c638548c380eef589205ed5ea4c6f3a2'; 34 35 // Field ID map: payload-key -> JotForm `submission[qid...]` key. 36 // Confirm these qid numbers against the actual form before going live. 37 const FIELD_MAP = { 38 firstName: 'submission[3_first]', 39 lastName: 'submission[3_last]', 40 email: 'submission[4]', 41 phone: 'submission[5_full]', 42 state: 'submission[7]', 43 species: 'submission[8]', 44 rivers: 'submission[9]', 45 experience: 'submission[10]', 46 licensed: 'submission[11]', 47 affiliation: 'submission[12]', 48 interest: 'submission[13]', 49 questions: 'submission[14]' 50 }; 51 52 // ââ Modal markup (string template, injected once) âââââââââââââââââââ 53 const MODAL_HTML = ` 54<div id="gti-modal" class="gti-modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="gti-modal-title"> 55 <div class="gti-overlay" data-gti-close></div> 56 <div class="gti-dialog"> 57 <button type="button" class="gti-close" data-gti-close aria-label="Close form">×</button> 58 59 <div class="gti-header"> 60 <span class="gti-badge">Field Technician Training</span> 61 <h2 id="gti-modal-title">Become a Field Technician</h2> 62 <p class="gti-lede">Join our field technician training program. Learn standardized protocols for collecting biological data safely and consistently from the fish you already encounter. No science background required.</p> 63 </div> 64 65 <div class="gti-form-wrap"> 66 <form id="gti-form" novalidate> 67 68 <div class="gti-group"> 69 <p class="gti-section-label">Your contact information</p> 70 71 <div class="gti-row"> 72 <div class="gti-field"> 73 <label for="gti-first-name">First Name <span class="gti-req">*</span></label> 74 <input type="text" id="gti-first-name" name="first_name" placeholder="First name" required> 75 </div> 76 <div class="gti-field"> 77 <label for="gti-last-name">Last Name <span class="gti-req">*</span></label> 78 <input type="text" id="gti-last-name" name="last_name" placeholder="Last name" required> 79 </div> 80 </div> 81 82 <div class="gti-row"> 83 <div class="gti-field"> 84 <label for="gti-email">Email Address <span class="gti-req">*</span></label> 85 <input type="email" id="gti-email" name="email" placeholder="[email protected]" required> 86 </div> 87 <div class="gti-field"> 88 <label for="gti-phone">Phone Number</label> 89 <input type="tel" id="gti-phone" name="phone" placeholder="Optional"> 90 </div> 91 </div> 92 93 <div class="gti-field"> 94 <label for="gti-state">State / Province</label> 95 <select id="gti-state" name="state_province"> 96 <option value="">Select your state or provinceâ¦</option> 97 <optgroup label="United States"> 98 <option>Alabama</option><option>Alaska</option><option>Arizona</option> 99 <option>Arkansas</option><option>California</option><option>Colorado</option> 100 <option>Connecticut</option><option>Delaware</option><option>Florida</option> 101 <option>Georgia</option><option>Hawaii</option><option>Idaho</option> 102 <option>Illinois</option><option>Indiana</option><option>Iowa</option> 103 <option>Kansas</option><option>Kentucky</option><option>Louisiana</option> 104 <option>Maine</option><option>Maryland</option><option>Massachusetts</option> 105 <option>Michigan</option><option>Minnesota</option><option>Mississippi</option> 106 <option>Missouri</option><option>Montana</option><option>Nebraska</option> 107 <option>Nevada</option><option>New Hampshire</option><option>New Jersey</option> 108 <option>New Mexico</option><option>New York</option><option>North Carolina</option> 109 <option>North Dakota</option><option>Ohio</option><option>Oklahoma</option> 110 <option>Oregon</option><option>Pennsylvania</option><option>Rhode Island</option> 111 <option>South Carolina</option><option>South Dakota</option><option>Tennessee</option> 112 <option>Texas</option><option>Utah</option><option>Vermont</option> 113 <option>Virginia</option><option>Washington</option><option>West Virginia</option> 114 <option>Wisconsin</option><option>Wyoming</option> 115 </optgroup> 116 <optgroup label="Canada"> 117 <option>Alberta</option><option>British Columbia</option><option>Manitoba</option> 118 <option>New Brunswick</option><option>Newfoundland and Labrador</option> 119 <option>Northwest Territories</option><option>Nova Scotia</option> 120 <option>Nunavut</option><option>Ontario</option><option>Prince Edward Island</option> 121 <option>Quebec</option><option>Saskatchewan</option><option>Yukon</option> 122 </optgroup> 123 </select> 124 </div> 125 </div> 126 127 <div class="gti-group"> 128 <p class="gti-section-label">About your guiding</p> 129 130 <div class="gti-field"> 131 <label>Primary Species You Guide</label> 132 <div class="gti-ms-wrapper" data-gti-ms="species"> 133 <div class="gti-ms-widget" data-gti-ms-toggle>
134 <span class="gti-ms-placeholder">Select all that applyâ¦</span> 135 </div> 136 <div class="gti-ms-dropdown"> 137 <label class="gti-ms-option"><input type="checkbox" name="species" value="Steelhead"> Steelhead</label> 138 <label class="gti-ms-option"><input type="checkbox" name="species" value="Rainbow"> Rainbow</label> 139 <label class="gti-ms-option"><input type="checkbox" name="species" value="Chinook Salmon"> Chinook Salmon</label> 140 </div> 141 </div> 142 </div> 143 144 <div class="gti-field"> 145 <label>Rivers / Waters You Work</label> 146 <div class="gti-ms-wrapper" data-gti-ms="rivers"> 147 <div class="gti-ms-widget" data-gti-ms-toggle> 148 <span class="gti-ms-placeholder">Select all that applyâ¦</span> 149 </div> 150 <div class="gti-ms-dropdown"> 151 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Alaska - Sandy"> Alaska â Sandy</label> 152 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Alaska - King Salmon"> Alaska â King Salmon</label> 153 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Alaska - Hoodoo"> Alaska â Hoodoo</label> 154 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Alaska - Ocean"> Alaska â Ocean</label> 155 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="BC - Babine"> BC â Babine</label> 156 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="BC - Skeena"> BC â Skeena</label> 157 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Klamath - Klamath"> Klamath â Klamath</label> 158 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Klamath - Williamson"> Klamath â Williamson</label> 159 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Klamath - Sprague"> Klamath â Sprague</label> 160 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Klamath - Trinity"> Klamath â Trinity</label> 161 <label class="gti-ms-option"><input type="checkbox" name="rivers" value="Klamath - Wood"> Klamath â Wood</label> 162 </div> 163 </div> 164 </div> 165 166 <div class="gti-row"> 167 <div class="gti-field"> 168 <label for="gti-experience">Years of Guiding Experience</label> 169 <select id="gti-experience" name="experience"> 170 <option value="">Select yearsâ¦</option> 171 <option value="1">1</option><option value="2">2</option><option value="3">3</option> 172 <option value="4">4</option><option value="5">5</option><option value="6">6</option> 173 <option value="7">7</option><option value="8">8</option><option value="9">9</option> 174 <option value="10">10</option><option value="11">11</option><option value="12">12</option> 175 <option value="13">13</option><option value="14">14</option><option value="15">15</option> 176 <option value="16">16</option><option value="17">17</option><option value="18">18</option> 177 <option value="19">19</option><option value="20">20</option><option value="21">21</option> 178 <option value="22">22</option><option value="23">23</option><option value="24">24</option> 179 <option value="25">25</option><option value="26">26</option><option value="27">27</option> 180 <option value="28">28</option><option value="29">29</option><option value="30">30+</option> 181 </select> 182 </div> 183 <div class="gti-field"> 184 <label for="gti-licensed">Licensed / Certified Guide?</label> 185 <select id="gti-licensed" name="licensed"> 186 <option value="">Selectâ¦</option> 187 <option value="Yes">Yes</option> 188 <option value="No">No</option> 189 </select> 190 </div> 191 </div> 192 193 <div class="gti-field"> 194 <label for="gti-affiliation">Affiliated with a Lodge or Outfitter?</label> 195 <input type="text" id="gti-affiliation" name="affiliation" placeholder="Lodge or outfitter name, or 'Independent'"> 196 <p class="gti-note">Helps us coordinate training with any partner lodges already in the program.</p> 197 </div> 198 </div> 199 200 <div class="gti-group"> 201 <p class="gti-section-label">Your interest in training</p> 202 203 <div class="gti-field"> 204 <label for="gti-interest">What draws you to the program?</label> 205 <textarea id="gti-interest" name="interest" placeholder="What appeals to you about learning field data collection? Any aspect of the science or conservation work that resonates with you?"></textarea> 206 <p class="gti-note">No science background needed â just tell us what's on your mind.</p> 207 </div> 208 209 <div class="gti-field"> 210 <label for="gti-questions">Any questions for us?</label> 211 <textarea id="gti-questions" name="questions" placeholder="Time commitment, what training involves, how data gets used â anything you'd want to know." style="min-height:85px;"></textarea> 212 </div> 213 </div> 214 215 <button type="submit" class="gti-submit">Submit Training Interest â</button> 216 217 <div class="gti-error" role="alert"> 218 Something went wrong submitting the form. Please try again or email us directly. 219 </div> 220 221 <div class="gti-success" role="status">
222 <span class="gti-success-icon">â</span> 223 <p>Thank you â we'll be in touch within a few business days with details about the next training opportunity.</p> 224 </div> 225 226 </form> 227 </div> 228 </div> 229</div>`; 230 231 // ââ Boot ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 232 function boot() { 233 // Guard against double-injection if script is included twice. 234 if (document.getElementById('gti-modal')) return; 235 236 const container = document.createElement('div'); 237 container.innerHTML = MODAL_HTML.trim(); 238 document.body.appendChild(container.firstChild); 239 240 const modal = document.getElementById('gti-modal'); 241 const form = document.getElementById('gti-form'); 242 const submit = form.querySelector('.gti-submit'); 243 const errEl = form.querySelector('.gti-error'); 244 const okEl = form.querySelector('.gti-success'); 245 246 // ââ Open / close âââââââââââââââââââââââââââââââââââââââââââââââââ 247 function openModal() { 248 modal.classList.add('gti-open'); 249 modal.setAttribute('aria-hidden', 'false'); 250 document.body.classList.add('gti-no-scroll'); 251 setTimeout(() => { 252 const first = form.querySelector('input, select, textarea'); 253 if (first) first.focus({ preventScroll: true }); 254 }, 50); 255 } 256 257 function closeModal() { 258 modal.classList.remove('gti-open'); 259 modal.setAttribute('aria-hidden', 'true'); 260 document.body.classList.remove('gti-no-scroll'); 261 modal.querySelectorAll('.gti-ms-dropdown').forEach(d => d.classList.remove('gti-open')); 262 resetForm(); 263 } 264 265 function resetForm() { 266 // Native reset: clears inputs, selects, textareas, and checkboxes. 267 form.reset(); 268 // form.reset() doesn't fire change events, so multi-select pills + the 269 // placeholder/pill display state need to be cleared by hand. 270 modal.querySelectorAll('.gti-ms-pill').forEach(p => p.remove()); 271 modal.querySelectorAll('.gti-ms-placeholder').forEach(ph => { ph.style.display = ''; }); 272 // Wipe any feedback messages from the previous open. 273 errEl.style.display = 'none'; 274 okEl.style.display = 'none'; 275 // After a successful submit, all controls + the submit button were 276 // disabled and the button label was overwritten â restore both. 277 form.querySelectorAll('input, select, textarea, button').forEach(el => { el.disabled = false; }); 278 submit.textContent = 'Submit Training Interest â'; 279 } 280 281 document.addEventListener('click', function (e) { 282 // Triggers: helper class OR [data-gti-open] (any value, since this is 283 // currently the only form). Add a value match later if other forms 284 // share the data attribute. 285 const opener = e.target.closest('.jf-guide-training-custom, [data-gti-open]'); 286 if (opener) { 287 e.preventDefault(); 288 openModal(); 289 return; 290 } 291 292 const closer = e.target.closest('[data-gti-close]'); 293 if (closer && modal.contains(closer)) { 294 e.preventDefault(); 295 closeModal(); 296 } 297 }); 298 299 document.addEventListener('keydown', function (e) { 300 if (e.key === 'Escape' && modal.classList.contains('gti-open')) closeModal(); 301 }); 302 303 // ââ Multi-select widgets âââââââââââââââââââââââââââââââââââââââââ 304 modal.querySelectorAll('[data-gti-ms]').forEach(function (wrap) { 305 const widget = wrap.querySelector('.gti-ms-widget'); 306 const dropdown = wrap.querySelector('.gti-ms-dropdown'); 307 const placeholder = widget.querySelector('.gti-ms-placeholder'); 308 309 widget.addEventListener('click', function (e) { 310 if (e.target.closest('.gti-ms-pill')) return; 311 const isOpen = dropdown.classList.contains('gti-open'); 312 modal.querySelectorAll('.gti-ms-dropdown').forEach(d => d.classList.remove('gti-open')); 313 if (!isOpen) dropdown.classList.add('gti-open'); 314 e.stopPropagation(); 315 }); 316 317 dropdown.addEventListener('change', function () { updatePills(wrap, placeholder, widget); }); 318 }); 319 320 modal.addEventListener('click', function (e) { 321 if (!e.target.closest('.gti-ms-wrapper')) { 322 modal.querySelectorAll('.gti-ms-dropdown').forEach(d => d.classList.remove('gti-open')); 323 } 324 }); 325 326 function updatePills(wrap, placeholder, widget) { 327 widget.querySelectorAll('.gti-ms-pill').forEach(p => p.remove()); 328 const checked = [...wrap.querySelectorAll('input[type="checkbox"]:checked')]; 329 if (checked.length === 0) { 330 placeholder.style.display = ''; 331 return; 332 } 333 placeholder.style.display = 'none'; 334 checked.forEach(cb => { 335 const pill = document.createElement('span'); 336 pill.className = 'gti-ms-pill'; 337 const label = document.createElement('span'); 338 label.textContent = cb.value; 339 const btn = document.createElement('button'); 340 btn.type = 'button'; 341 btn.setAttribute('aria-label', 'Remove ' + cb.value); 342 btn.textContent = 'Ã'; 343 btn.addEventListener('click', function (ev) { 344 ev.stopPropagation(); 345 cb.checked = false;
346 updatePills(wrap, placeholder, widget); 347 }); 348 pill.appendChild(label); 349 pill.appendChild(btn); 350 widget.appendChild(pill); 351 }); 352 } 353 354 // ââ Submit to JotForm REST API âââââââââââââââââââââââââââââââââââ 355 form.addEventListener('submit', async function (e) { 356 e.preventDefault(); 357 358 const firstName = document.getElementById('gti-first-name').value.trim(); 359 const lastName = document.getElementById('gti-last-name').value.trim(); 360 const email = document.getElementById('gti-email').value.trim(); 361 if (!firstName || !lastName || !email) { 362 alert('Please fill in your first name, last name, and email address.'); 363 return; 364 } 365 366 const species = [...form.querySelectorAll('input[name="species"]:checked')].map(c => c.value).join(', '); 367 const rivers = [...form.querySelectorAll('input[name="rivers"]:checked')].map(c => c.value).join(', '); 368 369 const payload = new URLSearchParams(); 370 payload.append(FIELD_MAP.firstName, firstName); 371 payload.append(FIELD_MAP.lastName, lastName); 372 payload.append(FIELD_MAP.email, email); 373 payload.append(FIELD_MAP.phone, document.getElementById('gti-phone').value.trim()); 374 payload.append(FIELD_MAP.state, document.getElementById('gti-state').value); 375 payload.append(FIELD_MAP.species, species); 376 payload.append(FIELD_MAP.rivers, rivers); 377 payload.append(FIELD_MAP.experience, document.getElementById('gti-experience').value); 378 payload.append(FIELD_MAP.licensed, document.getElementById('gti-licensed').value); 379 payload.append(FIELD_MAP.affiliation, document.getElementById('gti-affiliation').value.trim()); 380 payload.append(FIELD_MAP.interest, document.getElementById('gti-interest').value.trim()); 381 payload.append(FIELD_MAP.questions, document.getElementById('gti-questions').value.trim()); 382 383 submit.disabled = true; 384 submit.textContent = 'Submittingâ¦'; 385 errEl.style.display = 'none'; 386 387 try { 388 const res = await fetch( 389 'https://api.jotform.com/form/' + FORM_ID + '/submissions?apiKey=' + encodeURIComponent(API_KEY), 390 { 391 method: 'POST', 392 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 393 body: payload 394 } 395 ); 396 const data = await res.json(); 397 if (data.responseCode === 200) { 398 showSuccess(); 399 } else { 400 console.error('JotForm rejected the submission:', data); 401 throw new Error('JotForm responseCode ' + data.responseCode); 402 } 403 } catch (err) { 404 console.error('Guide Training Interest submission error:', err); 405 submit.disabled = false; 406 submit.textContent = 'Submit Training Interest â'; 407 errEl.style.display = 'block'; 408 } 409 }); 410 411 function showSuccess() { 412 form.querySelectorAll('input, select, textarea, button').forEach(el => el.disabled = true); 413 okEl.style.display = 'block'; 414 okEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); 415 } 416 } 417 418 if (document.readyState === 'loading') { 419 document.addEventListener('DOMContentLoaded', boot); 420 } else { 421 boot(); 422 } 423})();
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.