chrisgrim's avatar

Checking for google maps places json data using if statement

Hi, I am using google auto complete for my location entry form. I am able to get the data back from google but I am having an issue when I am trying to enter it into my data. Here is my data

data() {
            return {
                location:this.initializeEventObject(),
                center: { lat: 45.508, lng: -73.587 },
                markers: [],
                places: [],
                currentPlace: null,
            }
        },

        methods: {
            initializeEventObject() {
                return {
                    StreetAddress: '',
                    City: '',
                    State: '',
                    Country: '',
                    Zipcode: '',
                    HiddenLocation: '',
                    Lat: '',
                    Long: '',
                }
            },

and after the user enters their info I call

setPlace(place) {   
                    this.location = {
                        StreetAddress: place.address_components[0].long_name + ' ' + place.address_components[1].long_name,
                        City: place.address_components[3].long_name,
                        State: place.address_components[4].long_name,
                        Country: place.address_components[5].long_name,
                        Zipcode: place.address_components[6].long_name,
                        HiddenLocation: '',
                        Lat: place.geometry.location.lat(),
                        Long: place.geometry.location.lng(),
                    }
            },

This works great except if the user doesn't enter a street, instead only adds a city then I get this error

app.js:41532 [Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'long_name' of undefined"

found in

I tried to do this

if (place.address_components[0].long_name) {
                    this.location = {
                        StreetAddress: place.address_components[0].long_name + ' ' + place.address_components[1].long_name,
                        City: place.address_components[3].long_name,
                        State: place.address_components[4].long_name,
                        Country: place.address_components[5].long_name,
                        Zipcode: place.address_components[6].long_name,
                        HiddenLocation: '',
                        Lat: place.geometry.location.lat(),
                        Long: place.geometry.location.lng(),
                    }
                };

but I am still getting the error. I also have another issue that sometimes place.address_components[5] is Los Angeles or sometimes its Los Angeles County. Is there a better way to uniform the response instead of just using [number]?

Thanks!

0 likes
5 replies
skauk's avatar

In the solution where you tried to work around the problem, you're checking just one element but inside the condition's body you're referencing 5 other elements. An easy fix would be to add a fallback to each of them. For instance:

City: place.address_components[3].long_name || ''

This says either give me that value I'm after or if it's not there replace it with an empty string. This should suppress the error you are getting. However, if you want to get it right I recommend you to take a look at this example. In there you'll see how to iterate over address_components checking for types of each entry and getting exactly what you need from there, be it city name or county name, etc. so you can solve that other problem you mentioned in the end.

chrisgrim's avatar

HI @skauk Thanks for the response! I looked into the example and that took me down a whole different path. I saw immediately that using vue2-google-maps - npm was too restrictive to use the code form the example. For the example I need to call

 <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"
        async defer></script>

while Vue2-google-maps has me use the api key in the app.js file. So I tried using just the example you provided and am having trouble even getting it going. When I put all the code in I am getting this error

initautocomplete is not a function invalidvalueerror

here is my vue file

<template>
    <div class="create-content">
        <div class="create-title">
            <h2> Location</h2>
        </div>
        <div class="create-form locations">
            <div class="location-section-1">
                <div class="create-field">

                    <label>Is The Location Hidden?</label>
                    <multiselect 
                    v-model.trim="location.HiddenLocation" 
                    deselect-label="Can't remove this value" 
                    placeholder="Is the location is being withheld?" 
                    :options="locationOptions" 
                    open-direction="bottom"
                    :searchable="false" 
                    :allow-empty="false"
                    :class="{ active: hiddenActive}"
                    @click="hiddenActive = true"
                    @blur="hiddenActive = false"
                    />

                </div>
                <div class="create-field" v-if="showHiddenLocation">

                    <label> Please enter how participants will be notified </label>
                    <textarea 
                    v-model.trim="location.StreetAddress" 
                    class="create-input area" 
                    rows="8" 
                    :class="{ active: notifiedActive,'error': $v.location.StreetAddress.$error }"
                    placeholder=" " 
                    required 
                    autofocus
                    @click="notifiedActive = true"
                    @blur="notifiedActive = false"
                    @input="$v.location.StreetAddress.$touch"
                    />
                    <div v-if="$v.location.StreetAddress.$error" class="validation-error">
                        <p class="error" v-if="!$v.location.StreetAddress.required">The Street Address is required</p>
                    </div>
                    
                </div>
            </div>
            <div class="location-section-2">
                <div class="create-field">

                    <label v-if="showHiddenLocation"> City  </label>
                    <label v-else="specificLocation"> Street Address </label>
                        
                </div>
            </div>
            <div id="locationField">
              <input id="autocomplete"
                     placeholder="Enter your address"
                     onFocus="geolocate()"
                     type="text"/>
            </div>
            <div class="location-section-3">

                    <div v-if="currentPlace">
                        
                    </div>
                </div>
            </div>
            <div class="location-section-4">
                <div class="create-field">
                    
                    <label>Regions</label>
                    <multiselect 
                    v-model.trim="selectedRegions" 
                    :options="this.regions ? regionOptions : []" 
                    :multiple="true" 
                    placeholder="Select Region. You may select more than one." 
                    track-by="id"
                    open-direction="bottom"
                    required 
                    label="region"
                    @input="$v.selectedRegions.$touch"
                    :class="{ active: hiddenActive,'error': $v.selectedRegions.$error}"
                    @click="hiddenActive = true"
                    @blur="hiddenActive = false"
                    />

                    <div v-if="$v.selectedRegions.$error" class="validation-error">
                        <p class="error" v-if="!$v.selectedRegions.required">Please select at least one Region</p>
                    </div>
                </div>
            </div>
                
            <div class="create-button">
                <button :disabled="$v.$invalid" @click.prevent="submitLocation()" class="create"> Next </button>
            </div>

        </div>
    </div>
</template>

<script>
    import Multiselect from 'vue-multiselect';
    import _ from 'lodash';
    import { required, minLength } from 'vuelidate/lib/validators';

    var placeSearch, autocomplete;

    var componentForm = {
      street_number: 'short_name',
      route: 'long_name',
      locality: 'long_name',
      administrative_area_level_1: 'short_name',
      country: 'long_name',
      postal_code: 'short_name'
    };

    export default {
        props: {
            event: { type:Object },
            regions: { type:Array },
            pivots: { type:Array },
        },

        components: { 
            Multiselect
        },

        computed: {
            showHiddenLocation: function() {
                if( this.location.HiddenLocation === 'Yes' ) { 
                    return '1'; } else { return ''}
            },
        },

        data() {
            return {
                location:this.initializeEventObject(),
                hiddenLocation: 'false',
                regionOptions:this.regions,
                selectedRegions: this.pivots,
                locationOptions: [ 'Yes', 'No' ],
                eventUrl:_.has(this.event, 'slug') ? `/create-event/${this.event.slug}` : null,
                lat: '',
                lon: '',
                name: '',
                locationActive: false,
                notifiedActive: false,
                hiddenActive: false,
                center: { lat: 45.508, lng: -73.587 },
                markers: [],
                places: [],
                currentPlace: null,
                address: ''
            }
        },

        methods: {
            initializeEventObject() {
                return {
                    StreetAddress: '',
                    City: '',
                    State: '',
                    Country: '',
                    Zipcode: '',
                    HiddenLocation: '',
                    Lat: '',
                    Long: '',
                }
            },

            updateEventFields(input) {
                if ((input !== null) && (typeof input === "object") && (input.id !== null)) {
                    this.location = _.pick(input, _.intersection( _.keys(this.location), _.keys(input) ));
                }
            },

            async submitLocation() {

                if (this.$v.$invalid) { return false; }

                axios.get(url)
                .then(response => {
                    this.location.Lat = response.data[0].lat;
                    this.location.Long = response.data[0].lon;
                    let data = this.location;
                    data.Country = this.selectedCountry;
                    data.Region = this.selectedRegions.map(a => a.id);


                    axios.patch(`${this.eventUrl}/location`, data)
                    .then(response => {
                        console.log(response)
                    })
                    .catch(errorResponse => {
                        // show if there are server side validation errors
                        this.validationErrors = errorResponse.response.data.errors
                        console.log(errorResponse.response.data.errors)

                    });
                })
                .catch(errorResponse => {
                // show if there are server side validation errors
                });

            },

            addTag (newTag) {
                const tag = {
                    location: newTag,
                    id: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
                }
                this.regionOptions.push(tag)
                this.selectedRegions.push(tag)
            },

            toggle() {
                this.hiddenLocation = !this.hiddenLocation;
            },

            toggleActive(e) {
                this.flags[e.currentTarget.name] = !this.flags[e.currentTarget.name];
            },

            initAutocomplete() {
              // Create the autocomplete object, restricting the search predictions to
              // geographical location types.
              autocomplete = new google.maps.places.Autocomplete(
                  document.getElementById('autocomplete'), {types: ['geocode']});

              // Avoid paying for data that you don't need by restricting the set of
              // place fields that are returned to just the address components.
              autocomplete.setFields(['address_component']);

              // When the user selects an address from the drop-down, populate the
              // address fields in the form.
              autocomplete.addListener('place_changed', fillInAddress);
            },

            fillInAddress() {
              // Get the place details from the autocomplete object.
              var place = autocomplete.getPlace();

              for (var component in componentForm) {
                document.getElementById(component).value = '';
                document.getElementById(component).disabled = false;
              }

              // Get each component of the address from the place details,
              // and then fill-in the corresponding field on the form.
              for (var i = 0; i < place.address_components.length; i++) {
                var addressType = place.address_components[i].types[0];
                if (componentForm[addressType]) {
                  var val = place.address_components[i][componentForm[addressType]];
                  document.getElementById(addressType).value = val;
                }
              }
            },

            // Bias the autocomplete object to the user's geographical location,
            // as supplied by the browser's 'navigator.geolocation' object.
            geolocate() {
              if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(function(position) {
                  var geolocation = {
                    lat: position.coords.latitude,
                    lng: position.coords.longitude
                  };
                  var circle = new google.maps.Circle(
                      {center: geolocation, radius: position.coords.accuracy});
                  autocomplete.setBounds(circle.getBounds());
                });
              }
            },
                        

            
        },
    

        mounted() {
            this.updateEventFields(this.event.location);

            if (this.event.location.Country) {
                this.selectedCountry = this.event.location.Country;
            }

            if (this.event.location.HiddenLocation == 1) {
                this.hiddenLocation = 'true';
            }
            if (!this.event.location.HiddenLocation == 1) {
                this.location.HiddenLocation = 'No';
            }




        },

        validations: {
            selectedRegions: {
                required
            },
            location: {
                StreetAddress: {
                   required
                },
                City: {
                   required
                },
                State: {
                    required,
                },
                Zipcode: {
                    required
                },
            },
        },
            
};



</script>

Thanks!

skauk's avatar
skauk
Best Answer
Level 8

Notice in the URL for Google Maps JS SDK there's a part that goes callback=initAutocomplete as well as the example code itself has a function with this name. Well, your error says that this function doesn't exist in your code. You don't have to completely ditch the component that you were using before as you get full place object in setPlace() method it seems. What is relevant is this:

var componentForm = {
  street_number: 'short_name',
  route: 'long_name',
  locality: 'long_name',
  administrative_area_level_1: 'short_name',
  country: 'long_name',
  postal_code: 'short_name'
};
for (var i = 0; i < place.address_components.length; i++) {
    var addressType = place.address_components[i].types[0];
    if (componentForm[addressType]) {
      var val = place.address_components[i][componentForm[addressType]];
      document.getElementById(addressType).value = val;
    }
  }

You can substitute componentForm with your location object and insted of putting values in the form, populate the object itself.

chrisgrim's avatar

Hi @skauk

If you look at my code you can see

 initAutocomplete() {
              // Create the autocomplete object, restricting the search predictions to
              // geographical location types.
              autocomplete = new google.maps.places.Autocomplete(
                  document.getElementById('autocomplete'), {types: ['geocode']});

              // Avoid paying for data that you don't need by restricting the set of
              // place fields that are returned to just the address components.
              autocomplete.setFields(['address_component']);

              // When the user selects an address from the drop-down, populate the
              // address fields in the form.
              autocomplete.addListener('place_changed', fillInAddress);
            },

Isnt that function in my code?

I will probably do as you say and go back to the compontent I was using, but I was just seeing if there was a way to save money with google searches by only getting the specific data I need.

skauk's avatar

It needs to be in the global scope i.e. window.initAutocomplete = function() {}. Yours is in the scope of your component where Google Maps JS SDK can't see it.

Please or to participate in this conversation.