/* Copyright (c) 2018 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_Feature from 'ol/Feature.js' import ol_Overlay_Popup from './Popup.js' import ol_ext_element from '../util/element.js' /** Template attributes for popup * @typedef {Object} TemplateAttributes * @property {string} title * @property {function} format a function that takes an attribute and a feature and returns the formated attribute * @property {string} before string to instert before the attribute (prefix) * @property {string} after string to instert after the attribute (sudfix) * @property {boolean|function} visible boolean or a function (feature, value) that decides the visibility of a attribute entry */ /** Template * @typedef {Object} Template * @property {string|function} title title of the popup, attribute name or a function that takes a feature and returns the title * @property {Object.} attributes a list of template attributes */ /** * A popup element to be displayed on a feature. * * @constructor * @extends {ol_Overlay_Popup} * @fires show * @fires hide * @fires select * @fires attribute * @param {} options Extend Popup options * @param {String} options.popupClass the a class of the overlay to style the popup. * @param {bool} options.closeBox popup has a close box, default false. * @param {function|undefined} options.onclose: callback function when popup is closed * @param {function|undefined} options.onshow callback function when popup is shown * @param {Number|Array} options.offsetBox an offset box * @param {ol.OverlayPositioning | string | undefined} options.positioning * the 'auto' positioning var the popup choose its positioning to stay on the map. * @param {Template|function} [options.template] A template with a list of properties to use in the popup or a function that takes a feature and returns a Template, default use all feature properties * @param {ol.interaction.Select} options.select a select interaction to get features from * @param {boolean} options.keepSelection keep original selection, otherwise set selection to the current popup feature and add a counter to change current feature, default false * @param {boolean} options.canFix Enable popup to be fixed, default false * @param {boolean} options.showImage display image url as image, default false * @param {boolean} options.maxChar max char to display in a cell, default 200 * @api stable */ var ol_Overlay_PopupFeature = class olOverlayPopupFeature extends ol_Overlay_Popup { constructor(options) { options = options || {}; super(options); this.setTemplate(options.template); this.set('canFix', options.canFix); this.set('showImage', options.showImage); this.set('maxChar', options.maxChar || 200); this.set('keepSelection', options.keepSelection); // Bind with a select interaction if (options.select && (typeof options.select.on === 'function')) { this._select = options.select; options.select.on('select', function (e) { if (!this._noselect) { if (e.selected[0]) { this.show(e.mapBrowserEvent.coordinate, options.select.getFeatures().getArray(), e.selected[0]); } else { this.hide(); } } }.bind(this)); } } /** Set the template * @param {Template} [template] A template with a list of properties to use in the popup, default use all features properties */ setTemplate(template) { if (!template) { template = function (f) { var prop = f.getProperties(); delete prop[f.getGeometryName()]; return { attributes: Object.keys(prop) }; }; } this._template = template; this._attributeObject(this._template); } /** * @private */ _attributeObject(temp) { if (temp && temp.attributes instanceof Array) { var att = {}; temp.attributes.forEach(function (a) { att[a] = true; }); temp.attributes = att; } return temp.attributes; } /** Show the popup on the map * @param {ol.coordinate|undefined} coordinate Position of the popup * @param {ol.Feature|Array} features The features on the popup * @param {ol.Feature} current The current feature if keepSelection = true, otherwise get the first feature */ show(coordinate, features, current) { if (coordinate instanceof ol_Feature || (coordinate instanceof Array && coordinate[0] instanceof ol_Feature)) { features = coordinate; coordinate = null; } if (!(features instanceof Array)) features = [features]; this._features = features.slice(); if (!this._count) this._count = 1; // Calculate html upon feaures attributes this._count = 1; var f = this.get('keepSelection') ? current || features[0] : features[0]; var html = this._getHtml(f); if (html) { if (!this.element.classList.contains('ol-fixed')) this.hide(); if (!coordinate || features[0].getGeometry().getType() === 'Point') { coordinate = features[0].getGeometry().getFirstCoordinate(); } super.show(coordinate, html); } else { this.hide(); } } /** * @private */ _getHtml(feature) { if (!feature) return ''; var html = ol_ext_element.create('DIV', { className: 'ol-popupfeature' }); if (this.get('canFix')) { ol_ext_element.create('I', { className: 'ol-fix', parent: html }) .addEventListener('click', function () { this.element.classList.toggle('ol-fixed'); }.bind(this)); } var template = this._template; // calculate template if (typeof (template) === 'function') { template = template(feature, this._count, this._features.length); } else if (!template || !template.attributes) { template = template || {}; template.attributes = {}; for (var i in feature.getProperties()) if (i != 'geometry') { template.attributes[i] = i; } } // Display title if (template.title) { var title; if (typeof template.title === 'function') { title = template.title(feature); } else { title = feature.get(template.title); } ol_ext_element.create('H1', { html: title, parent: html }); } // Display properties in a table if (template.attributes) { var tr, table = ol_ext_element.create('TABLE', { parent: html }); var atts = this._attributeObject(template); var featureAtts = feature.getProperties(); Object.keys(atts).forEach(function(att) { if (featureAtts.hasOwnProperty(att)) { var a = atts[att]; var content, val = featureAtts[att]; // Get calculated value if (a && typeof (a.format) === 'function') { val = a.format(val, feature); } // Is entry visible? var visible = true; if (a && typeof (a.visible) === 'boolean') { visible = a.visible; } else if (a && typeof (a.visible) === 'function') { visible = a.visible(feature, val); } if (visible) { tr = ol_ext_element.create('TR', { click: function(e) { this.dispatchEvent({ type: 'attribute', feature: feature, attribute: att, originalEvent: e }) }.bind(this), parent: table }); ol_ext_element.create('TD', { className: 'ol-label', html: a ? a.title || att : att, parent: tr }); // Show image or content if (this.get('showImage') && /(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png)/.test(val)) { content = ol_ext_element.create('IMG', { src: val }); } else { if (a) { content = (a.before || '') + val + (a.after || ''); } else { content = ''; } var maxc = this.get('maxChar') || 200; if (typeof (content) === 'string' && content.length > maxc) { content = content.substr(0, maxc) + '[...]'; } } // Add value ol_ext_element.create('TD', { className: 'ol-value', html: content, parent: tr }); } } }.bind(this)) } // Zoom button ol_ext_element.create('BUTTON', { className: 'ol-zoombt', parent: html }) .addEventListener('click', function () { if (feature.getGeometry().getType() === 'Point') { this.getMap().getView().animate({ center: feature.getGeometry().getFirstCoordinate(), zoom: Math.max(this.getMap().getView().getZoom(), 18) }); } else { var ext = feature.getGeometry().getExtent(); this.getMap().getView().fit(ext, { duration: 1000 }); } }.bind(this)); // Counter if (!this.get('keepSelection') && this._features.length > 1) { var div = ol_ext_element.create('DIV', { className: 'ol-count', parent: html }); ol_ext_element.create('DIV', { className: 'ol-prev', parent: div, click: function () { this._count--; if (this._count < 1) this._count = this._features.length; html = this._getHtml(this._features[this._count - 1]); setTimeout(function () { ol_Overlay_Popup.prototype.show.call(this, this.getPosition(), html); }.bind(this), 350); }.bind(this) }); ol_ext_element.create('TEXT', { html: this._count + '/' + this._features.length, parent: div }); ol_ext_element.create('DIV', { className: 'ol-next', parent: div, click: function () { this._count++; if (this._count > this._features.length) this._count = 1; html = this._getHtml(this._features[this._count - 1]); setTimeout(function () { ol_Overlay_Popup.prototype.show.call(this, this.getPosition(), html); }.bind(this), 350); }.bind(this) }); } // Use select interaction if (this._select && !this.get('keepSelection')) { this._noselect = true; this._select.getFeatures().clear(); this._select.getFeatures().push(feature); this._noselect = false; } this.dispatchEvent({ type: 'select', feature: feature, index: this._count }); return html; } /** Fix the popup * @param {boolean} fix */ setFix(fix) { if (fix) this.element.classList.add('ol-fixed'); else this.element.classList.remove('ol-fixed'); } /** Is a popup fixed * @return {boolean} */ getFix() { return this.element.classList.contains('ol-fixed'); } } /** Get a function to use as format to get local string for an attribute * if the attribute is a number: Number.toLocaleString() * if the attribute is a date: Date.toLocaleString() * otherwise the attibute itself * @param {string} locales string with a BCP 47 language tag, or an array of such strings * @param {*} options Number or Date toLocaleString options * @return {function} a function that takes an attribute and return the formated attribute */ var ol_Overlay_PopupFeature_localString = function (locales , options) { return function (a) { if (a && a.toLocaleString) { return a.toLocaleString(locales , options); } else { // Try to get a date from a string var date = new Date(a); if (isNaN(date)) return a; else return date.toLocaleString(locales , options); } }; }; export {ol_Overlay_PopupFeature_localString} export default ol_Overlay_PopupFeature