PageSourceSearch

https://pittsburghbaseballnow.com/wp-content/plugins/mlb-press-box/assets/js/press-box-mlb.js?ver=1.1.3

js pittsburghbaseballnow.com collected 2026-10-02 05:03:31 UTC 9,487 bytes, 297 lines download raw bytes

1/**
2 * Press Box MLB — Front-end JavaScript
3 *
4 * Vanilla JS IIFE. Key behaviors:
5 * - UTC time conversion: [data-utc] elements → visitor's local timezone
6 * - Tab switching: standings + roster with localStorage persistence
7 * - Sortable table headers on roster
8 */
9(function () {
10	'use strict';
11
12	/**
13	 * Convert UTC timestamps to local timezone.
14	 */
15	function convertUtcTimes() {
16		var elements = document.querySelectorAll('[data-utc]');
17		elements.forEach(function (el) {
18			var utc = el.getAttribute('data-utc');
19			if (!utc) return;
20
21			var date = new Date(utc);
22			if (isNaN(date.getTime())) return;
23
24			var format = el.getAttribute('data-format') || 'time';
25			var options;
26
27			if (format === 'date') {
28				options = { month: 'short', day: 'numeric' };
29			} else if (format === 'datetime') {
30				options = { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' };
31			} else {
32				options = { hour: 'numeric', minute: '2-digit' };
33			}
34
35			el.textContent = date.toLocaleString(undefined, options);
36			el.removeAttribute('data-utc');
37		});
38	}
39
40	/**
41	 * Fetch a Team Tag panel's rendered HTML from REST and inject it.
42	 * Runs once per panel; subsequent tab clicks are no-ops.
43	 */
44	function loadLazyPanel(panel) {
45		var api = window.pbMlbApi;
46		if (!api || !api.root) return;
47
48		var slug = panel.getAttribute('data-pb-mlb-lazy');
49		var team = panel.getAttribute('data-pb-mlb-team');
50		if (!slug || !team) return;
51
52		// Prevent duplicate fetches (handles rapid tab-click toggling).
53		panel.removeAttribute('data-pb-mlb-lazy');
54
55		var url = api.root + 'team-tag/panel?panel=' + encodeURIComponent(slug) + '&team=' + encodeURIComponent(team);
56		fetch(url, { credentials: 'same-origin' })
57			.then(function (r) { return r.json(); })
58			.then(function (res) {
59				if (res && res.success && res.data && res.data.html) {
60					panel.innerHTML = res.data.html;
61					convertUtcTimes();
62					initTabs();
63					initSortableTables();
64					initHeadshotFallbacks();
65				} else {
66					var msg = (res && res.error && res.error.message) ? res.error.message : 'Unable to load.';
67					var safe = document.createElement('p');
68					safe.className = 'pb-mlb-team-tag__lazy-error';
69					safe.textContent = msg;
70					panel.innerHTML = '';
71					panel.appendChild(safe);
72				}
73			})
74			.catch(function () {
75				var safe = document.createElement('p');
76				safe.className = 'pb-mlb-team-tag__lazy-error';
77				safe.textContent = 'Unable to load.';
78				panel.innerHTML = '';
79				panel.appendChild(safe);
80				panel.setAttribute('data-pb-mlb-lazy', slug);
81			});
82	}
83
84	/**
85	 * Tab switching with localStorage persistence.
86	 */
87	function initTabs() {
88		var tabGroups = document.querySelectorAll('[data-pb-mlb-tabs]');
89		tabGroups.forEach(function (group) {
90			if (group.dataset.pbTabsBound) return;
91			group.dataset.pbTabsBound = '1';
92			var groupId = group.getAttribute('data-pb-mlb-tabs');
93			var buttons = group.querySelectorAll('[data-pb-mlb-tab]');
94			// Only match panels that are direct children of the same parent,
95			// not nested panels inside sub-templates (standings, roster tabs).
96			var parent = group.parentElement;
97			var panels = Array.prototype.filter.call(
98				parent.querySelectorAll('[data-pb-mlb-panel]'),
99				function (p) { return p.parentElement === parent; }
100			);
101
102			// Restore saved tab — skip for team-tag (always opens on Today).
103			if (groupId !== 'team-tag') {
104				var saved = localStorage.getItem('pb_mlb_tab_' + groupId);
105				if (saved) {
106					activateTab(saved);
107				}
108			}
109
110			buttons.forEach(function (btn) {
111				btn.addEventListener('click', function () {
112					var tab = btn.getAttribute('data-pb-mlb-tab');
113					activateTab(tab);
114					if (groupId !== 'team-tag') {
115						localStorage.setItem('pb_mlb_tab_' + groupId, tab);
116					}
117				});
118			});
119
120			function activateTab(tab) {
121				var activeBtn = null;
122				buttons.forEach(function (b) {
123					var isActive = b.getAttribute('data-pb-mlb-tab') === tab;
124					b.classList.toggle('pb-mlb-tab--active', isActive);
125					if (isActive) activeBtn = b;
126				});
127				panels.forEach(function (p) {
128					var active = p.getAttribute('data-pb-mlb-panel') === tab;
129					p.style.display = active ? '' : 'none';
130					if (active && p.hasAttribute('data-pb-mlb-lazy')) {
131						loadLazyPanel(p);
132					}
133				});
134				// Roster tab filters rows within a single shared table rather
135				// than swapping panels, so adjust row visibility + column layout.
136				if (groupId === 'roster' && activeBtn) {
137					var table = parent.querySelector('.pb-mlb-roster__table');
138					if (table) {
139						var layout = activeBtn.getAttribute('data-pb-mlb-roster-layout') || 'hitting';
140						table.setAttribute('data-pb-mlb-roster-layout', layout);
141						var rows = table.querySelectorAll('.pb-mlb-roster__row');
142						rows.forEach(function (row) {
143							row.hidden = (tab !== 'all') && (row.getAttribute('data-pb-mlb-position-group') !== tab);
144						});
145					}
146				}
147			}
148		});
149	}
150
151	/**
152	 * Initialize sortable table headers.
153	 *
154	 * Headers with data-sort="key" become clickable.
155	 * Cells use data-sort-{key}="value" attributes for sort values,
156	 * falling back to textContent if missing.
157	 * data-sort-type="text" on the th forces alphabetical sorting (default: numeric).
158	 * data-sort-default-dir="desc" sets the initial direction for first click.
159	 */
160	function initSortableTables() {
161		var tables = document.querySelectorAll('.pb-mlb-roster__table');
162
163		tables.forEach(function (table) {
164			if (table.dataset.pbSortBound) return;
165			table.dataset.pbSortBound = '1';
166
167			var headers = table.querySelectorAll('th[data-sort]');
168			if (!headers.length) return;
169
170			var thead = table.querySelector('thead');
171			var tbody = table.querySelector('tbody');
172			if (!thead || !tbody) return;
173
174			var allTh = thead.querySelectorAll('th');
175			var colIndex = {};
176			allTh.forEach(function (th, idx) {
177				var key = th.getAttribute('data-sort');
178				if (key) colIndex[key] = idx;
179			});
180
181			function isEmptyVal(v) {
182				var trimmed = (v || '').replace(/\s+/g, ' ').trim();
183				return trimmed === '' || trimmed === '\u2014';
184			}
185
186			function handleSort(th) {
187				var key = th.getAttribute('data-sort');
188				var idx = colIndex[key];
189				if (idx === undefined) return;
190
191				var currentDir = th.getAttribute('data-sort-dir');
192				var defaultDir = th.getAttribute('data-sort-default-dir') || 'asc';
193				var dir = currentDir ? (currentDir === 'asc' ? 'desc' : 'asc') : defaultDir;
194
195				headers.forEach(function (h) {
196					h.classList.remove('pb-mlb-sortable__th--sorted');
197					h.removeAttribute('data-sort-dir');
198					h.setAttribute('aria-sort', 'none');
199				});
200
201				th.classList.add('pb-mlb-sortable__th--sorted');
202				th.setAttribute('data-sort-dir', dir);
203				th.setAttribute('aria-sort', dir === 'asc' ? 'ascending' : 'descending');
204
205				var attrName = 'data-sort-' + key;
206				var isText = th.getAttribute('data-sort-type') === 'text';
207
208				// Pre-read sort values once per row to avoid repeated DOM lookups.
209				var entries = Array.prototype.map.call(tbody.querySelectorAll('tr'), function (row) {
210					var cell = row.children[idx];
211					var val = cell ? (cell.getAttribute(attrName) || cell.textContent || '') : '';
212					return { row: row, val: val };
213				});
214
215				entries.sort(function (a, b) {
216					var aEmpty = isEmptyVal(a.val);
217					var bEmpty = isEmptyVal(b.val);
218					if (aEmpty && bEmpty) return 0;
219					if (aEmpty) return 1;
220					if (bEmpty) return -1;
221
222					var cmp;
223					if (isText) {
224						cmp = a.val.localeCompare(b.val);
225					} else {
226						cmp = parseFloat(a.val) - parseFloat(b.val);
227					}
228					return dir === 'asc' ? cmp : -cmp;
229				});
230
231				entries.forEach(function (e) { tbody.appendChild(e.row); });
232			}
233
234			headers.forEach(function (th) {
235				th.setAttribute('role', 'button');
236				th.setAttribute('tabindex', '0');
237				th.setAttribute('aria-sort', 'none');
238
239				th.addEventListener('click', function () { handleSort(th); });
240				th.addEventListener('keydown', function (e) {
241					if (e.key === 'Enter' || e.key === ' ') {
242						e.preventDefault();
243						handleSort(th);
244					}
245				});
246			});
247		});
248	}
249
250	/**
251	 * Replace broken headshot images with the MLB logo silhouette.
252	 */
253	function initHeadshotFallbacks() {
254		function applyFallback(img) {
255			img.onerror = null;
256			img.style.display = 'none';
257			// Insert a CSS circle placeholder if one doesn't already exist.
258			if (!img.nextElementSibling || !img.nextElementSibling.classList.contains('pb-mlb-headshot-fallback')) {
259				var circle = document.createElement('span');
260				circle.className = 'pb-mlb-headshot-fallback';
261				circle.style.display = 'inline-block';
262				circle.style.width = (img.width || 48) + 'px';
263				circle.style.height = (img.height || 48) + 'px';
264				circle.style.borderRadius = '50%';
265				circle.style.background = '#002D72';
266				circle.style.flexShrink = '0';
267				img.parentNode.insertBefore(circle, img.nextSibling);
268			}
269		}
270
271		var imgs = document.querySelectorAll('img[src*="mlb-photos"][src*="headshot"]');
272		imgs.forEach(function (img) {
273			if (img.dataset.pbFallbackBound) return;
274			img.dataset.pbFallbackBound = '1';
275			img.addEventListener('error', function () { applyFallback(img); });
276			if (img.complete && img.naturalWidth === 0) {
277				applyFallback(img);
278			}
279		});
280	}
281
282	/**
283	 * Initialize on DOM ready.
284	 */
285	function init() {
286		convertUtcTimes();
287		initTabs();
288		initSortableTables();
289		initHeadshotFallbacks();
290	}
291
292	if (document.readyState === 'loading') {
293		document.addEventListener('DOMContentLoaded', init);
294	} else {
295		init();
296	}
297})();

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.