Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Search popup configurable #2084

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 43 additions & 7 deletions src/controls/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ const Search = function Search(options = {}) {
queryParameterName = 'q',
autocompletePlacement,
searchlistOptions = {},
queryType
queryType,
suppressDialog
} = options;

const searchlistPlacement = searchlistOptions.placement;
Expand All @@ -70,17 +71,30 @@ const Search = function Search(options = {}) {
viewer.removeOverlays(overlay);
}
}

/**
* Displays the search result in an overlay (featureInfo's infoWindow-setting is ignored). Does not require that the features are from a layer.
* @param {any} features Array of features to display, but ony the first is actually displayed
* @param {any} objTitle Titel in the popup
* @param {any} content The html-content to be displayed the popup.
*/
function showFeatureInfo(features, objTitle, content) {
const obj = {};
obj.feature = features[0];
obj.title = objTitle;
obj.content = content;
clear();
featureInfo.render([obj], 'overlay', getCenter(features[0].getGeometry()), { ignorePan: true });
// Call featureInfo to display search result in overlay. This does not obey featureInfo's 'infoWindow'-setting.
// Can't use featureInfo.ShowFeatureInfo here as that requires a layer to format content
featureInfo.render([obj], 'overlay', getCenter(features[0].getGeometry()), { ignorePan: true, suppressDialog });
viewer.zoomToExtent(features[0].getGeometry(), maxZoomLevel);
}

/**
* Shows an overlay with selected search result when there is no feature,
* just a coordinate and preformatted content.
* @param {any} data search result
* @param {any} coord where to show the popup
*/
function showOverlay(data, coord) {
clear();
const newPopup = popup(`#${viewer.getId()}`);
Expand All @@ -91,6 +105,7 @@ const Search = function Search(options = {}) {
map.addOverlay(overlay);

overlay.setPosition(coord);
// Take content from search result attribute named same as search attribute.
const content = data[name];
newPopup.setContent({
content,
Expand Down Expand Up @@ -124,39 +139,60 @@ const Search = function Search(options = {}) {
let content;
let coord;
if (layerNameAttribute && idAttribute) {
// This is option 1 above
// The search endpoint has only returned a layer name and a feature id. We must get the actual feature to get the geometry and
// let featureInfo format the popup content.
const source = viewer.getMapSource();
const projCode = viewer.getProjectionCode();
const proj = viewer.getProjection();
layer = viewer.getLayer(data[layerNameAttribute]);
id = data[idAttribute];
// Fetch the feature from map server in case it is not fetched already by the layers source (in case of BBOX)
// or the layer is WMS, in which case source holds no features. getFeature() has a strnge behaviour to try to fetch features from
// feom a "shadow"-wfs layer by just assuming there is an wfs endpoint in the same location as WMS layer (works for GeoServer)
getFeature(id, layer, source, projCode, proj)
.then((res) => {
let featureWkt;
let coordWkt;
if (res.length > 0) {
const featLayerName = layer.get('name');
featureInfo.showFeatureInfo({ feature: res, layerName: featLayerName }, { maxZoomLevel });
featureInfo.showFeatureInfo({ feature: res, layerName: featLayerName }, { maxZoomLevel, suppressDialog });
} else if (geometryAttribute) {
// Fallback if no geometry in response
// Fallback if the id was not present in the layer. Try to create feature from search endpoint result
// FIXME: this case is not documented and indicates some configuration error
featureWkt = mapUtils.wktToFeature(data[geometryAttribute], projectionCode);
coordWkt = featureWkt.getGeometry().getCoordinates();
showOverlay(data, coordWkt);
}
});
} else if (geometryAttribute && layerName) {
// This should probably be option 2 above, but the description is incorrect and it does not work.
// Feature is returned as a WKT and content is formatted using the layer's attributes configuration
// too bad the feature won't have any attributes as WKT only contains geometry.
// FIXME: according to description, feature should be fetched from server, not parsed from response and geometryattribute should not be necessary
// Maybe the intention was to receive a complete feature in geometryAttribute.
feature = mapUtils.wktToFeature(data[geometryAttribute], projectionCode);
featureInfo.showFeatureInfo({ feature: [feature], layerName }, { maxZoomLevel });
featureInfo.showFeatureInfo({ feature: [feature], layerName }, { maxZoomLevel, suppressDialog });
} else if (titleAttribute && contentAttribute && geometryAttribute) {
// This is option 3 above
// Search endpoint provides popup title, geometry as WKT and preformatted content.
feature = mapUtils.wktToFeature(data[geometryAttribute], projectionCode);

// Make sure the response is wrapped in a html element
content = utils.createElement('div', data[contentAttribute]);
showFeatureInfo([feature], data[titleAttribute], content);
} else if (geometryAttribute && title) {
// This is option 4
// Search endpoint provides geometry as WKT, content is taken from same attribute that is configured as search attribute.
// FIXME: it should be documented that the content should/can be provided in the search result.
feature = mapUtils.wktToFeature(data[geometryAttribute], projectionCode);
content = utils.createElement('div', data[name]);
showFeatureInfo([feature], title, content);
} else if (easting && northing && title) {
// This is option 5.
// An overlay is displayed at the position specified by the attributes in the search result.
// content is taken from same attribute that is configured as search attribute
// FIXME: it should be documented that the content should/can be provided in the search result.
coord = [data[easting], data[northing]];
showOverlay(data, coord);
} else {
Expand Down Expand Up @@ -392,7 +428,7 @@ const Search = function Search(options = {}) {
.then((res) => {
if (res.length > 0) {
const featureLayerName = layer.get('name');
featureInfo.showFeatureInfo({ feature: res, layerName: featureLayerName }, { maxZoomLevel });
featureInfo.showFeatureInfo({ feature: res, layerName: featureLayerName }, { maxZoomLevel, suppressDialog });
}
});
});
Expand Down
245 changes: 119 additions & 126 deletions src/featureinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,138 +312,131 @@ const Featureinfo = function Featureinfo(options = {}) {
* @param {any} coordinate
* @param {bool} ignorePan true if overlay should not be panned into view
*/
const doRender = function doRender(identifyItems, target, coordinate, ignorePan) {
const map = viewer.getMap();

items = identifyItems;
const doRender = function doRender(identifyItems, target, coordinate, ignorePan, suppressDialog) {
clear(false);
// FIXME: variable is overwritten in next row
let content = items.map((i) => i.content).join('');
content = '<div id="o-identify"><div id="o-identify-carousel" class="flex"></div></div>';
switch (target) {
case 'overlay':
{
popup = Popup(`#${viewer.getId()}`, { closeCb: onInfoClosed });
popup.setContent({
content,
title: getTitle(items[0])
});
const contentDiv = document.getElementById('o-identify-carousel');
const carouselIds = [];
items.forEach((item) => {
carouselIds.push(item.feature.ol_uid);
if (item.content instanceof Element) {
contentDiv.appendChild(item.content);
} else {
contentDiv.innerHTML = item.content;
}
});
popup.setVisibility(true);
initCarousel('#o-identify-carousel');
const firstFeature = items[0].feature;
const geometry = firstFeature.getGeometry();
const origostyle = firstFeature.get('origostyle');
const clone = firstFeature.clone();
clone.setId(firstFeature.getId());
// FIXME: should be layer name, not feature name
clone.layerName = firstFeature.name;
selectionLayer.clearAndAdd(
clone,
selectionStyles[geometry.getType()]
);
selectionLayer.setSourceLayer(items[0].layer);
const coord = geometry.getType() === 'Point' ? geometry.getCoordinates() : coordinate;
carouselIds.forEach((carouselId) => {
let targetElement;
const elements = document.getElementsByClassName(`o-image-carousel${carouselId}`);
Array.from(elements).forEach(element => {
if (!element.closest('.glide__slide--clone')) {
targetElement = element;
items = identifyItems;
if (target === 'infowindow') {
if (items.length === 1) {
selectionManager.addOrHighlightItem(items[0], { suppressDialog });
} else if (items.length > 1) {
selectionManager.addItems(items, { suppressDialog });
}
} else {
// Overlay or sidebar goes here
const map = viewer.getMap();

// Add the first feature to selection layer.
// Only one is added, the others are added when the carousel changes.
const firstFeature = items[0].feature;
const geometry = firstFeature.getGeometry();
const clone = firstFeature.clone();
clone.setId(firstFeature.getId());
selectionLayer.clearAndAdd(
clone,
selectionStyles[geometry.getType()]
);
selectionLayer.setSourceLayer(items[0].layer);

// Create the popup/side bar
if (!suppressDialog) {
const content = '<div id="o-identify"><div id="o-identify-carousel" class="flex"></div></div>';
switch (target) {
case 'overlay':
{
popup = Popup(`#${viewer.getId()}`, { closeCb: onInfoClosed });
popup.setContent({
content,
title: getTitle(items[0])
});
const contentDiv = document.getElementById('o-identify-carousel');
const carouselIds = [];
items.forEach((item) => {
carouselIds.push(item.feature.ol_uid);
if (item.content instanceof Element) {
contentDiv.appendChild(item.content);
} else {
contentDiv.innerHTML = item.content;
}
});
popup.setVisibility(true);
initCarousel('#o-identify-carousel');
const origostyle = firstFeature.get('origostyle');

const coord = geometry.getType() === 'Point' ? geometry.getCoordinates() : coordinate;
carouselIds.forEach((carouselId) => {
let targetElement;
const elements = document.getElementsByClassName(`o-image-carousel${carouselId}`);
Array.from(elements).forEach(element => {
if (!element.closest('.glide__slide--clone')) {
targetElement = element;
}
});
const imageCarouselEl = document.getElementsByClassName(`o-image-carousel${carouselId}`);
if (imageCarouselEl.length > 0) {
initImageCarousel(`#o-image-carousel${carouselId}`, `.o-image-content${carouselId}`, carouselId, targetElement);
}
});
const popupEl = popup.getEl();
const popupHeight = document.querySelector('.o-popup').offsetHeight + 10;
popupEl.style.height = `${popupHeight}px`;
const overlayOptions = { element: popupEl, positioning: 'bottom-center' };
if (!ignorePan) {
overlayOptions.autoPan = {
margin: 55,
animation: {
duration: 500
}
};
}
});
const imageCarouselEl = document.getElementsByClassName(`o-image-carousel${carouselId}`);
if (imageCarouselEl.length > 0) {
initImageCarousel(`#o-image-carousel${carouselId}`, `.o-image-content${carouselId}`, carouselId, targetElement);
}
});
const popupEl = popup.getEl();
const popupHeight = document.querySelector('.o-popup').offsetHeight + 10;
popupEl.style.height = `${popupHeight}px`;
const overlayOptions = { element: popupEl, positioning: 'bottom-center' };
if (!ignorePan) {
overlayOptions.autoPan = {
margin: 55,
animation: {
duration: 500
if (items[0].layer && items[0].layer.get('styleName')) {
const styleName = items[0].layer.get('styleName');
const itemStyle = viewer.getStyle(styleName);
if (itemStyle && itemStyle[0] && itemStyle[0][0] && itemStyle[0][0].overlayOptions) {
Object.assign(overlayOptions, itemStyle[0][0].overlayOptions);
}
}
};
}
if (items[0].layer && items[0].layer.get('styleName')) {
const styleName = items[0].layer.get('styleName');
const itemStyle = viewer.getStyle(styleName);
if (itemStyle && itemStyle[0] && itemStyle[0][0] && itemStyle[0][0].overlayOptions) {
Object.assign(overlayOptions, itemStyle[0][0].overlayOptions);
if (origostyle && origostyle.overlayOptions) {
Object.assign(overlayOptions, origostyle.overlayOptions);
}
if (overlayOptions.positioning) {
popupEl.classList.add(`popup-${overlayOptions.positioning}`);
}
overlay = new Overlay(overlayOptions);
map.addOverlay(overlay);
overlay.setPosition(coord);
break;
}
}
if (origostyle && origostyle.overlayOptions) {
Object.assign(overlayOptions, origostyle.overlayOptions);
}
if (overlayOptions.positioning) {
popupEl.classList.add(`popup-${overlayOptions.positioning}`);
}
overlay = new Overlay(overlayOptions);
map.addOverlay(overlay);
overlay.setPosition(coord);
break;
}
case 'sidebar':
{
sidebar.setContent({
content,
title: getTitle(items[0])
});
const contentDiv = document.getElementById('o-identify-carousel');
items.forEach((item) => {
if (item.content instanceof Element) {
contentDiv.appendChild(item.content);
} else {
contentDiv.innerHTML = item.content;
case 'sidebar':
{
sidebar.setContent({
content,
title: getTitle(items[0])
});
const contentDiv = document.getElementById('o-identify-carousel');
items.forEach((item) => {
if (item.content instanceof Element) {
contentDiv.appendChild(item.content);
} else {
contentDiv.innerHTML = item.content;
}
});
sidebar.setVisibility(true);

initCarousel('#o-identify-carousel');
break;
}

default:
{
break;
}
});
sidebar.setVisibility(true);
const firstFeature = items[0].feature;
const geometry = firstFeature.getGeometry();
const clone = firstFeature.clone();
clone.setId(firstFeature.getId());
// FIXME: should be layer name
clone.layerName = firstFeature.name;
selectionLayer.clearAndAdd(
clone,
selectionStyles[geometry.getType()]
);
selectionLayer.setSourceLayer(items[0].layer);
initCarousel('#o-identify-carousel');
break;
}
case 'infowindow':
{
if (items.length === 1) {
selectionManager.addOrHighlightItem(items[0]);
} else if (items.length > 1) {
selectionManager.addItems(items);
}
break;
}
default:
{
break;
const modalLinks = document.getElementsByClassName('o-identify-link-modal');
for (let i = 0; i < modalLinks.length; i += 1) {
addLinkListener(modalLinks[i]);
}
}
}

const modalLinks = document.getElementsByClassName('o-identify-link-modal');
for (let i = 0; i < modalLinks.length; i += 1) {
addLinkListener(modalLinks[i]);
}
// Don't send event for infowindow. Infowindow will send an event that triggers sending the event later.
if (target === 'overlay' || target === 'sidebar') {
dispatchToggleFeatureEvent(items[0]);
Expand All @@ -459,7 +452,7 @@ const Featureinfo = function Featureinfo(options = {}) {
* @param {any} opts Additional options. Supported options are : ignorePan, disable auto pan to popup overlay.
*/
const render = function render(identifyItems, target, coordinate, opts = {}) {
doRender(identifyItems, target, coordinate, opts.ignorePan);
doRender(identifyItems, target, coordinate, opts.ignorePan, opts.suppressDialog);
};
/**
* Renders the selectedItems after adding async content. Not actually defined as async as it is part of a sync call chain,
Expand All @@ -481,7 +474,7 @@ const Featureinfo = function Featureinfo(options = {}) {
alert('Kunde inte hämta relaterade objekt. En del fält från relaterade objekt kommer att vara tomma.');
})
.then(() => {
doRender(identifyItems, target, coordinate, opts.ignorePan);
doRender(identifyItems, target, coordinate, opts.ignorePan, opts.suppressDialog);
})
.catch(err => console.log(err));
}
Expand Down
Loading
Loading