简体   繁体   中英

VueJS: How to use Google places API with multiple refs

I'm using the Google places API for my work and everything works fine. However, I have a unique scenario where I need to have more than one delivery address, hence I'll need to make Google place API works that way.

Here is my current code:

<template>
  <div>
    <div v-for="(search, index) in searchInput" :key="index">
      <input type="text" ref="search" v-model="search.city">
    </div>
    <button type="button" @click="addMore">Add more</button>
  </div>
</template>

<script>
export default {
  name: "App",
  data: () => ({
    location: null,
    searchInput: [
      {
        city: ""
      }
    ]
  }),
  mounted() {
    window.checkAndAttachMapScript(this.initLocationSearch);
  },
  methods: {
    initLocationSearch() {
      let autocomplete = new window.google.maps.places.Autocomplete(
        this.$refs.search
      );
      autocomplete.addListener("place_changed", function() {
        let place = autocomplete.getPlace();
        if (place && place.address_components) {
          console.log(place.address_components);
        }
      });
    },
    addMore() {
      this.searchInput.push({ city: "" });
    }
  }
};
</script>

I get this error from the API: InvalidValueError: not an instance of HTMLInputElement and b.ownerDocument is undefined

How do I make the refs unique for the individual item that I add and then pass it to the initLocationSearch method? Please, any help would be appreciated.

Her is a Codesandbox demo

Given the details you provided for the complete scenario, here is how I would do it.

I would use a simple counter to keep track of the number of inputs and set its initial value to 1 and also use it to display the inputs.

<div v-for="(n, index) in this.counter" :key="index">
  <input type="text" ref="search">
</div>

Then bind the Autocomplete widget to the last added input, based on the counter.

autocomplete = new window.google.maps.places.Autocomplete(
  this.$refs.search[this.counter - 1]
);

And use your addMore() method to increment the counter.

addMore() {
  this.counter++;
}

Use updated() for whenever a new input is added to the DOM to trigger the initLocationSearch() method.

updated() {
  this.initLocationSearch();
},

On place_changed event, update the list of selected cities based on the various input values.

this.selectedCities = this.$refs["search"].map(item => {
  return { city: item.value };
});

This way you always have an up to date list of cities, whatever input is changed. Of course you would need to handle emptying an input and probably other edge cases.

Complete code:

<template>
  <div>
    <div v-for="(n, index) in this.counter" :key="index">
      <input type="text" ref="search">
    </div>
    <button type="button" @click="addMore">Add more</button>
    <hr>Selected cities:
    <ul>
      <li v-for="(item, index) in this.selectedCities" :key="index">{{ item.city }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "App",
  data: () => ({
    selectedCities: [],
    counter: 1
  }),
  mounted() {
    window.checkAndAttachMapScript(this.initLocationSearch);
  },
  updated() {
    this.initLocationSearch();
  },
  methods: {
    setSelectedCities() {
      this.selectedCities = this.$refs["search"].map(item => {
        return { city: item.value };
      });
    },
    initLocationSearch() {
      let self = this,
        autocomplete = new window.google.maps.places.Autocomplete(
          this.$refs.search[this.counter - 1],
          {
            fields: ["address_components"] // Reduce API costs by specifying fields
          }
        );

      autocomplete.addListener("place_changed", function() {
        let place = autocomplete.getPlace();
        if (place && place.address_components) {
          console.log(place);
          self.setSelectedCities();
        }
      });
    },
    addMore() {
      this.counter++;
    }
  }
};
</script>

CodeSandbox demo

You can dynamically bind to ref. Something like :ref="'search' + index"

<template>
  <div>
    <div v-for="(search, index) in searchInput" :key="index">
      <input type="text" :ref="'search' + index" v-model="search.city">
    </div>
    <button type="button" @click="addMore">Add more</button>
  </div>
</template>

Then in addMore, before creating a pushing on a new object to searchInput, grab the last index in searchInput. You can then get the newly created ref and pass that to initLocationSearch .

    initLocationSearch(inputRef) {
      let autocomplete = new window.google.maps.places.Autocomplete(
        inputRef
      );
      autocomplete.addListener("place_changed", function() {
        let place = autocomplete.getPlace();
        if (place && place.address_components) {
          console.log(place.address_components);
        }
      });
    },
    addMore() {
      const lastInputRef = this.$refs['search' + this.searchInput.length - 1];
      this.initLocationSearch(lastInputRef);
      this.searchInput.push({ city: "" });
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM