I really enjoyed using Stimulus JS since it has been released early this year. For once I can now easily organize my JS code without pulling a full-blown frontend framework.
Stimulus-flatpickr wrapper started as an experiment, I needed to use Flatpickr in a project and built a Stimulus Controller for it. I quickly realized that it could make sense to have a generic Stimulus controller for this library.
Today we will demonstrate this package through a simple, yet interesting example. I have worked quite a bit recently with date pickers and realized that it is always very simple to convert a field to a date pickers but they are many small details required to make it a real easy to use solution.
The goal is to create a basic booking system where the user:
- can only select available dates (the ones already booked must be disabled)
- gets a datepicker in his language
- see a date in the input field in the locale format
- see a list of all bookings
- all of this playing nicely in a Single Page Application
the stack is rather simple: Rails, Stimulus, Turbolinks, and Flatpickr for the date picking solution.
At the end we won't have any Ajax at all and less than 10 lines of Javascript to do this ๐๐๐
Lets get started: New app and stimulus setup
They are several tutorials out there, I won't get into all the details but settings up Stimulus in your Rails app if you already have webpack installed is as simple as that:
rails webpacker:install:stimulus
This will add Stimulus package to your package.json
, adds the initialization code in your main application.js
file and a new controllers directory under javascript for all of your new Stimulus
controllers
You can read this article for more details about setting up Stimulus in your rails
For today's demo, we are going to create a brand new app
rails new --webpack=stimulus stimulus-flatpickr
...and don't forget to add the packs
#app/views/layouts/application.html.erb
...
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
...
Appointment Model and Controller
We are doing a booking engine. We need an Appointment model with a single column a start_at date and a scope
method to get the up_comings
appointments for the next n days (we can only book for the next 60 days).
#models/appointment.rb
class Appointment < ApplicationRecord
validates :start_at, uniqueness: true
scope :up_comings, ->(nb_days) {
where('start_at >= ? AND start_at < ?',
Time.zone.now,
Time.zone.now + nb_days.days).order(start_at: :asc)
}
end
Our controller is pretty standard (for simplicity we only handle the success path here ๐ถ, no error managment)
class AppointmentsController < ApplicationController
before_action :set_appointment, only: %i[update destroy]
def index
@appointments = Appointment.up_comings(60)
@appointments_dates = @appointments.pluck(:start_at)
@appointment = Appointment.new
end
def create
redirect_to appointments_path if Appointment.create(appointment_params)
end
def update
redirect_to appointments_path if @appointment.update(appointment_params)
end
def destroy
@appointment.destroy
redirect_to appointments_path
end
private
def set_appointment
@appointment = Appointment.find(params[:id])
end
def appointment_params
params.require(:appointment).permit(:start_at)
end
end
stimulus-flatpickr controller
As per the documentation we are going to create a Stimulus controller that will extend the generic stimulus-flatpickr controller.
First, let's add the packages that we will need
yarn add stimulus-flatpickr
and add the new controller
// ./controllers/flatpickr_controller.js
import Flatpickr from "stimulus-flatpickr";
import "flatpickr/dist/themes/dark.css";
// creates a new Stimulus controller by extending stimulus-flatpickr wrapper controller
export default class extends Flatpickr {}
The views
Lets put a small structure to have the booking form on the left and the up comings appointments list on the right.
#app/views/appointments/index.html.erb
<div class="main">
<h2><%= t ".title" %></h2>
<div class="row">
<div class="col">
<h3><%= t ".new" %></h3>
<%= render "form", appointment: @appointment %>
</div>
<div class="col">
<h3><%= t ".appointments" %></h3>
<%= render @appointments %>
</div>
</div>
</div>
#app/views/appointments/_appointment.html.erb
<div class="appointments">
<%= render "form", appointment: appointment %>
<%= link_to "x", appointment_path(appointment), method: :delete%>
</div>
and our form where all the magic will happen ๐
#app/views/appointments/_form.html.erb
<%= form_with model: appointment do |f| %>
<%= f.text_field :start_at,
data: {
controller: "flatpickr",
flatpickr_min_date: Time.zone.now,
flatpickr_max_date: Time.zone.now + 60.days,
flatpickr_disable: Appointment.up_comings(60).pluck(:start_at),
} %>
<% end %>
This is the important part to understand :
We defined a flatpickr_controller.js
so whenever an HTML element has a data-controller="flatpickr"
attribute, then the stimulus controller will enter into action.
So here in the view, data: { controller: "flatpickr" }
will be converted to data-controller="flatpickr"
and therefore convert the field into a datepicker.
We can also pass options to the datepicker using the same data attributes(data-flatpickr-the-kebab-case-option-name)
. Here we set a min and max date to disable everything before today and everything after today + 60 days.
Also, we prepare for the next step where we will disable the dates that are already booked by passing an array of the current bookings.
Submit on select
Lets automatically submit whenever a user selects a date (not perfect UX! I know, it is mostly for simple demo purpose).
The flatpickr controller has all the official flatpickr hooks available (open, close, change etc). Here we need to submit the form when the value has changed. So let's override the change()
function.
export default class extends Flatpickr {
// automatically submit form when a date is selected
change(selectedDates, dateStr, instance) {
const form = this.element.closest("form");
Rails.fire(form, "submit");
}
}
Going Live
At this point we have the following result:
We start to have something interactive.... The cool thing is that Turbolinks being installed by default kicks in automatically and Ajaxify
all links. All Stimulus controllers are by designed working with Turbolinks. So there is nothing else to do here, it just works!. No custom Ajax call or SRJ to have this SPA look and feel. ๐ช ๐ โค๏ธ
Localizing the date picker and the date format
We have a SPA look & feel and the availabilities for our booking engine. The next step is to localize it correctly. Currently, it is only in English and the date format if rather ugly 2018-09-12
๐.
date formats
The stimulus-flatpickr wrapper offers a nice bonus feature over the standard library, it will convert strftime
date formats to flatpickr custom date formats.
So to customize the date format we can pass the local format directly to the date picker like this:
data: {
controller: "flatpickr",
flatpickr_alt_input: true,
flatpickr_alt_format: t("date.formats.long"),
flatpickr_default_date: appointment.start_at,
flatpickr_disable: @appointments_dates - [appointment.start_at],
flatpickr_min_date: Time.zone.now,
flatpickr_max_date: Time.zone.now + 60.days,
}
flatpickr_alt_format: t("date.formats.long")
-> will output "%B %d, %Y"
when the locale is :en
and "%e %B %Y"
when the locale is :fr
and this gets converted automatically to the nearest flatpickr format. As DRY as it can be ๐!
Translations
We now have almost everything. The last point is to correctly translate the datepicker for every locale.
We are going to import the different locales in our stimulus controller. Every time time Turbolinks silently replace the content of the page, the initialize()
function is of the stimulus controller is called. This is where we are going to set our local and pass it to flatpickr.
Our final controller looks like this
import Flatpickr from "stimulus-flatpickr";
// import a theme (could be in your main CSS entry too...)
import "flatpickr/dist/themes/dark.css";
// import the translation files and create a translation mapping
import { French } from "flatpickr/dist/l10n/fr.js";
import { english } from "flatpickr/dist/l10n/default.js";
// create a new Stimulus controller by extending stimulus-flatpickr wrapper controller
export default class extends Flatpickr {
locales = {
fr: French,
en: english
};
initialize() {
//set the locale and also sets the global flatpickr settings
this.config = {
locale: this.locale,
altInput: true,
showMonths: 2,
animate: false
};
}
// automatically submit form when a date is selected
change(selectedDates, dateStr, instance) {
const form = this.element.closest("form");
Rails.fire(form, "submit");
}
get locale() {
if (this.data.has("locale")) {
return this.locales[this.data.get("locale")];
}
}
}
That's all folks!
You can find the entire demo project here ๐ https://github.com/adrienpoly/rails_stimulus_flatpickr
and more important the stimulus-flatpickr wrapper ๐ https://github.com/adrienpoly/stimulus-flatpickr
I hope you enjoyed this introduction. I personally think we will see more and more standard Stimulus controllers and the Rails community will more and more drop Gems used for front end packages only.
I am not senior dev with tons of experience so feel free to Comments, issues, PR. They are all welcome and if you feel this package is useful for you, leave it a star โญ
Happy Coding ๐
Top comments (2)
Thanks a lot for this post. It really helped!
This is huge! Thank you!