28/05: Google Map Population
Category: JBoss Seam | Posted by: JohnHaselden
As promised waaaaay back, I'm finally going to get around to describing how you get your data back out of the database and back onto the map for multiple markers. I've come across a couple of issues while doing this and I'm going to point these out to you and describe the workarounds I've chosen. Going on from this, I will suggest how these workarounds will be transformed into better solutions.
Let's get started.
First up we need a way of accessing the objects within the database. For this example, I am going to be using shops as an example, so let's have an Action to help with this:
Pretty simple. Let's make the assumption that our Shop object has id, name and description attributes as well as our usual longitude and latitude attributes. We have one method that has been annotated as a 'Factory' method. Now, we need a facelet to do something useful with this method. Unfortunately, this is where it gets a little tricky as we have to pass the values within the objects into JavaScript to render the map correctly.
Let's see the whole facelet in one go and then I'll explain the sections.
Phew! OK, let's examine this in the order of execution as this makes much more sense than the order in which it is written. First up is the shopListController div.
What we are doing here is iterating over the Shop objects returned from the Factory method in the Action. Simple yes? Well, no. This is the location of the first problem: the data iterators supplied all are expecting to display the data that is being iterated which isn't what we want really. So you will notice that the actual div is hidden so that the logic is executed but the user is none the wiser (unless they examine the source!). What we are doing here is calling the 'addShop' JavaScript function with the latitude, longitude, id, name and description attributes.
The addShop function.
Very simply we are making an array within JavaScript containing a number of GMarker objects. Each GMarket object has the latitude and longitude stored in the GLatLng object and a HTML description. The description in this instance is the Name attribute, the Description attribute and a link to the page we would wish to use to display more detailed Shop information (see the Natural Conversations and SEO-friendly URLs blog post about what this link is doing if you aren't up to speed with SEO friendly URLs) and this segment of HTML is bound to the GMarker and will be displayed to the user on clicking on the marker. Finally the GMarker is added to the array.
However, you will notice that we also have a 'Get Directions' link added in there. Rather than having to code up directions within this example, we are basically opening up another window on Google Maps with the map centered on the shop and the travel directions will be handled from there.
Next we initialise the map from the rich:gmap component.
And this calls the init() JavaScript function:
init() loops over the array created by the calls to addShop() and adds the overlay onto the map.
Hopefully this is clearer!
Now, going forwards, it would be nice to not have to hide the data iteration div and there is a way of achieving this that I will cover at a later date.
Let's get started.
First up we need a way of accessing the objects within the database. For this example, I am going to be using shops as an example, so let's have an Action to help with this:
@Name("ShopMapAction");
@Scope(ScopeType.CONVERSATION);
public class ShopMapAction implements Serializable {
@In EntityManager entityManager;
@DataModel
private Listshops;
@Factory("shops")
public String findShops() {
shops = entityManager.createQuery("select s from Shop s").getResultList();
return "shops";
}
}
Pretty simple. Let's make the assumption that our Shop object has id, name and description attributes as well as our usual longitude and latitude attributes. We have one method that has been annotated as a 'Factory' method. Now, we need a facelet to do something useful with this method. Unfortunately, this is where it gets a little tricky as we have to pass the values within the objects into JavaScript to render the map correctly.
Let's see the whole facelet in one go and then I'll explain the sections.
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich" template="layout/template.xhtml">
<ui:define name="body">
<script type="text/javascript">
//<![CDATA[
var l_map;
var shops = new Array();
function init(map) {
for (i = 0; i < shops.length; i++) {
map.addOverlay(shops[i]);
}
}
function addShop(lat, lng, shopId, name, description) {
latLng = new GLatLng(lat, lng, true);
marker = new GMarker(latLng);
var html = "<b>"+name+"</b><br/>"+description+
'<br/><br/><a href="/ShopFinder/shop.seam/'+shopId+'">See Shop</a>'+
'<br/><a href="http://maps.google.co.uk/maps?f=d&hl=en&geocode=&saddr=&daddr='
+lat+','+lng+'" target="google_maps">Get Directions</a>';
marker.bindInfoWindowHtml(html);
shops[shops.length] = marker;
}
}
//]]>
</script>
<h:messages globalOnly="true" styleClass="message" />
<rich:panel>
<f:facet name="header">Shops Map</f:facet>
<h:form id="map_form">
<div id="map"><rich:gmap mapType="G_NORMAL_MAP" gmapVar="map"
gmapKey="Your GMap API Key"
lat="53.696705" lng="-4.482422" zoom="5" oninit="init(map)"
id="map" /></div>
</h:form>
<!-- yeah, I know this is clumsy! -->
<div id="shopListController" style="visibility: hidden;">
<rich:dataList var="shop" value="#{shops}">
<script type="text/javascript">
addShop('<h:outputText value="#{shop.latitude}"/>',
'<h:outputText value="#{shop.longitude}"/>',
<h:outputText value="#{shop.id}"/>,
'<h:outputText value="#{shop.name}"/>',
'<h:outputText value="#{shop.description}"/>');
</script>
</rich:dataList></div>
</rich:panel>
</ui:define>
</ui:composition>
Phew! OK, let's examine this in the order of execution as this makes much more sense than the order in which it is written. First up is the shopListController div.
<div id="shopListController" style="visibility: hidden;"><!-- yeah, I know this is clumsy! -->
<rich:dataList var="shop" value="#{shops}">
<script type="text/javascript">
addShop('<h:outputText value="#{shop.latitude}"/>',
'<h:outputText value="#{shop.longitude}"/>',
<h:outputText value="#{shop.id}"/>,
'<h:outputText value="#{shop.name}"/>',
'<h:outputText value="#{shop.description}"/>');
</script>
</rich:dataList></div>
What we are doing here is iterating over the Shop objects returned from the Factory method in the Action. Simple yes? Well, no. This is the location of the first problem: the data iterators supplied all are expecting to display the data that is being iterated which isn't what we want really. So you will notice that the actual div is hidden so that the logic is executed but the user is none the wiser (unless they examine the source!). What we are doing here is calling the 'addShop' JavaScript function with the latitude, longitude, id, name and description attributes.
The addShop function.
function addShop(lat, lng, shopId, name, description) {
latLng = new GLatLng(lat, lng, true);
marker = new GMarker(latLng);
var html = "<b>"+name+"</b><br/>"+description+
'<br/><br/><a href="/ShopFinder/shop.seam/'+shopId+'">See Shop</a>'+
'<br/><a href="http://maps.google.co.uk/maps?f=d&hl=en&geocode=&saddr=&daddr='
+lat+','+lng+'" target="google_maps">Get Directions</a>';
marker.bindInfoWindowHtml(html);
shops[shops.length] = marker;
}
Very simply we are making an array within JavaScript containing a number of GMarker objects. Each GMarket object has the latitude and longitude stored in the GLatLng object and a HTML description. The description in this instance is the Name attribute, the Description attribute and a link to the page we would wish to use to display more detailed Shop information (see the Natural Conversations and SEO-friendly URLs blog post about what this link is doing if you aren't up to speed with SEO friendly URLs) and this segment of HTML is bound to the GMarker and will be displayed to the user on clicking on the marker. Finally the GMarker is added to the array.
However, you will notice that we also have a 'Get Directions' link added in there. Rather than having to code up directions within this example, we are basically opening up another window on Google Maps with the map centered on the shop and the travel directions will be handled from there.
Next we initialise the map from the rich:gmap component.
<h:form id="map_form">
<div id="map"><rich:gmap mapType="G_NORMAL_MAP" gmapVar="map"
gmapKey="Your GMap API Key"
lat="53.696705" lng="-4.482422" zoom="5" oninit="init(map)"
id="map" /></div>
</h:form>
And this calls the init() JavaScript function:
function init(map) {
for (i = 0; i < shops.length; i++) {
map.addOverlay(shops[i]);
}
}
init() loops over the array created by the calls to addShop() and adds the overlay onto the map.
Hopefully this is clearer!
Now, going forwards, it would be nice to not have to hide the data iteration div and there is a way of achieving this that I will cover at a later date.
Comments
mac data recovery wrote:
The site's design just blew me away. Simple and to the point. Very direct.
05/05 10:50:04
Pump in style advanced wrote:
Thank you for making life easier with this, appreciate the hard work put!
16/06 18:59:20



Personal Injury Lawyers wrote: