Set up basic rails app
- we will use database PostgreSQL, esbuild to bundle the project's JavaScript, and TailwindCSS for its CSS. We'll also skip installing a test framework by passing the -T flag.
rails new leaflet_map -T -d postgresql --css=tailwind --javascript=esbuild
cd leaflet_map
rails db:create
- Configure Tailwind:
In
application.tailwind.css
replace the@tailwind
directives with@import
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
Create basic MVC
- create
Place
model which will have longitude, latitude and name attributes
rails g model Place name:string longitude:float latitude:float
rails db:migrate
- add simple validation for name, longitude and latitude on
Place
model
class Place < ApplicationRecord
validates :name, presence: true
validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }
validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }
end
- update
seed.rb
file to create some data for places
Place.create(name: 'Place-1', longitude: -70.06, latitude: 39.35)
Place.create(name: 'Place-2', longitude: -55.30, latitude: 35.20)
Place.create(name: 'Place-3', longitude: -80.20, latitude: 25.20)
Place.create(name: 'Place-4', longitude: -90.20, latitude: 15.20)
Run
rails db:seed
to seed dataCreate
PlacesController
rails g controller Places index
it will create PlacesController
with index action as well as view files for index
- Update
PlacesController
to show list of places
class PlacesController < ApplicationController
def index
@places = Place.all
end
end
- Also, update
route.rb
to make that index, home page
Rails.application.routes.draw do
root 'places#index'
end
To test that every thing is working so far, run rails server withbin/rails
and go to localhost
You can see default view provided by views/places/index.html.erb
Create basic map with leaflet
- install leaflet
yarn add leaflet
- update
views/layouts/index.html.erb
<!DOCTYPE html>
<html>
<head>
<title>LeafletMap</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
</head>
<body>
<main class="container mx-auto mt-28 px-5">
<%= yield %>
</main>
</body>
</html>
We just wrap main content that is <%= yield %>
with <main class="container mx-auto mt-28 px-5">
just to give it a little bit style
- Update
views/places/index.html.erb
this is place where we will display our map
<div class="w-full">
<h1 class="font-bold text-4xl mb-2">Places</h1>
<div data-controller="maps">
<div data-maps-target="container" class="h-[75vh] w-auto"></div>
</div>
</div>
The main things you need to focus here is data-controller="map"
indicates stimulus controller and data-map-target="container"
this allow us to define containerTarget
on our map stimulus controller
- Generate map stimulus controller
rails g stimulus map
- update
map_controller.js
import { Controller } from "@hotwired/stimulus"
import L from "leaflet"
export default class extends Controller {
static targets = ["container"]
connect() {
this.createMap()
this.map.setView([27.700769, 85.300140], 12);
}
createMap() {
this.map = L.map(this.containerTarget)
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(this.map);
}
disconnect() {
this.map.remove();
}
}
Here, first we import L from leaflet.when our element with attribute data-controller = "map"
enters DOM connect
will be called in which we use createMap
function to create map.
With line static targets = ["container"]
we add container target which we defined in our HTML with data-map-target="container"
now that element can be access with this.containerTarget
Inside of createMap()
function with line this.map = L.map(this.containerTarget)
we initialize empty map inside containerTarget
element and we added tile layer to our map with L.tileLayer
After creating map and adding tile, we set map's view to Kathmandu, Nepal with line this.map.setView([27.700769, 85.300140], 12)
- Add
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin=""/>
inside head section ofapplication.html.erb
file for styling map
<!DOCTYPE html>
<html>
<head>
<title>LeafletMap</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
crossorigin=""/>
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
</head>
<body>
<main class="container mx-auto mt-28 px-5">
<%= yield %>
</main>
</body>
</html>
Now, restart rails server you can see map of Kathmandu,Nepal
Showing our list of places on map
- Update
index.html.erb
<div class="w-full">
<h1 class="font-bold text-4xl mb-2">Places</h1>
<div data-controller="map" data-map-latlong-value="<%=@places.pluck(:latitude, :longitude)%>">
<div data-map-target="container" class="h-[75vh] w-auto"></div>
</div>
</div>
Here, we add data-map-latlong-value="<%=@places.pluck(:latitude, :longitude)%>"
which we can access with this.latlongValue
from our map_controller.js
- Update
map_controller.js
import { Controller } from "@hotwired/stimulus"
import L from "leaflet"
export default class extends Controller {
static targets = ["container"]
static values = { latlong: Array }
connect() {
this.createMap()
this.map.fitBounds(this.latlongValue)
this.latlongValue.forEach(place => this.addMarker(place))
}
createMap() {
this.map = L.map(this.containerTarget)
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(this.map);
}
addMarker(place) {
const [latitude, longitude] = place;
L.marker([latitude, longitude])
.addTo(this.map)
.bindPopup(`<div>latitude: ${latitude}</div><div>longitude: ${longitude}</div>`)
}
disconnect() {
this.map.remove();
}
}
Line static values = { latlong: Array }
add value latlong which datatype will be array and now we can access this value which we defined in index.html.erb
with this.latlongValue
Instead of showing map of Kathmandu, Nepal with setView()
function we use fitBounds()
which take list of latitude and longitude and map will be adjusted to fit all of provided latitude and longitude
And for each latitude and longitude value we call function addMarker
. Our addMarker
function is responsible for adding marker to provided longitude and latitude and we also add popup showing it's latitude and longitude which will appear while clicking on marker with bindPopup
Now our map is look like this
Top comments (0)