chore: refactor initCenter and controls ordering

We had an issue (not in Github :p) where a map was not loading
because the defaultView was set to "data", and the layers were
remote data layers. In this case, when computing the remote URL,
we allow to replace georelated variables (like east, west, north,
lat…), which needs the map to have a view.
So:
- the default view was expecting the data to be loaded (="data")
- the data to be loaded needed a default view…

So instead of adding yet another call to _setDefaultView in an
edge case, we reordered the way we initialize the map elements:

- first we initialize the controls (because initCenter needs the
  locate control to exist)
- then we call initCenter
- then we initialize the tile layers (because the miniMap needs it
  to render itself)
- then we call renderControls
This commit is contained in:
Yohan Boniface 2024-03-27 20:14:43 +01:00
parent 8392a748f1
commit 4669053b18
3 changed files with 58 additions and 30 deletions

View file

@ -1234,6 +1234,35 @@ U.StarControl = L.Control.extend({
}, },
}) })
/*
* Take control over L.Control.Locate to be able to
* call start() before adding the control (and thus the button) to the map.
*/
U.Locate = L.Control.Locate.extend({
initialize: function (map, options) {
// When calling start(), it will try to add a location marker
// on the layer, which is normally added in the addTo/onAdd method
this._layer = this.options.layer = new L.LayerGroup()
// When calling start(), it will call _activate(), which then adds
// location related event listeners on the map
this.map = map
L.Control.Locate.prototype.initialize.call(this, options)
},
onAdd: function (map) {
const active = this._active
const container = L.Control.Locate.prototype.onAdd.call(this, map)
this._active = active
return container
},
_activate: function () {
this._map = this.map
L.Control.Locate.prototype._activate.call(this)
this._map = null
}
})
U.Search = L.PhotonSearch.extend({ U.Search = L.PhotonSearch.extend({
initialize: function (map, input, options) { initialize: function (map, input, options) {
this.options.placeholder = L._('Type a place name or coordinates') this.options.placeholder = L._('Type a place name or coordinates')

View file

@ -67,7 +67,8 @@ U.Map = L.Map.extend({
this.description = this.options.description this.description = this.options.description
this.demoTileInfos = this.options.demoTileInfos this.demoTileInfos = this.options.demoTileInfos
this.options.zoomControl = zoomControl !== undefined ? zoomControl : true this.options.zoomControl = zoomControl !== undefined ? zoomControl : true
this.options.fullscreenControl = fullscreenControl !== undefined ? fullscreenControl : true this.options.fullscreenControl =
fullscreenControl !== undefined ? fullscreenControl : true
this.datalayersOnLoad = L.Util.queryString('datalayers') this.datalayersOnLoad = L.Util.queryString('datalayers')
if (this.datalayersOnLoad) { if (this.datalayersOnLoad) {
this.datalayersOnLoad = this.datalayersOnLoad.toString().split(',') this.datalayersOnLoad = this.datalayersOnLoad.toString().split(',')
@ -114,12 +115,12 @@ U.Map = L.Map.extend({
// Needed for actions labels // Needed for actions labels
this.help = new U.Help(this) this.help = new U.Help(this)
if (this.options.hash) this.addHash()
this.initTileLayers()
// Needs tilelayer to exist for minimap
this.initControls() this.initControls()
// Needs locate control and hash to exist // Needs locate control and hash to exist
this.initCenter() this.initCenter()
this.initTileLayers()
// Needs tilelayer to exist for minimap
this.renderControls()
this.handleLimitBounds() this.handleLimitBounds()
this.initDataLayers() this.initDataLayers()
@ -268,9 +269,9 @@ U.Map = L.Map.extend({
}, },
overrideSchema: function (schema) { overrideSchema: function (schema) {
for (const [key, extra] of Object.entries(schema)) { for (const [key, extra] of Object.entries(schema)) {
U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra) U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra)
} }
}, },
initControls: function () { initControls: function () {
@ -297,7 +298,7 @@ U.Map = L.Map.extend({
zoomOutTitle: L._('Zoom out'), zoomOutTitle: L._('Zoom out'),
}) })
this._controls.datalayers = new U.DataLayersControl(this) this._controls.datalayers = new U.DataLayersControl(this)
this._controls.locate = L.control.locate({ this._controls.locate = new U.Locate(this, {
strings: { strings: {
title: L._('Center map on your location'), title: L._('Center map on your location'),
}, },
@ -336,9 +337,6 @@ U.Map = L.Map.extend({
this.drop = new U.DropControl(this) this.drop = new U.DropControl(this)
this.share = new U.Share(this) this.share = new U.Share(this)
this._controls.tilelayers = new U.TileLayerControl(this) this._controls.tilelayers = new U.TileLayerControl(this)
this._controls.tilelayers.setLayers()
this.renderControls()
}, },
renderControls: function () { renderControls: function () {
@ -353,13 +351,13 @@ U.Map = L.Map.extend({
'umap-slideshow-enabled', 'umap-slideshow-enabled',
this.options.slideshow && this.options.slideshow.active this.options.slideshow && this.options.slideshow.active
) )
for (const i in this._controls) { for (const control of Object.values(this._controls)) {
this.removeControl(this._controls[i]) this.removeControl(control)
} }
if (this.options.noControl) return if (this.options.noControl) return
this._controls.attribution = new U.AttributionControl().addTo(this) this._controls.attribution = new U.AttributionControl().addTo(this)
if (this.options.miniMap && !this.options.noControl) { if (this.options.miniMap) {
this.whenReady(function () { this.whenReady(function () {
if (this.selected_tilelayer) { if (this.selected_tilelayer) {
this._controls.miniMap = new L.Control.MiniMap(this.selected_tilelayer, { this._controls.miniMap = new L.Control.MiniMap(this.selected_tilelayer, {
@ -392,6 +390,7 @@ U.Map = L.Map.extend({
if (this.getOption('permanentCredit')) this._controls.permanentCredit.addTo(this) if (this.getOption('permanentCredit')) this._controls.permanentCredit.addTo(this)
if (this.getOption('moreControl')) this._controls.more.addTo(this) if (this.getOption('moreControl')) this._controls.more.addTo(this)
if (this.getOption('scaleControl')) this._controls.scale.addTo(this) if (this.getOption('scaleControl')) this._controls.scale.addTo(this)
this._controls.tilelayers.setLayers()
}, },
initDataLayers: async function (datalayers) { initDataLayers: async function (datalayers) {
@ -652,26 +651,18 @@ U.Map = L.Map.extend({
}, },
initCenter: function () { initCenter: function () {
this._setDefaultCenter()
if (this.options.hash) this.addHash()
if (this.options.hash && this._hash.parseHash(location.hash)) { if (this.options.hash && this._hash.parseHash(location.hash)) {
// FIXME An invalid hash will cause the load to fail // FIXME An invalid hash will cause the load to fail
this._hash.update() this._hash.update()
} else if (this.options.defaultView === 'locate' && !this.options.noControl) { } else if (this.options.defaultView === 'locate' && !this.options.noControl) {
// When using locate as default map view AND activating easing
// Leaflet.locate will ask the map view to compute transition to user
// position, so in this case we do need a default center, so let's
// set it anyway
this._setDefaultCenter()
this._controls.locate.start() this._controls.locate.start()
} else if (this.options.defaultView === 'data') { } else if (this.options.defaultView === 'data') {
this.onceDataLoaded(() => { this.onceDataLoaded(this.fitDataBounds)
if (!this.fitDataBounds()) return this._setDefaultCenter()
})
} else if (this.options.defaultView === 'latest') { } else if (this.options.defaultView === 'latest') {
this.onceDataLoaded(() => { this.onceDataLoaded(() => {
if (!this.hasData()) { if (!this.hasData()) return
this._setDefaultCenter()
return
}
const datalayer = this.firstVisibleDatalayer() const datalayer = this.firstVisibleDatalayer()
let feature let feature
if (datalayer) { if (datalayer) {
@ -681,11 +672,7 @@ U.Map = L.Map.extend({
return return
} }
} }
// Fallback, no datalayer or no feature found
this._setDefaultCenter()
}) })
} else {
this._setDefaultCenter()
} }
}, },

View file

@ -148,6 +148,18 @@ def test_default_view_latest_with_polygon(map, live_server, page):
expect(layers).to_have_count(1) expect(layers).to_have_count(1)
def test_default_view_locate(browser, live_server, map):
context = browser.new_context(
geolocation={"longitude": 8.52967, "latitude": 39.16267},
permissions=["geolocation"],
)
map.settings["properties"]["defaultView"] = "locate"
map.save()
page = context.new_page()
page.goto(f"{live_server.url}{map.get_absolute_url()}")
expect(page).to_have_url(re.compile(r".*#18/39\.16267/8\.52967"))
def test_remote_layer_should_not_be_used_as_datalayer_for_created_features( def test_remote_layer_should_not_be_used_as_datalayer_for_created_features(
map, live_server, datalayer, page map, live_server, datalayer, page
): ):