1/* Autocomplete filter for listing page */ 2var taxonomies_with_terms = {}; 3 4// Check if the object exists and extract terms 5if (typeof listing_page_object !== 'undefined') { 6 // Store taxonomy terms 7 Object.entries(listing_page_object.taxonomies).forEach(([taxonomy_name, taxonomy_terms]) => { 8 taxonomies_with_terms[taxonomy_name] = taxonomy_terms; 9 }); 10} 11 12// Function to update subtopic dropdown when parent term is selected 13function updateSubtopicDropdown(taxonomy_name, parent_term_id, container, selectedSubtopic = null) { 14 const hasSubtopics = container.getAttribute('data-has-subtopics') === '1'; 15 16 if (!hasSubtopics) return; 17 18 const childClass = container.getAttribute('data-child-class'); 19 const wrapper = document.getElementById(childClass + '-wrapper'); 20 const subtopicSelect = document.getElementById(childClass); 21 22 if (!wrapper || !subtopicSelect) { 23 console.warn(`Subtopic elements not found for ${taxonomy_name}`); 24 return; 25 } 26 27 // Remember current selection if not provided 28 if (!selectedSubtopic) { 29 selectedSubtopic = subtopicSelect.value; 30 } 31 32 // Clear existing options 33 subtopicSelect.innerHTML = '<option value="0">Select option</option>'; 34 35 if (parent_term_id && parent_term_id !== '0') { 36 // Get child terms for the selected parent 37 const childTerms = taxonomies_with_terms[taxonomy_name] 38 ?.filter(term => term.parent == parent_term_id) || []; 39 40 if (childTerms.length > 0) { 41 // Show subtopic dropdown and enable it 42 wrapper.classList.remove('govuk-visually-hidden'); 43 subtopicSelect.removeAttribute('disabled'); 44 45 // Add child options 46 childTerms.forEach(term => { 47 const option = document.createElement('option'); 48 option.value = term.term_id; 49 option.textContent = term.name; 50 subtopicSelect.appendChild(option); 51 }); 52 53 // Restore the selected subtopic if it exists in the new options 54 if (selectedSubtopic && selectedSubtopic !== '0') { 55 const optionExists = childTerms.some(term => term.term_id == selectedSubtopic); 56 if (optionExists) { 57 subtopicSelect.value = selectedSubtopic; 58 } 59 } 60 } else {
61 // Hide subtopic dropdown if no children 62 wrapper.classList.add('govuk-visually-hidden'); 63 subtopicSelect.setAttribute('disabled', 'disabled'); 64 } 65 } else { 66 // Hide subtopic dropdown when no parent selected 67 wrapper.classList.add('govuk-visually-hidden'); 68 subtopicSelect.setAttribute('disabled', 'disabled'); 69 } 70} 71 72document.addEventListener("DOMContentLoaded", function() { 73 // Find all autocomplete containers 74 const containers = document.querySelectorAll('[id^="listing-template-autocomplete-container-"]'); 75 76 containers.forEach(container => { 77 const taxonomy_name = container.getAttribute('data-taxonomy'); 78 const selected_name = container.getAttribute('data-selected-name'); 79 const selected_value = container.getAttribute('data-selected-value'); 80 const exclude_terms = container.getAttribute('data-exclude-terms'); 81 const has_subtopics = container.getAttribute('data-has-subtopics') === '1'; 82 const show_option_all = container.getAttribute('data-show-option-all'); 83 84 if (!taxonomy_name) { 85 console.warn('No taxonomy name found for container:', container); 86 return; 87 } 88 89 // Get terms for this taxonomy 90 let taxonomy_terms = taxonomies_with_terms[taxonomy_name] || []; 91 92 // Filter out excluded terms if needed 93 if (exclude_terms && exclude_terms !== '') { 94 const excludeIds = exclude_terms.split(',').map(id => parseInt(id.trim())); 95 taxonomy_terms = taxonomy_terms.filter(term => !excludeIds.includes(parseInt(term.term_id))); 96 } 97 98 // For hierarchical taxonomies, only show parent terms in autocomplete 99 let source_terms; 100 if (has_subtopics) { 101 source_terms = taxonomy_terms 102 .filter(term => term.parent == 0) // Only parent terms 103 .map(term => term.name); 104 } else { 105 source_terms = taxonomy_terms.map(term => term.name); 106 } 107 108 // Validation function 109 function validateAndGetValue(input) { 110 if (!input) return ''; 111 const value = input.value?.trim() || ''; 112 return value && source_terms.includes(value) ? value : ''; 113 } 114 115 // Function to update hidden input (shared between all handlers) 116 function updateHiddenInput(selectedValue) { 117 // Determine what value to set 118 let valueToSet = ''; 119 let subtopicValue = ''; 120 121 // If we have a valid value, try to find the term 122 if (selectedValue && selectedValue.trim() !== '') { 123 const selectedTerm = taxonomy_terms.find(term => term.name === selectedValue); 124 if (selectedTerm) { 125 valueToSet = selectedTerm.term_id; 126 subtopicValue = selectedTerm.term_id; 127 } 128 } 129 130 // Update the hidden input (always, whether clearing or setting) 131 const hiddenInput = document.getElementById(`listing-template-hidden-input-${taxonomy_name}`); 132 if (hiddenInput) { 133 hiddenInput.value = valueToSet; 134 hiddenInput.dispatchEvent(new Event('change')); 135 } else { 136 console.warn(`Hidden input not found for ${taxonomy_name}`); 137 } 138 139 // Update subtopic dropdown (preserve existing selection when possible) 140 if (has_subtopics) { 141 updateSubtopicDropdown(taxonomy_name, subtopicValue, container); 142 } 143 } 144 145 // Form submission helper 146 function submitFormDelayed(delay = 50) { 147 setTimeout(() => { 148 const form = container.closest('form'); 149 if (form) { 150 form.submit(); 151 } else { 152 console.warn('Form not found for submission'); 153 } 154 }, delay); 155 } 156 157 // Create the autocomplete widget 158 accessibleAutocomplete({ 159 element: container, 160 id: `listing-template-autocomplete-${taxonomy_name}`, 161 name: `${taxonomy_name}-autocomplete-term`, 162 displayMenu: 'overlay', 163 placeholder: show_option_all || 'Select option', 164 defaultValue: selected_name || '', 165 source: function (query, populateResults) { 166 const results = source_terms.filter(term => 167 term.toLowerCase().includes(query.toLowerCase()) 168 ); 169 populateResults(results); 170 }, 171 showAllValues: true, 172 dropdownArrow: function (config) { 173 return `<svg class="${config.className}" 174 style="width: 12px; height: 12px; top: 16px;"
175 viewBox="0 0 512 512"> 176 <path d="M256,298.3L256,298.3L256,298.3l174.2-167.2c4.3-4.2,11.4-4.1,15.8,0.2l30.6,29.9c4.4,4.3,4.5,11.3,0.2,15.5L264.1,380.9 177 c-2.2,2.2-5.2,3.2-8.1,3c-3,0.1-5.9-0.9-8.1-3L35.2,176.7c-4.3-4.2-4.2-11.2,0.2-15.5L66,131.3c4.4-4.3,11.5-4.4,15.8-0.2L256,298.3z"/> 178 </svg>`; 179 }, 180 onConfirm: (selectedValue) => { 181 updateHiddenInput(selectedValue); 182 } 183 }); 184 185 // Initialize subtopics and selected subtopic on page load 186 const selected_subtopic = container.getAttribute('data-selected-subtopic'); 187 188 if (has_subtopics && selected_value && selected_value !== '0') { 189 // Pass the selected subtopic to preserve it 190 updateSubtopicDropdown(taxonomy_name, selected_value, container, selected_subtopic); 191 } 192 193 // Add event listeners with consistent validation 194 const autocompleteInput = container.querySelector('input[type="text"]'); 195 if (autocompleteInput) { 196 // Update hidden input when autocomplete input loses focus 197 autocompleteInput.addEventListener('blur', () => { 198 const validValue = validateAndGetValue(autocompleteInput); 199 updateHiddenInput(validValue); 200 }); 201 202 // Enter key handler 203 autocompleteInput.addEventListener('keydown', (e) => { 204 if (e.key === 'Enter') { 205 e.preventDefault(); 206 const validValue = validateAndGetValue(autocompleteInput); 207 updateHiddenInput(validValue); 208 submitFormDelayed(100); 209 } 210 }); 211 } 212 213 // Add form submit handler to ensure hidden input is updated before submission 214 const form = container.closest('form'); 215 if (form) { 216 form.addEventListener('submit', (e) => { 217 const autocompleteInput = container.querySelector('input[type="text"]'); 218 const validValue = validateAndGetValue(autocompleteInput); 219 220 if (validValue) { 221 e.preventDefault(); 222 updateHiddenInput(validValue); 223 submitFormDelayed(); 224 } 225 }); 226 } 227 }); 228});
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.