Google Map in Twitter Bootstrap Popver - javascript

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]);
});

Related

Kendo TabStrip and Leaflet Map

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!

Ng-map - infowindow on markers sometimes not appearing

I'm using the ng-map directive to display a map. The map has markers that show an infowindow whenever there's a mouseover on the marker. Sometimes however, the infowindow doesn't appear.
Other than this, I haven't been able to identify any pattern to what's happening, as the problem occurs for a different marker each time. I'm outputting data to the infowindow however the issue doesn't seem to be 'data related' as all data for the selected location seems to be correct at the point where the issue occurs.
I have a showInfo method that is being called on a mouseover like this:
showInfo(event, loc, infoWindowTemplate, map, mapsController) {
loc - data for the clicked location
infoWindowTemplate - the info window template to use (this is always the same for a particular map, however this is configurable, so if I'm showing a map for mobile, I use one infowindow template, if I'm showing a desktop map, I use a different one)
map - a reference to the NgMap object on the controller
mapsController - the controller itself (I strongly suspect that this is a bad code smell - it was the easiest way I could figure out to get reference back to the controller following the mouseover)
Here is the body of the method:
map.getMap(mapsController.mapId).then(function (myMap) {
var selectedMarker = myMap.markers["L: " + loc.position[0] + ", " + loc.position[1]];
selectedMarker.locationInfo = loc;
console.log("about to show infowindow - infoWindowTemplate = " + infoWindowTemplate);
// console output = "cached-myTemplate.html"
myMap.showInfoWindow(infoWindowTemplate, selectedMarker);
selectedMarker is definitely referring to the correct marker object. My template looks like this:
<script id="cached-myTemplate.html" type="text/ng-template">
<a class="map-location__link" href="/locations/{{anchor.locationInfo.locationId}}" target="_blank">
<img src="{{anchor.locationInfo.locationImageThumbnail}}" />
</a>
</script>
The issue seems to be that calling 'showInfoWindow' is intermittently failing somehow (although there are no errors in the console). Any comments or answers with ideas of what may be causing the issue or what else I can do to diagnose it will be appreciated!
I discovered that this is a timing issue. Delaying the 'turn' in which showInfoWindow is called (by adding a short timeout) fixed the issue:
map.getMap(mapsController.mapId).then(function (myMap) {
....
this.$timeout(function () {
dealmap.showInfoWindow(infoWindowTemplate, selectedMarker);
}, 100)
}.bind(this));

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.

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.

Customize OpenLayers Control

How can I easily customize OpenLayers map controls? Or at least, how can I minimize the controls' height?
Thank you.
PS. Is there any CSS override?
You can sub-class any of the openLayers controls. I just made a 'zoom-slider' by sub-classing PanZoomBar (panZoomBar.js), overriding the draw() method and commenting out all the button elements, just leaving the zoom slider.. like this:
function zoomSlider(options) {
this.control = new OpenLayers.Control.PanZoomBar(options);
OpenLayers.Util.extend(this.control,{
draw: function(px) {
// initialize our internal div
OpenLayers.Control.prototype.draw.apply(this, arguments);
px = this.position.clone();
// place the controls
this.buttons = [];
var sz = new OpenLayers.Size(18,18);
var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
this._addButton("zoomin", "zoom-plus-mini.png", centered.add(0, 5), sz);
centered = this._addZoomBar(centered.add(0, sz.h + 5));
this._addButton("zoomout", "zoom-minus-mini.png", centered, sz);
return this.div;
}
});
return this.control;
}
var panel = new OpenLayers.Control.Panel();
panel.addControls([
new zoomSlider({zoomStopHeight:11}),
new OpenLayers.Control.LayerSwitcher({'ascending':false}),
]);
map.addControl(panel);
There is a CSS file that comes with can controls all of the CSS commands for within openlayers generally .olZoombar { here}
It is probably the easiest way to edit those sorts of things otherwise you can edit the actual .js file for the control.
If you are talking about the PanZoomBar or ZoomBar, as has been mentioned, you need to edit the zoomStopHeight. However, You do not need to edit OpenLayers.js.
new OpenLayers.Control.PanZoomBar({zoomStopHeight: 7})
You could consider trying PanZoom, which has no bar.
To minimize the ZoomBar search for zoomStopHeight in OpenLayers.js and edit it as you wish.
Further reference: Link.
Take a look here - http://geojavaflex.blogspot.com/
I am in the process of showing how to do an involved customization of the LayerSwitcher. This might give you ideas on how to do what you are after.
There is a map on the page that shows how the control works, and subsequent posts will discuss the code in detail.
If you are just interested in code see the source of the page and look for the link to CustomLayerSwitcher.js for the customized version of the switcher.

Categories