PageSourceSearch

https://55mskn.netlify.app/resources/ol-search-layer.js

js 55mskn.netlify.app collected 2026-10-03 10:23:33 UTC 6,913 bytes, 206 lines download raw bytes

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.