1// Custom tour dates list. Pulls events from Bandsintown's public REST API and 2// renders them in the site's own style. If the request fails for any reason the 3// original Bandsintown widget is left untouched (it is only hidden on success). 4(function () { 5 "use strict"; 6 7 var ARTIST = "id_390867"; 8 var APP_ID = "js_www.oscarl.com"; 9 var API = "https://rest.bandsintown.com/artists/" + ARTIST + "/events"; 10 var MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]; 11 var DAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; 12 13 function esc(str) { 14 var div = document.createElement("div"); 15 div.textContent = str == null ? "" : String(str); 16 return div.innerHTML; 17 } 18 19 function ticketUrl(ev) { 20 var offers = ev.offers || []; 21 for (var i = 0; i < offers.length; i++) { 22 if (offers[i].type === "Tickets" && offers[i].url) return offers[i].url; 23 } 24 return ""; 25 } 26 27 function row(ev) { 28 var m = /^(\d{4})-(\d{2})-(\d{2})/.exec(ev.datetime || ""); 29 if (!m) return ""; 30 var d = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])); 31 var v = ev.venue || {}; 32 var place = [v.city, v.region && v.country === "United States" ? v.region : "", v.country].filter(Boolean).join(", "); 33 var tickets = ticketUrl(ev); 34 var past = new Date(ev.datetime) < new Date(Date.now() - 864e5); 35 var more = ev.url || "https://www.bandsintown.com/a/390867-oscar-l"; 36 var btn = past 37 ? "" 38 : (tickets 39 ? '<a class="tour-btn tour-btn-solid" href="' + esc(tickets) + '" target="_blank" rel="noopener">TICKETS</a>' 40 : '<a class="tour-btn" href="' + esc(more) + '" target="_blank" rel="noopener">INFO</a>'); 41 return ( 42 '<div class="tour-row">' + 43 '<div class="tour-date"><span class="tour-day">' + m[3] + '</span>' + 44 '<span class="tour-month">' + MONTHS[d.getUTCMonth()] + (past ? " " + m[1] : "") + '</span></div>' + 45 '<div class="tour-info"><span class="tour-venue">' + esc(v.name || ev.title || "") + '</span>' + 46 '<span class="tour-place">' + esc(DAYS[d.getUTCDay()]) + ' · ' + esc(place) + '</span></div>' + 47 '<div class="tour-actions">' + btn + '</div>' + 48 '</div>' 49 ); 50 } 51 52 function load(kind) { 53 return fetch(API + "?app_id=" + APP_ID + "&date=" + kind) 54 .then(function (r) { 55 if (!r.ok) throw new Error("HTTP " + r.status); 56 return r.json(); 57 }) 58 .then(function (list) { 59 if (!Array.isArray(list)) throw new Error("bad payload"); 60 return list; 61 }); 62 } 63 64 function init() { 65 var box = document.getElementById("tour-list"); 66 if (!box) return; 67 var wrap = box.parentNode; 68 var pastCache = null; 69 70 load("upcoming").then(function (upcoming) { 71 box.innerHTML = 72 '<div class="tour-tabs"><button type="button" class="tour-tab is-active" data-k="up">UPCOMING</button>' + 73 '<button type="button" class="tour-tab" data-k="past">PAST</button></div>' + 74 '<div class="tour-rows" id="tour-rows"></div>'; 75 var rows = document.getElementById("tour-rows"); 76 77 function show(list, empty) { 78 rows.innerHTML = list.length ? list.map(row).join("") : '<p class="tour-empty">' + empty + '</p>'; 79 } 80 show(upcoming, "No upcoming dates right now. Follow on Bandsintown to get notified."); 81 wrap.classList.add("tour-custom"); 82 83 var link = '<p class="tour-empty">More past dates on <a class="tour-link" href="https://www.bandsintown.com/a/390867-oscar-l?came_from=267&utm_medium=web&utm_source=oscarl.com&past=true" target="_blank" rel="noopener">Bandsintown</a>.</p>'; 84 var pageSize = 10; 85 86 function showPast(page) { 87 var total = pastCache.length; 88 var pages = Math.max(1, Math.ceil(total / pageSize)); 89 page = Math.min(Math.max(1, page), pages); 90 var slice = pastCache.slice((page - 1) * pageSize, page * pageSize); 91 var opts = [5, 10, 25, 50].map(function (n) { 92 return '<option value="' + n + '"' + (n === pageSize ? " selected" : "") + '>
92' + n + '</option>'; 93 }).join(""); 94 rows.innerHTML = slice.map(row).join("") + 95 '<div class="tour-pager">' + 96 '<label class="tour-per">Per page <select class="tour-select">' + opts + '</select></label>' + 97 '<span class="tour-nav">' + 98 '<button type="button" class="tour-page-btn" data-p="' + (page - 1) + '"' + (page <= 1 ? " disabled" : "") + '>← PREV</button>' + 99 '<span class="tour-count">' + page + ' / ' + pages + ' <em>(' + total + ' shows)</em></span>' + 100 '<button type="button" class="tour-page-btn" data-p="' + (page + 1) + '"' + (page >= pages ? " disabled" : "") + '>NEXT →</button>' + 101 '</span></div>' + link; 102 } 103 104 rows.addEventListener("click", function (e) { 105 var b = e.target.closest ? e.target.closest(".tour-page-btn") : null; 106 if (!b || b.disabled) return; 107 showPast(+b.getAttribute("data-p")); 108 box.scrollIntoView({ behavior: "smooth", block: "start" }); 109 }); 110 rows.addEventListener("change", function (e) { 111 if (!e.target.classList.contains("tour-select")) return; 112 pageSize = +e.target.value; 113 showPast(1); 114 }); 115 116 box.addEventListener("click", function (e) { 117 var t = e.target; 118 if (!t.classList || !t.classList.contains("tour-tab")) return; 119 var tabs = box.querySelectorAll(".tour-tab"); 120 for (var i = 0; i < tabs.length; i++) tabs[i].classList.remove("is-active"); 121 t.classList.add("is-active"); 122 if (t.getAttribute("data-k") === "up") return show(upcoming, "No upcoming dates right now."); 123 if (pastCache) return showPast(1); 124 rows.innerHTML = '<p class="tour-empty">Loadingâ¦</p>'; '<p class="tour-empty">More past dates on <a class="tour-link" href="https://www.bandsintown.com/a/390867-oscar-l?came_from=267&utm_medium=web&utm_source=oscarl.com&past=true" target="_blank" rel="noopener">Bandsintown</a>.</p>'; 125 // Bandsintown's API returns no past dates for this app id, so past shows come from 126 // tour-history.json (kept up to date daily by scripts/update_tour_history.py). 127 fetch("tour-history.json", { cache: "no-store" }) 128 .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); }) 129 .then(function (d) { 130 var today = new Date().toISOString().slice(0, 10); 131 var list = ((d && d.events) || []) 132 .filter(function (e) { return (e.datetime || "").slice(0, 10) < today; }) 133 .map(function (e) { 134 return { datetime: e.datetime, url: e.url, offers: [], 135 venue: { name: e.venue, city: e.city, region: e.region, country: e.country } }; 136 }); 137 pastCache = list; 138 if (!list.length) { rows.innerHTML = link; return; } 139 showPast(1); 140 }) 141 .catch(function () { rows.innerHTML = link; }); 142 }); 143 }).catch(function () { /* keep the Bandsintown widget as is */ }); 144 } 145 146 if (document.readyState === "loading") { 147 document.addEventListener("DOMContentLoaded", init); 148 } else { 149 init(); 150 } 151})();
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.