329 lines
12 KiB
JavaScript
329 lines
12 KiB
JavaScript
/* 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.<TemplateAttributes>} 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<number>} 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<ol.Feature>} 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
|