8

This is my first VueJS project and I've got vue2-google-maps up and running but I've come across an issue when I attempt to connect the map markers to my site's JSON feed (using the Wordpress REST API), the Lat and Lng values are returning undefined or NaN.

On further investigation (thanks to @QuỳnhNguyễn below) it seems like the Google Maps instance is being run before the data is ready. I have tried watching for the feed to be loaded before initialising the map, but it doesn't seem to work.

The marker locations are pulled in from the WordPress REST API using JSON and exist in an array (locations). The array is present and populated in Vue Dev Tools (51 records), but when checking on mounted, the array is empty. The data is pulled in at the created stage, so I don't know why it wouldn't be ready by the mounted stage.

The code in question is as below...

Template:

<template>
    <gmap-map v-if="feedLoaded" ref="map" :center="center" :zoom="zoom" :map-type-id="mapTypeId" :options="options">
        <gmap-marker 
            :key="index" v-for="(m, index) in locations" 
            :position="{ lat: parseFloat(m.place_latitude), lng: parseFloat(m.place_longitude) }" 
            @click="toggleInfoWindow(m,index)" 
            :icon="mapIconDestination">
        </gmap-marker>
        <gmap-info-window></gmap-info-window>
    </gmap-map>
</template>

Script

<script>
    const axios = require('axios');
    const feedURL = "API_REF";

    export default {
        props: {
            centerRef: {
                type: Object,
                default: function() {
                    return { lat: -20.646378400026226, lng: 116.80669825605469 }
                }
            },
            zoomVal: {
               type: Number,
               default: function() {
                   return 11
               }
            }
        },
        data: function() {
            return {
                feedLoaded: false,
                zoom: this.zoomVal,
                center: this.centerRef,
                options: {
                    mapTypeControl: false,
                    streetViewControl: false,
                },
                mapTypeId: 'styledMapType',
                mapIconDestination: '/images/map-pin_destination.png',
                mapIconActivity: '/images/map-pin_activity.png',
                mapIconAccommodation: '/images/map-pin_accommodation.png',
                mapIconEvent: '/images/map-pin_event.png',
                mapIconBusiness: '/images/map-pin_business.png',
                locations: [],
                markers: []
            }
        },
        created: function() {
            this.getData();
        },
        mounted: function() {
            this.$nextTick(() => {
                this.$refs.karrathaMap.$mapPromise.then((map) => {
                    var styledMapType = new google.maps.StyledMapType(
                        [...MAP_STYLE SETTINGS...]
                    )
                    map.mapTypes.set('styled_map', styledMapType);
                    map.setMapTypeId('styled_map');

                })

            });
        },
        watch: {
            feedLoaded: function() {
                if (this.feedLoaded == true) {
                    console.log(JSON.stringify(this.locations))
                }
            }
        },
        methods: {
            getData() {
                const url = feedURL;
                axios
                    .get(url)
                    .then((response) => {this.locations = response.data;})
                    .then(this.feedLoaded = true)
                    .catch( error => { console.log(error); }
                );
            }
        }
    }
</script>
AlxTheRed
  • 505
  • 6
  • 23
  • 1
    Please try this: `:position="google && new google.maps.LatLng(parseFloat(m.place_latitude), parseFloat(m.place_longitude))"` – Quynh Nguyen Dec 29 '18 at 06:07
  • If it's not work please try to hardcode `lat, lng` via a number – Quynh Nguyen Dec 29 '18 at 06:12
  • @QuỳnhNguyễn Won't that create a new instance of the map for each marker? Also, it works with hard-coded values. – AlxTheRed Dec 29 '18 at 06:39
  • No it's only check `google` is available for create new marker. – Quynh Nguyen Dec 29 '18 at 06:44
  • @QuỳnhNguyễn I've tried your code and it comes back with position: undefined. I cannot use hard-coded values as the data comes from the WordPress REST API (JSON feed). – AlxTheRed Dec 29 '18 at 06:45
  • That's mean `m.place_latitude` undefined. Can you please show your `console.log(locations)`? – Quynh Nguyen Dec 29 '18 at 06:46
  • @QuỳnhNguyễn Some additional info - I now get "Property or method "google" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties." in the console. I can't access "locations" in the console as it's a Vue app. My Vue dev tools show the array exists and is populated. – AlxTheRed Dec 29 '18 at 06:49
  • @QuỳnhNguyễn Here is the content of the array: locations:Array[51] 0:Object 1:Object acf:Object place_include-on-map:true place_latitude:"-22.695754" place_longitude:"118.269081" place_short-description:"An outback playground of natural wonders" id:12 parent:10 title:Object rendered:"Karijini National Park" – AlxTheRed Dec 29 '18 at 06:50
  • Please show your `console.log(JSON.stringify(locations))` – Quynh Nguyen Dec 29 '18 at 06:58
  • I'm pretty sure problem come from your data format. – Quynh Nguyen Dec 29 '18 at 06:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/185903/discussion-between-alxthered-and-qunh-nguyn). – AlxTheRed Dec 29 '18 at 06:59

3 Answers3

4

It turns out the issue was dirty data.

The JSON response was including locations that were not supposed to be included on the map, so it failed every time it came across an entry that didn't include the ACF fields, despite me setting the feed to only return data where include on map was true.

I have solved the issue by handling the data once the feed is loaded and creating a new array (markers) from it using the valid data, then using this, rather than the original (locations) array to place the markers on the map.

Cleaning the data:

watch: {
    feedLoaded: function() {
        if (this.feedLoaded == true) {

            var LocationList = this.locations;

            for (var i = 0; i < LocationList.length; i++) {
                var includeOnMap = LocationList[i].acf['place_include-on-map'];

                if (includeOnMap === true) {
                    var placeName = LocationList[i].title.rendered;
                    var placeDescription = LocationList[i].acf['place_short-description'];
                    var placeLatitude = LocationList[i].acf['place_latitude'];
                    var placeLongitude = LocationList[i].acf['place_longitude'];
                    var placeIcon = this.mapIconDestination;

                    this.markers.push({ name: placeName, lat: placeLatitude, lng: placeLongitude, icon: placeIcon });
                }

            }
        }
    }
}

Then, the gmap component:

<gmap-map ref="karrathaMap" :center="center" :zoom="zoom" :map-type-id="mapTypeId" :options="options">
    <gmap-marker v-if="feedLoaded == true" :key="index" v-for="(m, index) in markers" :position="{ lat: parseFloat(m.lat), lng: parseFloat(m.lng) }" @click="toggleInfoWindow(m,index)" :icon="m.icon"></gmap-marker>
    <gmap-info-window></gmap-info-window>
</gmap-map>

Thank you everybody who contributed to helping me get to the bottom of the issue. I will now spend some time rethinking how the data is structured.

AlxTheRed
  • 505
  • 6
  • 23
3

It appears to be related with data format. According to vue-devtools from provided screenshot your data is returned from WordPress REST API in the following format:

[
  {
    "acf": {
      "place_latitude": "-22.695754",
      "place_longitude": "118.269081",
      "place_short-description": "Karijini National Park"
    },
    "id": 12,
    "parent": 10,
    "title": {
      "rendered": "Karijini National Park"
    }
  },
  ... 
]

Having how locations array is getting initialized (getData method), position property could be passed like this:

<gmap-marker
    :key="index"
    v-for="(m, index) in locations"
    :position="{ lat: parseFloat(m.acf.place_latitude), lng: parseFloat(m.acf.place_longitude) }"
></gmap-marker>

Here is a demo

Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193
  • This is what the console.log for locations returns: `Locations returned: [{"id":10,"title":{"rendered":"Explore"},"parent":0},{"id":12,"title":{"rendered":"Karijini National Park"},"parent":10,"acf":{"place_include-on-map":true,"place_short-description":"An outback playground of natural wonders","place_latitude":"-22.695754","place_longitude":"118.269081"}},{"id":14,"title":{"rendered":"Karratha"},"parent":10,"acf":{"place_include-on-map":true,"place_short-description":"A bustling city in the Pilbara","place_latitude":"-20.735350","place_longitude":"116.845802"}}..]` – AlxTheRed Dec 30 '18 at 03:07
  • 1
    The undefined error was due to some of the entries in the JSON feed not having the necessary feeds. I have explained it in my answer below. Thanks for your help! – AlxTheRed Dec 30 '18 at 05:51
0

vuejs supports v-if directive on elements. I recommend you try as following code snippet.

<template>
  <div id="map" v-if="loaded">
    <gmap-map ref="map" :center="center" :zoom="zoom" :map-type-id="mapTypeId" :options="options">
      <gmap-marker
        :key="index" v-for="(m, index) in locations"
        :position="{ lat: parseFloat(m.place_latitude), lng: parseFloat(m.place_longitude) }"
        @click="toggleInfoWindow(m,index)"
        :icon="mapIconDestination">
      </gmap-marker>
      <gmap-info-window></gmap-info-window>
    </gmap-map>
  </div>
</template>


<script>
  export default {
    data() {
      return {
        loaded: false
      }
    },
    beforeMount: function () {
      const url = feedURL;
      axios
        .get(url)
        .then((response) => {
          this.locations = response.data;
          //activate element after api call response recieved
          this.loaded = true
        })
        .catch(error => {
            console.log(error);
          }
        );
    }
  }

</script>
Saeed Alizadeh
  • 1,417
  • 13
  • 23
  • I already have something like that setup, as you can see in the code (sorry the `v-if` on the component was missing in my example, I will put it back) - I still get `NaN` reutrned for the `LatLng`, and no markers. – AlxTheRed Dec 29 '18 at 15:26
  • 1
    @AlxTheRed Try to add `mounted() {setTimeout(() => this.loaded = true, 2000)}` and remove it from then. If you do this, please tell me if `LatLng` is still a `NaN`. – ulou Dec 29 '18 at 23:56
  • @ulou I increased the timeout to 5 seconds, and I now get the locations returned in the console.log, but the value is still `NaN`. I think my reference to the values is wrong, but nothing I try works... `[{"id":12,"title":{"rendered":"Karijini National Park"},"parent":10,"acf":{"place_include-on-map":true,"place_short-description":"An outback playground of natural wonders","place_latitude":"-22.695754","place_longitude":"118.269081"}}...]` – AlxTheRed Dec 30 '18 at 03:04
  • The undefined error was due to some of the entries in the JSON feed not having the necessary feeds. I have explained it in my answer below. Thank you both for your help! – AlxTheRed Dec 30 '18 at 05:52