From a9e333b73a810504b9f23eb08bb17c97f50bceed Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 3 Feb 2026 22:11:54 +0000 Subject: [PATCH] Add new custom element for handling report ID or location Looks purty. --- .../static/js/address-or-report-suggestion.js | 209 ++++++++++++++++++ rmo/template/status.html | 43 +--- 2 files changed, 213 insertions(+), 39 deletions(-) create mode 100644 html/static/js/address-or-report-suggestion.js diff --git a/html/static/js/address-or-report-suggestion.js b/html/static/js/address-or-report-suggestion.js new file mode 100644 index 00000000..5b6ea627 --- /dev/null +++ b/html/static/js/address-or-report-suggestion.js @@ -0,0 +1,209 @@ +class AddressOrReportInput extends HTMLElement { + // make element form-associated + static formAssociated = true; + + constructor() { + super(); + + this.attachShadow({mode: "open" }); + this.internals = this.attachInternals(); + this.render(); + + // Element references + this._input = this.shadowRoot.querySelector("input"); + this._suggestions = this.shadowRoot.querySelector(".suggestions-container"); + + // Bind methods + this._handleInput = this._handleInput.bind(this); + + // Debounce timer + this._debounceTimer = null; + + // The suggestion data + this._suggestionData = null; + } + + // Lifecycle: when element is added to the DOM + connectedCallback() { + this._input.addEventListener("input", this._handleInput); + } + + // Lifecycle: when element is removed from the DOM + disconnectedCallback() { + this._input.removeEventListener('input', this._handleInput); + } + + // Lifecycle: watch these attributes for changes + static get observedAttributes() { + return ['placeholder', 'api-key']; + } + + // Lifecycle: respond to attribute changes + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'placeholder' && this._input) { + this._input.placeholder = newValue; + } + + if (name === 'api-key') { + this._apiKey = newValue; + } + } + + // Properties API + get value() { + return this._input ? this._input.value : ''; + } + + set value(val) { + if (this._input) { + this._input.value = val; + const entries = new FormData(); + entries.append("address", val); + this.internals.setFormValue(entries); + } + } + + // Private methods + _handleInput(event) { + const searchText = event.target.value.trim(); + + // Clear previous timer + clearTimeout(this._debounceTimer); + + // Clear suggestions if input is less than 3 characters + if (searchText.length < 3) { + this._suggestions.innerHTML = ''; + return; + } + + // Debounce API calls (wait 300ms after typing stops) + this._debounceTimer = setTimeout(() => { + this._fetchAddressSuggestions(searchText) + .then(response => { + this._renderSuggestions(response.features); + }); + }, 300); + + } + + async _fetchAddressSuggestions(text) { + try { + const url = `https://api.mapbox.com/search/geocode/v6/forward?q=${encodeURIComponent(text)}&access_token=${this._apiKey}`; + + const response = await fetch(url); + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching geocoding suggestions:', error); + } + } + + _renderSuggestions(suggestions) { + console.log("Rendering suggestions", suggestions); + this._suggestions.innerHTML = suggestions.map((item, index) => { + if (item.properties.place_formatted != "") { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } else { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } + }).join(''); + + // Add click listeners to suggestions + this.shadowRoot.querySelectorAll('.suggestion-item').forEach(el => { + el.addEventListener('click', e => { + const index = parseInt(el.dataset.index); + const suggestion = suggestions[index]; + this.SetValue(suggestion); + // Dispatch custom event + this.dispatchEvent(new CustomEvent('address-selected', { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + location: suggestion + } + })); + }); + }); + } + + // Initial render of component + render() { + const placeholder = this.getAttribute('placeholder') || 'Enter address'; + + this.shadowRoot.innerHTML = ` + + +
+ + +
+
+ `; + } + + // Public methods + clear() { + if (this._input) { + this._input.value = ''; + this._suggestions.innerHTML = ''; + } + } + + SetValue(suggestion) { + this.value = suggestion.properties.full_address; + this._suggestions.innerHTML = ''; + } +} + +customElements.define('address-or-report-input', AddressOrReportInput); diff --git a/rmo/template/status.html b/rmo/template/status.html index 5795681d..a8f3e800 100644 --- a/rmo/template/status.html +++ b/rmo/template/status.html @@ -3,6 +3,7 @@ {{define "title"}}Status{{end}} {{define "extraheader"}} + @@ -10,40 +11,7 @@ {{end}} @@ -54,12 +22,9 @@ document.addEventListener('DOMContentLoaded', function() {
- -
- - -
+