Vue.js で googleMap に overlay view を表示するコンポーネントを作る

## google.maps.OverlayViewを継承する用クラス

``` GoogleOverlay.ts
export class GoogleOverlay {
  lat: string | number
  lng: string | number
  drawable: boolean = true
  div: HTMLDivElement | null
  className: string = ''
  google: any
  innerHtml: string
  updateClass: (className: string) => void
  updateHtml: (html: string) => void
  active: () => void
  inactive: () => void
  // extend google.maps.OverlayView
  getProjection: any
  getPanes: any
  setMap: any
  draw: () => void
  remove: () => void

  constructor(
    google: any,
    map: any,
    lat: string | number,
    lng: string | number,
    innerHtml: string,
    className: string = '',
    events: { click?: () => void } = {}
  ) {
    this.lat = lat
    this.lng = lng
    this.div = null
    this.google = google
    this.innerHtml = innerHtml
    this.className = className

    this.remove = () => {
      if (!this.div) return
      ;(this.div.parentNode as Node).removeChild(this.div)
      this.div = null
    }

    this.draw = () => {
      this.remove()
      if (!this.drawable) return
      const projection = this.getProjection()
      if (!projection) return
      const point = projection.fromLatLngToDivPixel(
        new this.google.maps.LatLng(this.lat, this.lng)
      )

      this.div = document.createElement('div')
      this.div.className = this.className
      this.div.innerHTML = this.innerHtml
      this.div.style.left = point.x + 'px'
      this.div.style.top = point.y + 'px'

      const panes = this.getPanes()
      panes.overlayMouseTarget.appendChild(this.div)
      google.maps.event.addDomListener(this.div, 'click', () => {
        events.click && events.click()
      })
    }

    this.updateHtml = (html: string) => {
      this.innerHtml = html
      this.draw()
    }

    this.updateClass = (className: string) => {
      this.className = className
      this.draw()
    }

    this.active = () => {
      this.drawable = true
      this.draw()
    }

    this.inactive = () => {
      this.drawable = false
      this.remove()
    }

    this.setMap(map)
  }
}

```
## GoogleMapにOverlayViewを表示する用のコンポーネント
```GoogleMapOverlayView.vue
<template>
  <div v-show="false">
    <slot />
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import { GoogleOverlay } from '../../lib/googleMap'

@Component
export default class GoogleMapOverlayView extends Vue {
  @Prop() private google!: any
  @Prop() private map!: any
  @Prop() private center!: { lat: number; lng: number }
  @Prop() private active!: boolean
  overlay: GoogleOverlay | null = null
  html: string = ''

  @Watch('active')
  setOrRemove() {
    if (this.active) {
      this.overlay ? this.overlay.active() : this.setOverlay()
      return
    }
    this.overlay && this.overlay.inactive()
  }

  mounted() {
    this.setOrRemove()
  }

  updated() {
    if (!this.overlay || !this.$el) return
    if (this.html === this.$el.innerHTML) return
    this.overlay.updateHtml(this.$el.innerHTML)
    this.html = this.$el.innerHTML
  }

  setOverlay() {
    GoogleOverlay.prototype = new this.google.maps.OverlayView()
    this.overlay = new GoogleOverlay(
      this.google,
      this.map,
      this.center.lat,
      this.center.lng,
      this.html,
      'google-map-overlay-view',
      { click: this.handleClick }
    )
  }

  handleClick() {
    this.$emit('click')
  }
}
</script>
```

## 使用例
```vuejs
<GoogleMap
  :zoom="17"
  :center="center"
  :height="240"
>
  <template slot-scope="scope">
    <GoogleMapOverlayView
      v-for="overlay in overlays"
      :key="overlay.key"
      :center="overlay.latLng"
      :google="scope.google"
      :map="scope.map"
      @click="handleSelectOverview(hotel)"
    >
      <div>overlayView</div>
    </GoogleMapOverlayView>
  </template>
</GoogleMap>
```