In this article, we will learn the steps to make a VueJS application that uses some features of Google Map such as searching addresses, displaying a list of 1 store in the area on the map, Show store details on a map.
Frontend will use VueJS, and the API will use Ruby on Rails.
Start initializing the Rails project:
mkdir map_shop && cd map_shop
rails new backend --api -d mysql
Because the Rails app and the VueJs app will be hosted on two separate servers, the first thing we need to handle is CORS (Cross-Origin Resource Sharing). In Rails, we find the following file config / initialzers / cors.rb and edit the file as follows:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Next is to add the gem gem “rack-cors”
and runbundle install
Create a Shop model containing basic information such as name, longitude, and latitude
rails generate scaffold Shops name:string address:string 'latitude:decimal{11,8}' 'longitude:decimal{11,8}'
#create database
rake db:create
rake db:migrate
Thus, we have the Shop model, and our generated routes.rb file will resourse
Rails.application.routes.draw do
resources :shops
end
In the shop controller, we define the basic action index
def index
@shops = Shop.all
render json: @shops
end
Check api has worked, we create a shop data
Shop.create!(name: '', latitude: '21.0133334', longitude: '105.7778877', address: 'Ho Guom')
Run rails sto start the server and then click on the link localhost:3000/shops
, it will display all the shops. So the API is OK.
We will create a separate frontend folder to contain the VueJS code, using the tool vue-cli
to create it.
165/5000
cd map_shop
# if you have not installed vue-cli yet
npm install -g vue-cli
# initialize project frontend
vue init webpack frontend
# run frontend
cd frontend && npm run dev
Visit the link localhost:8080
to enter Vue’s original interface
Install the router if you didn’t add it when initializing
npm install vue-router
Here, we need to solve the problem of searching for an address in Google Map, so it is necessary to activate the service Maps JavaScript API
and Places API
in the list of services that Google provides, so that we can use Google Map to solve the above problems.
Add a library vue2-google-maps
to use google map in VueJS
npm install vue2-google-maps
In the src / main.js file on the frontend, we add config:
import * as VueGoogleMaps from "vue2-google-maps"
Vue.use(VueGoogleMaps, {
load: {
key: GOOGLE-API-KEY,
libraries: "places"
}
});
Thus, finished integrating google map into VueJS.
Create the shop creation API in the backend:
# API create shop: POST /shops
def create
shop = Shop.new shop_params
if shop.save
render json: {success: true}
else
render json: {errors: shop.errors.full_messages}, status: :unprocessable_entity
end
end
private
def shop_params
params.permit :name, :latitude, :longitude, :address
end
Form creates a shop inside Vue, creates file /src/CreateShop.vue
<template>
<div class="--shop">
<selected-address-google-map-modal
:selectedAddress="selectedAddress"
@onSelectedAddress="onSelectedAddress"
/>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<input type="text" v-model="name" name="name" placeholder="Name"
class="form-control" :class="{ 'is-invalid': submitted && !name }" />
<div v-show="submitted && !name" class="invalid-feedback">Name is required</div>
</div>
<div class="form-group form-group--address">
<input type="text" v-bind:value="selectedPlace.address"
placeholder="Please select a place"
v-on:click="openSelectedAddressModal"
class="form-control"
>
</div>
<div class="form-group float-right">
<button class="btn btn-primary">Create</button>
</div>
</form>
</div>
</template>
<script>
import axios from 'axios'
import SelectedAddressGoogleMapModal from './SelectedAddressGoogleMapModal'
export default {
data () {
return {
submitted: false,
name: '',
selectedAddress: {
lat: null,
lng: null,
address: ''
}
}
},
components: { SelectedAddressGoogleMapModal },
methods: {
handleSubmit () {
this.submitted = true;
const {
name, selectedAddress
} = this;
# request to API to create Shop
axios.post('http://localhost:3000/shops', {
name: name,
latitude: selectedAddress.lat,
longitude: selectedAddress.lng,
address: selectedAddress.address
})
.then(response => {
# Restore form and add alert if needed
this.name = ''
this.selectedAddress = {}
})
.catch(e => {
this.error.push(e)
})
},
openSelectedAddressModal() {
this.$modal.show('selected-address-google-map-modal');
},
onSelectedAddress(address) {
this.selectedAddress = address;
},
};
</script>
We have used Modal to display Map to select the address, to use Modal in VueJS, you add the library vue-js-modal
with npm install and add config in main.js file.
import VModal from 'vue-js-modal'
Vue.use(VModal)
Create Modal showing Map to select the address, we create file /src/SelectedAddressGoogleMapModal.vue
<template>
<modal name="selected-address-google-map-modal" transition="pop-out" :width="900" :height="600" :reset="true">
<div class="search-place-map">
<h2>Search address</h2>
<div class="d-flex">
<gmap-autocomplete
class="form-control"
@place_changed="setAddress">
</gmap-autocomplete>
<button class="btn btn-primary" @click="addAddress">Set Address</button>
</div>
</div>
<br>
<gmap-map
:center="center"
:zoom="12"
style="width:100%; height: 400px;"
>
<gmap-marker
:key="index"
v-for="(m, index) in markers"
:position="m.position"
@click="center=m.position"
></gmap-marker>
</gmap-map>
</modal>
</template>
<script>
export default {
name: "SelectedAddressGoogleMapModal",
data() {
return {
center: { lat: 45.508, lng: -73.587 },
markers: [],
selectedAddress: null
};
},
mounted() {
this.geolocate();
},
methods: {
setAddress(address) {
this.selectedAddress = address;
},
addAddress() {
if (this.selectedAddress) {
const marker = {
lat: this.selectedAddress.geometry.location.lat(),
lng: this.selectedAddress.geometry.location.lng()
};
this.markers.push({ position: marker });
this.center = marker;
this.$emit('onSelectedAddress', {
lat: marker.lat,
lng: marker.lng,
address: this.selectedAddress.formatted_address
})
this.onSelectedAddress = null;
}
},
geolocate: function() {
navigator.geolocation.getCurrentPosition(position => {
this.center = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
});
}
}
};
</script>
Modal components are used
gmap-autocomplete
: allows displaying the list of address suggestions when typing
gmap-map
: show map
gmap-marker
: shows selected locations
The command this.$emit('onSelectedAddress', data)
is a method to call back the parent component, here is to update the selected address.
Thus, we have created a shop with lat, lng selected from the map.
Suppose, the user wants to display a list of shops in a certain area, like
then we do the following:
First, we need to create an API to return the list of shops in an area, so that we can filter shops in the area, we will rely on searching for latitude and longitude. The idea is to query in the DB to get all shops located in a rectangular box of a part of the map currently displayed, each corner of the rectangle will be created latitude, longitude.
def index
shops = Shop.all.where(
"latitude >= ? AND latitude <= ? AND longitude >= ? AND longitude <= ?",
params[:bottom_x], params[:top_x], params[:bottom_y], params[:top_y]
)
render json: shops
end
where the parameters bottom_x, top_x, bottom_y, top_y are the latitude and longitude values of the first 4 corners of the map in the xy coordinate direction
We create Component to display the shop listing in the map: / src / MapShops
<template>
<div class="section-box map-search">
<h4 class="section-box__heading">MAP</h4>
<gmap-map
ref="mapRef"
:center="center"
:zoom="12"
style="width:100%; height: 400px;"
@idle="searchShopsInMap"
>
<gmap-marker
:key="index"
v-for="(shop, index) in shops"
:position="shop.position"
></gmap-marker>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'MapShops',
data() {
return {
center: { lat: 35.652832, lng: 139.839478 },
boundMap: {},
shops: []
};
},
components: { SimpleStatementDetail },
mounted() {
this.geolocate();
},
methods: {
geolocate() {
navigator.geolocation.getCurrentPosition(position => {
this.center = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
});
},
searchShopsInMap() {
const map = this.$refs.mapRef;
const boundCoordinate = this.$refs.mapRef.$mapObject.getBounds();
if(!boundCoordinate){return}
const northEast = boundCoordinate.getNorthEast();
const southWest = boundCoordinate.getSouthWest();
const lats = [northEast.lat(), southWest.lat()];
const lngs = [northEast.lng(), southWest.lng()];
this.boundMap = {
top_x: _.max(lats),
bottom_x: _.min(lats),
top_y: _.max(lngs),
bottom_y: _.min(lngs)
}
axios.get('http://localhost:3000/shops')
.then(response => {
this.shops = response.data.map(shop => {
return {
...shop, position: {
lat: shop.latitude, lng: shop.longitude
}
}
})
})
.catch(e => {
this.error.push(e)
})
}
}
};
</script>
where the idle
gmap-map event is called after we have moved the map to another area, then we will calculate the coordinates of the frame and call the API to return the shops in that new area. .
We added component to create shop and display shop in map /src/App.vue file to display
<template>
<div id="app">
<div class="create-shop">
<create-shop />
</div>
<div class="list-shop-in-map">
<map-shops />
</div>
</div>
</template>
<script>
import MapShops from './MapShops'
import CreateShop from './CreateShop'
export default {
name: 'app',
components: {
MapShops, CreateShop
}
}
</script>
Above are the steps to build a VueJS app that uses Rails as an API, and in conjunction with Google map, allows easy integration of information into the map.
We can further improve the use vuexof state management, which helps solve the case when the shop is finished, will be able to immediately show that shop in the map containing the shop list. And add the shop information display when clicking on a marker in the map using gmap-info-window
.
Thanks for watching.
#vuejs #javascript #vue-js