I'm trying to implement Google maps in a Jade template. Using KeystoneJS as a CMS, I have a number of "profiles" (basically people with their addresses) that I want to add to a map as markers.
block js
script.
var map;
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(51.0360272, 3.7359072),
zoom: 8
};
map = new google.maps.Map(document.getElementById("map-canvas"),
mapOptions);
}
google.maps.event.addDomListener(window, 'load', initialize);
block content
.container
script(src='https://maps.googleapis.com/maps/api/js?key=<GOOGLE_API_KEY>&sensor=false')
if data.profiles
each profile in data.profiles
#{new google.maps.Marker({position: new google.maps.LatLng(profile.address.geo[1], profile.address.geo[0]), map: map, title: profile.name.full})}
div(id="map-canvas", style="width:100%; height:700px;")
The map shows correctly but when I add the 'each' code block I get an error "Cannot read property 'maps' of undefined".
How can I add a piece of js code that executes on 'each' in Jade?
You're really close, the only problem is that the inside of the variable, i.e. #{this_stuff}
is all executed in jade's context (which won't have the google
object, as this is client-side).
It's a bit tricky because you're dealing with two completely different javascript environments here: server side and client side.
So you need to output server-side variables in your jade, into javascript code that will be executed client-side.
On a related note, you can use Jade variable syntax in script blocks, but can't do other things (like loops).
Firstly, let's clean it up so all your script
tags are in the js
block (which, assuming you're using the example KeystoneJS templates would be down the bottom of the <body>
tag) and get those profiles being generated correctly:
block js
script(src='https://maps.googleapis.com/maps/api/js?key=<GOOGLE_API_KEY>&sensor=false')
script.
var map;
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(51.0360272, 3.7359072),
zoom: 8
};
map = new google.maps.Map(document.getElementById("map-canvas"),
mapOptions);
}
google.maps.event.addDomListener(window, 'load', initialise);
if data.profiles
each profile in data.profiles
script.
new google.maps.Marker({
position: new google.maps.LatLng(#{profile.address.geo[1]}, #{profile.address.geo[0]}),
map: map,
title: "#{profile.name.full}"
});
block content
.container
div(id="map-canvas", style="width:100%; height:700px;")
This is getting closer (and the Jade will generate what you're expecting now), but it won't work (yet) because you're potentially adding the markers to the map before the initialize
function has run.
It's also not escaping the values, so things like a "
character in the name would cause a syntax error.
A more robust way of doing it would be to populate a client-side array then loop over that after the map has been created. We'll also use JSON.stringify
to make sure the values are escaped properly.
block js
script(src='https://maps.googleapis.com/maps/api/js?key=<GOOGLE_API_KEY>&sensor=false')
script.
var map,
profiles = [];
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(51.0360272, 3.7359072),
zoom: 8
};
map = new google.maps.Map(document.getElementById("map-canvas"),
mapOptions);
for (var i = 0; i < profiles.length; i++) {
new google.maps.Marker({
position: new google.maps.LatLng(profiles[i].geo[1], profiles[i].geo[0]),
map: map,
title: profiles[i].name
});
}
}
google.maps.event.addDomListener(window, 'load', initialise);
if data.profiles
each profile in data.profiles
script.
profiles.push({
geo: !{JSON.stringify(profile.address.geo)},
name: !{JSON.stringify(profile.name.full)}
});
block content
.container
div(id="map-canvas", style="width:100%; height:700px;")
Note the change to !{variable} so the JSON isn't escaped
Finally, I'd recommend building up the profiles
array in your route .js
file for the view, rather than doing it in the jade template. It's a lot cleaner, and you won't end up with heaps of <script>
tags in your page.
So your route would look something like this (I am assuming a fair bit to give you the idea, and using underscore to make the code neater than vanilla javascript)
var keystone = require('keystone'),
_ = require('underscore');
exports = module.exports = function(req, res) {
var view = new keystone.View(req, res),
locals = res.locals;
// Load the profiles
view.query('profiles', keystone.list('Profile').model.find());
// Create the array of profile markers
view.on('render', function(next) {
locals.profileMarkers = locals.profiles ? _.map(locals.profiles, function(profile) {
return { geo: profile.address.geo, name: profile.name.full };
}) : [];
next();
});
// Render the view
view.render('profiles');
}
Then in your view template:
block js
script(src='https://maps.googleapis.com/maps/api/js?key=<GOOGLE_API_KEY>&sensor=false')
script.
var profileMarkers = !{JSON.stringify(profileMarkers)},
map;
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(51.0360272, 3.7359072),
zoom: 8
};
map = new google.maps.Map(document.getElementById("map-canvas"),
mapOptions);
_.each(profileMarkers, function(profile) {
new google.maps.Marker({
position: new google.maps.LatLng(profile.geo[1], profile.geo[0]),
map: map,
title: profile.name
});
});
}
google.maps.event.addDomListener(window, 'load', initialise);
block content
.container
div(id="map-canvas", style="width:100%; height:700px;")