Leaflet control Joomla issue - javascript

I've created a Joomla Component and i have a Leaflet map in the component window.
I've used Leaflet with Omnivore plugin to add GPX and KML to my map and I used the Leaflet controls to allow to add and remove the layers.
I've tested the controls in a clean joomla development installation and the controls are OK, as seen in the first image
enter image description here
When I use the component in my Joomla site che controls are not OK, there are some dirty entry as seen in the second figures
enter image description here
I think this is because of the templates and some script that interfere with Leaflet but I can't fix it.
The joomla versions are the same, the template no, the joomla site use gantry.
This is the function I used to populate the map:
function showRouteTracks(tracce, baseURI, popup=false, enableLayers=true, enableElevation=false){
var layerControl = new Array();
for (var i = 0; i < tracce.length; i++) {
var customLayer = L.geoJson(null, {
style: getStyle(i)
});
if(tracce[i][3]=='GPX'){
var layer = omnivore.gpx(baseURI+tracce[i][2], null, customLayer).on('ready', function() {
elevation(enableElevation,layer);
});
if(popup){
link=''+tracce[i][5]+''
layer.bindPopup(tracce[i][0]+"➝"+tracce[i][1]+"<br/>"+link);
}
lvrtMap.addLayer(layer);
layerControl[tracce[i][0]+"➝"+tracce[i][1]]=layer;
}
if(tracce[i][3]=='KML'){
var layer = omnivore.kml(baseURI+tracce[i][2], null, customLayer).on('ready', function() {
elevation(enableElevation,layer);
});
if(popup){
link=''+tracce[i][5]+''
layer.bindPopup(tracce[i][0]+"➝"+tracce[i][1]+"<br/>"+link);
}
lvrtMap.addLayer(layer);
layerControl[tracce[i][0]+"➝"+tracce[i][1]]=layer;
}
}
if(!enableLayers)
layerControl=null;
if(enableElevation)
L.control.layers(lvrtMapLayers,layerControl,{'position':'bottomright'}).addTo(lvrtMap);
else
L.control.layers(lvrtMapLayers,layerControl).addTo(lvrtMap);
}

Currently you're creating an array to store the title/layer items:
var layerControl = new Array();
But L.Control.Layers takes object literals as baselayer/overlay parameters:
var baseLayers = {
"Mapbox": mapbox,
"OpenStreetMap": osm
};
var overlays = {
"Marker": marker,
"Roads": roadsLayer
};
L.control.layers(baseLayers, overlays).addTo(map);
So you should use an object literal:
var layerControl = {};
You can add items the same way as you did before:
layerControl['MyTitle'] = myLayerInstance;
I'll bet you'll have no problem then. What happening now is that your trying to assign string keys to items in an array, which isn't supported (even supposed to work but that aside). A javascript array can only have numeric keys and nothing else.
That it works for you with a clean install and not in your production setup is that probably in production you have a javascript library/framework loaded which adds methods/properties to javascript's native array prototype and they are enumerable. Thus when the layercontrol instance iterates the array it also finds these extra methods/properties and tries to add them to the layercontrol.
Just use a object literal: {} not an Array, you'll be fine, good luck.
EDIT: Got curious and did some digging. As it turns out this is caused by Mootools and then i ran into this question: Looping through empty javascript array returns array object functions which gives some explanation and some other solutions but it's best if you just use a object literal because at the moment you're kind of abusing Array.

Related

Sort order of layers in Leaflet layer control

I have created a leaflet map to display some geoJSON data in separate vertical layers.
It works fine, except that the layer control will list the options out of order, i.e. "layer 0, layer 2, layer 1" as opposed to "layer 0, layer 1, layer 2". The data is loaded through AJAX calls, and so the layers are added to the control in the order which the calls complete, i.e. basically random.
According to the documentation for the layer control [ https://leafletjs.com/reference-1.3.0.html#control-layers ], if sortLayers is set to true, the default is to sort the layers by name, but that seems to not be happening.
So, I have tried passing in a sort function explicitly, but the doc does not give an example of what one should look like.
When my code looks like this:
var layerControlOptions = {
hideSingleBase : true,
autoZIndex : true,
sortLayers: true}
var layerControl = L.control.layers(null, null, layerControlOptions).addTo(map);
The control looks like this:
layer control with sortLayers: true and no sortFunction being defined
My best guess at writing my own sorting function:
var layerControlOptions = {
hideSingleBase : true,
autoZIndex : true,
sortLayers: true,
sortFunction: function(layerA, layerB, nameA, nameB){return
[nameA,nameB].sort();}}
var layerControl = L.control.layers(null, null,
layerControlOptions).addTo(map);
With this, the layer names appear in the same order as when no function is given. Not sure if I am writing it incorrectly, or it is simply equivalent to the default, which is returning the wrong order for some reason.
Seems like the same issue here: Leaflet Layer Control: Ordering
which has no accepted answer and makes no mention of the layer control options. I did try the solution which is proposed here, but it did not fix the problem either.
Any help on this would be greatly appreciated, thank you.
You may look at this solution to order layers in the controls (it does not order layers). I've mainly borrowed code from Leaflet specs code;
If you have not problem to sort things in the control but you have issue related to loading JSON/GeoJSON (hence layer too) in a particular order, you may want to combine fetch with Promise.all but it's not anymore about Leaflet but about JavaScript knowledge (look at this page to try to grasp fetch + Promise.all)
You can add the layers as you normally would and then sort them with a function that removes labels and re-add them as sorted.
function sortLabels() {
var controlLayers = {}
layerControl._layers.forEach(function(x) {
if (x.overlay) {
controlLayers[x.name] = x.layer
}
});
names = Object.keys(controlLayers).sort()
//remove and add sorted layernames
names.forEach(x => layerControl.removeLayer(controlLayers[x]))
names.forEach(x => layerControl.addOverlay(controlLayers[x], x))
}
var baseMaps = { "OpenStreetMap": tile_layer_osm, "ESRI World Imagery" : esri_satelite };
var overlayMaps = {nameC: layerC, nameA: layerA, nameB: layerB}
var layerControl = L.control.layers(baseMaps, overlayMaps, null, {collapsed: false}).addTo(map);
sortLabels()

leaflet maps - remove marker layer group and add another marker layer group

My objective here is to remove all the markers added to leaflet using layer group for a paginated listing page.When navigated to other page i am able to remove layer group of previous page, but on adding new layer group of markers(markers of next page) i am getting this error in browser and markers are not added to the map
error is - Uncaught TypeError: layer.onAdd is not a function;
code is
var leaflet_factory = {
//initializing map container
initialize: function() {
var map = L.map('mapresults');
var googleLayer = new L.Google('ROADMAP');
map.addLayer(googleLayer);
},
//set view of mao
setview: function(lat, long, zoom) {
map.setView([lat, long], zoom);
},
//add list of markers to maps
addMarkersList: function(marker_array) {
var markerArray = [];
$.each(marker_array, function(key, data) {
var marker_pointer = L.marker([data.lat, data.long]).bindPopup('<strong>' + $(this).attr('data-vendor').capitalize() + '</strong><br>' + $(this).attr('data-location').capitalize());
markerArray.push(marker_pointer);
});
window.page_makers_layer = L.layerGroup(markerArray);
window.page_makers_layer.addTo(map);
},
//remove the current marker layer group
removeMarkerLayer: function() {
map.removeLayer(window.page_makers_layer);
}
}
The problem with the above code is when calling addMarkersList for first time after initializing the map container it works.
But when i call addMarkerList with new list of markers(lat long pair) after calling removeMarkerLayer to remove existing marker layer it gives me following error which i am trying to debug.
Uncaught TypeError: layer.onAdd is not a function
Please point where i am doing wrong.
You're doing a few things wrong. Not catastrophically wrong, just antipattern wrong, e.g.:
map.removeLayer(window.page_makers_layer);
Do NOT use window globals to store references to your data (unless you're really, really, really sure of what you're doing). If you're wrapping map creation in a factory or a module, store your data in that scope.
var leaflet_factory = {
Do not name something a factory if it doesn't follow the factory pattern. It's very confusing. Just name it differently, make it a CJS module instead, or skip it entirely.
Research into common programming patterns. Do you have something that appears only once in the webpage? Consider singletons.
gives me following error which I am trying to debug.
How are you trying to debug it? Learn to use your browser debugging capabilities, and provide a complete stack trace.
You should be able to easily keep track of the value of the problematic variable, and see if it is an instance of L.LayerGroup when the call is made.

Get JSON WebMap from ArcGIS JavaScript API Map object

I'm trying to get a WebMap object (as JSON) from a JavaScript Map object in the ArcGIS JavaScript API. Is there any way to do this within the API, without using ArcGIS.com? Ideally something like:
webMapAsJSON = map.toWebMap();
From the "Export Web Map Task" documentation in the REST API, there's this line that suggests it should exist:
"The ArcGIS web APIs (for JavaScript, Flex, Silverlight, etc.) allow developers to easily get this JSON string from the map."
However, I don't see anything in the Map object or elsewhere in the API that would do this.
You can't. At least not officially. The steps outlined below are not recommended. They use part of the ArcGIS JS library that is not part of the public API and therefore this behavior may not work in the next version of the API or they may back-patch a previous version of the API and this could stop working even on something that previously did work.
That said, sometimes you need some "future" functionality right now and this is actually a pretty straightforward way of getting what you want using the common proxy pattern
Use the undocumented "private" function _getPrintDefinition
var proxy_getPrintDefinition = printTask._getPrintDefinition;
printTask._getPrintDefinition = function() {
var getPrintDefResult = proxy_getPrintDefinition.apply(this, arguments);
//Now you can do what you want with getPrintDefResults
//which should contain the Web_Map_as_JSON
console.log(Json.stringify(getPrintDefResult));
//make sure you return the result or you'll break this print task.
return getPrintDefResult;
}
_getPrintDefinition takes the map as the first argument and a PrintParameters object as the second.
so you'll have to create a PrintTask, redefine the _getPrintDefinition function on the newly created print task as outlined above, create a PrintParameters and then run:
myPrintTask._getPrintDefinition(myMap,myPrintParameters);
The results of this on my little test are:
{"mapOptions":{"showAttribution":false,"extent":{"xmin":-7967955.990468411,"ymin":5162705.099750506,"xmax":-7931266.216891576,"ymax":5184470.54355468,
"spatialReference":{"wkid":102100,"latestWkid":3857}},"spatialReference":{"wkid":102100,"latestWkid":3857}},
"operationalLayers":[
{"id":"layer0","title":"layer0","opacity":1,"minScale":591657527.591555,"maxScale":70.5310735,"url":"http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"},
{"id":"XXX-Redacted-XXX","title":"serviceTitle","opacity":1,"minScale":0,"maxScale":0,"token":"XXX-Redacted-XXX","url":"http://XXX-Redacted-XXX/arcgis/rest/services/TestService/MapServer"},
{"id":"XXX-Redacted-XXX","opacity":1,"minScale":0,"maxScale":0,"featureCollection":{"layers":[]}},
{"id":"featureGraphics","opacity":1,"minScale":0,"maxScale":0,"featureCollection":{"layers":[]}},
{"id":"map_graphics","opacity":1,"minScale":0,"maxScale":0,"featureCollection":{"layers":[]}}
]}
if you don't need to do any operations on the web map json and just need the output then you don't even need to use the proxy pattern.
#Suttikeat Witchayakul's answer above should work if your goal is to print the map using a print service.
However, if you are trying to export the map to the web map JSON spec so that you can save it to ArcGIS Online/Portal, or re-instantiate a map object from it later, you may have some problems. This is because the web map specification is not the same as the export web map specification, which what the print task generates and sends to printing services.
Unfortunately, the ArcGIS API for JavaScript does not provide any methods to export a map object to web map JSON. This is supposed to be coming in version 4... at some point. Until then, you can use the all but abandoned cereal library. However, if your map uses layer types that are not fully supported by cereal, it may not work for you as is and you would have to extend it.
If you want to use "esri/tasks/PrintTask" to export your map, you must use "esri/tasks/PrintParameters" for execute the printTask. Just set your map object directly to printParameter.
require([
"esri/map", "esri/tasks/PrintTemplate", "esri/tasks/PrintParameters", ...
], function(Map, PrintTemplate, PrintParameters, ... ) {
var map = new Map( ... );
var template = new PrintTemplate();
template.exportOptions = {
width: 500,
height: 400,
dpi: 96
};
template.format = "PDF";
template.layout = "MAP_ONLY";
template.preserveScale = false;
var params = new PrintParameters();
params.map = map;
params.template = template;
printTask.execute(params, printResult);
});

Uncaught TypeError: layer.onAdd is not a function

First attempt at working with Leaflet to display and work with a dynamic map, and I'm running into an error when I attempt to add a new layer of markers to a LayerGroup.
Here is my Map object, encapsulating the functionality of leaflet:
function Map() {
//properties/fields
var map;
var layers = [];
return {
setMap: function(aMap) {map=aMap;},
setView: function(aView) {map.setView(aView);},
addLayer: function(aLayer, name) {layers[name] = aLayer; map.addLayer(aLayer);},
addListings: function(anArr, name) {
var mLayer = [];
for (var i = 0; i < anArr.length; i++) {
var aMarker = L.marker([anArr[i][0], anArr[i][1]]);
mLayer.push(aMarker);
};
layers[name] = L.layerGroup(mLayer);
layers[name].addTo(map);
},
updateListings: function(anArr, name) {
var mLayer = [];
for (var i = 0; i < anArr.length; i++) {
console.log(anArr[i].entity.locations[0].lat, anArr[i].entity.locations[0].lng);
var aMarker = L.marker(anArr[i].entity.locations[0].lat, anArr[i].entity.locations[0].lng);
mLayer.push(aMarker);
}
layers[name].addLayer(mLayer);
},
clearLayer: function(name) { layers[name].clearLayers(); },
};
}
When I load the map initially, everything's fine:
myMap.setMap(L.map('tikit-map').setView([{{ $result['lat'] }}, {{ $result['lng'] }}], 12));
var listLocation = [];
#foreach ($result['company'] as $facility)
listLocation.push([{{ $facility['entity']['locations'][0]['lat'] }}, {{ $facility['entity']['locations'][0]['lng'] }}]);
#endforeach
myMap.addListings(listLocation, 'listings');
Then, when I need to refresh the screen (and the map) via an ajax call (with data coming back via the variable companies, I get the error:
myMap.clearLayer('listings');
myMap.updateListings(companies, 'listings');
The error specifically occurs in the line:
layers[name].addLayer(mLayer);
of updateListings
Anyone with some experience with leaflet that can offer some advice? Thanks
UPDATE: The issue I'm having revolves about why I can't "reuse" the
LayerGroup after I've cleared it, and how I would go about doing that. I've
struggled through the first half of this day for a solution and was
about to post the code as a demo on jsfiddle when I came across this:
https://github.com/Leaflet/Leaflet/blob/master/FAQ.md You can add the
Google Maps API as a Leaflet layer with a plugin. But note that the
map experience will not be perfect, because Leaflet will just act as a
proxy to the Google Maps JS engine, so you won't get all the
performance and usability benefits of using Leaflet when the Google
layer is on.
Because the requirement of the project is to use Google maps, I am
abandoning my efforts, perhaps someone needing to do this will benefit
from any future answers.
You are creating a plain vanilla javascript array with var mLayer = [];, when you really need to be using the Leaflet construct for arrays of layers, which is either L.layerGroup or L.featureGroup - depending on the amount of interactivity. It sounds like for your usecase, var mLayer = L.layerGroup would be fine.

How to access generated Javascript (ScriptObject) from Javascript?

I have a Silverlight application that generates a lot of Google Maps objects on the Silverlight site. For example a Map is created like this:
var map = HtmlPage.Window.CreateInstance(#"google.maps.Map", container, mapOptions);
var center = (ScriptObject)_map.Invoke("getCenter");
Everything works fine. But now I need to access the map object from Javascript directly. I think it could be done by exposing a map property as ScriptableMember and use it from Javascript. But that"s a bit odd because the map object lives already in the browser. But how do I access it?
Update
Just to make clearer what I'm talking about
Let's say I have created my map as shown above. Now I have a loaded Javasript file with this function:
function ReadMapCenter()
{
//Need the map object in Javascript
map.getCenter();
}
How can I access the existing map Object from Javascript?
If you just expose it as type ScriptObject I think the bridge will simply unpack the scripted object rather than create yet another layer of wrapping for it.
Alternative
Don't use CreateInstance
In your javascript at the global leve use:-
var map;
function createMap(container, mapOptions)
{
if (!map)
{
map = new google.maps.Map(container, mapOptions);
}
return map;
}
now your javascript has a map global it can use.
In silverlight use:-
var map = HtmlPage.Window.Invoke("createMap", container, mapOptions);

Categories

Resources