Reputation: 137
I am passing a slot from a parent to multiple children. In one of the child components, the div-id is not found, possibly because the slot is not being rendered fast enough and the id is not found when the component is mounted.
When spinning the application up, this results in
Map container element not found
Thrown because the map-google id was not found.
The child component:
<template>
<MapTileComponent
title="Google Maps"
@update:address="onAddressChanged"
@update:route="onRouteChanged"
@update:coordinates="onCoordinatesChanged"
>
<template #info>
<title>Add Map</title>
</template>
<template #map2>
<div id="map-google"></div>
</template>
</MapTileComponent>
</template>
<script setup lang="ts">
import axios from "axios";
import { Loader } from "google-maps";
const apiKey = "myKey";
const loader = new Loader(apiKey, {
version: "weekly",
libraries: ["marker"], // Load the marker library
});
const googleMap = ref<google.maps.Map | null>(null);
// Variables for map and marker
let map: google.maps.Map;
let marker: google.maps.Marker;
onMounted(() => {
// Ensure the DOM is fully updated, including slots, before accessing the element
nextTick().then(() => {
const mapElement = document.getElementById("map-google");
if (mapElement) {
loader
.load()
.then((google) => {
const center = new google.maps.LatLng(51.147578, 4.436328);
// Initialize the map
map = new google.maps.Map(mapElement, {
center: center,
zoom: 15,
});
// Assign map to the reactive variable
googleMap.value = map;
// Initialize the marker
marker = new google.maps.Marker({
position: center,
map: map,
title: "Your Location",
});
})
.catch((error) => {
console.error("Error loading Google Maps API:", error);
});
} else {
console.error("Map container element not found.");
}
});
});
const onAddressChanged = (address: string) => {
fetchCoordsByAddress(address);
};
const fetchCoordsByAddress = async (address: string) => {
try {
axios
.get(
`https://maps.googleapis.com/maps/api/geocode/json?address=${address}&key=${apiKey}`
)
.then((response) => {
if (response.data.status === "OK") {
let lat = response.data.results[0].geometry.location.lat;
let lng = response.data.results[0].geometry.location.lng;
const location = new google.maps.LatLng(lat, lng);
console.log(location);
// Update map center and marker position
map.setCenter(location);
marker.setPosition(location);
}
});
} catch (error) {
console.error(error);
}
};
const onCoordinatesChanged = (coordinates: Number[]) => {
fetCoordsByCoordindates(coordinates);
};
const fetCoordsByCoordindates = async (coordinates: Number[]) => {
try {
axios
.get(
`https://maps.googleapis.com/maps/api/geocode/json?latlng=${
(coordinates[0], coordinates[1])
}&key=${apiKey}`
)
.then((response) => {
if (response.data.status === "OK") {
let lat = response.data.results[0].geometry.location.lat;
let lng = response.data.results[0].geometry.location.lng;
const location = new google.maps.LatLng(lat, lng);
map.setCenter(location);
marker.setPosition(location);
}
});
} catch (error) {
console.error(error);
}
};
const onRouteChanged = () => {};
</script>
<style>
#map {
width: 100%;
height: 100%;
min-height: 380px;
padding: 0;
margin: 0;
}
</style>
MapGrid component:
<template>
<v-row>
<v-col>
<slot name="map1"></slot>
</v-col>
<v-col>
<slot name="map2"></slot>
</v-col>
</v-row>
<v-row>
<v-col>
<slot name="map3"></slot>
</v-col>
<v-col>
<slot name="map4"></slot>
</v-col>
</v-row>
</template>
Consisting of the MapTileComponents:
<template>
<v-container>
<!-- Menu container with transitions -->
<div class="ma-4 menu-container">
<v-btn
style="position: relative; z-index: 2"
@click="toggleMenu()"
id="menuButton"
>{{ title }}</v-btn
>
<v-slide-x-transition hide-on-leave>
<div v-if="showMenu" key="main-menu" class="menu">
<v-list>
<v-list-item
v-for="(menuItem, index) in subMenus"
:key="index"
@click="toggleSubMenu(menuItem)"
append-icon="mdi-chevron-right"
>
<v-list-item-title>{{
menuItem.title
}}</v-list-item-title></v-list-item
>
</v-list>
</div>
</v-slide-x-transition>
<v-slide-x-reverse-transition hide-on-leave>
<div v-if="showSubmenu" key="submenu" class="menu">
<v-list>
<v-list-item
@click="toggleSubMenu(undefined)"
append-icon="mdi-chevron-left"
>
<v-list-item-title>{{ currentSubMenu?.title }}</v-list-item-title>
</v-list-item>
<v-list-item
v-for="(item, index) in currentSubMenu?.items"
:key="index"
>
<v-list-item-subtitle v-if="item.subtitle">{{
item.subtitle
}}</v-list-item-subtitle>
<component :is="item.component()"></component>
</v-list-item>
</v-list>
</div>
</v-slide-x-reverse-transition>
</div>
<slot name="map"></slot>
</v-container>
</template>
<script setup lang="ts">
import { debounce } from "lodash";
import { VNode } from "vue";
import { VBtn, VSwitch, VTextField } from "vuetify/components";
interface ISubMenu {
title: string;
items: Array<{
subtitle?: string;
component: () => VNode;
}>;
}
const props = defineProps({
title: String,
});
const emit = defineEmits<{
(e: "update:address", value: string): void;
(e: "update:coordinates", value: Number[]): void;
(e: "update:route", value: { start: string; end: string }): void;
(e: "update:environment", value: { key: string; flag: boolean }): void;
}>();
const showMenu = ref(false);
const showSubmenu = ref(false);
const currentSubMenu = ref<ISubMenu>();
const routeStart = ref("Something");
const routeEnd = ref("Something");
const subMenus = ref([
{
title: "Geocode",
items: [
{
subtitle: "Forward",
component: () =>
h(VTextField, {
placeholder: "Enter an address",
variant: "outlined",
"onUpdate:modelValue": (val: string) => {
debounceAddress(val);
},
}),
},
{
subtitle: "Reverse",
component: () =>
h(VTextField, {
placeholder: "Enter coordinates",
variant: "outlined",
"onUpdate:modelValue": (val: string) => {
debounceCoordinates(val);
},
}),
},
],
},
{
title: "Routing",
items: [
{
component: () =>
h(VTextField, {
placeholder: "Enter a start",
modelValue: routeStart,
variant: "outlined",
}),
},
{
component: () =>
h(VTextField, {
placeholder: "Enter a destination",
modelValue: routeEnd,
variant: "outlined",
}),
},
{
component: () =>
h(VBtn, {
text: "Search",
style: "margin-bottom: 10px",
onClick: () => {
toggleMenu()
emit("update:route", {
start: routeStart.value,
end: routeEnd.value,
});
},
}),
},
],
},
{
title: "Environment",
items: [
{
component: () =>
h(VSwitch, {
label: "Air quality",
key: "AirQuality",
"onUpdate:modelValue": (val: any) =>
emit("update:environment", { key: "AirQuality", flag: val }),
}),
},
],
},
]);
// debounce
const debounceAddress = debounce((value: string) => {
emit("update:address", value);
}, 1000);
const debounceCoordinates = debounce((value: string) => {
emit("update:coordinates", getCoordinates(value));
}, 1000);
// misc
const getCoordinates = (coordVal: string): Number[] => {
try {
const [x, y] = coordVal.split(",");
return [parseFloat(x.trim()), parseFloat(y.trim())];
} catch (error) {
throw error;
}
};
// menu
const toggleMenu = () => {
if (showSubmenu.value) {
showMenu.value = false;
showSubmenu.value = false;
} else {
showMenu.value = !showMenu.value;
}
};
const toggleSubMenu = (subMenu: ISubMenu | undefined) => {
showMenu.value = !showMenu.value;
showSubmenu.value = !showSubmenu.value;
currentSubMenu.value = subMenu;
};
</script>
<style scoped>
.v-container {
height: 100%;
margin: 0;
padding: 0;
}
.v-card {
height: 100%;
min-height: 100%;
}
.menu-container {
position: absolute;
max-width: 25%;
z-index: 2;
}
.menu {
position: relative;
width: 175%;
z-index: 2;
top: 4px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
}
.menu .v-list {
padding-top: 0px;
border-radius: 6px;
}
</style>
Upvotes: 0
Views: 34