Show sourcecode
The following files exists in this folder. Click to view.
webbserverprogrammering/submissions/projekt-matkort-handler/public/js/
map.js
279 lines UTF-8 Windows (CRLF)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
function initMap() {
console.log("initMap started!");
console.log(
"Restaurants array length:",
restaurants ? restaurants.length : "undefined",
restaurants,
);
const centerMap = { lat: 59.2984738, lng: 18.0753175 };
const mapElement = document.getElementById("google-map");
if (!mapElement) {
console.error("Critical: #google-map element not found in DOM!");
return;
}
console.log("Map element found, initializing Google Map...");
const mapOptions = {
center: centerMap,
zoom: 13,
disableDefaultUI: true,
gestureHandling: "greedy",
styles: [
{ elementType: "geometry", stylers: [{ color: "#fce8d5" }] },
{ elementType: "labels.text.stroke", stylers: [{ color: "#fce8d5" }] },
{ elementType: "labels.text.fill", stylers: [{ color: "#e67d4f" }] },
{
featureType: "administrative.locality",
elementType: "labels.text.fill",
stylers: [{ color: "#c96236" }],
},
{
featureType: "poi",
elementType: "labels.text.fill",
stylers: [{ color: "#c96236" }],
},
{
featureType: "poi.park",
elementType: "geometry",
stylers: [{ color: "#f5d3b3" }],
},
{
featureType: "poi.park",
elementType: "labels.text.fill",
stylers: [{ color: "#c96236" }],
},
{
featureType: "road",
elementType: "geometry",
stylers: [{ color: "#ffffff" }],
},
{
featureType: "road",
elementType: "geometry.stroke",
stylers: [{ color: "#fce8d5" }],
},
{
featureType: "road",
elementType: "labels.text.fill",
stylers: [{ color: "#c96236" }],
},
{
featureType: "road.highway",
elementType: "geometry",
stylers: [{ color: "#ffd0a6" }],
},
{
featureType: "road.highway",
elementType: "geometry.stroke",
stylers: [{ color: "#fce8d5" }],
},
{
featureType: "road.highway",
elementType: "labels.text.fill",
stylers: [{ color: "#c96236" }],
},
{
featureType: "transit",
elementType: "geometry",
stylers: [{ color: "#fce8d5" }],
},
{
featureType: "transit.station",
elementType: "labels.text.fill",
stylers: [{ color: "#e67d4f" }],
},
{
featureType: "water",
elementType: "geometry",
stylers: [{ color: "#e67d4f" }],
},
{
featureType: "water",
elementType: "labels.text.fill",
stylers: [{ color: "#ffffff" }],
},
{
featureType: "water",
elementType: "labels.text.stroke",
stylers: [{ color: "#e67d4f" }],
},
],
};
const map = new google.maps.Map(mapElement, mapOptions);
const infoWindow = new google.maps.InfoWindow({
minWidth: 280,
maxWidth: 320,
});
// Close infoWindow automatically when clicking outside it on the map
map.addListener("click", function () {
infoWindow.close();
});
const bounds = new google.maps.LatLngBounds();
// Custom SVG Marker Icon
const mapPinSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="#e53935" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-map-pin"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>`;
const customIcon = {
url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(mapPinSvg),
scaledSize: new google.maps.Size(36, 36),
anchor: new google.maps.Point(18, 36),
};
if (restaurants && restaurants.length > 0) {
for (let i = 0; i < restaurants.length; i++) {
try {
const lat = parseFloat(restaurants[i]["lat"]);
const lng = parseFloat(restaurants[i]["lng"]);
if (isNaN(lat) || isNaN(lng)) {
console.warn("Invalid lat/lng for restaurant:", restaurants[i]);
continue;
}
const restaurant = new google.maps.Marker({
position: { lat: lat, lng: lng },
map: map,
title: restaurants[i]["name"],
icon: customIcon,
});
google.maps.event.addListener(restaurant, "click", function () {
// Show loading state first
infoWindow.setContent(
`<div class="info-window"><p>Laddar statistik...</p></div>`,
);
infoWindow.open(map, restaurant);
// Fetch stats for this restaurant
$.get(
`api/restaurant_ajax.php?action=get_stats&restaurant_id=${restaurants[i]["id"]}`,
).done(function (data) {
let statsHtml = "";
if (data.avg_health !== null && data.avg_happy !== null) {
const healthScore = parseFloat(data.avg_health || 0);
let healthStars = "";
for (let s = 1; s <= 5; s++) {
if (healthScore >= s - 0.5) {
healthStars += `<i data-feather="star" style="color:#facc15; fill:#facc15; width:16px; height:16px;"></i>`;
} else {
healthStars += `<i data-feather="star" style="color:#d1d5db; width:16px; height:16px;"></i>`;
}
}
const happyScore = parseFloat(data.avg_happy || 0);
let faceColor = "#22c55e"; // green default
let faceType = "smile";
if (happyScore < 1.5) {
faceColor = "#ef4444"; // red
faceType = "frown";
} else if (happyScore < 2.5) {
faceColor = "#facc15"; // yellow
faceType = "meh";
}
let happyIcons = "";
for (let s = 1; s <= 3; s++) {
if (happyScore >= s - 0.5) {
happyIcons += `<i data-feather="${faceType}" style="color:${faceColor}; width:16px; height:16px;"></i>`;
} else {
happyIcons += `<i data-feather="${faceType}" style="color:#d1d5db; width:16px; height:16px;"></i>`;
}
}
statsHtml = `
<div style="display:flex; align-items:center; gap:6px;">
<span style="font-size:0.9em; color:#444;">Health score:</span>
<div style="display:flex; gap:2px;">${healthStars}</div>
<strong style="margin-left:4px; font-size:1.0em;">${data.avg_health}/5</strong>
</div>
<div style="display:flex; align-items:center; gap:6px; margin-top:6px;">
<span style="font-size:0.9em; color:#444;">Happiness score:</span>
<div style="display:flex; gap:2px;">${happyIcons}</div>
<strong style="margin-left:4px; font-size:1.0em;">${data.avg_happy}/3</strong>
</div>
`;
} else {
statsHtml = `<p style="margin:0;"><em>Inga betyg ännu.</em></p>`;
}
let bookmarkIcon = "";
if (isLoggedIn) {
const fillClass = data.is_favorite ? "booked" : "unbooked";
bookmarkIcon = `<span style="cursor:pointer; display:inline-block;" onclick="toggleBookmark(${restaurants[i]["id"]}, this)"><i data-feather="bookmark" class="bookmark-icon ${fillClass}"></i></span>`;
}
const infoWindowContent = `
<div class="info-window" style="padding-top:4px;">
<div class="info-header" style="display: flex; gap: 8px; align-items: center; padding-right: 16px;">
<h3 style="margin: 0; font-size: 1.15em; line-height: 1.2;">${restaurants[i]["name"]}</h3>
<div style="display: flex; align-items: center;">${bookmarkIcon}</div>
</div>
<address style="margin: 4px 0 12px; color: #555; font-size: 0.85em; font-style: normal;">
${restaurants[i]["address"]}
</address>
<div class="info-stats" style="background:#f4f4f4; padding:8px 12px; border-radius:4px;">
${statsHtml}
</div>
</div>
`;
infoWindow.setContent(infoWindowContent);
// We have to recall feather.replace() inside the window to render the newly injected SVG
setTimeout(() => {
feather.replace();
}, 10);
});
});
infoWindow.addListener("closeclick", function () {
map.fitBounds(bounds);
});
bounds.extend(new google.maps.LatLng(lat, lng));
} catch (error) {
console.error(`Error processing restaurant index ${i}:`, error);
}
}
map.fitBounds(bounds);
} else {
console.warn("No restaurants data found or array is empty!");
}
}
// Global function to handle bookmarking
window.toggleBookmark = function (restaurantId, element) {
$.ajax({
url: "api/restaurant_ajax.php?action=toggle_favorite",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ restaurant_id: restaurantId }),
success: function (response) {
if (response.error) {
alert(response.error);
return;
}
// Element is our <span> wrapper. We target the <svg> inside it.
const icon = element.querySelector("svg") || element;
// Toggle SVG icon class visual appearance
if (response.status === "added") {
icon.classList.remove("unbooked");
icon.classList.add("booked");
} else if (response.status === "removed") {
icon.classList.remove("booked");
icon.classList.add("unbooked");
}
},
error: function () {
alert("Något gick fel med att spara favoriten.");
},
});
};