1(function (drupalSettings, olCounties, abbr, d3, gaHelp, umrbClick) { 2 "use strict"; 3 4 (function main() { 5 exportConfig(); 6 bindEventListeners(); 7 })(); 8 9 /** 10 * Sets all properties on a vector layer to allow users to click on them. 11 * @param {ol.layer.Vector} layer - A vector layer. Generally the Data Layer or state/county lines. 12 */ 13 function makeVectorLayerInteractive(layer) { 14 _makeVectorLayerClickable(layer); 15 _makeVectorLayerHighlightable(layer); 16 } 17 18 /** 19 * Sets property on all features to allow the layer to be clickable. 20 * @param {ol.layer.Vector} layer - A vector layer. Generally the Data Layer or state/county lines. 21 */ 22 function _makeVectorLayerClickable(layer) { 23 if (layer.getSource().getFeatures()[0]?.get("clickable") === "true") { 24 return; 25 } 26 layer.getSource().forEachFeature(_makeVectorFeatureClickable); 27 } 28 29 /** 30 * Sets property on all features to allow the layer to be highlighted on mouseover. 31 * @param {ol.layer.Vector} layer - A vector layer. Generally the Data Layer or state/county lines. 32 */ 33 function _makeVectorLayerHighlightable(layer) { 34 if (layer.getSource().getFeatures()[0]?.get("highlight") === "true") { 35 return; 36 } 37 layer.getSource().forEachFeature(_makeVectorFeatureHighlightable); 38 } 39 40 /** 41 * Sets property on a feature to allow the feature to be clickable. 42 * @param {ol.Feature} feature - A vector feature. Generally the Data Layer or state/county lines. 43 */ 44 function _makeVectorFeatureClickable(feature) { 45 feature.set("clickable", "true"); 46 } 47 48 /** 49 * Sets property on a feature to allow the feature to be highlighted on mouseover. 50 * @param {ol.Feature} feature - A vector feature. Generally the Data Layer or state/county lines. 51 */ 52 function _makeVectorFeatureHighlightable(feature) { 53 feature.set("highlight", "true"); 54 } 55 56 /** 57 * @param e {ol.event.Event} 58 */ 59 function handlePointerMove(e) { 60 if (e.dragging) { 61 return; 62 } 63 const map = this; 64 if (map.highlight) { 65 map.highlightLayer.getSource().clear(); 66 map.highlight.set("isHighlighted", undefined); 67 map.highlight = false; 68 } 69 const pixel = map.getEventPixel(e.originalEvent); 70 const features = getInteractiveFeatureFromPixel(map, pixel); 71 highlightFeature(map, pixel, features.highlight); 72 updateCursorType(map, pixel, features.click); 73 } 74 75 /** 76 * Highlights the feature underneath the mouse, if it should be. Clears highlight otherwise. 77 * @param {ol.Map} map - OpenLayers map object with extended config. 78 * @param {ol.pixel} pixel - Pixel the user is moused over. 79 */ 80 function highlightFeature(map, pixel, cachedFeature) { 81 if (map.mapElem.closest(".map-screenshot-overlay")) { 82 return; 83 } 84 const feature = cachedFeature; 85 // Only hightlight the states layer on specified pages. The USGS streamflow layer has a STATE property 86 if ( 87 feature && 88 (!feature.getProperties().STATE || 89 map.dataLayer.getSource().getFeatures().includes(feature) || 90 checkStatesClickable()) 91 ) { 92 if (feature !== map.highlight) { 93 if (map.highlight) { 94 map.highlightLayer.getSource().clear(); 95 map.highlight.set("isHighlighted", undefined); 96 } 97 feature.set("isHighlighted", true); 98 if (!(map.dataLayer.getSource().hasFeature && map.dataLayer.getSource().hasFeature(feature))) { 99 // Only utilize the highlight layer styles for area polygons, ie state/county/watershed/etc 100 map.highlightLayer.getSource().addFeature(feature); 101 } 102 map.highlight = feature; 103 } 104 return; 105 } 106 if (map.highlight) { 107 map.highlightLayer.getSource().clear(); 108 map.highlight.set("isHighlighted", undefined); 109 map.highlight = false; 110 } 111 } 112 113 /** 114 * Updates cursor to a pointer if the user is hovering over a clickable data layer. 115 * @param {ol.Map} map - Map which is being hovered over. 116 * @param {ol.pixel} pixel - Pixel which is being hovered over. 117 */ 118 function updateCursorType(map, pixel, cachedFeature) { 119 const feature = cachedFeature; 120 const props = feature ? feature.getProperties() : {}; 121 const dataLayer = map.dataLayer;
122 let cursorType = ""; 123 if (feature && props.clickable && dataLayer.getSource().hasFeature && dataLayer.getSource().hasFeature(feature)) { 124 cursorType = "pointer"; 125 } 126 if (feature && props.clickable && map.abv === "tribal" && map.tribalPage.getSource().hasFeature(feature)) { 127 cursorType = "pointer"; 128 } 129 map.getTargetElement().style.cursor = cursorType; 130 } 131 132 /** 133 * Finds the Vector Feature which was clicked, if one exists. 134 * @param {ol.Map} map - OpenLayers map object with extended config. 135 * @param {ol.pixel} pixel - Pixel the user is moused over. 136 * @return {ol.Feature|undefined} - The Vector feature. 137 */ 138 function getClickableFeatureFromPixel(map, pixel) { 139 let clickFeature, 140 foundDataFeatureFlag = false; 141 map.forEachFeatureAtPixel( 142 pixel, 143 function (feature) { 144 if (feature.get("clickable") === "true" && foundDataFeatureFlag === false) { 145 clickFeature = feature; 146 } 147 if (map.dataLayer.getSource().hasFeature && map.dataLayer.getSource().hasFeature(clickFeature)) { 148 foundDataFeatureFlag = true; 149 } 150 if (checkStatesClickable() && map.states.getSource().hasFeature(clickFeature)) { 151 foundDataFeatureFlag = true; 152 } 153 if (map.abv === "tribal" && map.tribalPage.getSource().hasFeature(clickFeature)) { 154 foundDataFeatureFlag = true; 155 } 156 }, 157 { 158 layerFilter(layer) { 159 return !!(map.clickableLayers && map.clickableLayers.indexOf(layer) !== -1); 160 }, 161 } 162 ); 163 return clickFeature; 164 } 165 166 /** 167 * Finds the Vector Feature which was clicked, if one exists. 168 * @param {ol.Map} map - OpenLayers map object with extended config. 169 * @param {ol.pixel} pixel - Pixel the user is moused over. 170 * @return {ol.Feature|undefined} - The Vector feature. 171 */ 172 function getInteractiveFeatureFromPixel(map, pixel) { 173 let clickFeature, 174 highlightableFeature, 175 foundClickFeatureFlag = false, 176 foundHighlightFeatureFlag = false; 177 map.forEachFeatureAtPixel( 178 pixel, 179 function (feature) { 180 if (feature.get("clickable") === "true" && foundClickFeatureFlag === false) { 181 clickFeature = feature; 182 } 183 if (feature.get("highlight") === "true" && foundHighlightFeatureFlag === false) { 184 highlightableFeature = feature; 185 } 186 if (map.dataLayer.getSource().hasFeature) { 187 if (clickFeature && map.dataLayer.getSource().hasFeature(clickFeature)) { 188 foundClickFeatureFlag = true; 189 } 190 if (highlightableFeature && map.dataLayer.getSource().hasFeature(highlightableFeature)) { 191 foundHighlightFeatureFlag = true; 192 } 193 } 194 if (checkStatesClickable()) { 195 if (clickFeature && map.states.getSource().hasFeature(clickFeature)) { 196 foundClickFeatureFlag = true; 197 } 198 if (highlightableFeature && map.states.getSource().hasFeature(highlightableFeature)) { 199 foundHighlightFeatureFlag = true; 200 } 201 } 202 if (map.abv === "tribal") { 203 if (clickFeature && map.tribalPage.getSource().hasFeature(clickFeature)) { 204 foundClickFeatureFlag = true; 205 } 206 if (highlightableFeature && map.tribalPage.getSource().hasFeature(highlightableFeature)) { 207 foundHighlightFeatureFlag = true; 208 } 209 } 210 if (foundClickFeatureFlag || foundHighlightFeatureFlag) { 211 //return true; 212 } 213 }, 214 { 215 layerFilter(layer) { 216 return [map.dataLayer, map.states, map.counties, map.watersheds, map.tribalPage].includes(layer); 217 }, 218 } 219 ); 220 return { 221 click: clickFeature, 222 highlight: highlightableFeature, 223 }; 224 } 225 226 /** 227 * Handles when a user clicks on a map. 228 * @param {Event} e - Click event 229 * @constructor 230 */ 231 function OLMapSingleClick(e) { 232 const map = this; 233 if (map.mapElem.closest(".map-screenshot-overlay")) { 234 return; 235 } 236 237 const dataSource = map.dataLayer.getSource(); 238 let dataUrl = ""; 239 if (dataSource.getUrl && typeof dataSource.getUrl === "function") { 240 dataUrl = map.dataLayer.getSource().getUrl(); 241 } else if (dataSource.getUrls && typeof dataSource.getUrls === "function") { 242 dataUrl = map.dataLayer.getSource().getUrls()[0]; 243 } 244 if (_checkMapIsNotClickable(dataUrl) && map.abv !== "tribal") { 245 return; 246 } 247 const coordinate = e.coordinate; 248 const feature = getClickableFeatureFromPixel(map, map.getEventPixel(e.originalEvent)); 249 if (!feature) { 250 return; 251 } 252 const properties = feature.getProperties(); 253 const popupContent = makePopupContent(map, properties, dataUrl); 254 const popupBody = map.popupContainer.querySelector(".popup-content"); 255 if (popupContent) { 256 if (typeof popupContent === "string") { 257 popupBody.innerHTML = popupContent; 258 } else { 259 popupBody.replaceChildren(popupContent); 260 } 261 if (!map.popupOverlay.autoPan) { 262 map.getView().animate({ 263 center: coordinate, 264 duration: 250, 265 }); 266 if (map.highlight) {
267 map.highlightLayer.getSource().removeFeature(map.highlight); 268 } 269 map.highlightLayer.getSource().addFeature(feature); 270 map.highlight = feature; 271 } 272 map.popupOverlay.setPosition(coordinate); 273 } 274 } 275 276 /** 277 * Checks if the data layer for a map is clickable. 278 * 279 * Generally also means the layer has will create a popup and should have a tabular view. No exceptions to this 280 * currently exist. Currently is the Streamflow layer, the NWS map, and the dams & lakes on the ACF/ACT pages. 281 * 282 * @param {string} url - URL for the primary data layer of the map. 283 * @return {boolean} - true if layer is clickable, false if not. 284 */ 285 function checkDataLayerIsClickable(url) { 286 return ( 287 url.indexOf("strmflow") !== -1 || 288 url.indexOf("usace-dams") !== -1 || 289 url.indexOf("apclakes") !== -1 || 290 url.indexOf("nws-dgt-pnts") !== -1 || 291 url.indexOf("sector/fire/fires.") !== -1 || 292 umrbClick.testClickable(url) || 293 url.includes("mrb/water/nwps") || 294 (url.toLowerCase().includes("usace") && url.toLowerCase().includes("hydrograph")) || 295 url.includes("acf/groundwater") || 296 checkStatesClickable() 297 ); 298 } 299 300 /** 301 * Checks if a map is NOT clickable. 302 * 303 * Currently lets users click on maps if there are counties, watersheds, or data layers defined in 304 * checkDataLayerIsClickable. 305 * @param {string} dataUrl - URL for the primary data layer of the map. 306 * @return {boolean} - true if a map does not have a clickable layer, false if it doesn't 307 * @private 308 */ 309 function _checkMapIsNotClickable(dataUrl) { 310 return ( 311 !drupalSettings.drawCountyOutlines && !drupalSettings.drawWatershedOutlines && !checkDataLayerIsClickable(dataUrl) 312 ); 313 } 314 315 /** 316 * Builds a temporary skeleton for popup content. 317 * @return {HTMLDivElement} - The skeleton 318 */ 319 function makePopupSkeleton() { 320 const div = document.createElement("div"); 321 div.classList.add("bg-base-lightest", "radius-md", "height-6"); 322 const spinner = document.createElement("div"); 323 spinner.classList.add("spinner", "spinner--centered", "icon-size--large"); 324 div.appendChild(spinner); 325 return div; 326 } 327 328 /** 329 * Builds and places a popup containing a copy of the map's legend 330 * @param {ol.Map} map - The OpenLayers Map object 331 */ 332 function handleLegendPopup(map) { 333 const legendElem = map.mapElem.closest(".map-content").querySelector(".legend-wrapper"); 334 const legendCopy = legendElem.cloneNode(true); 335 const popupBody = map.popupContainer.querySelector(".popup-content"); 336 const center = map.getView().getCenter(); 337 popupBody.replaceChildren(legendCopy); 338 map.popupOverlay.setPosition(center); 339 } 340 341 /** 342 * Builds the HTML element which populate the popup when a map is clicked. 343 * @param {ol.Map} map - Customized map which was clicked on. 344 * @param {{}} properties - Properties of the feature which was clicked on. 345 * @param {string} dataUrl - URL of the main data layer for the map. 346 * @return {string|Element} - HTML element or string of popup content. 347 */ 348 function makePopupContent(map, properties, dataUrl) { 349 if ((drupalSettings.drawCountyOutlines && properties.FIPS) || dataUrl === "county") { 350 // county popup 351 const countyName = properties.NAME; 352 const stateName = 353 drupalSettings.state_name || 354 drupalSettings.full_state?.toLowerCase().replace(" ", "-") || 355 abbr.toSlug(properties.STAB); 356 const path = "/states/" + stateName + "/county/" + countyName; 357 const countyType = olCounties.makeCountyType(map.abv || properties.STAB, true); 358 return _makePopupLinks([`Open ${countyName} ${countyType} Page`, path]); 359 } 360 if (dataUrl === "state-county") { 361 // county within state popup 362 return _makePopupLinks( 363 [`Open ${properties.name} Page`, `/states/${properties.slug}`], 364 [`Load ${properties.name} Counties`, "#"] 365 ); 366 } 367 if ( 368 (drupalSettings.drawWatershedOutlines && properties.Name && drupalSettings.watershedClickable !== false) || 369 dataUrl === "watershed" 370 ) { 371 return makeWatershedPopup(properties); 372 } 373 if ( 374 dataUrl.includes("acf/groundwater") || 375 (map.metadata && map.metadata.classes && ( 376 (map.metadata.classes.includes("gw-percent") || map.metadata.classes.includes("gw-compare")) 377 )) 378 ) { 379 const html = []; 380 if (properties.statnum) { 381 html.push(`<div class="margin-bottom-05"><strong>Monitoring Location</strong>: ${properties.statnum}</div>`); 382 } 383 if (properties.value !== undefined) { 384 html.push(`<div class="margin-bottom-05"><strong>Depth to water</strong>: ${properties.value} ft</div>`); 385 } 386 if (properties.stats_record_length_years !== undefined) { 387 html.push(`<div class="margin-bottom-05"><strong>Record length</strong>: ${properties.stats_record_length_years} years</div>`); 388 } 389 if (properties.observation_date) { 390 html.push(`<div class="margin-bottom-05"><strong>Latest measurement date</strong>: ${(new Date(properties.observation_date)).toLocaleDateString("en-US", {month: "2-digit", day: "2-digit", year: "numeric"})}</div>`); 391 } 392 if (properties.waterdata_link) { 393 html.push(`<div><a class="c--sbtn width-full" href="${properties.waterdata_link}" target="_blank">View More Data</a></div>`); 394 } 395 return html.join(""); 396 } 397 if (dataUrl.indexOf("strmflow") !== -1 && properties.MONITORING_LOCATION_ID) { 398 let link = properties.URL; 399 if (!link) { 400 link = "https://waterdata.usgs.gov/monitoring-location/" + properties.MONITORING_LOCATION_ID.toString(); 401 } 402 const name = properties.MONITORING_LOCATION_NAME || properties.MONITORING_LOCATION_ID; 403 return `<div class="margin-bottom-05 padding-bottom-2px"><strong>${name}</strong></div><div class="margin-bottom-05 padding-bottom-2px">Streamflow percentile: ${properties.PERCENTILE}</div><div class="margin-bottom-05 padding-bottom-2px"><a href="${properties.URL}" target="_blank" class="c--sbtn width-full">Monitoring Location Page</a></div><div><a href="${properties.DAILY_DURATION_URL}" target="_blank" class="c--sbtn width-full">Statistical Hydrographs</a></div>`; 404 //return _makePopupLinks([name, link, "ext"]); 405 } 406 if (dataUrl.indexOf("strmflow") !== -1 && properties.STAID) { 407 // Old Waterwatch -- should likely be removed after February 408 let link = properties.URL; 409 if (!link) { 410 link = "https://waterdata.usgs.gov/monitoring-location/" + properties.STAID.toString(); 411 } 412 return _makePopupLinks([properties.STANAME, link, "ext"]); 413 } 414 if (dataUrl.indexOf("strmflow") !== -1 && properties.CLASS) { 415 // Old Waterwatch -- should likely be removed after February 416 const gaugeId = properties.NAME.split(" ")[1]; 417 return ( 418 '<a href="https://waterdata.usgs.gov/monitoring-location/' + 419 gaugeId + 420 '" target="_blank">' + 421 properties.NAME + 422 "</a>" 423 ); 424 } 425 if (dataUrl.indexOf("usace-dams") !== -1) { 426 const lakeName = properties.LAKE; 427 const curLevel = properties.MDNGHTPOOL === "M" ? "Missing Data" : `${properties.MDNGHTPOOL} ft MSL`; 428 const oneWeekForecast = properties.WK1FRCST; 429 let popupContent = `<div class="text-primary text-bold">${lakeName}</div><div>Current Level: ${curLevel}</div>`; 430 if (oneWeekForecast !== "-9.0") { 431 popupContent += `<div>
431One Week Forecast: ${oneWeekForecast} ft MSL</div>`; 432 } 433 return popupContent; 434 } 435 if (dataUrl.indexOf("apclakes") !== -1) { 436 const lakeName = properties.LAKE; 437 const urlFragment = properties.URLKEY; 438 return `<div class="text-primary text-bold"><a href="https://apcshorelines.com/lake/${urlFragment}" target="_blank">View ${lakeName} Lake conditions.</a></div>`; 439 } 440 if (dataUrl.toLowerCase().includes("usace") && dataUrl.toLowerCase().includes("hydrograph")) { 441 const name = `<span class="font-body-md text-bold">${properties["Gauge Name"]}</span>`; 442 const link = `<a href="${properties["Hydrograph Link"]}" class="c--btn margin-top-05" target="_blank">View Hydrograph</a>`; 443 return `<div>${name}</div><div>${link}</div>`; 444 } 445 if (dataUrl.indexOf("nws-dgt-pnts") !== -1) { 446 const wfo = properties.WFO; 447 const city = properties.CITY; 448 const state = properties.STATE; 449 const date = properties.DATE; 450 if (wfo) { 451 const line1 = _makePopupText(`<b>${city}, ${state} Weather Forecast Office (${wfo})</b>`); 452 const line2 = _makePopupText(`<b>Statement Date: </b> ${date}`); 453 const btn = _makePopupLinks(["View This Statement", properties.URL, "ext"]); 454 const wrapper = document.createElement("div"); 455 wrapper.append(line1, line2, btn); 456 return wrapper; 457 } 458 } else if (dataUrl === "dews") { 459 const dewsName = 460 properties.dews === "california-nevada" 461 ? "California-Nevada" 462 : properties.dews 463 .split("-") 464 .map((word) => { 465 return word[0].toUpperCase() + word.substring(1); 466 }) 467 .join(" "); 468 const path = "/dews/" + properties.dews; 469 return _makePopupLinks([`Open ${dewsName} Page`, path]); 470 } else if (dataUrl.includes("sector/fire/fires.")) { 471 return _makeFirePopup(properties); 472 } else if (dataUrl === "locale") { 473 const address = 474 properties.RegionAbbr === "HI" 475 ? encodeURIComponent(properties.LongLabel) 476 : encodeURIComponent(properties.Match_addr); 477 return _makePopupLinks([ 478 `Open ${properties.City}, ${properties.Region} (${properties.Postal}) Page`, 479 `/location/${address}`, 480 ]); 481 } 482 if (dataUrl.includes("mrb/water/nwps")) { 483 return _makeRiverStagePopup(properties); 484 } 485 if (umrbClick.testClickable(dataUrl)) { 486 return umrbClick.createPopup(dataUrl, properties, map.metadata); 487 } 488 if (checkStatesClickable() || dataUrl === "state") { 489 return _handleFrontClick(properties); 490 } 491 if (map.abv === "tribal") { 492 return _handleTribalClick(properties); 493 } 494 } 495 496 /** 497 * Creates a popup for the active wildfires layer. Currently found on sectors/wildfire-management. 498 * https://storage.googleapis.com/noaa-nidis-drought-gov-data/current-conditions/json/v1/sector/fire/fires.geojson 499 * @param {{}} properties - Properties of the clicked feature. 500 * @return {string} - Formatted string for popup content. 501 */ 502 function _makeFirePopup(properties) { 503 const name = properties.firename; 504 const date = properties.reportdate.replaceAll("\\", ""); 505 const area = properties.area.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 506 let dateStr = ""; 507 if (date !== "01/20/70") { 508 dateStr = `<br><strong>Fire Discovery Date:</strong> ${date}`; 509 } 510 return `<div><strong>Name:</strong> ${name}${dateStr}<br><strong>Size:</strong> ${area} acres</div>`; 511 } 512 513 /** 514 * Creates popup for River Gauges 515 * @param {{}} properties - Feature properties 516 * @return {string} HTML as string. 517 */ 518 function _makeRiverStagePopup(properties) { 519 const title = `<div class="padding-bottom-05 margin-bottom-1 border-bottom-1px border-black"><strong>${properties.NAME}: ${properties.GAUGE_ID}</strong></div>`; 520 const low = `<div class="margin-bottom-05"><strong>Low Water Threshold</strong>: ${_getRiverVal(properties, "LOW_THRESH")}</div>`; 521 const current = `<div class="margin-bottom-05"><strong>Current Level</strong>: ${_getRiverVal(properties, "OBSERVED")}</div>`; 522 const max = `<div class="margin-bottom-05"><strong>
522Max Forecast Level</strong>: ${_getRiverVal(properties, "FORECAST")}</div>`; 523 const link = `<div><a class="c--sbtn" target="_blank" href="${properties.URL}">View More Gauge Info</a></div>`; 524 return ["<div>", title, low, current, max, link, "</div>"].join(""); 525 } 526 527 /** 528 * Creates the popup for Watersheds. 529 * @param {{}} properties - Feature properties. "Name" is the only property required. 530 * @return {HTMLDivElement} - Div with however many links are needed. 531 */ 532 function makeWatershedPopup(properties) { 533 let watershedName = properties.Name; 534 const watershedLinkName = watershedName.replace(" Region", "").replace(" ", "-"); 535 const path = "/watersheds/" + watershedLinkName; 536 const links = []; 537 if (watershedName === "Mississippi River Basin Dashboard") { 538 return _makePopupLinks(["Open Mississippi River Basin Dashboard", "/watersheds/mississippi-river-dashboard"]); 539 } 540 if (watershedName === "Colorado") { 541 watershedName = "Colorado River Basin"; 542 } 543 if (watershedName === "Upper Colorado Region" || watershedName === "Lower Colorado Region") { 544 links.push(["Open Colorado River Basin Page", "/watersheds/colorado"]); 545 } 546 if (watershedName === "Colorado River Basin") { 547 links.push( 548 ["Open Upper Colorado Region Page", "/watersheds/upper-colorado"], 549 ["Open Lower Colorado Region Page", "/watersheds/lower-colorado"] 550 ); 551 } 552 links.push([`Open ${watershedName} Page`, path]); 553 if ( 554 watershedName === "Arkansas-White-Red Region" || 555 watershedName === "Lower Mississippi Region" || 556 watershedName === "Missouri Region" || 557 watershedName === "Ohio Region" || 558 watershedName === "Tennessee Region" || 559 watershedName === "Upper Mississippi Region" 560 ) { 561 links.push(["Open Mississippi River Basin Dashboard", "/watersheds/mississippi-river-dashboard"]); 562 } 563 return _makePopupLinks(...links); 564 } 565 566 /** 567 * Gets value from a River Gauge feature. 568 * @param {{}} properties - Feature Properties 569 * @param {string} key - Key of feature. 570 * @return {string} - Not Available or Value formatted as feet. 571 */ 572 function _getRiverVal(properties, key) { 573 if (!properties || !properties[key] || properties[key] === "null") { 574 return "Not Available"; 575 } 576 return properties[key] + "'"; 577 } 578 579 /** 580 * Creates text wrapped in a p tag. 581 * @param {string} text - Text for a popup. Can contain sub HTML tags. 582 * @return {HTMLParagraphElement} - Text wrapped in a p tag. 583 */ 584 function _makePopupText(text) { 585 const p = document.createElement("p"); 586 p.innerHTML = text; 587 return p; 588 } 589 590 /** 591 * Creates multiple popup links. 592 * @param {[string, string]|[string, string, string]} args - Any number of 2/3 element arrays. First element is link 593 * text, second is link href and third is an optional flag. 594 * @return {HTMLDivElement} - Div with however many links are needed. 595 */ 596 function _makePopupLinks(...args) { 597 const div = document.createElement("div"); 598 div.className = "c--sbtn-stack"; 599 args.forEach((arg) => { 600 div.appendChild(_makePopupLink(arg)); 601 }); 602 div.lastElementChild.classList.add("c--sbtn"); 603 return div; 604 } 605 606 /** 607 * Creates an individual link for a popup. Arguments must be passed as an array. 608 * @param {string} text - Text for the link 609 * @param {string} link - URL for the link. 610 * @param {string} flag - Makes link external if the value is exactly "ext". 611 * @return {HTMLAnchorElement} - Link for the popup. 612 */ 613 function _makePopupLink([text, link, flag]) { 614 const a = document.createElement("a"); 615 a.href = link; 616 a.textContent = text; 617 if (flag === "ext") { 618 a.target = "_blank"; 619 a.classList.add("ext"); 620 } 621 return a; 622 } 623 624 /** 625 * Gets the HTML element for the state popup. Inserted via replaceChildren. 626 * @param {{}} properties - From https://openlayers.org/en/latest/apidoc/module-ol_Feature-Feature.html#getProperties 627 * @return {Element} - HTML element for the state popup. Link to state formatted as button. 628 */ 629 function _handleFrontClick(properties) { 630 const stateProperties = abbr.getAll(properties.STATE); 631 if (stateProperties.name === "District of Columbia") { 632 stateProperties.slug = "District-Of-Columbia/county/District%20Of%20Columbia"; 633 } 634 const links = _makePopupLinks([`Go to ${stateProperties.name} Page`, `/states/${stateProperties.slug}`]); 635 links.classList.add("c--state-link"); 636 return links; 637 } 638 639 /** 640 * Creates the HTML for the tribal popup. Inserted via replaceChildren. 641 * @param {{}} properties - From https://openlayers.org/en/latest/apidoc/module-ol_Feature-Feature.html#getProperties 642 * @return {Element} The conents for the tribal popup. Currently just the title of the area. 643 */ 644 function _handleTribalClick(properties) { 645 const wrapper = document.createElement("div"); 646 const title = document.createElement("h4"); 647 title.textContent = properties.NAMELSAD; 648 title.className = "margin-0"; 649 wrapper.append(title); 650 return wrapper; 651 } 652 653 /** 654 * Checks if the states should be clickable. Currently current conditions, front, national, and outlooks and forecasts 655 * pages. 656 * @return {Element} - Used as a truthy/falsy variable. 657 */ 658 function checkStatesClickable() { 659 return ( 660 document.querySelector(".page-node-type-current-condition") || 661 document.querySelector(".page-node-type-front-page") || 662 document.querySelector(".page-node-type-national") || 663 document.querySelector(".page-node-type-outlooks-and-forecasts") 664 ); 665 } 666 667 /** 668 * Creates click specific custom events. 669 */ 670 function bindEventListeners() { 671 document.addEventListener("click", _handleFrontClickEvent); 672 document.addEventListener("click", handleMabTabClick); 673 } 674 675 /** 676 * Delegates the event for when a user clicks on a state link from a map. 677 * @param {MouseEvent} e - The event. 678 */ 679 function _handleFrontClickEvent(e) { 680 const t = e.target; 681 if (!checkStatesClickable() || t.tagName.toLowerCase() !== "a" || !t.closest(".c--state-link")) { 682 return; 683 } 684 gaHelp.registerEvent("map_interaction", { 685 action: "state_link_click", 686 direction: _getStateLinkState(t.textContent), 687 section: _getStateLinkPageType(), 688 }); 689 } 690 691 /** 692 * Clears a map popup if it exists when the map is changed. 693 * @param {MouseEvent} e - The event. 694 */ 695 function handleMabTabClick(e) { 696 const t = e.target; 697 if (t.tagName.toLowerCase() !== "a" || !t.classList.contains("c--tab")) { 698 return; 699 } 700 const mapElem = window.mapsUtil.findMap(t); 701 if (!mapElem) { 702 return; 703 } 704 const map = window.jQuery(mapElem).data("map"); 705 if (!map?.popupOverlay) { 706 return; 707 } 708 map.popupOverlay.setPosition(undefined); 709 } 710 711 /** 712 * Gets human readable name of the page type. 713 * @return {string} - The page type. 714 */ 715 function _getStateLinkPageType() { 716 if (document.querySelector(".page-node-type-current-condition")) { 717 return "Current Conditions"; 718 } 719 if (document.querySelector(".page-node-type-front-page")) { 720 return "Front"; 721 } 722 if (document.querySelector(".page-node-type-national")) { 723 return "National"; 724 } 725 if (document.querySelector(".page-node-type-outlooks-and-forecasts")) { 726 return "Outlooks and Forecasts"; 727 } 728 } 729 730 /** 731 * Gets the name of the target state from the link text. 732 * @param {string} text - Link Text. Expects "Go to STATENAME page" as defined in _handleFrontClick. 733 * @return {string} - Name of state 734 */ 735 function _getStateLinkState(text) { 736 return text.replace("Go to ", "").replace(" page", ""); 737 } 738 739 /** 740 * Exports useful functions. 741 */ 742 function exportConfig() { 743 window.olClick = { 744 checkDataLayerIsClickable, 745 checkStatesClickable, 746 makeVectorLayerInteractive, 747 highlightFeature, 748 handlePointerMove, 749 getClickableFeatureFromPixel, 750 handleLegendPopup, 751 OLMapSingleClick, 752 makePopupSkeleton, 753 makePopupContent, 754 }; 755 } 756})(window.drupalSettings, window.olCounties, window.abbr, window.d3, window.gaHelp, window.umrbClick);
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.