/* Copyright (c) 2019 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_control_Control from 'ol/control/Control.js' import ol_Collection from 'ol/Collection.js' import ol_source_Vector from 'ol/source/Vector.js' import ol_ext_element from '../util/element.js' /** * This is the base class for Select controls on attributes values. * Abstract base class; * normally only used for creating subclasses and not instantiated in apps. * * @constructor * @extends {ol_control_Control} * @fires select * @param {Object=} options * @param {string} options.className control class name * @param {Element} options.content form element * @param {Element | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {ol.Collection} options.features a collection of feature to search in, the collection will be kept in date while selection * @param {ol.source.Vector | Array} options.source the source to search in if no features set * @param {string} options.btInfo ok button label */ var ol_control_SelectBase = class olcontrolSelectBase extends ol_control_Control { constructor(options) { options = options || {}; var element = document.createElement('div'); super({ element: element, target: options.target }); this._features = this.setFeatures(options.features); if (!options.target) { element.className = 'ol-select ol-unselectable ol-control ol-collapsed'; ol_ext_element.create('BUTTON', { type: 'button', on: { 'click touchstart': function (e) { element.classList.toggle('ol-collapsed'); e.preventDefault(); } }, parent: element }); } if (options.className) element.classList.add(options.className); var content = options.content || ol_ext_element.create('DIV'); element.appendChild(content); // OK button ol_ext_element.create('BUTTON', { html: options.btInfo || 'OK', className: 'ol-ok', on: { 'click': this.doSelect.bind(this) }, parent: content }); this.setSources(options.source); } /** Set the current sources * @param {ol.source.Vector|Array|undefined} source */ setSources(source) { if (source) { this.set('source', (source instanceof Array) ? source : [source]); } else { this.unset('source'); } } /** Set feature collection to search in * @param {ol.Collection} features */ setFeatures(features) { if (features instanceof ol_Collection) this._features = features; else this._features = null; } /** Get feature collection to search in * @return {ol.Collection} */ getFeatures() { return this._features; } /** Escape string for regexp * @param {*} s value to escape * @return {string} */ _escape(s) { return String(s || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } /** * Test if a feature check aconditino * @param {ol.Feature} f the feature to check condition * @param {Object} condition an object to use for test * @param {string} condition.attr attribute name * @param {string} condition.op operator * @param {any} condition.val value to test * @param {boolean} usecase use case or not when testing strings * @return {boolean} * @private */ _checkCondition(f, condition, usecase) { if (!condition.attr) return true; var val = f.get(condition.attr); // Try to test numeric values var isNumber = (Number(val) == val && Number(condition.val) == condition.val); if (isNumber) val = Number(val); // Check var rex; switch (condition.op) { case '=': if (isNumber) { return val == condition.val; } else { rex = new RegExp('^' + this._escape(condition.val) + '$', usecase ? '' : 'i'); return rex.test(val); } case '!=': if (isNumber) { return val != condition.val; } else { rex = new RegExp('^' + this._escape(condition.val) + '$', usecase ? '' : 'i'); return !rex.test(val); } case '<': return val < condition.val; case '<=': return val <= condition.val; case '>': return val > condition.val; case '>=': return val >= condition.val; case 'contain': rex = new RegExp(this._escape(condition.val), usecase ? '' : 'i'); return rex.test(val); case '!contain': rex = new RegExp(this._escape(condition.val), usecase ? '' : 'i'); return !rex.test(val); case 'regexp': rex = new RegExp(condition.val, usecase ? '' : 'i'); return rex.test(val); case '!regexp': rex = new RegExp(condition.val, usecase ? '' : 'i'); return !rex.test(val); default: return false; } } /** Selection features in a list of features * @param {Array} result the current list of features * @param {Array} features to test in * @param {Object} condition * @param {string} condition.attr attribute name * @param {string} condition.op operator * @param {any} condition.val value to test * @param {boolean} all all conditions must be valid * @param {boolean} usecase use case or not when testing strings */ _selectFeatures(result, features, conditions, all, usecase) { conditions = conditions || []; var f; for (var i = features.length - 1; f = features[i]; i--) { var isok = all; for (var k = 0, c; c = conditions[k]; k++) { if (c.attr) { if (all) { isok = isok && this._checkCondition(f, c, usecase); } else { isok = isok || this._checkCondition(f, c, usecase); } } } if (isok) { result.push(f); } else if (this._features) { this._features.removeAt(i); } } return result; } /** Get vector source * @return {Array} */ getSources() { if (this.get('source')) return this.get('source'); var sources = []; function getSources(layers) { layers.forEach(function (l) { if (l.getLayers) { getSources(l.getLayers()); } else if (l.getSource && l.getSource() instanceof ol_source_Vector) { sources.push(l.getSource()); } }); } if (this.getMap()) { getSources(this.getMap().getLayers()); } return sources; } /** Select features by attributes * @param {*} options * @param {Array|undefined} options.sources source to apply rules, default the select sources * @param {bool} options.useCase case sensitive, default false * @param {bool} options.matchAll match all conditions, default false * @param {Array} options.conditions array of conditions * @return {Array} * @fires select */ doSelect(options) { options = options || {}; var features = []; if (options.features) { this._selectFeatures(features, options.features, options.conditions, options.matchAll, options.useCase); } else if (this._features) { this._selectFeatures(features, this._features.getArray(), options.conditions, options.matchAll, options.useCase); } else { var sources = options.sources || this.getSources(); sources.forEach(function (s) { this._selectFeatures(features, s.getFeatures(), options.conditions, options.matchAll, options.useCase); }.bind(this)); } this.dispatchEvent({ type: "select", features: features }); return features; } } /** List of operators / translation * @api */ ol_control_SelectBase.prototype.operationsList = { '=': '=', '!=': '≠', '<': '<', '<=': '≤', '>=': '≥', '>': '>', 'contain': '⊂', // ∈ '!contain': '⊄', // ∉ 'regexp': '≃', '!regexp': '≄' }; export default ol_control_SelectBase