PageSourceSearch

https://theconservationangler.org/assets/forms/guide-training.js

js theconservationangler.org collected 2026-10-04 02:17:04 UTC 21,858 bytes, 423 lines download raw bytes

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">&times;</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.