PageSourceSearch

https://countyconnection.com/wp-content/themes/cccta/js/bus-tracker.js?ver=1.0.4

js countyconnection.com collected 2026-10-01 11:38:45 UTC 12,979 bytes, 453 lines download raw bytes

1jQuery(async function ($) {
2  var bustimeAPI = '/wp-content/themes/cccta/api/bustime.php';
3  var routeList;
4  var stopList;
5  var selectedStop;
6
7  function capitalize(string) {
8    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
9  }
10
11  function formatPrediction(time) {
12    var ms = moment(time, 'YYYYMMDD HH:mm').diff(moment());
13    return Math.floor(moment.duration(ms).asMinutes()) + ' min';
14  }
15
16  async function getRouteList() {
17    try {
18      const response = await fetch('/wp-json/wp/v2/route?per_page=100');
19      if (!response.ok) {
20        throw new Error(`HTTP error! status: ${response.status}`);
21      }
22      const data = await response.json();
23      routeList = data;
24    } catch (error) {
25      console.log(error);
26    }
27  }
28
29  async function getRoutes() {
30    try {
31      const params = new URLSearchParams({ command: 'getroutes' });
32      const response = await fetch(`${bustimeAPI}?${params}`);
33      if (!response.ok) {
34        throw new Error(`HTTP error! status: ${response.status}`);
35      }
36      const data = await response.json();
37      
38      if (data['bustime-response'].error) {
39        return console.error(data['bustime-response'].error)
40      }
41      var routes = data['bustime-response'].routes;
42      var options = routes.map(function (route) {
43        return $('<option>')
44          .text('Route ' + route.rt)
45          .attr('value', route.rt);
46      });
47
48      $('#real-time-arrival-route option').not(':first-child').remove();
49      $('#real-time-arrival-route')
50        .append(options)
51        .prop('disabled', false);
52
53      clearDirections();
54      clearStops();
55    } catch (error) {
56      console.log(error);
57    }
58  }
59
60  async function getStopList() {
61
62    const mapRowsToObjects = ({ fields, rows }) =>
63      rows.map(row => Object.fromEntries(fields.map((field, index) => [field, row[index]])))
64  
65    try {
66    const response = await fetch('/data/stops.json');
67      stopList = mapRowsToObjects(await response.json());
68    } catch (error) {
69      console.log(error);
70    }
71    const parentStations = _.compact(_.uniqBy(stopList, stop => stop.parent_station).map(stop => stop.parent_station));
72    const source = _.sortBy(stopList, stop => stop.is_parent_station)
73      .map(stop => {
74        const stopDisplayId = parentStations.includes(stop.stop_id) ? 'All' : stop.stop_id;
75
76        return {
77          name: `${stop.stop_name} (${stopDisplayId})`,
78          id: stop.stop_id
79        }
80      })
81
82    $('#real-time-arrival-stop-id').typeahead({
83      source,
84      autoSelect: true,
85      afterSelect: currentItem => {
86        selectedStop = currentItem;
87        $('#real-time-arrival-stop-id-form').submit()
88      },
89      items: 15
90    });
91  }
92
93  async function getDirections(routeId) {
94    try {
95      const params = new URLSearchParams({
96        command: 'getdirections',
97        rt: routeId
98      });
99      const response = await fetch(`${bustimeAPI}?${params}`);
100      if (!response.ok) {
101        throw new Error(`HTTP error! status: ${response.status}`);
102      }
103      const data = await response.json();
104      
105      clearDirections();
106
107      if (data['bustime-response'].error) {
108        return console.error(data['bustime-response'].error)
109      }
110      var directions = data['bustime-response'].directions;
111      var options = directions.map(function (direction) {
112        return $('<option>')
113          .text(capitalize(direction.name.toLowerCase()))
114          .attr('value', direction.name);
115      });
116
117      $('#real-time-arrival-direction')
118        .append(options)
119        .prop('disabled', false);
120
121      clearStops();
122
123      if (directions.length === 1) {
124        $('#real-time-arrival-direction')
125          .val(directions[0].name)
126          .trigger('change');
127      }
128    } catch (error) {
129      console.log(error);
130    }
131  }
132
133  async function getStops(routeId, directionId) {
134    try {
135      const params = new URLSearchParams({
136        command: 'getstops',
137        rt: routeId,
138        dir: directionId
139      });
140      const response = await fetch(`${bustimeAPI}?${params}`);
141      if (!response.ok) {
142        throw new Error(`HTTP error! status: ${response.status}`);
143      }
144      const data = await response.json();
145      
146      clearStops();
147
148      if (data['bustime-response'].error) {
149        return console.error(data['bustime-response'].error)
150      }
151
152      var stops = data['bustime-response'].stops;
153      var options = stops.map(function (stop) {
154        return $('<option>')
155          .text(stop.stpnm)
156          .attr('value', stop.stpid);
157      });
158
159      $('#real-time-arrival-stop')
160        .append(options)
161        .prop('disabled', false);
162    } catch (error) {
163      console.log(error);
164    }
165  }
166
167  function showResults(results) {
168    $('#real-time-arrival-results').empty()
169      .append(results);
170  }
171
172  async function getPredictions(routeId, directionId, stopIds) {
173    async function fetchPredictions(routeId, directionId, stopIdsToFetch) {
174      const params = new URLSearchParams({
175        command: 'getpredictions'
176      });
177  
178      if (routeId) {
179        params.set('rt', routeId);
180      }
181      if (directionId) {
182        params.set('dir', directionId);
183      }
184      if (stopIdsToFetch.length > 0) {
185        params.set('stpid', stopIdsToFetch.join(','));
186      }
187  
188      const response = await fetch(`${bustimeAPI}?${params}`);
189      if (!response.ok) {
190        throw new Error(`HTTP error! status: ${response.status}`);
191      }
192      return await response.json();
193    }
194  
195    $('#refresh-results .fa-sync-alt').addClass('fa-spin');
196    $('.real-time-arrival-prediction').each(function () {
197      $(this).addClass('stale');
198    });
199
200    try {
201      // Split stopIds into chunks of 10
202      const stopIdChunks = [];
203      const stopIdsCopy = [...stopIds];
204      while (stopIdsCopy.length > 0) {
205        stopIdChunks.push(stopIdsCopy.splice(0, 10));
206      }
207
208      const results = await Promise.all(stopIdChunks.map(stopIdChunk => 
209        fetchPredictions(routeId, directionId, stopIdChunk)
210      ));
211
212      const predictions = [];
213      const errors = [];
214
215      results.forEach(data => {
216        if (!data?.['bustime-response']) {
217          errors.push({ msg: 'Invalid response from server' });
218          return;
219        }
220
221        if (data['bustime-response'].error) {
222          errors.push(...data['bustime-response'].error);
223        }
224
225        if (data['bustime-response'].prd) {
226          predictions.push(...data['bustime-response'].prd);
227        }
228      });
229
230      $('#refresh-results .fa-sync-alt').removeClass('fa-spin');
231      renderPredictions(routeId, predictions, errors);
232    } catch (error) {
233      console.error('Error fetching predictions:', error);
234      showResults('Unable to fetch predictions. Please try again.');
235      $('#refresh-results .fa-sync-alt').removeClass('fa-spin');
236    }
237  }
238
239  function renderPredictions(routeId, predictions, errors) {
240    if (!predictions.length) {
241      var results = 'No predictions available.';
242
243      if (errors.length > 0) {
244        results = _.uniq(errors.map(error => error.msg)).join(', ')
245      }
246
247      showResults(results);
248      return;
249    }
250
251    predictions.forEach(prediction => {
252      const routeInfo = _.find(routeList, {
253        route_short_name: prediction.rt
254      });
255      if (routeInfo) {
256        prediction.route_short_name = routeInfo.route_short_name;
257        prediction.route_color = routeInfo.route_color;
258        prediction.route_text_color = routeInfo.route_text_color;
259      }
260    });
261
262    var futurePredictions = predictions.filter(prediction => {
263      return moment(prediction.prdtm, 'YYYYMMDD HH:mm').diff(moment()) >= 0;
264    })
265
266    var results = _.sortBy(futurePredictions, prediction => {
267      return parseInt(prediction.route_short_name, 10);
268    }).map(prediction => {
269      var routeCircle = '';
270
271      if (prediction.route_short_name) {
272        routeCircle = $('<a>')
273          .attr('href', `/routes/${prediction.route_short_name}`)
274          .addClass('route-circle route-circle-medium')
275          .css('background-color', prediction.route_color)
276          .css('color', prediction.route_text_color)
277          .text(prediction.route_short_name);
278      }
279
280      return $('<div>')
281        .addClass('row no-gutters real-time-arrival-prediction')
282        .append($('<div>')
283          .addClass('col-sm-9 col-8 d-flex align-items-center')
284          .append(routeCircle)
285          .append($('<span>')
286            .addClass('font-weight-bold')
287            .text(prediction.des))
288        )
289        .append($('<div>')
290          .addClass('font-weight-bold col-sm-3 col-4 d-flex align-items-center justify-content-end')
291          .text(formatPrediction(prediction.prdtm)));
292    });
293
294    var headers = $('<div>')
295      .addClass('row no-gutters mb-1')
296      .append($('<div>')
297        .addClass('real-time-arrival-label col-sm-9 col-8 d-flex align-items-end')
298        .text('ROUTE')
299      )
300      .append($('<div>')
301        .addClass('real-time-arrival-label col-sm-3 col-4 text-right')
302        .text('ESTIMATED ARRIVAL')
303      )
304
305    results.unshift(headers);
306
307    if (_.uniqBy(predictions, 'stpid').length === 1) {
308      results.unshift($('<div>').addClass('real-time-arrival-label').text('Stop ID: ' + predictions[0].stpid));
309    }
310
311    if (routeId) {
312      var direction = $('#real-time-arrival-direction option:selected').text();
313      results.unshift($('<div>').addClass('real-time-arrival-label').text('Direction: ' + direction));
314    }
315
316    results.unshift(
317      $('<div class="d-flex justify-content-between">')
318      .append([
319        $('<h4>').addClass('mt-2 mb-0').text(predictions[0].stpnm),
320        $('<a class="results-time" href="#" id="refresh-results">').append(`<i class="fas fa-sync-alt"></i> ${moment().format('h:mm A')}`)
321      ])
322    );
323
324    showResults(results);
325  }
326
327  function resetRoutes() {
328    $('#real-time-arrival-route option').first().prop('selected', true);
329  }
330
331  function clearDirections() {
332    $('#real-time-arrival-direction option').not(':first-child').remove();
333    $('#real-time-arrival-direction').prop('disabled', true);
334  }
335
336  function clearStops() {
337    $('#real-time-arrival-stop option').not(':first-child').remove();
338    $('#real-time-arrival-stop').prop('disabled', true);
339    clearResults();
340  }
341
342  function clearResults() {
343    $('#real-time-arrival-results').empty();
344  }
345
346  $('#real-time-arrival-route').change(function () {
347    var routeId = $('#real-time-arrival-route').val();
348
349    if (routeId === '') {
350      clearDirections();
351      clearStops();
352      return;
353    }
354
355    getDirections(routeId);
356  });
357
358  $('#real-time-arrival-direction').change(function () {
359    var routeId = $('#real-time-arrival-route').val();
360    var directionId = $('#real-time-arrival-direction').val();
361
362    if (directionId === '') {
363      clearStops();
364      return;
365    }
366
367    getStops(routeId, directionId);
368  });
369
370  $('#real-time-arrival-stop').change(function () {
371    refreshPredictions();
372  });
373
374  $('input[name="arrival_type"]').change(function () {
375    var type = $('input[name="arrival_type"]:checked').val();
376
377    $('#' + type).prop('hidden', false)
378      .siblings().prop('hidden', true);
379
380    resetRoutes();
381    clearDirections();
382    clearStops();
383    clearResults();
384  });
385
386  $('#real-time-arrival-stop-id').change(() => {
387    selectedStop = undefined;
388  })
389
390  $('#real-time-arrival-stop-id-form').submit(function (e) {
391    e.preventDefault();
392
393    var stopIds;
394
395    if (selectedStop) {
396      // Check if selected item is parent_station
397      const childStations = stopList.filter(stop => stop.parent_station === selectedStop.id);
398
399      if (childStations.length > 0) {
400        stopIds = childStations.map(stop => stop.stop_id);
401      } else {
402        stopIds = [selectedStop.id]
403      }
404    } else {
405      var stopId = $('#real-time-arrival-stop-id').val();
406
407      if (!stopId) {
408        return;
409      }
410
411      stopIds = [stopId]
412    }
413
414    getPredictions(undefined, undefined, stopIds);
415  })
416
417  $('#real-time-arrival-route-form').submit(function (e) {
418    e.preventDefault();
419    
420    var routeId = $('#real-time-arrival-route').val();
421    var directionId = $('#real-time-arrival-direction').val();
422    var stopId = $('#real-time-arrival-stop').val();
423
424    if (stopId === '') {
425      return;
426    }
427
428    getPredictions(routeId, directionId, [stopId]);
429  })
430
431  $('#real-time-arrival-results').on('click', '#refresh-results', e => {
432    e.preventDefault();
433    refreshPredictions();
434  })
435
436  function refreshPredictions() {
437    if ($('input[name="arrival_type"]:checked').val() === 'arrival-type-stop-id') {
438      $('#real-time-arrival-stop-id-form').submit();
439    } else if ($('input[name="arrival_type"]:checked').val() === 'arrival-type-route') {
440      $('#real-time-arrival-route-form').submit();
441    }
442  }
443
444  // Re-request predictions every 30 seconds
445  setInterval(() => {
446    refreshPredictions()
447  }, 30000)
448
449  // On page load
450  await getRoutes();
451  await getRouteList();
452  await getStopList();
453});

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.