Google maps v3
Vandaag een technisch verhaal aan de hand van de website onlinereisbeurs.nl gefocused op de scripting die nodig was voor de kaart (google maps) in combinatie met panoramio voor de foto's van de gekozen locatie.
Laten we beginnen met het makkelijkste: de HTML
<_h2>Win uw droomreis t.w.v. €10.000,- Stap 1/2: Sleep de koffer naar uw droombestemming
* <_h2> uiteraard zonder de _ maar anders komt cuffon er weer overheen :p
Eigenlijk maken we hier alleen een div aan voor de map met een blok er overheen voor de info en een apart spannetje voor de pijl die beweegd; met "optimalisatie" voor IE6 omdat die browser zo goed omgaat met png's ...
Met de CSS er bij krijgen een we een set elementen die over elkaar heen vallen en een vlak voor Google maps:
#googleMap {
height:400px;
margin:0 6px 20px 20px;
}
#popup {
background:transparent url(../images/uitleg_bg.png) no-repeat scroll 0 0;
height:100px;
left:350px;
padding:20px 20px 0;
position:absolute;
top:281px;
width:380px;
z-index:100;
}
#popup h2 {
color:#00468E;
font-size:18px;
font-weight:bold;
}
#arrow {
color:#584436;
display:block;
font-size:12px;
font-weight:bold;
height:35px;
padding:10px 10px 0 0;
}
#arrow .blue {
color:#00468E;
}
#arrowImg {
background:transparent url(../images/pijl_icon.png) no-repeat scroll right top;
height:36px;
position:absolute;
right:13px;
top:38px;
width:32px;
}
Door goed gebruikte ID's te gebruiken zijn bijna alle elementen direct te benaderen wat een positieve invloed heeft op het laden van de pagina. In eerste instantie had ik de pijl als achtergrond van de ID arrow meegegeven met een background positie op rechts maar daar kan IE6 niets mee. Aangezien de achtergrond semi transparant is moet dit pijltje wel een png zijn; anders hebben we lelijke randen en leg dat je designer maar weer uit ;) Door het pijltje in een aparte span op te nemen verhelpen we dat probleem; een bijkomend voordeel is dat het weer iets makkelijker is om dit pijltje te animeren; al kan dat voor moderne browsers net zogoed via de de background-position properties.
Nu hebben we een opgemaakt vlak met een 2e vlak met textuele uitleg er over heen, laten we google maps er maar eens bijpakken.
var latLng = new google.maps.LatLng(-10, 150); // we zetten een bepaalde marker neer; hier later meer over
map = new google.maps.Map(document.getElementById('googleMap'), {
zoom: 1, // we kunnen zoomen van lvl 0 (alles tot 21; straatniveau)
center: new google.maps.LatLng(0, 0), // wat is het centrum van de kaart?
mapTypeId: google.maps.MapTypeId.TERRAIN, // welk type map willen we gebruiken
mapTypeControl: false // willen we de gebruiker de controle geven over welk type map ze kunnen kiezen? Nu niet iig
});
var markerImg = "/include/images/koffer.png"; // plaatje gebruikt voor de marker
marker = new google.maps.Marker({
position: latLng, // welke positie heeft de marker
title: 'Sleep de koffer naar uw droombestemming', // wat is de titel van de marker als je er met je muis overheen gaat?
map: map, // welke map gebruikt deze marker; ja we kunnen meerdere maps op 1 pagina zetten
draggable: true, // kan je deze marker slepen?
icon: markerImg // verrek ons eerder gedefinieerde plaatje voor de marker
});
// Update current position info.
geocodePosition(latLng, false); // tja leuk die lat.lon. getallen maar waar zijn we dan?
google.maps.event.addListener(marker, 'dragend', function() {
geocodePosition(marker.get_position(), true); // we hebben de marker versleept, update de positie
});
function geocodePosition(pos, remove) {
if (remove) $("#popup").hide(); // moeten we onze div met begin informatie verwijderen?
if (remove) $.scrollTo("#googleMap", 1000); // om de focus echt op de map te leggen scrollen we daar heen zodat de map goed in beeld is
geocoder.geocode({
latLng: pos
}, function(responses) {
if (responses && responses.length > 0) {
/*
de geocoder zal een set van resultaten terug geven, van zo precies mogelijk tot aan eigenlijk "ja je zit nog op de aarde"
Zodra we dus een set resultaten hebben gaan we bekijken welke we nu exact willen gebruiken
*/
var lat = responses[0].geometry.location.lat(); // we nemen de meest nauwkeurige; de gegevens die wij nodig hebben zitten ook opgeslagen in dit object
var lng = responses[0].geometry.location.lng();
/*
Door het zoekgebied te vergroten krijg je niet alleen resultaten van de straat terug maar van de stad; onderstaande berekening is nogal grof maar werkt behoorlijk goed
*/
var minLng = Math.floor((lat) * 100) / 100;
var maxLng = Math.ceil((lat) * 100) / 100;
var minLat = Math.floor((lng) * 100) / 100;
var maxLat = Math.ceil((lng) * 100) / 100;
// console.log("getting photo's for LAT: " + lat + ", LON: " + lng);
// panoramio
/*
Op panoramio staan mooie foto's; meer op de omgeving dan op mensen gefocussed in tegenstelling tot flickr. Panoramio heeft ook een API om via JSON mooi een set resultaten terug te krijgen; ons zoekgebied is waar de marker gesleept is + de omgeving
*/
$.getJSON("http://www.panoramio.com/map/get_panoramas.php?order=popularity&set=full&" +
"from=0&to=10&minx=" + minLat + "&miny=" + minLng + "&maxx=" + maxLat + "&maxy=" + maxLng + "&size=small&callback=?",
{},
function(data) {
$("#photos").html(""); // reset de div waar de foto's staan/stonden
$(data).each(function(i, item) {
for (var j = 0; j < item.count; j++) {
try {
var img = new Image();
img.src = item.photos[j].photo_file_url; // maak een image object aan voor elke foto die we terugkrijgen, via het javascript image object zorgen we er direct voor dat deze preloaded worden
var currentHTML = $("#photos").html();
$("#photos").html(currentHTML + "
"); // voeg de nieuwe foto toe aan de HTML
} catch (e) { }
} if (item.count == 0) {
$("#photos").html("");
// Tja geen foto's zouden we nu ons zoekscherm moeten uitbreiden misschien? versie 2.0 ;)
}
});
}
);
if (remove) updateMarkerAddress(formatAddress(responses[0]), true); // nieuw adres = laat dit nieuwe adres ook zien
} else {
updateMarkerAddress('Geen locatie gevonden', false); // aah geen adres gevonden; komt eigenlijk alleen maar voor als je in de zee een straat probeert te vinden ;)
}
});
}
function formatAddress(address) {
var wantedTypes = ["locality", "country"]; // welke types van het adres willen we zien? Je kan postcodes terug krijgen, huisadressen, landmarks e.d. maar wij willen nu alleen de locality (stad) en country (land)
var formattedAddress = [];
for (var i = 0; i < address.address_components.length; i++) { var component = address.address_components[i]; // een google address bestaat uit verschillende componenten, daar gaan we es doorheen var type = component.types[0]; if ($.inArray(type, wantedTypes) > -1) {
formattedAddress.push(component.long_name); // ja dir compontent willen we!
}
}
if (formattedAddress.length > 1) { // kunnen we een adres opbouwen dat uit meerdere compontenten bestaat?
return formattedAddress[0] + " - " + formattedAddress[1]; // zo ja dan graag stad - land (volgorde bepaal je in de array
} else {
return formattedAddress[0]; // zo niet dan laten we maar zien wat we hebben (alleen land)
}
}
function updateMarkerAddress(str, isAbleToGo) {
$("#results .text").html(str); // laat maar zien
var fontSize = 17;
if (str.length > 13) fontSize = 15;
if (str.length > 23) fontSize = 13;
if (str.length > 28) fontSize = 11;
/*
we passen de fontsize aan aan de hoeveelheid karakters
*/
$("#results .text").css("fontSize", fontSize);
$("#results a").attr("href", "step2.html?loc=" + escape(str)); // we sturen het adres escapped en al door naar de volgende pagina; mits je klikt natuurlijk
(isAbleToGo) ? $("#results a").show() : $("#results a").hide(); // de knop laten we alleen zien als je er ook daadwerkelijk heen kan, helaas geen droomreis mogelijk naar de bodem van de oceaan
$("#results").show(); // laat de div met resultaten zien.
}
Zo een lap code is het wel, maar het resultaat is er ook naar, een volledig interactieve map met een versleepbare marker die zodra je die ergens neerzet foto's uit de buurt ophaalt, het adres ophaalt, het adres formatteerd naar een gewenst formaat en vervolgens deze gegevens keurig toont.
Google maps in onlinereisbeurs.nl
Bezig
Op het moment ben ik bezig met een google maps project waarbij ik verschillende onderdelen van de nieuwe google maps v3 api gebruik om een interactieve "sleur en pleur" ervaring te bewerkstelligen met een onderwerp waar veel mensen wel iets mee hebben: Vakantie!
Binnenkort online en vlak erna de uitleg, wees gerust ;)
Een kleine verdieping op het gebied van google maps, geocoding, utf8, "using the cloud" en meer. Voor nu hou ik het even op een mooie functie in javascript die soms erg makkelijk kan zijn: (un)escape
Situatie
We hebben een input van een gebruiker die via de url doorgestuurd moet worden naar een andere pagina (GET, iframe)
Als je dit post zal je browser dit automagisch escapen naar
/?send=hello%20world
En als je vervolgens dit in een veld wilt tonen, bv met ID jwz dan zal de %20 meegenomen worden als je het volgende gebruikt:
document.getElementById("jwz").innerHTML = document.location.search.substr(document.location.search.indexOf("=") + 1);
Om hier van af te komen gebruik je de unescape functie van javascript
document.getElementById("jwz").innerHTML = unescape(document.location.search.substr(document.location.search.indexOf("=") + 1));
Maar stel je wilt dit formulier via AJAX posten (ik gebruik nu even jQuery omdat ik dat het meest gebruik, maar (un)escaping is standaard javascript)
$("form").submit(function () {
var escaped = escape($("toSend").val());
// do stuff with the escaped string
});
Nu is de var escaped ook via de URL door te sturen zonder dat je rekening hoeft te houden met of er niet correcte karakters worden gebruikt.
Soms is het handig om te hebben en nu weet je het ;)
jquery.xhtml()
tired of firefox messing up perfectly good xhtml when you put it in a jqeury object? check out the projects page, i might just have the solution for you
Projects @ lo-ga-rie
I made a page for all my projects, well scripts actually, that i made in the past and present that i want to share with the world. Check them out and give me feedback where needed. All script are released under the GPL licence.
Look at them here the first script is also added and i call it fancyButton a simple script that will transform any normal link into an apealing one without altering the plain HTML page so people without scripting enabled will still be able to click the link.
IE …
Vorige post klopt niet helemaal; want natuurlijk bestaat IE nog.
Het is niet
$('iframe').load(function() {
maar
$('iframe').ready(function() {
het load event wordt niet afgevuurd in IE. Andere browsers verwachten wel het load event dus om het helemaal compleet te maken is dit de snippet die je kan gebruiken voor alle browsers:
if ($.browser.mise) {
$('iframe').ready(function() {
onIframeInit();
});
} else {
$('iframe').load(function() {
onIframeInit();
});
}
function onIframeInit() {
// code goes here
}
Met dank aan Pim die een bugje in de code vond :)
Iframes die meegroeien met de content / font-size
Stel je maakt een site met een iframe waarvan de content d.m.v. zoom buttons kan veranderd worden.
Men neme:
- buttons om de font-size aan te passen
- een iframe met content
- een paar regels script
Om initieel de iframe de juiste hoogte te geven kan je het volgende gebruiken
$('iframe').load(function(){
this.style.height =
this.contentWindow.document.body.offsetHeight + 30 + 'px';
});
De 30 extra px is voor webkit browsers.
Als je de font-size knopjes indrukt dan kan je in je click event de volgende code gebruiken om iframe mee te laten scalen:
var iframeDoc = $("iframe").contents();
iframeDoc.find("body").attr("class", $(this).attr("class")); // $(this) is de font-size knop met een class 'normal' of 'big' of 'bigger'
$("#myContent").css("height", iframeDoc.find("body").height() + 30);
Dit werkt goed als je de CSS zo opbouwd dat alle font-sizes em based zijn dan kan je via
body.normal {
font-size: 100%;
}
body.big {
font-size: 120%;
}
body.bigger {
font-size: 140%;
}
De font-size van het document en de iframe in 1x aanpassen.
Van json naar atompub
Men neme een blog bericht met een auteur, een header en een berichtblok. Deze halen we van de server via JSON en krijgen het volgende terug: We nemen even aan dat "ci1" een ContentItem is en dat deze ID ook wordt gebruikt als ID voor de div waarin het item gerenderd wordt. Deze div heeft ook de class "editMode" (als enige op de pagina)
var ci1 = {
"entry" : { "@xmlns" : "http://www.w3.org/2005/Atom" ,
"title" : "[Titel] Lorem Ipsum",
"id" : "generated-id-1",
"updated" : "2005-10-07T17:43:07Z",
"author" : {
"name" : "Logged in user"
},
"content" : { "@type" : "xhtml",
"div" : Lorem ipsum dolor sit amet"
}
}
}
Vervolgens kunnen we de content hiervan heel makkelijk aanpassen door het volgende uit te voeren:
var id = $(".editMode").attr("id");
eval(id).entry.content.div = "Andere content";
Nu willen we deze data opslaan met atompub, wat niet meer is dan een XML met bepaalde elementen, verrek net dé elementen die ik ook in de json heb gebruikt, we kunnen dus de json 1 op 1 omzetten naar XML en dat opsturen naar de server
$.post("/atompub.ashx", { data: json2xml(eval(id))},
function(data){
// ga maar wat leukt doen met de return value
}, "xml");
De gebruikte json2xml komt van deze site
Al met al een geslaagd dagje. Even wat technieken aan elkaar knopen en je hebt een volledig in javascript geschreven client/server oplossing die robuust maar toch veelzijdig is. Door gebruik te maken van json is de data erg makkelijk aan te passen en atompub zorgt voor de omsluiting (het lijkt wel soap ...)
Met het nieuwe CMS heb ik ook weer stappen kunnen zetten. Alle soorten contentitems zijn nu qua frontend klaar en volledig via frontend editing aan te passen. Een begin gemaakt met het maken van een pagina template waarin je via drag & drop de volgorde kan aanpassen van alle gebruikte contentitems. Ja het is heulemaal web 2.0. Van de text editor hebben we nu een jQuery plugin gemaakt waardoor we makkelijk een veld kunnen omzetten in een editor en andersom
$(this).find(".edit_html").tinyfy();$(this).find(".edit_html).tinyfy.untinyfy();
Voor opslag gaan we gebruik maken van Atompub; dat is werk voor volgende week; morgen eerst de verschillende pagina templates maken en bekijken hoe de interactie tussen verschillende contentitems gaat verlopen als er meer dan 10 op een pagina staan.

