1/** 2 * Events Calendar - Homepage section ----------------------------------------------------------------- 3 * Functionality for events calendar on homepage in section - events-calendar 4 */ 5let timezone = "America/Chicago"; // Timezone From Settings 6if (typeof jTimezone !== "undefined") { 7 timezone = jTimezone; 8} 9let today = new Date(); 10let todayClone = new Date(today); 11let currentMonth = today.getMonth(); 12let currentYear = today.getFullYear(); 13let firstDayOfCalendar = setFirstDayOfCalendar(todayClone, currentMonth, currentYear); 14let lastDayOfCalendar = setLastDayOfCalendar(todayClone, currentMonth, currentYear); 15let firstDayOfMonth = new Date(todayClone.setFullYear(currentYear, currentMonth, 1)); 16let lastDayOfMonth = new Date(todayClone.setFullYear(currentYear, currentMonth + 1, 0)); 17let todayDay = today.getDate(); 18let todayMonth = today.getMonth(); 19let todayYear = today.getFullYear(); 20let events = []; 21// let eventCategories = []; 22let eventCategory = null; 23let monthAndYear = ""; 24 25let eventsList, eventsListContainer, eventsListDate, calendarBodyTable; 26 27eventsList = waitForElement("#events-calendar-list"); 28eventsListContainer = waitForElement("#eventsCalendarListContainerContent"); 29eventsListDate = waitForElement("#events-calendar-list-date"); 30calendarBodyTable = waitForElement("#calendar-body"); 31 32var eventsListEmpty = "No content."; 33var calendar = document.getElementById("events-calendar-table"); 34 35var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
35November", "December"]; 36var dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 37 38var dayNamesContent = "<tr>"; 39dayNames.forEach((name) => { 40 dayNamesContent += `<th class="text-center text-capitalize" data-days="${name}">${name}</th>`; 41}); 42dayNamesContent += "</tr>"; 43if ((jActiveHomepageSections && jActiveHomepageSections.includes("Events-calendar") && window.location.pathname == "/") || window.location.pathname.includes("customizer")) { 44 let headMonth = document.getElementById("thead-month"); 45 if (headMonth !== null) { 46 headMonth.innerHTML = dayNamesContent; 47 monthAndYear = document.getElementById("monthAndYearTitle"); 48 } 49} 50 51// Handle next month 52async function eventCalendarNext() { 53 currentYear = currentMonth === 11 ? currentYear + 1 : currentYear; 54 currentMonth = (currentMonth + 1) % 12; 55 let cloneDate = new Date(today); 56 firstDayOfCalendar = setFirstDayOfCalendar(cloneDate, currentMonth, currentYear); 57 lastDayOfCalendar = setLastDayOfCalendar(cloneDate, currentMonth, currentYear); 58 firstDayOfMonth = new Date(cloneDate.setFullYear(currentYear, currentMonth, 1)); 59 lastDayOfMonth = new Date(cloneDate.setFullYear(currentYear, currentMonth + 1, 0)); 60 events = await getEvents(eventCategory); 61 showCalendar(currentMonth, currentYear); 62} 63 64// Handle previous month 65async function eventCalendarPrev() { 66 currentYear = currentMonth === 0 ? currentYear - 1 : currentYear; 67 currentMonth = currentMonth === 0 ? 11 : currentMonth - 1; 68 let cloneDate = new Date(today); 69 firstDayOfCalendar = setFirstDayOfCalendar(cloneDate, currentMonth, currentYear); 70 lastDayOfCalendar = setLastDayOfCalendar(cloneDate, currentMonth, currentYear); 71 firstDayOfMonth = new Date(cloneDate.setFullYear(currentYear, currentMonth, 1)); 72 lastDayOfMonth = new Date(cloneDate.setFullYear(currentYear, currentMonth + 1, 0)); 73 events = await getEvents(eventCategory); 74 showCalendar(currentMonth, currentYear); 75} 76 77// Show Calendar 78async function showCalendar(month, year) { 79 // console.log(events) 80 var firstDay = new Date(year, month).getDay(); 81 let tbl = document.getElementById("calendar-body"); 82 // console.log(tbl) 83 tbl.innerHTML = ""; 84 monthAndYear.innerHTML = months[month] + " " + year; 85 let calendarRows = getCalendarRows(); 86 // console.log(calendarRows) 87 // Create all calendar items 88 89 calendarRows.forEach((calendarRow) => { 90 var row = document.createElement("tr"); 91 calendarRow.forEach((day) => { 92 let cell = document.createElement("td"); 93 cell.setAttribute("role", "gridcell"); 94 cell.setAttribute("tabindex", "0"); 95 cell.setAttribute("aria-label", `${months[day.getMonth()]} ${day.getDate()}, ${day.getFullYear()}`); 96 97 if (isDateOfPreviousMonth(day)) { 98 cell.classList.add("previous-month"); 99 cell.setAttribute("aria-disabled", "true"); 100 cell.setAttribute("tabindex", "-1"); 101 } 102 103 if (isDateOfNextMonth(day)) { 104 cell.classList.add("next-month"); 105 cell.setAttribute("aria-disabled", "true"); 106 cell.setAttribute("tabindex", "-1"); 107 } 108 109 // check if calendar date has events 110 let dayEvents = getEventOnDay(day); 111 112 let div = document.createElement("div"); 113 div.className = "text-center"; 114 115 let todayCloneForCalendarCell = new Date(today); 116 todayCloneForCalendarCell.setHours(0, 0, 0, 0); 117 if (todayCloneForCalendarCell.getTime() === day.getTime()) { 118 cell.classList.add("tw-calendar-day-active"); 119 cell.setAttribute("aria-current", "date"); 120 } 121 122 if (dayEvents.length) { 123 cell.classList.add("event-item-btn"); 124 cell.setAttribute("aria-selected", "false"); 125 div.classList.add("cursor-pointer"); 126 div.innerHTML = `<div class="text-center">${day.getDate()}</div>`; 127 128 let moreEvents = 0; 129 130 dayEvents.forEach((event, index) => { 131 if (index > 1) { 132 moreEvents += 1; 133 } else { 134 let eventStartDate = new Date( 135 new Date(event.starts_at).toLocaleString("en", { 136 timeZone: timezone, 137 }), 138 ); 139 140 let timeString = eventStartDate
141 .toLocaleString("en-US", { 142 hour: "numeric", 143 minute: "numeric", 144 hour12: true, 145 }) 146 .replace(" AM", "am") 147 .replace(" PM", "pm"); 148 div.innerHTML += `<div class="tw-calendar-day-event-title ps-1">${timeString} ${event.name}</div>`; 149 } 150 }); 151 152 if (moreEvents) { 153 div.innerHTML += `<div class="text-start ps-1">+${moreEvents} more</div>`; 154 } 155 cell.setAttribute("aria-label", `${months[day.getMonth()]} ${day.getDate()}, ${day.getFullYear()}, ${dayEvents.length} event${dayEvents.length === 1 ? "" : "s"}`); 156 } else { 157 div.innerText = day.getDate(); 158 cell.setAttribute("aria-label", `${months[day.getMonth()]} ${day.getDate()}, ${day.getFullYear()}, no events`); 159 } 160 161 cell.appendChild(div); 162 163 cell.setAttribute("data-date", day); 164 cell.setAttribute("data-month", day.getMonth() + 1); 165 cell.setAttribute("data-year", day.getFullYear()); 166 cell.setAttribute("data-month_name", months[day.getMonth()]); 167 168 cell.addEventListener("keydown", (event) => { 169 if (event.key === "Enter" || event.key === " ") { 170 event.preventDefault(); 171 cell.click(); 172 } 173 }); 174 175 row.appendChild(cell); 176 }); 177 if (tbl) { 178 tbl.appendChild(row); 179 } 180 }); 181} 182 183function getCalendarRows() { 184 let calendarDays = getDaysBetweenDates(firstDayOfCalendar, lastDayOfCalendar); 185 186 let rows = []; 187 188 while (calendarDays.length > 0) { 189 rows.push(calendarDays.splice(0, 7)); 190 } 191 192 return rows; 193} 194 195function setFirstDayOfCalendar(date, month, year) { 196 let cloneDate = new Date(date); 197 let firstDayOfMonth = new Date(cloneDate.setFullYear(year, month, 1)); 198 return getFirstDayOfWeek(firstDayOfMonth, 0); 199} 200 201function setLastDayOfCalendar(date, month, year) { 202 let cloneDate = new Date(date); 203 let lastDayOfMonth = new Date(cloneDate.setFullYear(year, month + 1, 0)); 204 return getLastDayOfWeek(lastDayOfMonth, 6); 205} 206 207function getFirstDayOfWeek(dateObject, firstDayOfWeekIndex) { 208 const dayOfWeek = dateObject.getDay(), 209 firstDayOfWeek = new Date(dateObject), 210 diff = dayOfWeek >= firstDayOfWeekIndex ? dayOfWeek - firstDayOfWeekIndex : 6 - dayOfWeek; 211 212 firstDayOfWeek.setDate(dateObject.getDate() - diff); 213 firstDayOfWeek.setHours(0, 0, 0, 0); 214 215 return new Date(firstDayOfWeek); 216} 217 218function getLastDayOfWeek(dateObject, lastDayOfWeekIndex) { 219 const dayOfWeek = dateObject.getDay(), 220 lastDayOfWeek = new Date(dateObject), 221 diff = dayOfWeek >= lastDayOfWeekIndex ? dayOfWeek - lastDayOfWeekIndex : 6 - dayOfWeek; 222 223 lastDayOfWeek.setDate(dateObject.getDate() + diff); 224 lastDayOfWeek.setHours(0, 0, 0, 0); 225 226 return new Date(lastDayOfWeek); 227} 228 229function getDaysBetweenDates(startDate, endDate) { 230 let dates = []; 231 232 while (startDate.getTime() <= endDate.getTime()) { 233 let toPush = new Date(startDate); 234 dates.push(toPush); 235 let cloneStartDate = new Date(startDate); 236 startDate = new Date(cloneStartDate.setDate(startDate.getDate() + 1)); 237 } 238 239 return dates; 240} 241 242// check if date is of calendar current motnh 243function isDateOfCurrentMonth(date) { 244 return date.getTime() >= firstDayOfMonth.getTime() && date.getTime <= lastDayOfMonth.getTime(); 245} 246 247// check if date is from calendar previous month 248function isDateOfPreviousMonth(date) { 249 let firstDayOfMonthClone = new Date(firstDayOfMonth); 250 firstDayOfMonthClone.setHours(0, 0, 0, 0); 251 return date.getTime() < firstDayOfMonthClone.getTime(); 252} 253 254// check if date is from calendar next month 255function isDateOfNextMonth(date) { 256 let lastDayOfMonthClone = new Date(lastDayOfMonth); 257 lastDayOfMonthClone.setHours(0, 0, 0, 0); 258 return date.getTime() > lastDayOfMonthClone.getTime(); 259} 260 261// Get Events for selected period - Getting Events from HeyGov 262async function getEvents(categoryId = null, args = null) { 263 let startDate = formatDate(new Date(firstDayOfCalendar)) + "T00:00:00.000Z"; 264 let endDate = formatDate(new Date(lastDayOfCalendar)) + "T23:59:59.000Z"; 265 266 let categoryFilter = "";
267 if (categoryId && categoryId !== "0") { 268 categoryFilter += "&category=" + categoryId; 269 } 270 271 let requestUrl = `https://api.heygov.com/${jHeyGovJurisdiction}/events?`; 272 let response; 273 274 if (args) { 275 let urlSearchParamsArr = []; 276 277 if (args.date_start && args.date_end) { 278 urlSearchParamsArr.push(`date_start=${args.date_start}&date_end=${args.date_end}`); 279 } 280 281 if (args.limit) { 282 urlSearchParamsArr.push(`limit=${args.limit}`); 283 } 284 285 let urlSearchParams = urlSearchParamsArr.join("&"); 286 287 requestUrl += urlSearchParams; 288 289 response = await fetch(requestUrl + categoryFilter); 290 } else { 291 response = await fetch(requestUrl + "date_start=" + startDate + "&date_end=" + endDate + categoryFilter); 292 } 293 294 const events = await response.json(); 295 return events; 296} 297 298// Get Event Categories for jurisdiction - Getting Them from HeyGov 299async function getEventCategories() { 300 const response = await fetch("https://api.heygov.com/" + jHeyGovJurisdiction + "/events/categories"); 301 // const eventCategories = await response.json() 302 // return eventCategories; 303} 304 305// Get Events on selected day of month 306function getEventOnDay(day) { 307 let dayEvents = []; 308 let cloneDay = new Date(day); 309 cloneDay.setHours(0, 0, 0, 0); 310 events.forEach((event) => { 311 let eventStartDate = new Date( 312 new Date(event.starts_at).toLocaleString("en", { 313 timeZone: timezone, 314 }), 315 ); 316 317 eventStartDate.setHours(0, 0, 0, 0); 318 if (eventStartDate.getTime() === cloneDay.getTime()) { 319 dayEvents.push(event); 320 } 321 }); 322 return dayEvents; 323} 324 325// Set date for events list (list of day events) 326function setEventsListDate(date) { 327 let value = ""; 328 value = formatProperDate(date); 329 eventsListDate.innerHTML = value; 330} 331 332// Generate list of day events 333async function generateDayEventsList(day, month, year) { 334 let dayClone = new Date(day); 335 336 let dayEvents = getEventOnDay(dayClone); 337 338 let eventsList = ""; 339 340 if (dayEvents.length > 0) { 341 let eDate = new Date( 342 new Date(dayEvents[0].starts_at).toLocaleString("en", { 343 timeZone: timezone, 344 }), 345 ); 346 setEventsListDate(eDate); 347 } else { 348 let eDate = new Date(new Date(year, month, dayClone.getDate()).toLocaleString("en")); 349 /* let eDate = new Date( 350 new Date(year, month, day).toLocaleString("en", { 351 timeZone: "Etc/UTC" 352 }) 353 ) */ 354 setEventsListDate(eDate); 355 } 356 357 let moreEvents = 0; 358 359 dayEvents.forEach((e, index) => { 360 if (index > 1) { 361 moreEvents += 1; 362 } else { 363 let startsAt = new Date( 364 new Date(e.starts_at).toLocaleString("en", { 365 timeZone: timezone, 366 }), 367 ); 368 let endsAt = new Date( 369 new Date(e.starts_at).toLocaleString("en", { 370 timeZone: timezone, 371 }), 372 ); 373 let startTime = formatAMPM(startsAt); 374 // let endTime = formatAMPM(endsAt) 375 if (e.metadata.hide_end_time) { 376 endTime = ""; 377 } 378 379 eventsList += `<a class="d-inline-flex mb-3 w-100" href="/${jEventsPage}/?heygov-event=${e.pid}"> 380 <article class="tw-calendar-event-article bg-white w-100"> 381 <div class="row"> 382 <div class="col-3 col-md-2"> 383 <div class="tw-calendar-event-article-time"><span>${startTime}</span></div> 384 </div> 385 <div class="col"> 386 <div class="tw-calendar-event-article-title mb-3"> 387 ${e.name} 388 </div> 389 <div class="tw-calendar-event-article-venue">${e.venues.length ? e.venues[0] : ""}</div> 390 </div> 391 </div> 392 </article> 393 </a>`; 394 } 395 }); 396 397 if (moreEvents) { 398 eventsList += `<div>+${moreEvents} more event${moreEvents === 1 ? "" : "s"}</div>`; 399 } 400 401 if (eventsList == "") { 402 eventsList = eventsListEmpty; 403 } 404 eventsListContainer.then((el) => (el.innerHTML = eventsList)); 405} 406 407// Format Time to AM/PM 408const formatAMPM = (date) => { 409 let hours = date.getHours(); 410 let minutes = date.getMinutes(); 411 const ampm = hours >= 12 ? "pm" : "am"; 412 413 hours %= 12; 414 hours = hours || 12; 415 minutes = minutes < 10 ? `0${minutes}` : minutes; 416 417 const strTime = `${hours}:${minutes}${ampm}`; 418 419 return strTime; 420}; 421 422// Format Date to Sunday, January 14, 2023 423const formatProperDate = (date) => { 424 let dateFormated = ""; 425 let weekday = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][date.getDay()]; 426 let monthName = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
426November", "December"][date.getMonth()]; 427 let dateNumber = date.getDate(); 428 let yearNumber = date.getFullYear(); 429 dateFormated = weekday + ", " + monthName + " " + dateNumber + ", " + yearNumber; 430 return dateFormated; 431}; 432 433// Format date 434function formatDate(date = new Date()) { 435 const year = date.toLocaleString("default", { year: "numeric" }); 436 const month = date.toLocaleString("default", { 437 month: "2-digit", 438 }); 439 const day = date.toLocaleString("default", { day: "2-digit" }); 440 441 return [year, month, day].join("-"); 442} 443 444// Calculate days in month 445function daysInMonth(iMonth, iYear) { 446 return 32 - new Date(iYear, iMonth, 32).getDate(); 447} 448 449/** 450 * END Events Calendar - Homepage section --------------------------------------------------------------------------- 451 */ 452 453/** 454 * Meetings --------------------------------------------------------------------------------------------------------- 455 * Function is created with vanila JavaScript 456 */ 457var meetingItemsPerTable = 10; 458var searchCategory = ""; 459var dateFrom = ""; 460var dateTo = ""; 461var categories = []; 462var pager = 1; 463var pagerCategory = 0; 464 465// Fetch categories from JSON 466function fetchCategories() { 467 fetch(`/json/meetings/categories.json?sk=${Date.now()}`) 468 .then((response) => response.json()) 469 .then((data) => { 470 categories = data; 471 console.log("Categories Loaded â "); 472 if (window.location.pathname.includes(jmeetingPage)) { 473 generateMeetingTables(); 474 generateCategoriesSelect(); 475 } 476 }) 477 .catch((error) => { 478 console.error("Error loading categories â", error); 479 }); 480} 481 482// Search Meetings by Date or Category 483function searchMeetings() { 484 let categoryId = document.getElementById("categoriesSelect").value; 485 searchCategory = categoryId; 486 dateFrom = document.getElementById("dateFrom").value; 487 dateTo = document.getElementById("dateTo").value; 488 generateMeetingTables(); 489} 490 491// Generate Categories Select Input for search 492function generateCategoriesSelect() { 493 var categoriesSelect = document.getElementById("categoriesSelect"); 494 var categoriesSelectOptions = `<option value="" aria-label="All Categories">All Categories</option>`; 495 categories.forEach((category) => { 496 categoriesSelectOptions += `<option value="${category.id}" aria-label="${category.name}">${category.name}</option>`; 497 }); 498 categoriesSelect.innerHTML = categoriesSelectOptions; 499} 500 501// Load more items in table per category 502function loadMore(categoryId) { 503 if (categoryId == pagerCategory) { 504 pager = pager + 1; 505 } else { 506 pagerCategory = categoryId; 507 pager = 2; 508 } 509 var loader = document.getElementById("load-" + categoryId); 510 loader.classList.add("fa-spin"); 511 let category = categories.find((element) => element.id == categoryId); 512 category.perPage = meetingItemsPerTable * pager; 513 populateCategoryTable(category); 514} 515 516// Generate tables for categories with no data 517function generateCategoryEmptyTable(category) { 518 // Get a reference to the table element 519 var content = document.getElementById("juniperMeetings"); 520 let html = `<div id="Category-${category.id}"> 521 <h3 class="twd-category-name"><span>${category.name}</span></h3> 522 <table class="table table-striped twd-table tw-meeting-repo-table"> 523 <thead> 524 <tr>`; 525 if (jMeetingActiveColumns.includes("meeting_date")) { 526 html += `<th class="meeting-date-column">Meeting Date</th>`; 527 } 528 if (jMeetingActiveColumns.includes("agenda")) { 529 html += `<th class="agenda-column">Agenda</th>`; 530 } 531 if (jMeetingActiveColumns.includes("agenda_packet")) { 532 html += `<th class="agenda-packet-column">Agenda Packet</th>`; 533 } 534 if (jMeetingActiveColumns.includes("minutes")) { 535 html += `<th class="meeting-minutes-column">Meeting Minutes</th>`; 536 } 537 if (jMeetingActiveColumns.includes("audio")) { 538 html += `<th class="audio-column">Audio</th>`; 539 } 540 if (jMeetingActiveColumns.includes("video")) { 541 html += `<th class="video-column">Video</th>`; 542 } 543 if (jMeetingActiveColumns.includes("additional_documents")) { 544 html += `<th class="additional-documents-column">Additional Documents</th>`; 545 } 546 if (jMeetingActiveColumns.includes("notes")) { 547 html += `<th class="notes-column">Notes</th>`; 548 } 549 html += `</tr> 550 </thead> 551 <tbody id="table-category-${category.id}"> 552 553 </tbody> 554 <tfoot id="table-category-footer-${category.id}"></tfoot> 555 </table> 556 </div>`; 557 558 content.innerHTML = content.innerHTML + html; 559} 560 561// Check is meeting date in range between search dates 562function inRange(meetingDate) { 563 let show = false;
564 if (dateFrom !== "") { 565 dateF = Date.parse(dateFrom); 566 } else { 567 dateF = ""; 568 } 569 if (dateTo !== "") { 570 dateT = Date.parse(dateTo); 571 } else { 572 dateT = ""; 573 } 574 if (dateF !== "" && dateT !== "") { 575 if (dateF <= meetingDate && dateT >= meetingDate) { 576 show = true; 577 } 578 } 579 if (dateF !== "" && dateT == "") { 580 if (dateF <= meetingDate) { 581 show = true; 582 } 583 } 584 if (dateF == "" && dateT !== "") { 585 if (dateT >= meetingDate) { 586 show = true; 587 } 588 } 589 if (dateF == "" && dateT == "") { 590 show = true; 591 } 592 return show; 593} 594 595// Filtering meetings by date 596function filterDateData(data) { 597 let filteredData = []; 598 data.forEach((item) => { 599 let checkDate = Date.parse(item.meeting_date); 600 if (inRange(checkDate)) { 601 filteredData.push(item); 602 } 603 }); 604 return filteredData; 605} 606 607// Generate data for category table 608function populateCategoryTable(category) { 609 fetch(`/json/meetings/category-${category.id}.json?sk=${Date.now()}`) 610 .then((response) => response.json()) 611 .then((data) => { 612 let html = ""; 613 data = filterDateData(data); 614 // Generate table rows for the municipalities 615 for (var i = 0; i < category.perPage; i++) { 616 if (i >= data.length) { 617 break; 618 } 619 html += `<tr>`; 620 621 if (jMeetingActiveColumns.includes("meeting_date")) { 622 html += `<td class="meeting-date-column">${new Date(data[i].meeting_date).toLocaleDateString("default", { 623 dateStyle: "medium", 624 })}</td>`; 625 } 626 627 if (jMeetingActiveColumns.includes("agenda")) { 628 if (data[i].agenda_file_path) { 629 html += `<td class="agenda-column"> 630 <a href="${data[i].agenda_file_path}" target="_blank" aria-label="Download PDF Agenda for ${data[i].title} ${data[i].meeting_date}"><i class="fa-solid fa-file-pdf"></i> Agenda</a> 631 </td>`; 632 } else { 633 html += `<td class="agenda-column"></td>`; 634 } 635 } 636 637 if (jMeetingActiveColumns.includes("agenda_packet")) { 638 if (data[i].agenda_pack_file_path) { 639 html += `<td class="agenda-packet-column"> 640 <a href="${data[i].agenda_pack_file_path}" target="_blank" aria-label="Download PDF Agenda Pack for ${data[i].title} ${data[i].meeting_date}"><i class="fa-solid fa-file-pdf"></i> Packet</a> 641 </td>`; 642 } else { 643 html += `<td class="agenda-packet-column"></td>`; 644 } 645 } 646 647 if (jMeetingActiveColumns.includes("minutes")) { 648 if (data[i].meeting_minutes_file_path) { 649 html += `<td class="meeting-minutes-column"> 650 <a href="${data[i].meeting_minutes_file_path}" target="_blank" aria-label="Download PDF Minutes for ${data[i].title} ${data[i].meeting_date}"><i class="fa-solid fa-file-pdf"></i> Minutes</a> 651 </td>`; 652 } else { 653 html += `<td class="meeting-minutes-column"></td>`; 654 } 655 } 656 657 if (jMeetingActiveColumns.includes("audio")) { 658 if (data[i].audio_public_url && data[i].audio_public_url !== "") { 659 html += `<td class="audio-column"> 660 <a href="${data[i].audio_public_url}" target="_blank" aria-label="Download Audio for ${data[i].title} ${data[i].meeting_date}"><i class="fa-solid fa-file-pdf"></i> Audio</a> 661 </td>`; 662 } else { 663 html += `<td class="audio-column"></td>`; 664 } 665 } 666 667 if (jMeetingActiveColumns.includes("video")) { 668 if (data[i].video_public_url && data[i].video_public_url !== "") { 669 html += `<td class="video-column"> 670 <a href="${data[i].video_public_url}" target="_blank" aria-label="Download Video for ${data[i].title} ${data[i].meeting_date}"><i class="fa-solid fa-file-video"></i> Video</a> 671 </td>`; 672 } else { 673 html += `<td class="video-column"></td>`; 674 } 675 } 676 677 if (jMeetingActiveColumns.includes("additional_documents")) { 678 if (data[i].additional_file_path && data[i].additional_file_path !== "") { 679 html += `<td class="additional-documents-column"> 680 <a href="${data[i].additional_file_path}" target="_blank" aria-label="Download Video for ${data[i].title} ${data[i].meeting_date}"><i class="fa-solid fa-file-pdf"></i> ${data[i].additional_doc}</a> 681 </td>`; 682 } else if (data[i].additional_url && data[i].additional_url !== "") { 683 html += `<td class="additional-documents-column"> 684 <a href="${data[i].additional_url}" target="_blank" aria-label="Download Video for ${data[i].title} ${data[i].meeting_date}"><i class="fa-solid fa-file-pdf"></i> ${data[i].additional_doc}</a> 685 </td>`; 686 } else { 687 html += `<td class="additional-documents-column"></td>`;
688 } 689 } 690 691 if (jMeetingActiveColumns.includes("notes")) { 692 if (data[i].notes && data[i].notes !== "") { 693 html += `<td class="notes-column"> 694 ${data[i].notes} 695 </td>`; 696 } else { 697 html += `<td class="notes-column"></td>`; 698 } 699 } 700 701 html += `</tr>`; 702 } 703 704 if (data.length == 0) { 705 html += `<tr><td class="text-center" colspan="8">No data in this category</td></tr>`; 706 } 707 708 if (category.perPage < data.length) { 709 let tfootElement = document.getElementById(`table-category-footer-${category.id}`); 710 tfoot = `<tr> 711 <td colspan="8"> 712 <a 713 tabindex="0" 714 aria-label="View more agendas & minutes" 715 role="button" 716 onclick="loadMore(${category.id})" 717 class="load-more" 718 > 719 <i id="load-${category.id}" class="fa fa-refresh"></i> View more 720 </a> 721 </td> 722 </tr>`; 723 tfootElement.innerHTML = tfoot; 724 } else { 725 let tfootElement = document.getElementById(`table-category-footer-${category.id}`); 726 tfootElement.innerHTML = ""; 727 } 728 let categoryContent = document.getElementById("table-category-" + category.id); 729 categoryContent.innerHTML = html; 730 }) 731 .catch((error) => { 732 console.error("Error fetching data:", error); 733 }); 734} 735 736// Generate meering tables 737function generateMeetingTables() { 738 var content = document.getElementById("juniperMeetings"); 739 content.innerHTML = ""; 740 // Initiali populate tables 741 categories.forEach((category) => { 742 if (searchCategory == "") { 743 category.perPage = meetingItemsPerTable; 744 generateCategoryEmptyTable(category); 745 populateCategoryTable(category); 746 } else { 747 if (searchCategory == category.id) { 748 category.perPage = meetingItemsPerTable; 749 generateCategoryEmptyTable(category); 750 populateCategoryTable(category); 751 } 752 } 753 }); 754} 755 756// END Meetings ---------------------------------------------------------------------------------------------------------------- 757 758/** 759 * Handle category selection from parameter 760 */ 761 762// Function to get the value of a URL parameter by name 763function getParameterByName(name) { 764 const urlParams = new URLSearchParams(window.location.search); 765 return urlParams.get(name); 766} 767 768function updateURLParameter(param, paramVal) { 769 const url = new URL(window.location.href); 770 url.searchParams.set(param, paramVal); 771 window.history.replaceState(null, null, url); 772} 773 774function handleMeetingCategoryFromUrl() { 775 // Get the 'category' parameter from the URL 776 const categoryText = getParameterByName("category"); 777 778 // Find the select box 779 const selectBox = document.getElementById("categoriesSelect"); 780 781 // If the category parameter exists, find the option with matching inner text 782 if (categoryText) { 783 setTimeout(() => { 784 const options = selectBox.options; 785 for (let i = 0; i < options.length; i++) { 786 if (options[i].innerText === categoryText) { 787 selectBox.selectedIndex = i; 788 break; 789 } 790 } 791 // Automatically click the button after selecting the option 792 document.getElementById("searchMeetings").click(); 793 }, 500); 794 } 795 796 // Listen for changes in the select box 797 const setCat = document.getElementById("searchMeetings"); 798 if (setCat) { 799 document.getElementById("searchMeetings").addEventListener("click", function () { 800 const selectedText = selectBox.options[selectBox.selectedIndex].text; 801 // Update the URL with the new category parameter 802 updateURLParameter("category", selectedText); 803 }); 804 } 805} 806 807$(document).ready(async function ($) { 808 handleMeetingCategoryFromUrl(); 809 // EVENTS CALENDAR HOMEPAGE --------------------------------------------------- 810 // eventCategories = await getEventCategories(); 811 // Load events from HeyGov 812 eventCategory = $("#events-calendar-2019 .tw-data").data("event_categories_filter"); 813 814 if (eventCategory) { 815 eventCategory = eventCategory.trim(); 816 let lastChar = eventCategory.slice(-1); 817 if (lastChar == ",") { 818 eventCategory = eventCategory.slice(0, -1); 819 } 820 } 821 // Handle events only if pathname is / and events-calendar is active on homepage 822 if ((jActiveHomepageSections && jActiveHomepageSections.includes("Events-calendar") && window.location.pathname == "/") || window.location.pathname.includes("customizer")) { 823 calendarBodyTable.then(async () => { 824 events = await getEvents(); 825 showCalendar(currentMonth, currentYear); 826 setEventsListDate(today); 827 generateDayEventsList(today, todayMonth, todayYear); 828 }); 829 } 830 // Events calendar item click 831 $("body").on("click", ".event-item-btn", function () { 832 // tw-calendar-day-active 833 $("#events-calendar-table .event-item-btn").removeClass("tw-calendar-day-active").attr("aria-selected", "false"); 834 $(this).addClass("tw-calendar-day-active").attr("aria-selected", "true"); 835 generateDayEventsList($(this).data("date"), $(this).data("month"), $(this).data("year")); 836 }); 837 // Events calendar next month 838 $("body").on("click", ".event-calendar-next", function () { 839 eventCalendarNext(); 840 }); 841 // Events calendar prev month 842 $("body").on("click", ".event-calendar-prev", function () { 843 eventCalendarPrev(); 844 }); 845 // Events calendar filter by category 846 $("body").on("click", "#eventsCalendarListContainer .tw-event-category-tab", async function (e) { 847 let targetIsActive = $(this).hasClass("active"); 848 849 $("#eventsCalendarListContainer .tw-event-category-tab").removeClass("active"); 850 eventCategory = null; 851 852 if (!targetIsActive) { 853 $(this).addClass("active"); 854 eventCategory = $(this).data("tw-event-category-id"); 855 } 856 events = await getEvents(eventCategory); 857 showCalendar(currentMonth, currentYear); 858 generateDayEventsList(today, todayMonth, todayYear); 859 }); 860 861 // #EVENTS CALENDAR HOMEPAGE --------------------------------------------------- 862 863 // Fetch categories for Meetings 864 if (window.location.pathname == "/" + jmeetingPage || window.location.pathname == "/" + jmeetingPage + "/") { 865 fetchCategories(); 866 } 867 868 let $window = $(window), 869 height = $window.height(), 870 width = $window.width(), 871 navheight = $("#defaultHeader #topHeader").height(); 872 873 let scrollDirection, oldScrollDirection; 874 let oldScrollTop = 0; 875 let fixedInitClass = "fixed-init"; 876 877 let headerHeight = $("#defaultHeader").height(); 878 let headerBottomHeight = $("#defaultHeader #bottomHeader").height(); 879 if ($("div#menuHero ul").length) { 880 let heroLineMenuTopOffset = $("div#menuHero ul").offset().top; 881 let headerHeroMenuDistance = heroLineMenuTopOffset - headerHeight; 882 } 883 let stickyPoint = false;
884 $window.on("resize orientationchange", setMeasures); 885 886 // Menu snippet 887 let viewWidth = document.documentElement.clientWidth; 888 let elements = document.querySelectorAll(".menu-level-one li > ul"); 889 const resizeWatcher = new ResizeObserver((entries) => { 890 for (const entry of entries) { 891 let elementObject = entry.target.getBoundingClientRect(); 892 893 if (elementObject.right > viewWidth) { 894 entry.target.style.left = "auto"; 895 entry.target.style.right = "100%"; 896 } 897 } 898 }); 899 900 elements.forEach((element) => { 901 resizeWatcher.observe(element); 902 }); 903 904 function setMeasures() { 905 ((height = $window.height()), (width = $window.width()), (navheight = $("#defaultHeader #topHeader").height())); 906 907 headerHeight = $("#defaultHeader").height(); 908 headerBottomHeight = $("#defaultHeader #bottomHeader").height(); 909 910 if ($("div#menuHero ul").length) { 911 heroLineMenuTopOffset = $("div#menuHero ul").offset().top; 912 headerHeroMenuDistance = heroLineMenuTopOffset - headerHeight; 913 } 914 } 915 916 // Function Menu Hero Sticky 917 // function sticky() { 918 // var scrollTop = $window.scrollTop() 919 // if (scrollTop > oldScrollTop) { 920 // scrollDirection = "down" 921 // } else { 922 // scrollDirection = "up" 923 // } 924 // oldScrollTop = scrollTop 925 926 // if (!$("#defaultHeader #bottomHeader").hasClass(scrollDirection)) { 927 // $("#defaultHeader #bottomHeader").removeClass(oldScrollDirection).addClass(scrollDirection) 928 // } 929 930 // if (scrollTop > navheight) { 931 // $("#defaultHeader").addClass("fixed-header") 932 // $("#defaultHeader #bottomHeader").addClass("fixed") 933 // $("#defaultHeader #bottomHeader").removeClass("fixed-init").addClass(fixedInitClass) 934 // $("#defaultHeader nav.navbar").addClass("fixed-navbar") 935 // fixedInitClass = "" 936 // $('body [data-bs-toggle="tooltip"]').tooltip("enable") 937 // if ($("div#menuHero ul").length) { 938 // if (!stickyPoint) { 939 // $("div#menuHero").css({ 940 // top: "180px" 941 // }) 942 // } 943 // stickyPoint = true 944 // $("div#menuHero").addClass("fixed-side-menu").removeClass("absolute-side-menu") 945 // } 946 // } else { 947 // $("#defaultHeader").removeClass("fixed-header") 948 // $("#defaultHeader #bottomHeader").removeClass("fixed") 949 // $("#defaultHeader nav.navbar").removeClass("fixed-navbar") 950 // $('body [data-bs-toggle="tooltip"]').tooltip("disable") 951 952 // fixedInitClass = "fixed-init" 953 954 // if ($("div#menuHero ul").length) { 955 // if (stickyPoint) { 956 // $("div#menuHero").css({ 957 // top: 0 958 // }) 959 // } 960 // stickyPoint = false 961 // $("div#menuHero").addClass("absolute-side-menu").removeClass("fixed-side-menu") 962 // } 963 // } 964 // oldScrollDirection = scrollDirection 965 // } 966 967 // $window.on("scroll resize orientationchange", sticky) 968 969 // Back to top button 970 $(window).scroll(function () { 971 if ($(this).scrollTop() != 0) { 972 $("#back-to-top").fadeIn(); 973 } else { 974 $("#back-to-top").fadeOut(); 975 } 976 }); 977 978 $("#back-to-top").on("click touchstart", function (e) { 979 e.preventDefault(); 980 $("body,html").animate({ scrollTop: 0 }, 700); 981 return false; 982 }); 983 // Tickerbar marquee ----------------------------------------------------------------------------------------------- 984 $(".marquee").marquee({ 985 //duration in milliseconds of the marquee 986 // duration: 7000, 987 speed: 120, 988 //gap in pixels between the tickers 989 gap: 50, 990 //time in milliseconds before the marquee will start animating 991 delayBeforeStart: 0, 992 //'left' or 'right' 993 direction: "left", 994 //true or false - should the marquee be duplicated to show an effect of continues flow 995 duplicated: false, 996 //true or false - should the marquee animation pause on hover 997 pauseOnHover: true, 998 }); 999 1000 let owl = $("#sectionNewsDefault .owl-carousel"); 1001 let owlOptions = { 1002 stagePadding: 0, 1003 loop: true, 1004 dots: true, 1005 margin: 0, 1006 nav: true, 1007 navText: ['<i class="fas fa-angle-left fa-2x" aria-hidden="true"></i>', '<i class="fas fa-angle-right fa-2x" aria-hidden="true"></i>'], 1008 // navContainer: '.custom-nav', 1009 navClass: ["owl-prev text-white", "owl-next float-end tw-secondary"], 1010 items: 3, 1011 autoWidth: false, 1012 center: true, 1013 URLhashListener: true, 1014 autoplayHoverPause: false, 1015 startPosition: "URLHash", 1016 responsive: { 1017 0: { 1018 items: 1, 1019 }, 1020 992: { 1021 items: 1, 1022 }, 1023 }, 1024 }; 1025 var owlActive = owl.owlCarousel(owlOptions); 1026 $("#sectionNewsDefault .owl-prev").attr("aria-label", "Previous news item").removeAttr("role"); 1027 $("#sectionNewsDefault .owl-next").attr("aria-label", "Next news item").removeAttr("role"); 1028 1029 $(".customizer-content a").click(function (e) { 1030 e.preventDefault(); 1031 }); 1032 1033 let owlEvents = $("#tw-events .owl-carousel"); 1034 let owlOptionsEvents = { 1035 // stagePadding: 0, 1036 loop: false, 1037 dots: false, 1038 // margin:0, 1039 nav: true, 1040 navText: ['<i class="fas fa-angle-left fa-2x" aria-hidden="true"></i>', '<i class="fas fa-angle-right fa-2x" aria-hidden="true"></i>'], 1041 // navContainer: '.custom-nav', 1042 navClass: ["owl-prev tw-secondary", "owl-next float-end tw-secondary"], 1043 items: 5, 1044 autoWidth: false, 1045 center: false, 1046 URLhashListener: true, 1047 autoplayHoverPause: false, 1048 startPosition: "URLHash", 1049 responsive: { 1050 0: { 1051 items: 2, 1052 }, 1053 768: { 1054 items: 3, 1055 }, 1056 992: { 1057 items: 4, 1058 }, 1059 1184: { 1060 items: 5, 1061 }, 1062 }, 1063 }; 1064 var owlActiveEvents = owlEvents.owlCarousel(owlOptionsEvents); 1065 $("#tw-events .owl-prev").attr("aria-label", "Previous events").removeAttr("role"); 1066 $("#tw-events .owl-next").attr("aria-label", "Next events").removeAttr("role"); 1067 1068 let owlHeroQuickLinks = $("#homepage-nav.owl-carousel"); 1069 let owlOptionsHeroQuickLinks = { 1070 items: 4, 1071 nav: true, 1072 loop: false, 1073 rewind: true, 1074 dots: false, 1075 margin: 0, 1076 checkVisible: false, 1077 responsiveBaseElement: "#homepage-nav", 1078 responsiveRefreshRate: "0ms", 1079 navText: ['<i class="fa fa-chevron-left fa-1x" aria-hidden="true"></i>', '<i class="fa fa-chevron-right fa-1x" aria-hidden="true"></i>'], 1080 responsive: { 1081 1280: { 1082 items: 4, 1083 }, 1084 1199: { 1085 margin: 140, 1086 }, 1087 991: { 1088 items: 4, 1089 margin: 90, 1090 }, 1091 767: { 1092 items: 3, 1093 margin: 15, 1094 }, 1095 400: { 1096 items: 2, 1097 margin: 0, 1098 }, 1099 0: { 1100 items: 1, 1101 margin: 0, 1102 autoHeight: true, 1103 autoHeightClass: "owl-height", 1104 }, 1105 }, 1106 }; 1107 var owlActiveHeroQuickLinks = owlHeroQuickLinks.owlCarousel(owlOptionsHeroQuickLinks); 1108 $("#homepage-nav .owl-prev").attr("aria-label", "Previous quick links").removeAttr("role"); 1109 $("#homepage-nav .owl-next").attr("aria-label", "Next quick links").removeAttr("role"); 1110
1111 function markHeroClonesInert() { 1112 $("#homepage-nav .owl-item.cloned").each(function () { 1113 var $clone = $(this); 1114 $clone.attr({ "aria-hidden": "true", inert: "" }); 1115 $clone.find("a").each(function () { 1116 var $a = $(this); 1117 if ($a.is("[href]") && !$a.data("twOrigHref")) { 1118 $a.data("twOrigHref", $a.attr("href")); 1119 } 1120 $a.removeAttr("href") 1121 .attr({ "tabindex": "-1", "aria-hidden": "true", role: "presentation" }); 1122 }); 1123 }); 1124 } 1125 markHeroClonesInert(); 1126 owlHeroQuickLinks.on("translated.owl.carousel refreshed.owl.carousel resized.owl.carousel", markHeroClonesInert); 1127 1128 // jQuery(".homepage-nav").owlCarousel({ 1129 // items:4, 1130 // nav:true, 1131 // dots: false, 1132 // loop:true, 1133 // margin:0, 1134 // checkVisible: false, 1135 // responsiveBaseElement: '.homepage-menu', 1136 // responsiveRefreshRate: '0ms', 1137 // navText:['<i class="fa fa-chevron-left fa-1x"></i>','<i class="fa fa-chevron-right fa-1x"></i>'], 1138 // responsive:{ 1139 // 1280:{ 1140 // items:4 1141 // }, 1142 // 1199:{ 1143 // margin:140 1144 // }, 1145 // 991:{ 1146 // items:4, 1147 // margin:90 1148 // }, 1149 // 767:{ 1150 // items:3, 1151 // margin:15 1152 // }, 1153 // 479:{ 1154 // items:2, 1155 // margin:0 1156 // }, 1157 // 0:{ 1158 // items:1, 1159 // margin:0, 1160 // autoHeight: true, 1161 // autoHeightClass: 'owl-height' 1162 // } 1163 // } 1164 // }); 1165 1166 // Handle search ----------------------------------------------------------------------------------------------------------------------------------------------- 1167 function generateSearchItem(item) { 1168 if (jActiveAdvancedSearch == 0) { 1169 var dom = new DOMParser().parseFromString(item.content, "text/html"); 1170 var content = dom.body.textContent; 1171 content = content.substring(0, 150) + "..."; 1172 } 1173 let html = ""; 1174 html = ` 1175 <div class='col-md-4 mb-3'><h2><a href=" 1176 ${jActiveAdvancedSearch == 1 ? item.url : window.location.protocol + "//" + window.location.host + item.slug} 1177 "> 1178 ${item.title} 1179 </a></h2><div> 1180 ${content || item.content} 1181 </div></div> 1182 `; 1183 return html; 1184 } 1185 1186 if (window.location.pathname.includes("search")) { 1187 const queryString = window.location.search; 1188 const urlParams = new URLSearchParams(queryString); 1189 const q = urlParams.get("q"); 1190 // Load the locations once, on page-load. 1191 if (jActiveAdvancedSearch == 1) { 1192 $(async function () { 1193 const res = await fetch("/search/search.php", { 1194 method: "POST", 1195 headers: { 1196 "Content-Type": "application/json", 1197 }, 1198 body: JSON.stringify({ q: q }), 1199 }); 1200 const data = await res.json(); 1201 1202 let searchContainer = document.getElementById("search-results"); 1203 let searchString = document.getElementById("search-string"); 1204 searchString.innerHTML = q; 1205 let searchArr = []; 1206 // Iterate over each element in the array 1207 for (var i = 0; i < data.length; i++) { 1208 searchContainer.innerHTML += generateSearchItem(data[i]); 1209 searchArr.push(data[i]); 1210 } 1211 if (searchArr.length < 1) { 1212 searchContainer.innerHTML += '<div class="text-center text-warning fs-1">There are no results found</div>'; 1213 } 1214 // console.log(data); 1215 }); 1216 } else { 1217 $(function () { 1218 $.getJSON(window.location.protocol + "//" + window.location.host + `/json/search/search.json?sk=${Date.now()}`) 1219 .done(function (data) { 1220 let searchContainer = document.getElementById("search-results"); 1221 let searchString = document.getElementById("search-string"); 1222 searchString.innerHTML = q; 1223 let searchArr = []; 1224 // Iterate over each element in the array 1225 for (var i = 0; i < data.length; i++) { 1226 var content = data[i].content; 1227 // look for the entry with a matching `code` value 1228 if (data[i].title.toLowerCase().includes(q.toLowerCase())) { 1229 searchContainer.innerHTML += generateSearchItem(data[i]); 1230 searchArr.push(data[i]); 1231 } else { 1232 if (content && content.toLowerCase().includes(q.toLowerCase())) { 1233 searchContainer.innerHTML += generateSearchItem(data[i]); 1234 searchArr.push(data[i]); 1235 } 1236 } 1237 } 1238 if (searchArr.length < 1) { 1239 searchContainer.innerHTML += '<div class="text-center text-warning fs-1">There are no results found</div>'; 1240 } 1241 }) 1242 .fail(function (data) { 1243 console.log("Error loading results â"); 1244 }); 1245 }); 1246 } 1247 } 1248 1249 // Handle Contact Us ----------------------------------------------------------------------------------------------------------------------------------------------------- 1250 // $(".contact-form #submit-btn").click(function (e) { 1251 // e.preventDefault() 1252 // data = $(".contact-form").serialize() 1253 // url = window.location.protocol + "//" + window.location.host 1254 // $.ajax({ 1255 // method: "post", 1256 // url: jurl + "/static/contact", // NEED TO UPDATE 1257 // data: $(".contact-form").serialize() + "&url=" + url, 1258 // success: function (msg) { 1259 // if (msg.message == "Success") { 1260 // $(".contact-success").removeClass("d-none") 1261 // $(".contact-success").html(jcontactSuccessMsg) 1262 // setTimeout(cleanContactUs, 3000) 1263 // } 1264 // }, 1265 // fail: function (err) { 1266 // console.log("Error submitting contact form â") 1267 // } 1268 // }) 1269 // }) 1270 1271 $("form.contact-form").submit(function (e) { 1272 data = $("form.contact-form").serialize(); 1273 url = window.location.protocol + "//" + window.location.host; 1274 $.ajax({ 1275 method: "post", 1276 url: jurl + "/static/contact", // NEED TO UPDATE 1277 data: $("form.contact-form").serialize() + "&url=" + url, 1278 success: function (msg) { 1279 if (msg.message == "Success") { 1280 $(".contact-success").removeClass("d-none"); 1281 $(".contact-success").html(jcontactSuccessMsg); 1282 setTimeout(cleanContactUs, 3000); 1283 } 1284 }, 1285 error: function (xhr, status, error) { 1286 console.log("Error submitting contact form â"); 1287 }, 1288 }); 1289 }); 1290 1291 // Clean inputs and hide success message after 3000ms 1292 function cleanContactUs() { 1293 $("#first").val(""); 1294 $("#last").val(""); 1295 $("#email").val(""); 1296 $("#phone").val(""); 1297 $("#message").val(""); 1298 $(".contact-success").addClass("d-none"); 1299 } 1300 1301 // Handle Quick Subscribe ------------------------------------------------------------------------------------------------------------------------------------------------- 1302 1303 function validateEmail(email) { 1304 var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 1305 return re.test(String(email).toLowerCase()); 1306 } 1307 1308 $("#sub_phone").mask("999-999-9999", { 1309 placeholder: "___-___-____", 1310 clearIfNotMatch: true, 1311 }); 1312 1313 $("#subs_button").click(function (e) { 1314 e.preventDefault(); 1315 username = $("#sub_name").val(); 1316 if (username.length === 0) { 1317 $("#sub_name").addClass("has-error"); 1318 } else { 1319 if ($("#sub_name").hasClass("has-error")) { 1320 $("#sub_name").removeClass("has-error"); 1321 } 1322 } 1323 1324 email = $("#sub_email").val(); 1325 if (email.length === 0 || !validateEmail(email)) { 1326 $("#sub_email").addClass("has-error"); 1327 } else { 1328 if ($("#sub_email").hasClass("has-error")) { 1329 $("#sub_email").removeClass("has-error"); 1330 } 1331 } 1332
1333 phone = $("#sub_phone").val(); 1334 if (email.length === 0 || !validateEmail(email)) { 1335 $("#sub_email").addClass("has-error"); 1336 } else { 1337 if ($("#sub_email").hasClass("has-error")) { 1338 $("#sub_email").removeClass("has-error"); 1339 } 1340 } 1341 if (username != "" && email != "" && validateEmail(email)) { 1342 let btn_html = $("#subs_button").html(); 1343 $("#subs_button + div.alert").remove(); 1344 $("#subs_button").html('<i class="fa fa-spinner fa-spin"></i> Working'); 1345 1346 $("#sub_name").attr("readonly", true); 1347 $("#sub_email").attr("readonly", true); 1348 $("#sub_phone").attr("readonly", true); 1349 1350 $.ajax({ 1351 method: "post", 1352 url: jurl + "/subscribers/register?client_id=" + jid, 1353 data: { 1354 name: username, 1355 email: email, 1356 phone: phone, 1357 quick: true, 1358 }, 1359 success: function (msg) { 1360 //remove errors and display success 1361 if ($("#sub_name").hasClass("has-error")) { 1362 $("#sub_name").removeClass("has-error"); 1363 } 1364 if ($("#sub_email").hasClass("has-error")) { 1365 $("#sub_email").removeClass("has-error"); 1366 } 1367 if (msg.type != "success") { 1368 errrMessage = msg.message; 1369 if (msg && msg.errors && msg.errors.email) { 1370 errrMessage = msg.errors.email[0]; 1371 } 1372 $("#subs_button").addClass("btn-danger homepage-subscribed-danger font-weight-bold"); 1373 $("#subs_button").html("Error Occurred!"); 1374 $("#homepage-subscriber-form").after( 1375 `<div class="row ms-1"><div id="subscribe-error" class="alert alert-danger alert-dismissible" role="alert"> ${errrMessage}<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></div></div>`, 1376 ); 1377 //Remove alert, button danger & button text after 3 sec 1378 setTimeout(function () { 1379 $("#subs_button").removeClass("btn-danger"); 1380 $("#subs_button").addClass("subscribe-button"); 1381 $("#subs_button").html("Subscribe now"); 1382 $("#subscribe-error").fadeOut(); 1383 }, 6000); 1384 } else { 1385 if ($("#subs_button").hasClass("homepage-subscribed-danger")) { 1386 $("#subs_button").removeClass("homepage-subscribed-danger"); 1387 } 1388 if ($("#subs_button").hasClass("btn-danger")) { 1389 $("#subs_button").removeClass("btn-danger"); 1390 } 1391 $("#subs_button").addClass("btn-success homepage-subscribed-success font-weight-bold"); 1392 $("#subs_button").html("Successfully Subscribed!"); 1393 // Remove success class after 3 sec 1394 setTimeout(function () { 1395 $("#subs_button").removeClass("btn-success"); 1396 $("#subs_button").addClass("subscribe-button"); 1397 $("#subs_button").html("Subscribe now"); 1398 }, 6000); 1399 } 1400 //clear input values 1401 $("#sub_name").val(""); 1402 $("#sub_email").val(""); 1403 $("#sub_phone").val(""); 1404 $("#sub_name").attr("readonly", false); 1405 $("#sub_email").attr("readonly", false); 1406 $("#sub_phone").attr("readonly", false); 1407 }, 1408 fail: function (jqXHR, textStatus) { 1409 //display error message 1410 if ($("#sub_name").hasClass("has-error")) { 1411 $("#sub_name").removeClass("has-error"); 1412 } 1413 if ($("#sub_email").hasClass("has-error")) { 1414 $("#sub_email").removeClass("has-error"); 1415 } 1416 $("#subs_button").html("Error"); 1417 1418 //clear input values 1419 $("#sub_name").val(""); 1420 $("#sub_email").val(""); 1421 $("#sub_phone").val(""); 1422 $("#sub_name").attr("readonly", false); 1423 $("#sub_email").attr("readonly", false); 1424 $("#sub_phone").attr("readonly", false); 1425 }, 1426 }); 1427 } 1428 }); 1429 1430 // Hande Subscribe ------------------------------------------------------------------------------------------------------------------------------------------------------------ 1431 1432 // Create meeting categories 1433 function generateMeetingCategories() {
1434 let categoriesHtml = ""; 1435 let categoriesContainer = $("#juniper-meetings-categories"); 1436 let meetingCategories = JSON.parse(jMeetingsSubscribableCategories); 1437 meetingCategories.forEach((category) => { 1438 categoriesHtml += `<div class="sub"> 1439 <input id="juniper-subscriber-agenda-${category.id}" class="juniper-child-checkbox" type="checkbox" name="agenda_terms[]" value="${category.id}"> 1440 <label for="juniper-subscriber-agenda-${category.id}">${category.name}</label><br> 1441 </div>`; 1442 }); 1443 categoriesContainer.html(categoriesHtml); 1444 } 1445 1446 function generateNewsCategories() { 1447 let newsCategoriesHtml = ""; 1448 let newsCategoriesContainer = $("#juniper-news-categories"); 1449 let newsCategories = JSON.parse(jNewsSubscribableCategories); 1450 newsCategories.forEach((category) => { 1451 newsCategoriesHtml += `<div class="sub"> 1452 <input id="juniper-subscriber-news-${category.id}" class="juniper-child-checkbox" type="checkbox" name="post_terms[]" value="${category.id}"> 1453 <label for="juniper-subscriber-news-${category.id}">${category.name}</label><br> 1454 </div>`; 1455 }); 1456 newsCategoriesContainer.html(newsCategoriesHtml); 1457 } 1458 1459 // Invoke generate meeting categories 1460 generateMeetingCategories(); 1461 // Invoke generate news categories 1462 generateNewsCategories(); 1463 1464 // Mask for Phone 1465 $("#juniper-subscriber-phone").mask("(999) 999-9999"); 1466 1467 // Checkbox 1468 $(".juniper-parent-checkbox") 1469 .click(function () { 1470 $(this).parent().find(".juniper-child-checkbox").prop("disabled", !this.checked).prop("checked", this.checked); 1471 }) 1472 .trigger("click"); 1473 1474 // Phone required when sms checked 1475 $("#juniper-subscriber-receive-text-message") 1476 .click(function () { 1477 $("#juniper-subscriber-phone, #juniper-subscriber-carrier").prop("required", this.checked); 1478 }) 1479 .trigger("click"); 1480 1481 // Handle subscribe now button 1482 $("body").on("submit", "#juniper-subscriber-form", function () { 1483 $.ajax({ 1484 type: "POST", 1485 url: jurl + "/subscribers/register?client_id=" + jid, 1486 data: $(this).serialize(), 1487 success: function (data) { 1488 if (data.type == "success") { 1489 $("#juniper-subscribe-complete").removeClass("d-none"); 1490 } 1491 if (data.type == "error") { 1492 let text = ""; 1493 for (const [key, value] of Object.entries(data.errors)) { 1494 text += `<p>${value}</p>`; 1495 } 1496 $("#juniper-subscribe-error").removeClass("d-none"); 1497 $("#juniper-subscribe-error").html(text); 1498 } 1499 }, 1500 fail: function (error) { 1501 console.log("Error submiting subscriber form â"); 1502 }, 1503 }); 1504 1505 return true; 1506 }); 1507 1508 // Handle unsubscribe button 1509 $("body").on("click", "#twd-unsubscribe-btn", function () { 1510 if (confirm("Are you sure?")) { 1511 $.ajax({ 1512 type: "GET", 1513 url: $(this).attr("href"), 1514 dataType: "html", 1515 }).done(function (date) { 1516 $("#twd-subscribe-complete").html(date); 1517 $("#twd-subscriber-submit").val("Subscribe"); 1518 $("#twd-subscriber-form").slideDown(); 1519 }); 1520 } 1521 return false; 1522 }); 1523}); 1524 1525function waitForElement(selector) { 1526 return new Promise((resolve) => { 1527 if (document.querySelector(selector)) { 1528 return resolve(document.querySelector(selector)); 1529 } 1530 1531 const observer = new MutationObserver((mutations) => { 1532 if (document.querySelector(selector)) { 1533 observer.disconnect(); 1534 resolve(document.querySelector(selector)); 1535 } 1536 }); 1537 1538 observer.observe(document.body, { 1539 childList: true, 1540 subtree: true, 1541 }); 1542 }); 1543} 1544 1545/*****************************************/ 1546/*****************************************/ 1547/***********CALENDAR 2019*****************/ 1548/*****************************************/ 1549/*****************************************/ 1550jQuery(document).ready(function () { 1551 (async () => { 1552 $('body [data-bs-toggle="tooltip"]').tooltip("enable"); 1553 let eventsList2019, eventsListContainer2019, headMonth2019, monthAndYear2019, calendarBodyTable2019; 1554 1555 eventsList2019 = waitForElement("#events-calendar-2019-list"); 1556 eventsListContainer2019 = waitForElement("#events-calendar-2019-list-container"); 1557 headMonth2019 = waitForElement("#thead-month-2019"); 1558 monthAndYear2019 = waitForElement("#monthAndYearTitle2019"); 1559 calendarBodyTable2019 = waitForElement("#calendar-2019-body"); 1560 1561 let eventsListEmpty = "No Events Today."; 1562 1563 let dayNames2019 = ["S", "M", "T", "W", "T", "F", "S"]; 1564 1565 let dayNames2019Content = "<tr>"; 1566 dayNames2019.forEach((name) => { 1567 dayNames2019Content += `<th class="text-center text-capitalize" data-days="${name}">${name}</th>`; 1568 }); 1569 dayNames2019Content += "</tr>"; 1570 1571 headMonth2019.then((el) => (el.innerHTML = dayNames2019Content)); 1572 1573 if ((jActiveHomepageSections && jActiveHomepageSections.includes("Events-calendar-2019") && window.location.pathname == "/") || window.location.pathname.includes("customizer")) { 1574 calendarBodyTable2019.then(async () => { 1575 events = await getEvents(eventCategory); 1576 showCalendar2019(currentMonth, currentYear); 1577 // setEventsListDate(today) 1578 generateDayEvents2019List(today, todayMonth, todayYear); 1579 }); 1580 } 1581 1582 // Show Calendar 2019 1583 async function showCalendar2019(month, year) { 1584 let firstDay = new Date(year, month).getDay(); 1585 let tbl = await waitForElement("#calendar-2019-body"); 1586 tbl.innerHTML = ""; 1587 monthAndYear2019.then((el) => (el.innerHTML = months[month] + ", " + year)); 1588 // Create all calendar items 1589 1590 let calendarRows = getCalendarRows(); 1591 // console.log(calendarRows) 1592 // Create all calendar items 1593 1594 calendarRows.forEach((calendarRow) => { 1595 var row = document.createElement("tr"); 1596 calendarRow.forEach((day) => { 1597 let cell = document.createElement("td"); 1598 1599 if (isDateOfPreviousMonth(day)) { 1600 cell.classList.add("previous-month"); 1601 } 1602 1603 if (isDateOfNextMonth(day)) { 1604 cell.classList.add("next-month"); 1605 } 1606 1607 // check if calendar date has events 1608 let dayEvents = getEventOnDay(day); 1609 1610 let div = document.createElement("div"); 1611 div.className = "text-center"; 1612 1613 let todayCloneForCalendarCell = new Date(today); 1614 todayCloneForCalendarCell.setHours(0, 0, 0, 0); 1615 if (todayCloneForCalendarCell.getTime() === day.getTime()) { 1616 cell.classList.add("tw-calendar-day-active"); 1617 } 1618 1619 if (dayEvents.length) { 1620 cell.classList.add("has-events"); 1621 div.classList.add("cursor-pointer"); 1622 div.innerHTML = `<a href="${jEventsPage}/?heygov-event=${dayEvents[0]["pid"]}" class="d-flex justify-content-center text-decoration-none">${day.getDate()}</a>`; 1623 } else { 1624 div.innerText = day.getDate(); 1625 } 1626 1627 cell.appendChild(div); 1628 1629 cell.setAttribute("data-date", day); 1630 cell.setAttribute("data-month", day.getMonth() + 1); 1631 cell.setAttribute("data-year", day.getFullYear()); 1632 cell.setAttribute("data-month_name", months[day.getMonth()]); 1633 1634 row.appendChild(cell); 1635 }); 1636 if (tbl) { 1637 tbl.appendChild(row); 1638 } 1639 }); 1640 } 1641 1642 // Generate list of day events 1643 async function generateDayEvents2019List(day, month, year) { 1644 let dateEnd = new Date(day); 1645 dateEnd.setFullYear(dateEnd.getFullYear() + 2); 1646 1647 let args = { 1648 limit: 6, 1649 date_start: formatDate(day), 1650 date_end: formatDate(dateEnd), 1651 }; 1652 1653 let eventsFor2019List = await getEvents(eventCategory, args); 1654 let events2019List = ""; 1655 1656 eventsFor2019List.forEach((e, index) => { 1657 let startsAt = new Date(
1658 new Date(e.starts_at).toLocaleString("en", { 1659 timeZone: timezone, 1660 }), 1661 ); 1662 1663 let dateStart = startsAt.toLocaleString("en-US", { month: "long" }) + " " + startsAt.getDate() + ", " + startsAt.getFullYear(); 1664 1665 events2019List += `<div class="single-homepage-event d-flex pb-3"> 1666 <article class="d-flex flex-grow-1"> 1667 <div class="d-flex flex-grow-1 flex-column event-item"> 1668 <div class="article-thumbnail"> 1669 ${ 1670 e.image_path || e.image_url || jeventDefaultFeaturedImage 1671 ? `<a href="${jEventsPage}/?heygov-event=${e.pid}"> 1672 <img src="${e.image_path || e.image_url ? (e.image_path ? jHeyGovStorage + e.image_path : e.image_url) : jeventDefaultFeaturedImage}" class="img-responsive" data-uw-rm-alt-original="" alt="text" data-uw-rm-alt="BE"> 1673 1674 </a>` 1675 : "" 1676 } 1677 </div> 1678 1679 <div class="event-item-card d-flex flex-grow-1 flex-column"> 1680 <div class="event-date"> 1681 ${dateStart} 1682 </div> 1683 <div class=""> 1684 <a href="${jEventsPage}/?heygov-event=${e.pid}"> 1685 <h4>${e.name}</h4> 1686 </a> 1687 <div class="entry-content"> 1688 <p> 1689 ${(() => { 1690 if (!e.description) return ""; 1691 // Remove HTML links <a>...</a> but keep the text 1692 const descWithoutLinks = e.description 1693 .replace(/<a[^>]*>([^<]*)<\/a>/gi, "$1") // Remove <a> tags but keep inner text 1694 .replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1") // Markdown links [text](url) -> text 1695 .replace(/https?:\/\/[^\s]+/g, ""); // Plain URLs 1696 if (descWithoutLinks.length > 100) { 1697 return descWithoutLinks.slice(0, 100).replace(/\p{P}\s*$/u, "") + "..."; 1698 } 1699 return descWithoutLinks; 1700 })()} 1701 1702 </p> 1703 </div> 1704 ${e.description && e.description.length > 100 ? `<div class="tw-read-more mt-auto"><a class="read-more-link" href="${jEventsPage}/?heygov-event=${e.pid}">Read More</a></div>` : ""} 1705 </div> 1706 </div> 1707 </div> 1708 </article> 1709 </div>`; 1710 }); 1711 1712 if (events2019List == "") { 1713 events2019List = eventsListEmpty; 1714 } 1715 eventsListContainer2019.then((el) => (el.innerHTML = events2019List)); 1716 1717 // Fix for tooltip to go allways left 1718 const tooltipLinks = document.querySelectorAll('#tw-quick-links a[data-bs-toggle="tooltip"]'); 1719 tooltipLinks.forEach((link) => { 1720 const tooltipInstance = bootstrap.Tooltip.getInstance(link); 1721 if (tooltipInstance) { 1722 tooltipInstance.dispose(); 1723 } 1724 link.setAttribute("data-bs-placement", "left"); 1725 new bootstrap.Tooltip(link); 1726 }); 1727 } 1728 })(); 1729 document.querySelectorAll("#main-menu .dropdown-menu .dropdown-toggle").forEach((item) => { 1730 item.addEventListener("click", function (event) { 1731 event.stopPropagation(); // Prevent Bootstrap from closing the entire dropdown 1732 let submenu = this.nextElementSibling; 1733 if (submenu) { 1734 submenu.classList.toggle("show"); 1735 } 1736 }); 1737 }); 1738 1739 // Close all submenus when clicking outside 1740 document.addEventListener("click", function () { 1741 document.querySelectorAll("#main-menu .dropdown-menu .show").forEach((submenu) => { 1742 submenu.classList.remove("show"); 1743 }); 1744 }); 1745});
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.