UNPKG

kmap-ui

Version:

A components of zmap base on vue2.X

453 lines (423 loc) 14.9 kB
/* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ import ol_ext_inherits from '../util/ext' import ol_control_Control from 'ol/control/Control' import {unByKey as ol_Observable_unByKey} from 'ol/Observable' import ol_ext_element from '../util/element' /** * Search Control. * This is the base class for search controls. You can use it for simple custom search or as base to new class. * @see ol_control_SearchFeature * @see ol_control_SearchPhoton * * @constructor * @extends {ol_control_Control} * @fires select * @fires change:input * @param {Object=} options * @param {string} options.className control class name * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {boolean | undefined} options.reverse enable reverse geocoding, default false * @param {string | undefined} options.inputLabel label for the input, default none * @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false * @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300. * @param {integer | undefined} options.minLength minimum length to start searching, default 1 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems * @param {function} options.getTitle a function that takes a feature and return the name to display in the index. * @param {function} options.autocomplete a function that take a search string and callback function to send an array */ var ol_control_Search = function(options) { var self = this; if (!options) options = {}; if (options.typing == undefined) options.typing = 300; // Class name for history this._classname = options.className || 'search'; var classNames = (options.className||'')+ ' ol-search' + (options.target ? '' : ' ol-unselectable ol-control ol-collapsed'); var element = ol_ext_element.create('DIV',{ className: classNames + ' ol-collapsed' }) if (!options.target) { this.button = document.createElement("BUTTON"); this.button.setAttribute("type", "button"); this.button.setAttribute("title", options.label||"search"); this.button.addEventListener("click", function() { element.classList.toggle("ol-collapsed"); if (!element.classList.contains("ol-collapsed")) { element.querySelector("input.search").focus(); var listElements = element.querySelectorAll("li"); for (var i = 0; i < listElements.length; i++) { listElements[i].classList.remove("select"); } // Display history if (!input.value) { self.drawList_(); } } }); element.appendChild(this.button); } // Input label if (options.inputLabel) { var label = document.createElement("LABEL"); label.innerText = options.inputLabel; element.appendChild(label); } // Search input var tout, cur=""; var input = this._input = document.createElement("INPUT"); input.setAttribute("type", "search"); input.setAttribute("class", "search"); input.setAttribute("autocomplete", "off"); input.setAttribute("placeholder", options.placeholder||"Search..."); input.addEventListener("change", function(e) { self.dispatchEvent({ type:"change:input", input:e, value:input.value }); }); var doSearch = function(e) { // console.log(e.type+" "+e.key)' var li = element.querySelector("ul.autocomplete li.select"); var val = input.value; // move up/down if (e.key=='ArrowDown' || e.key=='ArrowUp' || e.key=='Down' || e.key=='Up') { if (li) { li.classList.remove("select"); li = (/Down/.test(e.key)) ? li.nextElementSibling : li.previousElementSibling; if (li) li.classList.add("select"); } else element.querySelector("ul.autocomplete li").classList.add("select"); } // Clear input else if (e.type=='input' && !val) { setTimeout(function(){ self.drawList_(); }, 200); } // Select in the list else if (li && (e.type=="search" || e.key =="Enter")) { if (element.classList.contains("ol-control")) input.blur(); li.classList.remove("select"); cur = val; self._handleSelect(self._list[li.getAttribute("data-search")]); } // Search / autocomplete else if ( (e.type=="search" || e.key =='Enter') || (cur!=val && options.typing>=0)) { // current search cur = val; if (cur) { // prevent searching on each typing if (tout) clearTimeout(tout); tout = setTimeout(function() { if (cur.length >= self.get("minLength")) { var s = self.autocomplete (cur, function(auto) { self.drawList_(auto); }); if (s) self.drawList_(s); } else self.drawList_(); }, options.typing); } else self.drawList_(); } // Clear list selection else { li = element.querySelector("ul.autocomplete li"); if (li) li.classList.remove('select'); } }; input.addEventListener("keyup", doSearch); input.addEventListener("search", doSearch); input.addEventListener("cut", doSearch); input.addEventListener("paste", doSearch); input.addEventListener("input", doSearch); if (!options.noCollapse) { input.addEventListener('blur', function() { setTimeout(function(){ if (input !== document.activeElement) { element.classList.add('ol-collapsed'); this.set('reverse', false); element.classList.remove('ol-revers'); } }.bind(this), 200); }.bind(this)); input.addEventListener('focus', function() { if (!this.get('reverse')) { element.classList.remove('ol-collapsed'); element.classList.remove('ol-revers'); } }.bind(this)); } element.appendChild(input); // Reverse geocode if (options.reverse) { var reverse = ol_ext_element.create('BUTTON', { type: 'button', class: 'ol-revers', title: 'click on the map', click: function() { if (!this.get('reverse')) { this.set('reverse', !this.get('reverse')); input.focus(); element.classList.add('ol-revers'); } else { this.set('reverse', false); } }.bind(this) }); element.appendChild(reverse); } // Autocomplete list var ul = document.createElement('UL'); ul.classList.add('autocomplete'); element.appendChild(ul); ol_control_Control.call(this, { element: element, target: options.target }); if (typeof (options.getTitle)=='function') this.getTitle = options.getTitle; if (typeof (options.autocomplete)=='function') this.autocomplete = options.autocomplete; // Options this.set('copy', options.copy); this.set('minLength', options.minLength || 1); this.set('maxItems', options.maxItems || 10); this.set('maxHistory', options.maxHistory || options.maxItems || 10); // History this.restoreHistory(); this.drawList_(); }; ol_ext_inherits(ol_control_Search, ol_control_Control); /** * Remove the control from its current map and attach it to the new map. * Subclasses may set up event handlers to get notified about changes to * the map here. * @param {ol.Map} map Map. * @api stable */ ol_control_Search.prototype.setMap = function (map) { if (this._listener) ol_Observable_unByKey(this._listener); this._listener = null; ol_control_Control.prototype.setMap.call(this, map); if (map) { this._listener = map.on('click', this._handleClick.bind(this)); } }; /** Get the input field * @return {Element} * @api */ ol_control_Search.prototype.getInputField = function () { return this._input; }; /** Returns the text to be displayed in the menu * @param {any} f feature to be displayed * @return {string} the text to be displayed in the index, default f.name * @api */ ol_control_Search.prototype.getTitle = function (f) { return f.name || "No title"; }; /** Force search to refresh */ ol_control_Search.prototype.search = function () { var search = this.element.querySelector("input.search"); this._triggerCustomEvent('search', search); }; /** Reverse geocode * @param {Object} event * @param {ol.coordinate} event.coordinate * @private */ ol_control_Search.prototype._handleClick = function (e) { if (this.get('reverse')) { document.activeElement.blur(); this.reverseGeocode(e.coordinate); } }; /** Reverse geocode * @param {ol.coordinate} coord * @param {function | undefined} cback a callback function, default trigger a select event * @api */ ol_control_Search.prototype.reverseGeocode = function (/*coord, cback*/) { // this._handleSelect(f); }; /** Trigger custom event on elemebt * @param {*} eventName * @param {*} element * @private */ ol_control_Search.prototype._triggerCustomEvent = function (eventName, element) { ol_ext_element.dispatchEvent(eventName, element); }; /** Set the input value in the form (for initialisation purpose) * @param {string} value * @param {boolean} search to start a search * @api */ ol_control_Search.prototype.setInput = function (value, search) { var input = this.element.querySelector("input.search"); input.value = value; if (search) this._triggerCustomEvent("keyup", input); }; /** A line has been clicked in the menu > dispatch event * @param {any} f the feature, as passed in the autocomplete * @param {boolean} reverse true if reverse geocode * @param {ol.coordinate} coord * @param {*} options options passed to the event * @api */ ol_control_Search.prototype.select = function (f, reverse, coord, options) { var event = { type:"select", search:f, reverse: !!reverse, coordinate: coord }; if (options) { for (var i in options) { event[i] = options[i]; } } this.dispatchEvent(event); }; /** * Save history and select * @param {*} f * @param {boolean} reverse true if reverse geocode * @param {*} options options send in the event * @private */ ol_control_Search.prototype._handleSelect = function (f, reverse, options) { if (!f) return; // Save input in history var hist = this.get('history'); // Prevent error on stringify var i; try { var fstr = JSON.stringify(f); for (i=hist.length-1; i>=0; i--) { if (!hist[i] || JSON.stringify(hist[i]) === fstr) { hist.splice(i,1); } } } catch (e) { for (i=hist.length-1; i>=0; i--) { if (hist[i] === f) { hist.splice(i,1); } } } hist.unshift(f); var size = Math.max(0, this.get('maxHistory')||10) || 0; while (hist.length > size) { hist.pop(); } this.saveHistory(); // Select feature this.select(f, reverse, null, options); //this.drawList_(); }; /** Current history */ ol_control_Search.prototype._history = {}; /** Save history (in the localstorage) */ ol_control_Search.prototype.saveHistory = function () { if (this.get('maxHistory')>=0) { try { localStorage["ol@search-"+this._classname] = JSON.stringify(this.get('history')); } catch (e) { /* ok */ } } else { localStorage.removeItem("ol@search-"+this._classname); } }; /** Restore history (from the localstorage) */ ol_control_Search.prototype.restoreHistory = function () { if (this._history[this._classname]) { this.set('history', this._history[this._classname]); } else { try { this._history[this._classname] = JSON.parse(localStorage["ol@search-"+this._classname]); this.set('history', this._history[this._classname]); } catch(e) { this.set('history', []); } } }; /** * Remove previous history */ ol_control_Search.prototype.clearHistory = function () { this.set('history', []); this.saveHistory(); this.drawList_(); }; /** * Get history table */ ol_control_Search.prototype.getHistory = function () { return this.get('history'); }; /** Autocomplete function * @param {string} s search string * @param {function} cback a callback function that takes an array to display in the autocomplete field (for asynchronous search) * @return {Array|false} an array of search solutions or false if the array is send with the cback argument (asnchronous) * @api */ ol_control_Search.prototype.autocomplete = function (s, cback) { cback ([]); return false; // or just return []; }; /** Draw the list * @param {Array} auto an array of search result * @private */ ol_control_Search.prototype.drawList_ = function (auto) { var self = this; var ul = this.element.querySelector("ul.autocomplete"); ul.innerHTML = ''; this._list = []; if (!auto) { var input = this.element.querySelector("input.search"); var value = input.value; if (!value) { auto = this.get('history'); } else { return; } ul.setAttribute('class', 'autocomplete history'); } else { ul.setAttribute('class', 'autocomplete'); } var li, max = Math.min (self.get("maxItems"),auto.length); for (var i=0; i<max; i++) { if (auto[i]) { if (!i || !self.equalFeatures(auto[i], auto[i-1])) { li = document.createElement("LI"); li.setAttribute("data-search", this._list.length); this._list.push(auto[i]); li.addEventListener("click", function(e) { self._handleSelect(self._list[e.currentTarget.getAttribute("data-search")]); }); var title = self.getTitle(auto[i]); if (title instanceof Element) li.appendChild(title); else li.innerHTML = title; ul.appendChild(li); } } } if (max && this.get("copy")) { li = document.createElement("LI"); li.classList.add("copy"); li.innerHTML = this.get("copy"); ul.appendChild(li); } }; /** Test if 2 features are equal * @param {any} f1 * @param {any} f2 * @return {boolean} */ ol_control_Search.prototype.equalFeatures = function (/* f1, f2 */) { return false; }; export default ol_control_Search