Kendo TabStrip and Leaflet Map - javascript

I am trying to get a leaflet map to function/draw up correctly inside a Kendo Tabstrip.
The tabstrip is defined by a script inside a jsp page. Bootstrap is being used to handle placement. When the user clicks on a previous page this executes javascript and generates the tabstrip with data being queried and supplied to the resulting tabstrip from ajax data sources.
The javascript uses the kendo script to generate the html elements i.e. the kendo tabstrip. When the template script is called, it does create the map, but the data within the map does not render properly. There are gray tiles showing. If you resize the window, the map draws perfectly. This seemingly, from everything I've read online, relates to Leaflet having issues with tabs and containers created on the fly, so to speak.
I've used the 'show' event on the tabstrip to execute the population of the map (creating markers, setting the extent to these etc), so at least I know the tabstrip has been opened and at that point I update the map. This satisfies FireFox. I've used the L.Util.requestAnimFrame without success.
L.Util.requestAnimFrame(map_realEstateDetails.invalidateSize,map_realEstateDetails,!1,map_realEstateDetails._container);
Nor does invalidateSize(); I've gone through searches online, and there are a varity of solutions offered. I've tried all and I am able to get my code to work in Firefox but not Chrome or IE.
inside the jsp we have
script type="text/x-kendo-tmpl" id="templateNewRealEstateTab">
div id="tabWrapperRealEstate_#=id#"
div class="col-md-3 pt-15"
div id="map_realEstateDetails" class="map_realEstateDetails" /div
/div
in css
.map_realEstateDetails { height:320px; z-index: 0;}
The javascript uses the kendo script to generate the html elements i.e. the kendo tabstrip
var newTabTemplate = kendo.template($('#templateNewRealEstateTab').html());
$("#realEstateDetailTabStrip").kendoTabStrip({show: popRealEstateMapDetails('newId')});
function positionRealEstateMapDetails() {
if (map_realEstateDetails != null) {
baseTileLayer = getBaseTileLayer();
map_realEstateDetails = L.map('map_realEstateDetails', {
center: [18.8964, 34.3794],
zoom: 3,
layers: [baseTileLayer] });
}
}
function popRealEstateMapDetails(id){
var del = [];
$.ajax({
url: urldel.delegations,
async:false,
success: function (listdel) {
del = listdel;
},
dataType: "json"
});
var markers = {};
markerMapGroup = new L.featureGroup();
map_realEstateDetails.addLayer(markerMapGroup);
for (var i = 0; i < del.length; i++) {
if (del[i].id == delegationId) {
console.log(del[i].id);
markers[del[i].id] = L.marker(del[i].coordinates, {icon:_buildingIcon, title: del[i].tooltip});
markers[del[i].id].addTo(markerMapGroup);
markers[del[i].id]._icon.id = del[i].id;
var latlongs = [markers[del[i].id].getLatLng()];
var markerBounds = L.latLngBounds(latlongs);
map_realEstateDetails.fitBounds(markerBounds);
map_realEstateDetails.setZoom(3);
}
}
What I should see is a leaflet map in the resulting tabstrip with relevant markers. Works in FF but IE and Chrome need to have the window resized to have the map render properly. Otherwise the map is mostly gray.

So after debugging and help from a great colleague. He discovered a solution. If we delayed the execution (setTimeout) of the function populating the map with markers and throw in the L.Util.requestAnimFrame.... at the end of this function. It works in Chrome!

Related

Leaflet - access specific polyline feature (GeoJSON)?

The situation: My web application shows a map with different trails of interest (my so called POIs) and a sidebar with information about each POI. Selecting a panel of the sidebar, the related POI should be selected/highlighted on the map.
Data and platforms used: I work with Leaflet and JavaScript, no jQuery. The data are added within Leaflet as GeoJSON. The trails are represented as polylines, but I call them POIs (just to clarify). I do not and cannot use jQuery.
What works: The trails (polylines) are added like this:
var pois = L.geoJson(pois,
{
style: style_pois,
onEachFeature: onEachFeature
});
pois.addTo(map);
function onEachFeature(feature, layer)
{
layer.on('click', function (e)
{
sidebar.open('pois');
//get the ID of the clicked POI
number = feature.properties.number;
//Open the accordion tab
var panel = document.getElementsByClassName('accordion-panel');
panel[number-1].style.display="block";
//highlight the selected POI
var selectedFeature = e.target;
selectedFeature.setStyle(style_pois_selected);
});
}
What does not work: Selecting a panel of the accordion, I get the ID of the related trail (polyline), but I cannot access and highlight this certain polyline feature within Leaflet.
This is the JavaScript code, where the accordion behavior is controlled:
var acc = document.getElementsByClassName('accordion');
var panel = document.getElementsByClassName('accordion-panel');
for (var i = 0; i < acc.length; i++)
{
(function(index){
acc[i].onclick = function()
{
// Toggle between adding and removing the "active" class,
//to highlight the button that controls the panel
this.classList.toggle("active");
//Toggle between hiding and showing the active panel
var panel = this.nextElementSibling;
console.log("panel " + acc[0]);
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
var myIndex = index + 1;
alert("INDEX " + myIndex);
}
})(i);
}
Question: Is there a possibility, based on a layer that is included as GeoJSON in Leaflet to access a certain feature based on any property?
What I tried: I only came across solutions where the different behavior of a certain polyline is accessed within the onclick function. There it is easily possible to apply another color (setStyle). I need to access it from outside the layer. I already tried to again load the pois layer as I did above, just inside the accordion JavaScript and filter it for the certain ID so that only the one polyline is represented, but it only gave me an error that it is an invalid GeoJSON object (maybe a scope issue?).
I appreciate any help!
For anyone who might come across the same problem - I found a solution.
I looked for hours to find out, if one can access a specific feature from a GeoJSON layer within Leaflet, but it seemed that there is no such method.
Although there is no official method for it, for me worked the following.
When inside the accordion, one can just access the already loaded GeoJSON dataset, in my case pois and get the layer (this actually gets the feature, not the layer! a bit misleading) at the index position. For this one, a style can then be applied.
pois.getLayer(index).setStyle(style_pois)
To read out the index of the clicked accordion panel, I asked another question and was pointed in the right direction: Simple JavaScript accordion - how to get the index of the clicked panel?
NOTE: I'd recommend you to set some JFiddle to reproduce your problem.
A solution I often use is to set the ID/Class property in each of the markers/points:
$.getJSON("data/displacement.geojson", function(data){
pathsLayer = L.geoJson(data,{
className: function(feature){ //Sets the class on element
//Assuming your JSON has a property called ID
return "ID-" + feature.properties.ID;
},
style: function (feature) {
//If needed, you can also set style based on properties
},
})
});
After that you can set a global variable to keep record of the selection ID, and then use it to select and modify the specific element. Since Leaflet uses SVG elements, I recommend you to use D3.js to select/modify elements, for instance:
var selectedID = null; //Declare global variable
// You modify selectedID by actions on sidebar, e.g.:
selectedID = 001
d3.select(".ID-" + selectedID)
.transition() //You can set easily transitions on attribute changes
.duration(1000) // in ms
.attr("attributeName", "attributeValue");
You can find an example here (although I know is a bit tricky to read using View Page Source (Ctrl + U))

Google map js api version 3 infowindow glitch

I am developing an application which uses google maps api v3 to show markers and infowindows.
Well, I have N markers stored within an array and a global infowindow used to show some information.
The infowindow contents are shown simply by clicking a marker, created in this way:
/* global js stuff */
var g_map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var g_current_popup = new google.maps.InfoWindow({ content: "" });
var g_markers = [];
/* create marker function */
function addMarker(p_infowindow_contents)
{
var l_marker = new google.maps.Marker( all the stuff needed );
google.maps.event.addListener(l_marker, 'click', function()
{
g_current_popup.close(); // if already open, it must be closed and reloaded
g_current_popup.setContent(p_infowindow_contents);
g_current_popup.open(g_map, l_marker);
});
g_markers.push(l_marker);
}
Everything works as expected, except for a little graphical glitch: when the infowindow is appearing, I see the infowindow 'tip' positioned at an unknown location for a tenth of a second, then it disappears and I see the correct infowindow.
Look at this screenshot took just before the tip disappears:
Does anyone experienced something like this?
Could it be some CSS issue?
Thanks
It does not look like this is a problem with your code, I think it more likely a browser issue. I was able to validate the same thing looking at the infowindow example that Google provides in Firefox, but not in Chrome:
https://developers.google.com/maps/documentation/javascript/examples/infowindow-simple
it seems to happen more visibly when the map has to scroll to fit the infowindow, but I would say that it is not a requirement for it to do so. It is likely just an artifact with the screen taking a few clock cycles to catch up with the DOM.

HTML Link to a Leaflet / Mapbox marker

I use Mapbox for a dynamic map on a website. It works really well except, I have a sidebar which list the pins with a little more description and an image. I want to make it so when I click on that sidebar, it would fire the click event of that marker on the map.
I used to do this all the time with Google Maps but now I'm stuck because even in the case that I can keep the instance of the marker, I cannot fire the click on it for some reason. It simply does nothing (maybe I need to re-bind the click event on it but I don't know how with mapbox)
I've encountered a few questions about this on Google and SO but none bring a real answer to that question except "keep the instance" which isn't always possible in some cases.
So basically I have a jQuery click event like this:
var marker = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [lng, lat]
},
properties: {}
};
if (isPin) {
marker.properties = pinStyles.pin;
} else if (isWinery) {
marker.properties = pinStyles.winery;
} else {
marker.properties = pinStyles.user;
}
marker.properties.title = locationName;
marker.properties.description = pin.description;
var markerObject = L.mapbox.markerLayer(marker);
// Add to cluster
markers.addLayer(markerObject);
$('#marker_list a.marker_item:last').click(function() {
var geoJson = markerObject.getGeoJSON();
markerObject.fire('click'); // does nothing (openPopup makes "Uncaught TypeError: Object [object Object] has no method 'openPopup' " so I guess I'm not doing it right)
});
And I have this (click event for mapbox marker):
map.markerLayer.on('click', function(e) {
map.setView(e.layer.getLatLng(), map.getZoom());
});
Anyone has an idea about wether 1) fix the non-firing event OR 2) make an HTML link fire a mapbox marker click event OR .openPopup?
Thanks and have a nice day!
MapBox's marker layer is a collection of Leaflet markers. You can create an href to a function that look for a particular marker based on it's layer id.
map.markerLayer.getLayers() returns an array of layer objects that contain both a _leaflet_id and the method togglePopup.
Try matching your href call to the leaflet id and then fire map.markerLayer.getLayers()[i].togglePopup()
Let me know if this helps.

Google Map in Twitter Bootstrap Popver

I'm using Twitter's Bootstrap, and want to show a Google Map in a popover.
The way it works right now I'm doing something like this
$ ->
$('.thumbnails a.js-popover').popover
html: true,
content: ->
uid = $(this).data('profileUid')
popover_container = $('.popover-contents:data(profileUid=' + uid + ')')
_.each window.Maps, (map) ->
google.maps.event.trigger map, 'resize' // I hoped this would re-draw the map
popover_container.html()
The popover loads it's content from a .popover-contents div which is hidden, and connected to the a with data attributes (so that I can find the correct popover to show)
The map works perfectly when not in a popover, and I think it's connected with being copied via html() in jQuery to another DOM element. Twitter's bootstrap doesn't provide a modal opened callback, and I'm genuinely not sure how to make the maps work.
As you can see the map works correctly on the full profile page, the markup is the same (rails partial), and the javascript is shared, too - I can only assume the GoogleMaps API really doesn't like having it's dom messed with, and is thus causing issues.
If you're using popovers, your best bet might be to use google's static API and avoid the headaches associated with an interactive map. Borrowing a very simple case from the documentation, you might do something like this:
var options = { content: '<img src="http://maps.googleapis.com/maps/api/staticmap?center=Brooklyn+Bridge,New+York,NY&zoom=14&size=512x512&maptype=roadmap&sensor=false">' };
$('#example').popover(options)
Wrapping it up into a reusable function yields:
var getMap = function(opts) {
var src = "http://maps.googleapis.com/maps/api/staticmap?",
params = $.extend({
center: 'New York, NY',
zoom: 14,
size: '512x512',
maptype: 'roadmap',
sensor: false
}, opts),
query = [];
$.each(params, function(k, v) {
query.push(k + '=' + encodeURIComponent(v));
});
src += query.join('&');
return '<img src="' + src + '" />';
}
var content = getMap({center: 'Fritz-Walter Stadion, Kaiserslautern'});
$('#example').popover({ content: content })
*Another valid answer might be found here, but it's not the solution I took. *
It seems to be widely accepted that rendering a Google map in an invisible DOM element leads to rendering bugs, and the solution (cribbed from http://sethmatics.com/articles/classipress-hidden-map-tab-and-redraw-google-map-canvas/ ) appears to look something like this:
jQuery('.tabprice ul.tabnavig li a').click(function() {
    if(jQuery(this).attr('href') == '#priceblock1') {
        //the element has to be visible on the page for google maps to render properly
        jQuery('#priceblock1').show();
        //rebuild the google map canvas to the proper size
        google.maps.event.trigger(map, 'resize');
        //ClassiPress javascript function to build map at address
        codeAddress();
    }
});
I do think it might be worth moving the dom element a long way off the left/right/bottom of the screen in order to avoid it flashing to the user, or doing something with Z-Indexing to make sure that the user doesn't see an unwelcome popup.
In my case, however the static maps API as suggested by rjz was perfect.
The problem is that google maps api requires visible element for container, so you should call the api inside the shown.bs.modal event handler. Something like this:
$picker.popover({
html: true,
content: '<div class="superPopover">'
});
$picker.on('shown.bs.popover', function () {
var $container = $('.superPopover');
new google.maps.Map($container[0]);
});

How do I activate a feature + popup when clicking outside of a map in Openlayers?

I'm re-parsing the KML that's already been loaded onto the map similar to the example here:
http://openlayers.org/dev/examples/sundials.html and turning it into a clickable list that will center the map on the point clicked, and display the popup window for it.
This was really easy to do in Google Maps, but I can't find any similar Openlayers examples. Is there any easier way to do this? Something built-in that I'm missing?
HTML:
<ul id="locationTable">
</ul>
JS:
htmlRows = "";
for(var feat in features) {
// Build details table
featId = features[feat].id; // determine the feature ID
title = jQuery(f).filter('[name=TITLE]').text();
htmlRow = "<li>"+title+"</li>";
htmlRows = htmlRows + htmlRow;
}
jQuery('#locationTable').append(htmlRows);
And then for the selectFeature function:
function selectFeature(fid) {
for(var i = 0; i<kml.features.length;++i) {
if (kml.features[i].id == fid)
{
selected = new OpenLayers.Control.SelectFeature(kml.features[i]);
selected.clickFeature(); // make call to simulate Click event of feature
break;
}
}
}
I think you should remove the "selected.clickFeature" call, and instead create an event listener for the "featureselected" event in your feature layer:
OpenLayers.Layer.Vector
If you display the popup in that event, you will only have to find it and select it with your existing code, and remove the line
selected.clickFeature();
Sidenote: Can your feature server deliver data in other formats? WFS for instance? Parsing KML data shouldn't be needed.

Categories