'use strict' // This module provides helpers for Boston.gov // --------------------------- var Boston = (function () { var emailRE = /^(([^<>()\[\]\\.,;:\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,}))$/; var zipRE = /(^\d{5}$)|(^\d{5}-\d{4}$)/; // Returns the child element based on selector // Parent needs an ID function child(el, selector) { return document.querySelectorAll('#' + el.id + ' ' + selector); } // Returns the child element based on selector // Parent needs a selector function childByEl(parent, selector) { return parent.getElementsByClassName(selector); } function disableButton(form, label) { var button = Boston.childByEl(form, 'btn'); if (button.length > 0) { for (var i = 0; i < button.length; i++) { button[i].disabled = true; button[i].innerHTML = label; } } } function enableButton(form, label) { var button = Boston.childByEl(form, 'btn'); if (button.length > 0) { for (var i = 0; i < button.length; i++) { button[i].disabled = false; button[i].innerHTML = label; } } } function hasClass(element, cls) { return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1; } function invalidateField(field, message) { var errors = document.createElement('div'); errors.className = "t--subinfo t--err m-t100"; errors.innerHTML = message; field.parentElement.appendChild(errors); } function request(obj, token) { var request = new XMLHttpRequest(); request.open(obj.method, obj.url, true); request.onload = function() { if (request.status >= 200 && request.status < 400) { obj.success(request); } else { obj.error(request); } }; if (token) { request.setRequestHeader("Authorization", "Token " + token); } request.onerror = function() { obj.error(request); }; if (obj.data) { request.send(obj.data); } else { request.send(); } } return { request: request, child: child, childByEl: childByEl, disableButton: disableButton, enableButton: enableButton, emailRE: emailRE, hasClass: hasClass, invalidateField: invalidateField, zipRE: zipRE } })(); 'use strict' // This module controls the City of Boston video component // --------------------------- var BostonContact = (function () { var to_address; var o_message = false; var o_subject = false; var o_phone = false; function initEmailLink(emailLink) { // Handle the onclick event emailLink.addEventListener('click', handleEmailClick); } function handleEmailClick(ev) { ev.preventDefault(); if (document.getElementById('contactFormTemplate')) { document.body.classList.add('no-s'); var template = document.getElementById('contactFormTemplate'); var container = document.createElement('div'); container.id = "contactFormModal"; container.innerHTML = template.innerHTML; document.body.appendChild(container); if (ev.target.title && ev.target.title !== '') { document.getElementById('contactMessage').innerHTML = ev.target.title; } // -> DU Jan 2024: DIG-3829 let title; document.getElementById('contactFormModal').getElementsByClassName('sh-title')[0].innerHTML = "Contact Us"; if ((title = extract(ev.target.getAttribute('href'), "title")) && title !== '') { document.getElementById('contactFormModal').getElementsByClassName('sh-title')[0].innerHTML = decodeURIComponent(title); } // <- DU Jan 2024: DIG-3829 var btn = document.getElementById("contactFormModal"); // Setting new role attributes btn.setAttribute("role", "dialog"); var close = Boston.childByEl(container, 'md-cb'); close[0].addEventListener('click', handleEmailClose); var form = document.getElementById('contactForm'); form.addEventListener('submit', handleFormSubmit); // clear error message on keyup of input field handleInputKeyup(); // Set the hidden fields setBrowser(ev.currentTarget); setURL(ev.currentTarget); setToAddress(ev.currentTarget); setBodyMessage(ev.currentTarget); setSubject(ev.currentTarget); setPhone(ev.currentTarget); setToken(ev.currentTarget); } } function handleInputKeyup(form) { var inputFields = document.getElementsByClassName('txt-f'); for (var i = 0; i < inputFields.length; i++) { inputFields[i].addEventListener("keyup", function() { var errorMessage = this.nextElementSibling; if (errorMessage) { errorMessage.remove("t--err"); } }); } } function handleEmailClose(ev) { ev.preventDefault(); document.body.classList.remove('no-s'); document.getElementById('contactFormModal').remove(); } function handleFormSubmit(ev) { ev.preventDefault(); var form = document.getElementById('contactForm'); // Reset the form resetForm(form); var isValid = validateForm(form); var formData = new FormData(form); if (isValid) { Boston.disableButton(form, 'Loading...'); Boston.request({ data: formData, url: form.getAttribute('action'), method: 'POST', success: function (response) { if (response.status === 200) { form.parentElement.innerHTML = "
Thank you for contacting us. We appreciate your interest in the City. If you don’t hear from anyone within five business days, please contact BOS:311 at 3-1-1 or 617-635-4500.
"; } else { handleError(form); } }, error: function() { handleError(form); } }, "618917a240ee275b780f00bn5aa0d0e6apx08c00600eaa77fgh739322c3f66062f6912lkc435dg67"); } } function handleError(form) { Boston.enableButton(form, 'Send Message'); } function validateForm(form) { var email = Boston.childByEl(form, 'bos-contact-email'); var email2 = Boston.childByEl(form, 'bos-contact-email2'); var name = Boston.childByEl(form, 'bos-contact-name'); var phone = Boston.childByEl(form, 'bos-contact-phone'); var subject = Boston.childByEl(form, 'bos-contact-subject'); var message = Boston.childByEl(form, 'bos-contact-message'); var address_to = document.getElementById('contactFormToAddress'); var email_two = document.getElementById('contact-address-two'); var phoneno = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/; var phone_input = document.getElementById("contact-phone"); var valid = true; if (email[0].value == '' || !Boston.emailRE.test(email[0].value)) { Boston.invalidateField(email[0], "Please enter a valid email address"); valid = false; } if (email_two != 'undefined') { if (email_two != null) { if (email2[0].value == '' || !Boston.emailRE.test(email2[0].value)) { Boston.invalidateField(email2[0], "Please enter a valid email address"); valid = false; } else if (email2[0].value != email[0].value) { Boston.invalidateField(email2[0], "Email does not match"); valid = false; } } } else { valid = true; } if (name[0].value == '') { Boston.invalidateField(name[0], "Please enter your full name"); valid = false; } if (subject[0].value == '') { Boston.invalidateField(subject[0], "Please enter a subject"); valid = false; } if (message[0].value == '') { Boston.invalidateField(message[0], "Please enter a message"); valid = false; } if (address_to.value !== to_address) { valid = false; } if (o_subject && subject[0].value !== o_subject) { valid = false; } if (phone[0].value !== '') { if (!phone_input.value.match(phoneno)) { Boston.invalidateField(phone[0], "Please enter a valid phone number"); valid = false; } } return valid; } function setBrowser(link) { var browserField = document.getElementById('contactFormBrowser'); browserField.value = navigator.userAgent; } function setToAddress(link) { var toField = document.getElementById('contactFormToAddress'); to_address = extract(link.getAttribute('href'), "mailto"); toField.value = to_address; } function setBodyMessage(link) { var messageField = document.getElementById('contact-message'); if (o_message = extract(link.getAttribute('href'), "body")) { o_message = decodeURIComponent(o_message); messageField.value = o_message; } } function setPhone(link) { var phoneField = document.getElementById('contact-phone'); if (o_phone = extract(link.getAttribute('href'), "phone")) { o_phone = decodeURIComponent(o_phone); phoneField.value = o_phone; } } function setSubject(link) { var subjectField = document.getElementById('contact-subject'); if (o_subject = extract(link.getAttribute('href'), "subject")) { o_subject = decodeURIComponent(o_subject); subjectField.value = o_subject; subjectField.type = "hidden"; subjectField.parentElement.classList.add("hidden"); } } // Request unique session token ID via Drupal endpoint function setToken() { Boston.request({ url: '/rest/email_token/create', method: 'POST', success: function (response) { if (response.status === 200) { var token = JSON.parse(response.response).token_session; document.getElementById('contact-token').value = token; } else { console.log("token response error"); } }, error: function() { console.log("token request error"); } }); } function extract(mailtoLink, element) { var result = false; var linkParts = mailtoLink.split('?'); if (typeof linkParts[0] !== "undefined" && element.toLowerCase() == "mailto") { result = linkParts[0].replace('mailto:', '') } if (!result && linkParts.length > 1) { var linkElements = linkParts[1].split('&'); for (var i = 0; i < linkElements.length; i++) { var defaultField = linkElements[i].split("="); if (typeof defaultField[0] !== "undefined" && defaultField[0] == element) { if (typeof defaultField[1] !== undefined) { result = defaultField[1]; } else { result = true; } break; } } } return result; } function setURL(link) { var urlField = document.getElementById('contactFormURL'); urlField.value = window.location.href; } function resetForm(form) { var errors = Boston.childByEl(form, 't--err'); for (var i = 0; i < errors.length; i++) { errors[i].remove(); i--; } } function start() { // The page needs to include a template with id of contactMessage if (document.getElementById('contactFormTemplate')) { var emailLinks = document.querySelectorAll('a[href^=mailto]:not(.hide-form)'); if (emailLinks.length > 0) { for (var i = 0; i < emailLinks.length; i++) { initEmailLink(emailLinks[i]); } } } } return { start: start, close: handleEmailClose } })(); BostonContact.start(); 'use strict' // This module controls the City of Boston table component // --------------------------- var BostonInput = (function () { var checkboxes; // Activate keyboard focus on checkboxes. function chkbxfield(){ for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].addEventListener('keypress', function(ev) { ev.preventDefault(); ev.stopPropagation(); if (checkboxes) { if (ev.keyCode === 13) { this.click(); } } }) } } function start() { // Find all checkboxes and add click trigger to "enter" keyboard key. checkboxes = document.querySelectorAll('input[type="checkbox"]'); if(checkboxes){ chkbxfield(); } } return { start: start } })(); BostonInput.start(); 'use strict' // This module controls the City of Boston newsletter component // --------------------------- var BostonHeader = (function () { var guideTitle; var headerGuideTitle; var header; var searchIcon; var burgerIcon; var burgerckbx; var searchckbx; function handleGuideTitleTrigger(show) { if (show) { headerGuideTitle.classList.add('h-gt--active'); } else { headerGuideTitle.classList.remove('h-gt--active'); } } function setupGuideTitle() { headerGuideTitle = document.createElement('div'); headerGuideTitle.className = 'h-gt'; headerGuideTitle.innerHTML = guideTitle.innerHTML; header.appendChild(headerGuideTitle); } function setupSearchIcon() { if (!searchIcon.addEventListener || !document.querySelector) { return; } searchIcon.addEventListener('click', function() { var searchField = document.querySelector('.sf-i-f'); if (searchField) { // setTimeout so that the search box appears via the CSS before we focus // it. window.setTimeout(function() { searchField.focus(); }, 0); } }) searchIcon.addEventListener('keydown', function(e) { e.stopImmediatePropagation(); if (e.keyCode == 13) { this.click(); } }) // make close button clickable with keyboard keys burgerIcon.addEventListener('keydown', function(e) { e.stopImmediatePropagation(); if (e.keyCode == 13) { this.click(); } }) } function start() { guideTitle = document.getElementById('topicTitle'); header = document.getElementById('main-menu'); burgerckbx = document.querySelector('label[for="brg-tr"]'); searchckbx = document.querySelector('label[for="s-tr"]'); if (document.querySelector) { // The search icon in the header is the label that controls this checkbox, // which in turn makes the search field hide/show via CSS. searchIcon = document.querySelector('label[for="s-tr"]'); burgerIcon = document.querySelector('label[for="brg-tr"]'); } if (burgerckbx) { burgerckbx.setAttribute("tabIndex", "0"); } if (searchckbx) { searchckbx.setAttribute("tabIndex", "0"); } if (guideTitle) { setupGuideTitle(); } if (searchIcon) { setupSearchIcon(); } } return { start: start, handleGuideTitleTrigger: handleGuideTitleTrigger } })(); BostonHeader.start(); 'use strict' // This module controls the City of Boston newsletter component // --------------------------- var BostonMap = (function () { var map = []; function createPopup (p) { return function (layer) { return L.Util.template(p, layer.feature.properties); }; } function createLegend(d) { return function (map) { return d; }; } function toggleMap(e, mapContainer) { e.preventDefault(); var isActive = Boston.hasClass(mapContainer, 'is-active'); if (isActive) { mapContainer.className = 'mp'; } else { mapContainer.className = 'mp is-active'; } } function initMap(mapContainer) { var mapEl = Boston.childByEl(mapContainer, 'map')[0]; var buttonEl = Boston.childByEl(mapContainer, 'mp-v')[0]; var closeEl = Boston.childByEl(mapContainer, 'mp-e')[0]; if (buttonEl && closeEl) { // Toggle the view buttonEl.addEventListener('click', function (e) { toggleMap(e, mapContainer); }); // Toggle the view closeEl.addEventListener('click', function (e) { toggleMap(e, mapContainer); }); } // Set the Map ID used to create a unique canvas for each map. var mapID = mapEl.id; // Get array of map objects from Drupal. var mapJSON = mapData[mapID]; // Convert JSON into javascript object. var mapObj = JSON.parse(mapJSON); // Set ESRI Feed title, url, and color info. var feeds = mapObj.feeds; // Set Custom Pins title, desc, latitude and longitude info. var points = mapObj.points; // Set Map Options (0 = Static, 1 = Zoom). var mapOptions = mapObj.options; // Set Basemap URL. var basemapUrl = mapObj.basemap; // Set Latitude to component value if it exists, if not set to ESRI Lat, if nothing exists set hardcoded value. var latitude = mapObj.componentLat ? mapObj.componentLat : mapObj.esriLat ? mapObj.esriLat : 42.357004; // Set Longitude to component value if it exists, if not set to ESRI Lat, if nothing exists set hardcoded value. var longitude = mapObj.componentLong ? mapObj.componentLong : mapObj.esriLong ? mapObj.esriLong : -71.062309; // Set Zoom to component value if it exists, if not set to ESRI Lat, if nothing exists set hardcoded value. var zoom = mapObj.componentZoom ? mapObj.componentZoom : mapObj.esriZoom ? mapObj.esriZoom : 14; // Apply default coordinates and zoom level. var map = L.map(mapID, {zoomControl: false}).setView([latitude, longitude], zoom); if (mapOptions == 1) { // Add zoom control to bottom right. L.control.zoom({ position:'bottomright' }).addTo(map); } // Add custom pins created in Map component. for (var j = 0; j < points.length; j++) { var customPin = L.marker([points[j].lat, points[j].long]).addTo(map); customPin.bindPopup( '' + '' + points[j].name + '' + '' + '' + points[j].desc + '
' ); } // Add mapbox basemap. L.tileLayer(basemapUrl).addTo(map); // Set the legend position. var legend = L.control({position: 'topleft'}); var div = L.DomUtil.create('div', 'info legend'); // Add layer for ESRI feed(s) and add item for legend. for (var k = 0; k < feeds.length; k++) { // Check if pins should be clustered. var baseObj = (feeds[k].cluster == 1) ? L.esri.Cluster : L.esri; var layerObj = baseObj.featureLayer({ url: feeds[k].url, style: { "color": feeds[k].color, "weight": 3 } }).addTo(map); // Create popups for pin markers layerObj.bindPopup(createPopup(feeds[k].popup)); // Add item to legend. div.innerHTML += ' ' + feeds[k].title + '