Ranjeet Karki
Posted on July 20, 2022
In this post, I will show you how you can create a Google autocomplete address using Google Place API. You might have seen on some websites while filling out the address form it will automatically suggest the address and on clicking that address, it will fill in the rest of the fields like street line1, street line2, city, state, postcode
etc.
So what do we need to follow this tutorial?
- Setup laravel project:
composer create-project laravel/laravel googleautocomplete
- Install Guzzle:
composer require guzzlehttp/guzzle
- Valid Google API key
- Check the Google Place Autocomplete API Docs: Google Place Autocomplete Api
Let's make a controller with index
method that returns a form
php artisan make:controller GoogleAutocompleteController
GoogleAutocompleteController.php
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
use Illuminate\Http\Request;
class GoogleAutocompleteController extends Controller
{
public function index(): View
{
return view('form');
}
}
Inside resources/views/form.blade.php
I have created a simple form with bootstrap that has a street address field, city field, state field, postcode field, and country field
.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Google autocomplete</div>
<div class="card-body">
<div class="form-group">
<label for="streetaddress">Street address</label>
<input type="text" class="form-control ui-widget autocomplete-google" id="street_address_1">
</div>
<div class="form-group">
<label for="streetaddress2">Street address 2</label>
<input type="text" class="form-control" id="street_address_2">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" class="form-control" id="city">
</div>
<div class="form-group">
<label for="state">State</label>
<input type="text" class="form-control" id="state">
</div>
<div class="form-group">
<label for="postcode">Postcode</label>
<input type="text" class="form-control" id="postcode">
</div>
<div class="form-group">
<label for="country">Country</label>
<input type="text" class="form-control" id="country">
</div>
</div>
</div>
</div>
</div>
</div>
In the above form, I have added a class
ui-widget
in the Street address field because I am using jQuery autocomplete library to pop up the address returned by Google API when the user actually starts tying the address in the Street address field. This class will enable our autocomplete.
Finally, we will register a route in web.php
to view our form.
Route::get('/autocomplete',[GoogleAutocompleteController::class,'index']);
Now, when you visit the /autocomplete
url in the browser, you will see this form
Part 2
In this part, we will use jQuery autocomplete library and place API. Google place API will return the place id and based on the place id we will fill all the fields of the form.
Step1: go to jQuery autocomplete
copy jquery-ui.js
and jquery-ui.css
links and paste it in your app.blade.php
Now my layouts/app.blade.php
looks like this
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@endif
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
@yield('script')
</main>
</div>
</body>
</html>
As you can see I have added
@yield('script')
above, which means everything that I wrapped within the script will be injected here. Later we will wrap javascript within@section('script')//..js code @endsection
inform.blade.php
Step2: go to Google Place API
You can see types, You can restrict results from a Place Autocomplete request to be of a certain type by passing the types parameter. This parameter specifies a type or a type collection, as listed in Place Types. If nothing is specified, all types are returned.
We need address
types
So, copy this URL and open your postman or browser, paste it and hit the send button.
https://maps.googleapis.com/maps/api/place/autocomplete/json?input= kathmandu nepal &types=address&key=YOUR_KEY
You should get a response like this:
Note: we are interested in the place_id
of the response. This is because with the help of place_id,
we can get country, street address, postcode, city
etc.
Step3: Open your config/services.php
and set the key
'googlekey'=>[
'key'=> env('GOOGLE_KEY', null),
],
and in your .env
file
GOOGLE_KEY=YOUR_GOOGLE_KEY
Step4: Now let's make one more route in web.php
Route::get('/placeid',[ GoogleAutocompleteController::class,'getPlaceId'])->name('placeid');
and GoogleAutocompleteController.php
looks like this
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
use Illuminate\Http\Request;
class GoogleAutocompleteController extends Controller
{
public function index(): View
{
return view('form');
}
public function getPlaceId(Request $request)
{
//get user-typed address via Ajax request
}
}
Step5: In our form.blade.php
let's add jQuery code to enable autocomplete dropdown, ajax request to send user typed address to our endpoint /placeId
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
});
});
</script>
Now our complete resources/form.blade.php
looks like this
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Google autocomplete</div>
<div class="card-body">
<div class="form-group">
<label for="streetaddress">Street address</label>
<input type="text" class="form-control ui-widget autocomplete-google" id="street_address_1">
</div>
<div class="form-group">
<label for="streetaddress2">Street address 2</label>
<input type="text" class="form-control" id="street_address_2">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" class="form-control" id="city">
</div>
<div class="form-group">
<label for="state">State</label>
<input type="text" class="form-control" id="state">
</div>
<div class="form-group">
<label for="postcode">Postcode</label>
<input type="text" class="form-control" id="postcode">
</div>
<div class="form-group">
<label for="country">Country</label>
<input type="text" class="form-control" id="country">
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('script')
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
});
});
</script>
@endsection
Step 6: Now let's make a dedicated Handler class for the autocomplete. We could write all code in getPlaceId()
of the GoogleAutocompleteController
, but to clean up the code I think it is a good idea to move all your logic to another PHP class.
let's make AutocompleteHandler.php
inside the Integration
folder in the app directory.
<?php
namespace App\Integration;
use GuzzleHttp\Client;
class AutocompleteHandler
{
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
public string $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function placeId(string $address)
{
//
}
}
In the above code, I have set the base URL
and key
which we will use later. In above placeId(string $address)
method we are receiving an address as a string parameter which we will receive from GoogleAutocompleteController getPlaceId()
method as below.
Step7: Now let's inject AutocompleteHandler.php
in GoogleAutocompleteController.php
and pass address from getPlaceId()
of GoogleAutocompleteController.php
to placeId()
of AutocompleteHandler.php
Now our GoogleAutocompleteController.php
looks like this
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Integration\AutocompleteHandler;
class GoogleAutocompleteController extends Controller
{
public $googleAutocomplete;
public function __construct(AutocompleteHandler $googleAutocomplete)
{
$this->googleAutocomplete = $googleAutocomplete;
}
public function index(): View
{
return view('form');
}
public function getPlaceId(Request $request): JsonResponse
{
return $this->googleAutocomplete->placeId($request->inputData);
}
}
Above, We receive address as an inputData
and we pass this inputData
as an address
parameter to placeId()
Step8: Now let's work on AutocompleteHandler.php
. I will include the steps that we will perform here
<?php
namespace App\Integration;
use GuzzleHttp\Client;
class AutocompleteHandler {
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
public string $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function placeId(string $address)
{
$url= "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=kathmandu&types=address&key=YOUR_API_KEY"
// build the readable url with http_build_query and sprintf()
try {
// step1: instantiate GuzzleHttp client
// step2: hit the url
// step3: get json response
// step4: convert json to array
// step5: loop over the predictions array of response
// step6: and return place_id as id and description as label
} catch (\Exception $e) {
//catch error
}
}
}
So our final AutocompleteHandler.php
looks like this
<?php
namespace App\Integration;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use GuzzleHttp\Client;
class AutocompleteHandler
{
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
public string $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function placeId(string $address): JsonResponse
{
$url = sprintf(
'%s/autocomplete/json?%s',
self::BASE_URL,
http_build_query([
'input' => $address,
'types' => 'address',
'key' => $this->key,
])
);
try {
$client = new Client();
$response = $client->request('get', $url);
$responseJson = $response->getBody()->getContents();
$responseArray = json_decode($responseJson, true);
return response()->json(collect($responseArray['predictions'])->map(
fn ($value) =>
[
'id' => $value['place_id'],
'label' => $value['description'],
]
));
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage(),
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
In the above, I have converted $responseArray
to the collection with collect()
and mapped over it to return place_id
and description
.
Now as you start typing in the form it will start suggesting the address to you as below.
Part 3
This is the last part of Google Place autocomplete. In this part, we will fill the rest of the form field when the user chooses one address from the dropdown.
Now, we need the place_id
from the previous response, and if you hit this URL: https://maps.googleapis.com/maps/api/place/details/json?place_id=EhtOZXBhbCBUYXIsIEthdGhtYW5kdSwgTmVwYWwiLiosChQKEgk9yaxKzhjrORHvFQWGXi5RGhIUChIJv6p7MIoZ6zkR6rGN8Rt8E7U&key=YOUR_KEY
You will get your response in this format.
So we are getting a result as a response and all the needed data such as street address, city, postcode
etc are within the address_components
Step 1: In the above, we are returning place_id
as the id, and now when the user clicks the address we will make another Ajax request to get all the details of the place based on place_id
.
So, after the source, we can add select like the below
select: function(event, ui) {
var placeId = ui.item.id;
console.log(placeId)
}
So our jQuery code looks like this
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
select: function(event, ui) {
var placeId = ui.item.id;
console.log(placeId)
}
});
});
</script>
If you click the selected address, it will return the place id in the console.
Step2: let's make an endpoint that accepts the place id and returns place details based on place id
In web.php
Route::get('address',[ GoogleAutocompleteController::class,'findAddressBasedOnPlaceId'])->name('address');
So now web.php
looks like this
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\GoogleAutocompleteController;
Route::get('/autocomplete',[ GoogleAutocompleteController::class,'index']);
Route::get('/placeid',[ GoogleAutocompleteController::class,'getPlaceId'])->name('placeid');
Route::get('address',[ GoogleAutocompleteController::class,'findAddressBasedOnPlaceId'])->name('address');
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Step2: let's make this method in GoogleAutocompleteController.php
public function findAddressBasedOnPlaceId(Request $request)
{
//get place_id via Ajax request when the user selects the address from dropdown suggestions
}
Step 3: From the above response returned from API, we can see that our data is in address_components
and all responses may or may not return postcode, locality name, etc for that place_id
. So we manually need to check if the required keys are present in the response or not in order to prevent the error on the page. for example, if a postcode is present in the response then we return the postcode, and so on. So, instead of writing an if-else statement for each key let's make a helper function that returns the required key if it is in the response
Now in AutocompleteHandler.php
I will add these checks
public function getDataFromAddressComponent(array $addressComponents, string $searchFor): ?string
{
return collect($addressComponents)->map(fn ($addressComponent) => collect($addressComponent['types'])->contains($searchFor) ? $addressComponent['long_name'] : null)->filter()->first();
}
Above, I am looping the $addressComponents and I am checking if we have $searchFor in that array. If true return something else return null. However, I have used the collection here.
Alternatively, we can do this
foreach ($addressComponents as $address) {
if (in_array($searchFor, $address['types'])) {
return $address['long_name'];
}
}
return null;
Step4: In GoogleAutocompleteController.php
, we will accept the place id from the Ajax request and pass it to addressBasedOnPlaceId()
method of AutocompleteHandler.php
, Which will return the details of the place
public function findAddressBasedOnPlaceId(Request $request): JsonResponse
{
return $this->googleAutocomplete->addressBasedOnPlaceId($request->placeId);
}
Step5: Let's send placeId to findAddressBasedOnPlaceId()
method via ajax
We will add getAddressDetails(placeId)
in
select: function(event, ui) {
var placeId = ui.item.id;
getAddressDetails(placeId);
}
getAddressDetails()
function will make an ajax request to our endpoint /address
Now our, resource/form.blade.php
looks like this
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Google autocomplete</div>
<div class="card-body">
<div class="form-group">
<label for="streetaddress">Street number</label>
<input type="text" class="form-control ui-widget autocomplete-google" id="street_address_1">
</div>
<div class="form-group">
<label for="streetaddress2">Street address 2</label>
<input type="text" class="form-control" id="street_address_2">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" class="form-control" id="city">
</div>
<div class="form-group">
<label for="state">State</label>
<input type="text" class="form-control" id="state">
</div>
<div class="form-group">
<label for="postcode">Postcode</label>
<input type="text" class="form-control" id="postcode">
</div>
<div class="form-group">
<label for="country">Country</label>
<input type="text" class="form-control" id="country">
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('script')
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
select: function(event, ui) {
var placeId = ui.item.id;
getAddressDetails(placeId);
}
});
});
function getAddressDetails(placeId) {
$.ajax({
url: "/address",
type: 'GET',
dataType: "json",
data: {
placeId: placeId,
},
success: function(data) {
$('#country').val(data.country);
$('#city').val(data.locality);
$('#postcode').val(data.postal_code);
$('#state').val(data.state);
$('#street_address_1').val(data.streetNumber);
$('#street_address_2').val(data.streetName);
},
catch: function(error) {
console.log('error');
}
});
}
</script>
@endsection
In the above, on the success we get the result and we fill the form with country, city, postcode, state, etc, our API is yet not ready to return place details based on place ID.
Step6: In AutocompleteHandler.php
we will make an addressBasedOnPlaceId()
that will accept the place Id send from our findAddressBasedOnPlaceId()
of GoogleAutocompleteController.php
public function addressBasedOnPlaceId(string $placeId): JsonResponse
{
$url = $url = // build the readable url with http_build_query and sprintf()
https://maps.googleapis.com/maps/api/place/details/json?place_id=EhtOZXBhbCBUYXIsIEthdGhtYW5kdSwgTmVwYWwiLiosChQKEgk9yaxKzhjrORHvFQWGXi5RGhIUChIJv6p7MIoZ6zkR6rGN8Rt8E7U&key=YOUR_KEY
);
try {
// step1: instantiate GuzzleHttp client
// step2: hit the URL
// step3: get JSON response
// step4: Convert JSON to Array
// step5: return required data such as street name, city, postcode, country, etc
} catch (\Exception $e) {
//catch error
}
}
Step7: The final AutocompleteHandler.php
looks like this
<?php
namespace App\Integration;
use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class AutocompleteHandler
{
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
private $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function getDataFromAddressComponent(array $addressComponents, string $searchFor): ?string
{
return collect($addressComponents)->map(fn ($addressComponent) => collect($addressComponent['types'])->contains($searchFor) ? $addressComponent['long_name'] : null)->filter()->first();
}
public function placeId(string $address): JsonResponse
{
$url = sprintf(
'%s/autocomplete/json?%s',
self::BASE_URL,
http_build_query([
'input' => $address,
'types' => 'address',
'key' => $this->key,
])
);
try {
$client = new Client();
$response = $client->request('get', $url);
$responseJson = $response->getBody()->getContents();
$responseArray = json_decode($responseJson, true);
return response()->json(collect($responseArray['predictions'])->map(
fn ($value) =>
[
'id' => $value['place_id'],
'label' => $value['description'],
]
));
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage(),
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function addressBasedOnPlaceId(string $placeId): JsonResponse
{
$url = sprintf(
'%s/details/json?%s',
self::BASE_URL,
http_build_query([
'place_id' => $placeId,
'key' => $this->key,
])
);
try {
$client = new Client();
$response = $client->request('get', $url);
$responseJson = $response->getBody()->getContents();
$responseArray = json_decode($responseJson, true);
return response()->json([
'streetNumber' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'street_number'),
'streetName' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'route'),
'locality' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'locality'),
'state' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'administrative_area_level_1'),
'administrative_area_level_2' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'administrative_area_level_2'),
'country' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'country'),
'postal_code' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'postal_code')
]);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage(), 'exception' => get_class($e)], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
So, we have returned the streetNumber, streetName, locality, state, postcode, and country in the response and we filled the rest of the form fields with these values.
Posted on July 20, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 12, 2024