1function hasClass(el, cls) { 2 return el.className && new RegExp('(\\s|^)' + 3 cls + '(\\s|$)').test(el.className); 4} 5 6function addClass(elem, className) { 7 if (!hasClass(elem, className)) { 8 elem.className += ' ' + className; 9 } 10} 11 12function removeClass(elem, className) { 13 var newClass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' '; 14 if (hasClass(elem, className)) { 15 while (newClass.indexOf(' ' + className + ' ') >= 0) { 16 newClass = newClass.replace(' ' + className + ' ', ' '); 17 } 18 elem.className = newClass.replace(/^\s+|\s+$/g, ''); 19 } 20} 21 22 23class SearchLayer extends ol.control.Control { 24 constructor(optOptions) { 25 const horseyComponentRef = { current: null }; 26 const selectRef = { current: null }; 27 28 const options = optOptions || {}; 29 if (!options.layer) { 30 throw new Error('Missing layer in options'); 31 } 32 33 options.maxResults = (optOptions && typeof optOptions.maxResults === 'number') 34 ? optOptions.maxResults 35 : 10; 36 37 options.map = optOptions.map; 38 options.colName = optOptions.colName; 39 options.showOnFocus = typeof optOptions.showOnFocus === 'boolean' ? optOptions.showOnFocus : false; 40 let source; 41 if (options.layer instanceof ol.layer.Image && 42 options.layer.getSource() instanceof ol.source.ImageVector) { 43 source = options.layer.getSource().getSource(); 44 } else if (options.layer instanceof ol.layer.Vector) { 45 source = options.layer.getSource(); 46 } 47 if (source instanceof ol.source.Cluster) { 48 source = source.getSource(); 49 } 50 51 // Create button 52 const button = document.createElement('button'); 53 const toggleHideShowInput = () => { 54 const input = document.querySelector('form > .search-layer-input-search'); 55 if (hasClass(input, 'search-layer-collapsed')) { 56 removeClass(input, 'search-layer-collapsed'); 57 // Mostra suggerimenti solo se showOnFocus true 58 if (options.showOnFocus && horseyComponentRef.current) { 59 horseyComponentRef.current.hide(); 60 setTimeout(() => { 61 if (input === document.activeElement) { 62 horseyComponentRef.current.show(); 63 } 64 }, 0); 65 } 66 } else { 67 input.value = ''; 68 addClass(input, 'search-layer-collapsed'); 69 if (horseyComponentRef.current) { 70 horseyComponentRef.current.hide(); 71 } 72 if (selectRef.current) { 73 selectRef.current.getFeatures().clear(); 74 } 75 } 76 }; 77 78 button.addEventListener('pointerup', toggleHideShowInput, false); 79 80 // Create input 81 const form = document.createElement('form'); 82 form.setAttribute('id', 'random'); 83 form.onsubmit = undefined; 84 85 const input = document.createElement('input'); 86 input.setAttribute('id', 'ol-search-input'); 87 const defaultInputClass = ['search-layer-input-search']; 88 if (optOptions.collapsed) { 89 defaultInputClass.push('search-layer-collapsed'); 90 } 91 input.setAttribute('class', defaultInputClass.join(' ')); 92 input.setAttribute('placeholder', 'Search ...'); 93 input.setAttribute('type', 'text'); 94 form.appendChild(input); 95 96 // immediate focus on mobile 97 input.addEventListener('touchstart', () => { 98 input.focus(); 99 }, { passive: true }); 100 101 // Build control element 102 const element = document.createElement('div'); 103 element.className = 'search-layer ol-unselectable ol-control'; 104 element.appendChild(button); 105 element.appendChild(form); 106 107 // Initialize base class 108 super({ 109 element: element, 110 target: options.target 111 }); 112 113 // =============================== 114 // MOBILE TOUCH SUPPORT 115 // =============================== 116 // ol does not intercept the tap 117 const stopMapPropagation = (e) => { 118 e.stopPropagation(); 119 }; 120 // modern pointer events (desktop + mobile) 121 element.addEventListener('pointerdown', stopMapPropagation, { passive: true }); 122 // touch fallback 123 element.addEventListener('touchstart', stopMapPropagation, { passive: true }); 124 // mouse fallback 125 element.addEventListener('mousedown', stopMapPropagation); 126 127 // Create select interaction 128 const select = new ol.interaction.Select({ 129 id: options.selectId || 'defaultSearchLayer', 130 layers: [options.layer], 131 condition: ol.events.condition.never 132 }); 133 134 selectRef.current = select; 135 136 const map = options.map; 137 map.addInteraction(select); 138 139 // Setup horsey autocomplete 140 const typesToZoomToExtent = [ 141 'MultiPoint', 'LineString', 'MultiLineString', 'MultiPolygon', 'Polygon' 142 ]; 143 const typesToZoomToCenterAndZoom = ['Point']; 144 145 const returnHorsey = (input, source, map, select, options) => { 146 return horsey(input, { 147 source: [{
148 list: source.getFeatures().map((el, i) => { 149 if (el.getId() === undefined) { 150 el.setId(i); 151 } 152 return { 153 text: el.get(options.colName), 154 value: el.getId() 155 }; 156 }) 157 }], 158 getText: 'text', 159 getValue: 'value', 160 limit: options.maxResults, 161 blankSearch: !!options.showOnFocus, 162 predictNextSearch: function(info) { 163 const feat = source.getFeatureById(info.selection.value); 164 const featType = feat.getGeometry().getType(); 165 166 if (typesToZoomToCenterAndZoom.includes(featType)) { 167 const newCenter = ol.extent.getCenter(feat.getGeometry().getExtent()); 168 map.getView().setCenter(newCenter); 169 map.getView().setZoom(options.zoom || 12); 170 } else if (typesToZoomToExtent.includes(featType)) { 171 map.getView().fit(feat.getGeometry().getExtent(), map.getSize()); 172 } 173 174 select.getFeatures().clear(); 175 select.getFeatures().push(feat); 176 } 177 }); 178 }; 179 180 if (source.getState() === 'ready') { 181 horseyComponentRef.current = returnHorsey(input, source, map, select, options); 182 // if required, show suggestions on focus/click 183 if (options.showOnFocus) { 184 input.addEventListener('focus', function showOnFocusHandler() { 185 if (horseyComponentRef.current) horseyComponentRef.current.show(); 186 }); 187 input.addEventListener('click', function showOnClickHandler() { 188 if (horseyComponentRef.current) horseyComponentRef.current.show(); 189 }); 190 } 191 } 192 source.once('change', () => { 193 if (source.getState() === 'ready') { 194 horseyComponentRef.current = returnHorsey(input, source, map, select, options); 195 if (options.showOnFocus) { 196 input.addEventListener('focus', function showOnFocusHandler() { 197 if (horseyComponentRef.current) horseyComponentRef.current.show(); 198 }); 199 input.addEventListener('click', function showOnClickHandler() { 200 if (horseyComponentRef.current) horseyComponentRef.current.show(); 201 }); 202 } 203 } 204 }); 205 } 206}
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.