February 19, 2024
•Last updated July 11, 2024
Add Geolocation to Search with Rails and Stimulus.js
In this tutorial, I'll show you how to add Geolocation support to a search form using Ruby on Rails and Stimulus.js
By integrating geolocation capabilities, users can search for locations based on their current position which saves time and effort.
New up a Rails application
To kick things off let’s create a new app I'll call geodemo
. I prefer Tailwind for my CSS and esbuild for JavaScript. We'll leverage Stimulus.js for this tutorial so you can choose whatever JavaScript engine you prefer as it adapts to each gracefully.
rails new geodemo -c tailwind -j esbuild
Add Dependencies
Let’s install some dependencies. A lovely gem called Geocoder will help us find locations based on an address or set of latitude and longitude lookups. It’s super powerful!
bundle add geocoder
Add a Search Controller
For this tutorial, we’ll make a global search rails controller. I won’t get into the weeds of what we would search but you could use something like pg_search to build a query from a model you need to return results of.
rails g controller search index
I’ll put a basic form in app/views/search/index.html.erb
coming up.
Add a SearchLocationController
We need a view and request cycle to make this all work so let’s generate a SearchLocation
controller with Rails to handle the POST
request. For the sake of the guide, I'll add a route for the index action to display the form we'll leverage coming up.
rails g controller search_location create
This should generate the controller in app/controllers
a couple of views and some default routes. Let's amend the routes.
Rails.application.routes.draw do
get 'search/index'
post 'search_location', to: "search_location#create"
get "up" => "rails/health#show", as: :rails_health_check
root "search#index"
end
Now you can boot up your server and hopefully see the new root path active at localhost:3000
Add the search form
Inside app/views/search/index.html.erb
I've added this erb code:
<div class="max-w-xl mx-auto my-16">
<h1 class="font-bold text-3xl mb-4">Search by location</h1>
<div data-controller="geolocation">
<%= form_tag url: search_index_url, method: :get do %>
<div class="relative">
<%= text_field_tag :query, nil, class: "px-6 py-3 rounded border border-gray-300/80 focus:ring-4 focus:ring-gray-200/60 focus:outline-none w-full rounded-full text-lg", placeholder: "Search state or city", data: { geolocation_target: 'input' } %>
<button type="submit" class="absolute top-1.5 right-2 bg-white rounded-full px-5 py-2 border border-gray-300/80 shadow-sm bg-gray-50/50 hover:bg-gray-50">Search</button>
</div>
<% end %>
<button type="button" data-action="click->geolocation#handleClick" class="underline font-medium mt-2">
<span>Find my location</span>
</button>
</div>
</div>
This code scaffolds a basic search form that would perform a GET request to our search controller index action.
If we had real search records in our app we could return real data in some fashion after submission.
I’ll leave that for you to sort out since this tutorial is more about the geolocation aspect.
If you look closely you’ll see some data attributes on our HTML code.
These will be how Stimulus.js knows about our form fields and actions.
The button “Find my location” has an action that our Stimulus.js controller I’ll call geolocation_controller.js
will work with. Let’s make that next.
Set up the Geolocation Stimulus.js Controller
Let's create a Stimulus controller to handle geolocation functionality.
Create a new file named geolocation_controller.js
in the app/javascript/controllers
directory.
You can also run this handy rails command to generate one fast.
rails g stimulus geolocation
Inside the new file I’ve added the following code:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input"]
handleClick() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
this.handleSuccess.bind(this),
this.handleError.bind(this)
)
} else {
console.error("Geolocation is not supported by this browser.")
}
}
handleSuccess(position) {
const { latitude, longitude } = position.coords
this.sendLocationToRails(latitude, longitude)
}
handleError(error) {
console.error(`Geolocation error: ${error.message}`)
}
sendLocationToRails(latitude, longitude) {
const csrfToken = document.querySelector('meta[name="csrf-token"]').content
fetch("/search_location", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken,
},
body: JSON.stringify({ latitude, longitude }),
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
return response.json()
})
.then((data) => {
if (data.address.state) {
this.inputTarget.value = data.address.state
} else if (data.address.city) {
this.inputTarget.value = data.address.city
}
})
.catch((error) => {
console.error("Error communicating with Rails:", error)
})
}
}
Here’s a quick outline of what this code is responsible for:
- Targets: It declares a static property
targets
, specifying the targets that the controller will interact with. In this case, it specifies an input target. - handleClick Method: This method is triggered when a user clicks on an element associated with this controller. It checks if the browser supports geolocation. If it does, it calls
getCurrentPosition
method of thenavigator.geolocation
object, passing two callback functions for success and error handling. - handleSuccess Method: This method is invoked upon successfully retrieving the user's geolocation. It extracts the latitude and longitude from the position object and calls
sendLocationToRails
method with these coordinates. - handleError Method: This method handles errors encountered during geolocation retrieval by logging an error message to the console.
- sendLocationToRails Method: This method sends the user's location (latitude and longitude) to a Rails backend endpoint using a fetch request. It retrieves the CSRF token from the meta tags, includes it in the request headers, and sends the location data as JSON in the request body. It then processes the response, updating the input field value based on the data received (either a zip code or a city name). Any errors during this process are caught and logged to the console.
Handle Geolocation Response in Rails Controller
In your Rails controller (search_controller.rb
), implement the logic to handle the geolocation data received from the frontend:
class SearchLocationController < ApplicationController
def create
# Access latitude and longitude parameters sent from frontend
latitude = params[:latitude]
longitude = params[:longitude]
# Implement your logic to determine a location based on coordinates
location_data = Geocoder.search([latitude, longitude])
render json: location_data.first.data
end
end
This code leverages the Geocoder gem we added before and returns JSON based on the values passed from the front end.
Testing it out
If all goes well we should be able to click the “Find my location” link and propagate the input field with either a postal code or city depending on what returns. This is a one-click option to make it easier on your users who would rather give their location as opposed to typing one in the form manually.
We extract the latitude and longitude coordinates from the navigator.geolocation
browser API. Those coordinates get submit via JavaScript as a POST request to our search_location#create
method in the SearchLocationController
, which then performs the query to decode into a US state. Finally, that response returns to the front end and sets the input's value dynamically.
Happy dance, it works!
In this tutorial, we've successfully integrated geolocation support into a search form in a Rails application using Stimulus.js.
The lazy users (we all know we are one) can now conveniently search for locations based on their current position in one click.
Now there is plenty of room for improvement here, but I hope I’ve guided you in a good direction to build something useful.
Experiment with additional features, like error handling and showing user feedback such as loading states and more.
Happy coding!
Categories
Collection
Part of the Hotwire and Rails collection
Products and courses
-
Hello Hotwire
A course on Hotwire + Ruby on Rails.
-
Hello Rails
A course for newcomers to Ruby on Rails.
-
Rails UI
UI templates and components for Rails.