Andy from Webcrunch

Subscribe for email updates:

Portrait of Andy Leverenz
Andy Leverenz

February 19, 2024

Last updated February 19, 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:

  1. Targets: It declares a static property targets, specifying the targets that the controller will interact with. In this case, it specifies an input target.
  2. 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 the navigator.geolocation object, passing two callback functions for success and error handling.
  3. 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.
  4. handleError Method: This method handles errors encountered during geolocation retrieval by logging an error message to the console.
  5. 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!

Link this article
Est. reading time: 6 minutes
Stats: 413 views

Collection

Part of the Ruby on Rails collection