Back in February I wrote a simple introduction to using Google Maps. ColdFusion makes this relatively easy, but there are times when you may want to use the Google Map service natively. It's also important to note that CFMAP makes use of the previous version of Google Maps. If you want to make use of the new hotness, you have to do it "by hand." As my blog post (hopefully) showed, this isn't terribly difficult at all. I thought I'd post a sequel that dealt with another part of the puzzle - markers. ColdFusion makes this really easy as well with the cfmapitem tag. Here's what I found when digging into the docs.
Google groups markers into an area they called overlays. Overlays include:
- Markers (duh)
- Lines
- Polygons (you see - Math is important)
- Circles
- and finally - "Info Windows"
The last item, "Info Windows", is actually something you have already seen if you've played with cfmap. When you create a marker with cfmapitem and supply a value for markerwindowcontent, you are actually creating both a marker and an Info Window. The info window is the little "cartoon voice" window that pops up when you click on the marker. Let's look at a simple example that creates a map and then puts a marker on top of it:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 450px; height: 450px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {
var myLatlng = new google.maps.LatLng(-25.363882,131.044922);
var myOptions = {
zoom: 4,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var marker = new google.maps.Marker({
position: myLatlng,
title:"Hello World!"
});
// To add the marker to the map, call setMap();
marker.setMap(map);
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
So I assume you read the previous entry and I don't need to explain the code that initializes the map. Immediately after this code is the marker code, let's focus on that:
var marker = new google.maps.Marker({
position: myLatlng,
title:"Hello World!"
});
// To add the marker to the map, call setMap();
marker.setMap(map);
I create the marker object with with a position (a longitude and latitude - we've all memorized our long/lat values, right?) and a title. There's more options to the marker object, but this will give you a basic marker with a title. The setMap function simply allows me to set the map for the marker. You can also provide it when you create the marker:
var marker = new google.maps.Marker({
position: myLatlng,
title:"Hello World!",
map:map
});
You can run this yourself here: http://www.coldfusionjedi.com/demos/april132011/test1.html
That works - but let's get some ColdFusion involved - check out this next example:
<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
select orderid, total, orderdate, address, city, state, postalcode
from orders
</cfquery>
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 450px; height: 450px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var map;
function initialize() {
var latlng = new google.maps.LatLng(38, -90);
var myOptions = {
zoom: 3,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
geocoder = new google.maps.Geocoder();
<cfoutput query="getorders">
geocoder.geocode( { 'address': '#address#'}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location,
title: "Order #orderid# for #dollarFormat(total)#"
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
</cfoutput>
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
First - I added a query on top to get order information from the database. This includes address information for each order. Now look into our JavaScript. Because my order data isn't geocoded, I have to ask Google to geocode the data for me. I loop over each order and request such data and when I get it back, I use it to create a marker. Note the title includes a bit of information about the order itself. Not terribly complex but closer to a real application. You can see this one here: http://www.coldfusionjedi.com/demos/april132011/test2.cfm In case it isn't obvious, the title for the marker shows on mouse over.
Ok - so what about the cute little info windows? Let's look at our next example:
<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
select orderid, total, orderdate, address, city, state, postalcode
from orders
</cfquery>
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 450px; height: 450px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var map;
function initialize() {
var latlng = new google.maps.LatLng(38, -90);
var myOptions = {
zoom: 3,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
geocoder = new google.maps.Geocoder();
<cfoutput query="getorders">
geocoder.geocode( { 'address': '#address# #city# #state# #postalcode#'}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location,
title: "Order #orderid# for #dollarFormat(total)#"
});
var infowindow = new google.maps.InfoWindow({
content: "<h3>Order #orderid#</h3>Address: #address#, #city#, #state# #postalcode#<br/>Ordered: #dateFormat(orderdate)#"
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.open(map,marker);
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
</cfoutput>
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
I'm going to focus on what's new here and that's the code right after the marker creation. You can see I make a new infowindow object. The content here is totally arbitrary, but I figured order details would be nice. Finally we add a click event to make it open when the marker is clicked. Still relatively simple, right? You can run this one here: http://www.coldfusionjedi.com/demos/april132011/test3.cfm
Time for our final demo. What spurred this blog post was a request from someone for the ability to center a map based on a click elsewhere in the page. Here's how I solved that - and note - there is probably a much nicer way to do this.
<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
select orderid, total, orderdate, address, city, state, postalcode
from orders
where orderid != 4
</cfquery>
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript">
var map;
var markers = [];
var lastinfowindow;
function initialize() {
var latlng = new google.maps.LatLng(38, -90);
var myOptions = {
zoom: 3,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
geocoder = new google.maps.Geocoder();
<cfoutput query="getorders">
geocoder.geocode( { 'address': '#address# #city# #state# #postalcode#'}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location,
title: "Order #orderid# for #dollarFormat(total)#"
});
var infowindow = new google.maps.InfoWindow({
content: "<h3>Order #orderid#</h3>Address: #address#, #city#, #state# #postalcode#<br/>Ordered: #dateFormat(orderdate)#"
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.open(map,marker);
});
marker.orderid = #orderid#;
marker.infowindow = infowindow;
markers[markers.length] = marker;
} else {
//alert("Geocode was not successful for the following reason: " + status);
}
});
</cfoutput>
$(".order").click(function() {
var thisorder = $(this).data("orderid");
for(var i=0; i<markers.length; i++) {
if(markers[i].orderid == thisorder) {
console.log("found my match");
//get the latlong
if(lastinfowindow instanceof google.maps.InfoWindow) lastinfowindow.close();
console.dir(markers[i]);
map.panTo(markers[i].getPosition());
markers[i].infowindow.open(map, markers[i]);
lastinfowindow = markers[i].infowindow;
}
}
});
}
</script>
<style>
#map_canvas {
width: 450px; height: 450px;
float:left;
}
#orders {
margin-top: 0px;
padding-top: 0px;
margin-left: 10px;
float:left;
height: 450px;
overflow-y: scroll;
}
.order {
border-style:solid;
border-width:thin;
width: 300px;
padding: 5px;
cursor:pointer;
margin-top:0px;
}
</style>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
<div id="orders">
<cfloop query="getorders">
<cfoutput>
<p class="order" data-orderid="#orderid#">
<b>Order #orderid#</b><br/>
#address#, #city#, #state# #postalcode#
</p>
</cfoutput>
</cfloop>
</div>
</body>
</html>
So first a quick note - I modified my query to not get orderid 4. That order was consistently not geolocating so I thought i'd just remove it. Right away you may notice something new - I've created an array called markers and a variable called lastinfowindow. The reason why for this will become evident real quick like.
Scroll down a bit to where I create the marker. I wanted a way to associate the order ID with the marker (and again, why will be made clear), so I kinda cheated a bit. Objects in JavaScript can be manipulated much like CFCs in ColdFusion. I can add an arbitrary value - as I've done here - to store the order id. That kind of bugs me. I could have create a new object instead and stuffed the marker as one key and the value as another - but this works. I also store a copy of the infowindow object. Finally this is appended to my array. The end result is that I've now got a global object that contains a reference to all my markers. Apparently (and I could be wrong), Google Maps do not provide an easy way to get all the markers in a map. From what I see they recommend exactly this approach - storing them within an array.
So - next up is a click handler defined using jQuery syntax. If you look down a bit you can see I'm outputting the orders in a set of P blocks. I've asked jQuery to set up a click handler on those paragraphs. When you click, I grab the order id (I love data-* properties!) and then look for it within my markers array. If I find it - I first see if I need to close an existing infowindow. I then pan the map to the location and open the infowindow. All in all - rather simple? If you want to demo this, please be aware of the console.log messages I used to debug. Use in a Chrome browser or Firebug-enabled Firefox.
A big thank you to Todd Sharp for help with the CSS.
Archived Comments
Nice followup post. I was curious, since I haven't used the maps API in awhile, does the title property do anything for a marker? I didn't notice "Order #orderid# for #dollarFormat(total)#" displaying anywhere.
This was the first ORM project I did when CF9 came out (I also chose not to use CF's version of google maps so I could update the maps stuff without upgrading CF).
I went with making "markers" into an object and using the orderid as properties of the object (and that property in turn was the corresponding marker object). That way when paging through the results I could use the markers object as a cache and only create a new marker if that ID didn't exist as a property of the markers object. I don't know how much memory creating a marker takes but I figured it's still best not to let someone loading them infinitely :)
Very nice! Just one issue. It is not working for me in in IE9
@Chris: That's the console.log messages. I warned ya about them. ;)
@Jeremy: They show up (or should) on mouse over.
@Ray You're totally right, I originally read the post on my Xoom so I wasn't seeing them. Get them fine in desktop browsers, didn't know about that property, very nice.
Beside the mouseover not working, how did it look in the Xoom? Mine is upstairs and I'm too lazy to go get it.
You should try out some of the competition, they don't get enough publicity. http://www.openlayers.org/ http://polymaps.org/ http://tile5.com/
Here's one I made with polymaps http://drewwells.net/craigm...
@Drew: Your map doesn't work for me in IE9 and it is very slow. I also much prefer a Google map
@Ray It looked just as good on the Xoom, snappy too.
Sweetness. Will try it today at lunch. Oddly - my upstairs office is wired, not wireless, so it's hard to use devices. Got wifi downstairs in the main part of my house.
Random sidenote, Drew's map doesn't work in IE because of line 687 in polymap.js:
window.addEventListener("resize", resizer, false);
window.addEventListener("load", resizer, false);
which in IE needs to be window.addEvent() because I don't believe IE supports addEventLister() in recent versions, usually you can just conditionally add the events based on browser type.
http://www.quirksmode.org/j...
Marker: 12 Gover St, Oakland, CA 45794 displays at Glover Street in San Francisco. Apparantly Google maps gets lost at times.
Interesting. If you input that address in maps.google.com, it can't find it, and the first location it suggests is Glover in San Fran. So in this case I'm guessing the geolocation is just using the best result.
You can use the Google chart api to change up the icon for your map marker (http://code.google.com/apis... You pass the url of the marker you come up with to the icon parameter of marker. Here's a marker that has a blue pin with a black letter C in it:
var contactMarker = new google.maps.Marker({
position: contactLocn,
map: map,
title:"Here they are!",
icon:'https://chart.googleapis.co...|87cefa|000000',
animation: google.maps.Animation.DROP
});
nb - you can use http or https for maps and charts now.
Wow, that's kinda cool. Thanks Matt!
@Ray - Thanks for this article Ray. It has been of great help
@Dale - Had a play with the Google Chart icons this evening. Yes. Very cool. Only problem is, they are not very accurate, especially the not so standard looking pins and other icons
I have getting error one i fetch number of record in cfoutput
OVER_QUERY_LIMIT indicates that you are over your quota.
Its means when i have above 50 records this will not work.
Please help me about this
Thanks in advance
Regards,
Tariq
Um - use less markers? Seriously - I can't help you with the Google quota limits. I'm sure you could pay them for more. You could try limiting the number of markers you show at one time.
Thanks again Ray. Indebted as ever.Struggling trying to get the infoWindow to go to the right point. It always picks up the last record even thought the title is correct.
<cfoutput query="Sightings">
var latlng = new google.maps.LatLng(#Sightings.lattitude#, #Sightings.longitude#);
var marker = new google.maps.Marker({
map: map,
position:latlng,
title: "#label_en#- #lattitude#, #longitude#",
});
var infowindow = new google.maps.InfoWindow({
content: "<B>#label_en#</B><br/><a href='http://127.0.0.1:8500/WildlifeHeroWebCFM/WildlifeHero/tree-of-life/wildlifebinomial.cfm?binomial=#URLEncodedFormat(binomial)#'>(#binomial#)</a><br/>#eventDate#<br>#city#,#weather#c<p>#eventnotes#"
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.open(map,marker);
});
</cfoutput>
I think you want to remove "Sightings." from here:
var latlng = new google.maps.LatLng(#Sightings.lattitude#, #Sightings.longitude#);
Wow thanks for coming back so quick. Tried that, it makes no difference at all. The hover over title is correct but when I open the information window it is always displaying the last record.
Is it online where I can see?
Hi Ray, I have it online now at http://www.wildlifehero.com... Bit infuriating this one. I assume the varables are overwriiten by the time I get to use them in the infoWindow. By the way I am using your galleon forums here too so you are an official Wildlife Hero
Ok - you are doing things a bit differently since you have lat/lng values and don't have to geocode.
What follows - I stress - should be taken with a grain of salt as it involves scoping issues in JavaScript and I'm still a bit... unsure there.
If you look at my code, I've got N "blocks" of geocoding going on. Within the block, I use var statements to define markers, strings, etc, and then append a marker to a global object.
In your code, you don't have those wrappers. So I think the variables are 'going over' each other.
As a quick fix, try this. I imagine you are looping over a query, right? So you have access to somequery.currentRow. Take that value and add it to the end of
latlgnS
image
contentString
infowindow
marker
Or - conversely - use a loop in JavaScript.
If you want to give me FTP access, I can take a stab at it.
Actually.... thinking... try adding the dynamic part ONLY to marker. So
var marker =
becomes
var marker#somequery.currentRow# =
Hmmm. The more I think about it, the more I know it is a scoping issue. I'd have to really get in to see the best fix though.
Adding current row causes the window not function at all. Can u send me your email address I have lost it. Thanks
I emailed you this morning about a ... issue... on your server. :)
Dang it Ray, I'm having the same problem as Terry. Been working on it for several hours. Wishing I had more brain cells.
If I can help, let me know via email.
Hi...Thanks for the nice post. I want to know if there is a way to search through the markers..I have a custom map with around a hundred markers and I want to search among them..
You would have to do it via JavaScript. If you have the data for your markers in a JavaScript array, you would just loop over them.
Ray, what about adding an option that allows the end-user to "drag" the pin to a different location that is saved back to Coldfusion (like this example: http://www.putyourlightson....
I'm confused - what part do you need help with? In this demo, he gives you the JS. To 'store' it you would just do an AJAX call. If you've never done AJAX with CF, let me know, but if you have, that's all that is involved here.
The example I linked to is using the old v2 the Google API so I needed an example using v3.. This example is v3 & grabs the Lat/Long. (http://www.geocodezip.com/v...
Question: How do I "grab" those updated Lat/Long values and save to URL variables for posting to SalesForce/ColdFusion?
All I can recommend is doing what I'd do. I'd look at the first example and see how they enabled drag/drop for the markers. Compare that to the docs you can read for v3 and see if they are the same, or if not, close. Make any required changes for v3, and then you are done.
Wouldn't you agree that should work?
Thank You !!!
is there a way when an information window open, other window close automatically without click on the X? where to modify the code to accomplish this?
Yes, it is possible. You would need to write code to remember what windows were open so you can close them via the API.