bakerkretzmar
11/27/2019 - 3:25 PM

A Vue + Places.js widget

A Vue + Places.js widget

<template>
  <!-- container for places.js -->
  <div>
    <div id="algolia-places" />
  </div>
</template>

<script>
import { createWidgetMixin } from 'vue-instantsearch';
import places from 'places.js';
import { places_config, store } from '../store.js';
let placeName;

const connectPlaces = (
  renderFn,
  unmountFn
) => (/* widgetParams, if this would become reusable */) => {
  return {
    init() {
      renderFn({ nearMe: undefined }, true);
    },
    render({ state }) {
      renderFn({ nearMe: state.aroundLatLngViaIP === 'true' }, false);
    },
    dispose() {
      unmountFn();
    },
    getWidgetState(uiState, { searchParameters }) {
      return {
        ...uiState,
        latlng: searchParameters.aroundLatLng,
        nearme: searchParameters.aroundLatLngViaIP,
        place: placeName,
      };
    },
    getWidgetSearchParameters(searchParameters, { uiState }) {
      placeName = uiState.place;
      return searchParameters
        .setQueryParameter('aroundLatLng', uiState.latlng)
        .setQueryParameter('aroundLatLngViaIP', uiState.nearme);
    },
  };
};

export default {
  mixins: [createWidgetMixin({ connector: connectPlaces })],
  data() {
    return {
      instance: null,
    };
  },
  mounted() {
    // make sure Vue does not know about the input
    // this way it can properly unmount
    this.input = document.createElement('input');
    this.input.placeholder = 'e.g. town, city, street';
    if (placeName) {
      setTimeout(() => {
        this.instance.setVal(placeName);
      }, 0);
    }
    this.$el.appendChild(this.input);
    this.addNearMeListener();

    this.instance = places({
      appId: places_config.APP_ID,
      apiKey: places_config.API_KEY,
      container: this.input,
    }).configure({
      countries: ['GB', 'IE'],
    });

    this.onChange = e => {
      placeName = e.suggestion.value;
      this.instantSearchInstance.helper
        .setQueryParameter(
          'aroundLatLng',
          e.suggestion.latlng.lat + ',' + e.suggestion.latlng.lng
        )
        .setQueryParameter('aroundLatLngViaIP', undefined);
      this.instantSearchInstance.helper.search();
    };

    this.onClear = () => {
      store.setPlacesDialogOpened(false);
      placeName = null;
      this.instantSearchInstance.helper
        .setQueryParameter('aroundLatLng', undefined)
        .setQueryParameter('aroundLatLngViaIP', undefined);
      this.instantSearchInstance.helper.search();
    };

    this.onShown = () => {
      store.setPlacesDialogOpened(true);
    }

    this.onClosed = () => {
      store.setPlacesDialogOpened(false);
    }

    this.instance.on('change', this.onChange);
    this.instance.on('clear', this.onClear);
    this.instance.autocomplete.on('autocomplete:shown', this.onShown);
    this.instance.autocomplete.on('autocomplete:closed', this.onClosed);
  },
  beforeDestroy() {
    this.removeNearMeListener();
    // if you had any "this.instance.on", also call "off" here
    this.instance.off('change', this.onChange);
    this.instance.off('clear', this.onClear);
    this.instance.autocomplete.off('autocomplete:shown', this.onShown);
    this.instance.autocomplete.off('autocomplete:closed', this.onClosed);
    this.instance.destroy();
  },
  methods: {
    addNearMeListener() {
      this.onInputChange = () => {
        if (this.state.nearMe && this.input.value !== 'Near Me') {
          placeName = null;
          this.instance.setVal('');
          this.instantSearchInstance.helper
            .setQueryParameter('aroundLatLng', undefined)
            .setQueryParameter('aroundLatLngViaIP', undefined);
          this.instantSearchInstance.helper.search();
          this.removeNearMeListener();
        }
      };
      this.input.addEventListener('input', this.onInputChange);
    },
    removeNearMeListener() {
      if (this.onInputChange) {
        this.input.removeEventListener('input', this.onInputChange);
        this.onInputChange = null;
      }
    },
  },
};
</script>

<style lang="scss" scoped>
/deep/ .ap-dropdown-menu.ap-with-places .ap-suggestion .ap-suggestion-icon {
  background-size: 14px 21px;
  background-repeat: no-repeat;
  background-image: url(~@/assets/location-pin.png);
  display: inline-block;
  width: 14px;
  height: 21px;

  svg {
    display: none;
  }
}

/deep/ .algolia-places .ap-icon-pin {
  display: none;
}

/deep/ .ap-input {
  background-size: 18px 27px;
  background-repeat: no-repeat;
  background-image: url(~@/assets/location-pin.png);
  background-position: 17px 11px;
  padding: 15px 15px 15px 51px !important;
}

// mobile header
@media screen and (max-width: $tablet) {
  /deep/ .ap-dropdown-menu.ap-with-places {
    width: calc(100% + 44px);
    top: 60px !important;
    left: -22px !important;
  }

  /deep/ .algolia-places .ap-icon-clear {
    position: absolute !important;
    top: 15px;
    right: 6px !important;
    width: 21px;
    height: 21px;
    border: none;
    padding: 0;
    cursor: pointer;

    svg {
      width: 100%;
      height: 100%;
      fill: #fff;
      @include theme-value(background-color, brand-color-one);
      border-radius: 100%;
      padding: 4px
    }
  }
}
</style>