Just lately I shared a few of my struggles writing kinds in standalone JavaScript UI frameworks.
Types are:
- an inevitable actuality in net improvement
- cruft that we have to seize information
- issues that enable us to really present worth to customers
Re-inventing the wheel on primary considerations like validation, storing and composing kind information,
and persisting it means we’re shedding time and vitality we’d moderately dedicate to extra invaluable options.
It’s unhappy after we quit the elegant instruments Rails offers us to rapidly construct out kinds.
Equally, I’ve discovered utilizing trendy styling options can really feel cumbersome with Rails kinds. Tailwind is nice, particularly
in a component-based UI. There’s a clear translation from parts to partials / views, however I’ve by no means discovered the
proper method to consider how we construct kinds.
Prior to now, I’ve been responsible of copy-pasting from different kinds within the venture and tweaking to fulfill a
new requirement. That’s the trail of least resistance while you don’t have an honest technique in place or are
unfamiliar with the place the precise place to make an abstraction. It additionally results in dangerous design decisions by breeding
inconsistency throughout groups. Each kind drifts ever so barely away from the unique, like a nasty recreation of phone.
Discovering the precise method to compose these kinds is hard!
I don’t suppose I’m alone there. All kinds of gems attempt to assist easy out that have and make kinds
really feel extra ‘component-y’. Easy kind is a good instance! Should you’ve
by no means tried these gems, they could attraction to you!
These days, I’ve been making an attempt to interact my curiosity in regards to the instruments I exploit earlier than leaping to make use of a dependency.
Dependencies aren’t dangerous by any means! We simply wish to be very acutely aware of what tradeoff we’re making
by together with them. Typically it’s well worth the tradeoff, however by working straight to them, I
restrict my alternative to go deeper in my understanding of the stack I exploit most frequently.
Queue an amazing concept from Justin Searls. He’s proven me methods to lean on Rails defaults for constructing kinds in a method that I
suppose hits a candy spot between views and Tailwind styling. With a small little bit of exploration of the Rails internals,
we’d have the ability to free our kinds from leaning on one other dependency.
Be aware: I’ll be writing .erb fashion syntax with out their requisite <%= %> && <% %>
tags, so that they format extra kindly in markdown. Please forgive me :`)
Should you’ve additionally tried styling your Rails app with Tailwind, you might need
made a kind that appears like this:
# In our bakery present view, someplace
form_with mannequin: @bakery do |f|
f.label :bakery_name, "Bakery Identify", class: "block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"
f.text_field :identify, class: "mt-1 block w-full rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
f.label :bakery_email, "Bakery E mail", class: "block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"
f.text_field :e mail, class: "mt-1 block w-full rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
f.label :bakery_country, "Bakery Nation", class: "block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"
f.text_field :nation, class: "mt-1 block w-full rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
f.label :bakery_open, "Open?", class: "block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"
f.check_box :open, disabled: true
finish
The ideas of Tailwind and ActionView can really feel opposed to one another.
The form_with
helper offers us a reusable construction for creating kinds. We straight work together
with mannequin attributes, with a simple DSL to construct kinds for an underlying object. Rails is opinionated about
the behaviour, so we all know what to do, with sufficient flexibility to customise the HTML output.
We get so many issues totally free. Validations in opposition to the mannequin are tied to the matching subject on the shape. We will simply
move error messages to the view. The shape is aware of the place to submit itself and methods to bundle up its kind information. And all
the opposite issues.
Tailwind offers us a concise CSS DSL, blended with an inline first method to styling that matches the componentized
world of net improvement. Tailwind is closely opinionated about making use of fashion inline together with your HTML,
with loads of advantages. No extra deserted CSS lessons. Keep away from abstractions that evolve, develop, bloat,
and deteriorate. Cell-first styling, simple theming, darkish mode, and so forth.
After we use these two instruments collectively, we will wind up with a little bit of a multitude.
The instance code above is a working example. Even a easy kind with just a few attributes has us copy-pasting the identical
styling to all of them. It looks like we’re doing one thing mistaken. The styling is so moist that we lose sight of the
easy kind syntax that makes us love Rails, however we have to maintain our styling inline with our HTML.
How can we reconcile this?
We will begin by leaning on app-wide theme styling, letting Tailwind care for these considerations at our config layer.
Using a Tailwind theme for issues like our model colours is the best way.
If we slip and think about using @apply
so as to add customized lessons to our kind components, we break conference.
# no, dangerous. Do not be tricked!
form_with mannequin: @bakery, class: 'bakery_form' do |f|
f.label :bakery_name, "Bakery Identify", class: "form-label"
f.text_field :identify, class: "form-text-input"
f.label :bakery_email, "Bakery E mail", class: "form-label"
f.text_field :e mail, class: "form-text-input"
f.label :bakery_country, "Bakery Nation", class: "form-label"
f.text_field :nation, class: "form-text-input"
f.label :bakery_open, "Open?", class: "form-label"
f.check_box :open, disabled: true, class: "form-check-box"
finish
We see this DRY fashion and really feel higher. It’s a extra snug learn if we’re used to conventional CSS approaches.
If we fall for this entice, actuality will smack us because it all the time does.
What occurs when we have to prolong a single enter fashion, one time, on one kind? That one-off will get forgotten and
regresses someplace down the road.
What occurs if bakery_form
kinds get reused? As soon as it makes a single leap to a different kind, it’ll make 3 extra earlier than
you blink. Then we’ve got a CSS class to take care of throughout a number of kinds and contexts.
Altering international CSS is terrifying on massive initiatives.
Inevitably, these abstractions push us away from what’s superior about Tailwind. There are an entire lot of causes
Tailwind tells us to keep away from the
@apply
abstraction. Worry not!
Tailwind additionally offers steerage on managing
duplication of styling, knowledge akin to “tolerate some quantity of repetition over untimely abstraction”.
They coach us to dry out our inline styling in two methods; loop intelligently, and lean on partials.
When potential, we must always map over our assortment of issues to be rendered. If that doesn’t match, lean into partials
to share styling throughout contexts.
Types fall right into a difficult center floor the place neither of those decisions solves our drawback with out points.
Looping by mannequin attributes to generate an enter assortment creates interdependency between every enter.
It’s additionally onerous to reuse kind partials. My Bakery mannequin may be very totally different from my Cake mannequin, however I most likely need
similarity in the best way the kinds look.
There’s a greater method to lean into Rails kinds with Tailwind in an anticipated and concise method; Kind builders.
The Rails Gods give us a method to make Customized Kind Builders,
and @searls pointed me in the direction of a artistic tie-in to using them successfully whereas styling kinds throughout an
software.
TL;DR:
We will make a small seam within the FormBuilder
Rails gives to us to use kinds, then bounce proper again to the default
performance.
I’ve received a pattern app in case you’d want I present you the code.
There’s a precept I believe pairs properly with the shape builder method:
Wrap with construction, fill with fashion.
The pattern repo’s “completed product” has some intelligent optimizations. Should you’re snug
with inheritance, metaprogramming, and a small little bit of recursion, I believe you’ll be set to hop in and grok the precept.
If these issues are new to you, otherwise you’d like a refresher, no worries! I’ve received you! I’ll element the most straightforward
implementation of this builder and incrementally work in the direction of these intelligent optimizations step-by-step within the sections
under.
Rails lets us outline a customized Kind Builder to alter what occurs after we name form_with
in a view.
We’ll make a customized builder whose solely job is to insert our styling.
Then the builder will level us straight again to Rails magic land, the place we get validation, submission,
information wrangling, and all the opposite issues totally free from the form_with
helper.
First, I would like you to recollect the first guideline that’s going to assist us stability Tailwind and Rails
greatest practices.
Wrap with construction, fill with fashion.
We wish to do format styling in our view, wrapping our form_with
components.
The shape builder ought to fill in our look styling.
There are 4 issues we have to get began:
- Tailwind configuration, so it sees kinds on the builder
- Find out how to name your kind builder
- Implementing the
FormBuilder
- Leveraging the builder alongside our kind
The pattern app and the examples under are based mostly on an Adventurer
mannequin with a easy edit view.
├── app
│ ├── helpers
│ │ └── application_helper.rb
│ ├── lib
│ │ └── form_builders
│ │ └── tailwind_form_builder.rb
│ └── views
│ └── adventurers
│ └── present.html.erb
└── tailwind.config.js
First, we wish to configure Tailwind to examine for styling within the kind builder.
Discover your tailwind.config.js
, and embrace:
module.exports = {
content material: [
'./app/views/**/*.html.erb', // you probably have all these ones!
'./app/helpers/**/*.rb',
'./app/assets/stylesheets/**/*.css',
'./app/javascript/**/*.js',
'./app/lib/form_builders/**/*.rb', // Add this one! Or, whatever directory you'd like to store your builder
]
}
The 1st step is completed!
There are two major methods Rails expects a customized builder to be invoked. The precise place relies on what abstraction is acceptable to your venture.
Each form_with
helper takes an elective parameter builder
, which is the place we will move a builder class.
Within the instance app, our present view invokes it this fashion:
<%= form_with mannequin: @adventurer, builder: MyFormBuilder do |f| %>
One other place you may take into account invoking your builder is as a project-wide default:
# app/helpers/application_helper.rb
module ApplicationHelper
ActionView::Base.default_form_builder = MyFormBuilder
finish
Should you use the default, form_with
doesn’t have to be given a builder possibility.
Which method must you go? As all the time, it relies upon!
Should you’re constructing greenfield or have only a few kind views, setting a default makes loads of sense!
You will get a uniform look on each kind you construct and arrange an efficient sample to customise each as you go.
Should you’ve received a lot of pre-existing Rails views / kinds you’re wrangling, it might be preferable to go view-by-view,
manually passing your builder possibility. Drying up your kinds this fashion can assist untangle construction from look
styling. It might additionally reveal if there are a number of “varieties” of kinds in your area you’d like to visualise in a different way.
Working in the direction of a default implementation is perhaps an honest purpose, or it’s possible you’ll must maintain working with just a few totally different
kind styling choices.
Regardless, the implementation of our first customized builder would be the identical!
In Working Successfully With Legacy Code, Michael Feathers describes the idea of a code seam.
A seam is a spot the place two components of a system work together, and we will introduce some new behaviour.
We’re going to create our personal seam within the
FormBuilder api.
First, we outline our builder and inherit from the default FormBuilder
supplied by ActionView
.
# /app/lib/form_builders/tailwind_form_builder.rb
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
finish
finish
I’ve opted to namespace this instance, however you don’t must.
Our final purpose is to provide the form_with
block entry to every of the enter subject strategies.
For instance:
form_with mannequin: @adventurer do |f|
f.label :adventurer_name, "Adventurer Identify", class: "block text-gray-500
font-bold md:text-right mb-1 md:mb-0 pr-4"
f.text_field :identify, class: "mt-1 block w-full rounded-md shadow-sm
focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
f.label :adventurer_city, "Adventurer Metropolis", class: "block text-gray-500
font-bold md:text-right mb-1 md:mb-0 pr-4"
f.text_field :metropolis, class: "mt-1 block w-full rounded-md shadow-sm
focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
finish
The simplest first step to drying this up is to outline a text_field
technique on our customized builder that does all of the
styling.
# /app/lib/form_builders/tailwind_form_builder.rb
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
def text_field(technique, choices={})
default_style = "mt-1 block w-full rounded-md shadow-sm focus:ring
focus:ring-indigo-200 focus:ring-opacity-50"
tremendous(technique, choices.merge({class: default_style}))
finish
finish
finish
What’s occurring right here?
We’re shifting our Tailwind class string into the text_field
technique and out of our view.
Then we leverage our inheritance powers to name the text_field
technique that Rails already gives for us on the
default ActionView::Helpers::FormBuilder
, by invoking tremendous
.
Now we will clear up our view!
form_with mannequin: @adventurer do |f|
f.label :adventurer_name, "Adventurer Identify", class: "block text-gray-500
font-bold md:text-right mb-1 md:mb-0 pr-4"
f.text_field :identify
f.label :adventurer_city, "Adventurer Metropolis", class: "block text-gray-500
font-bold md:text-right mb-1 md:mb-0 pr-4"
f.text_field :metropolis
finish
All of our textual content fields get the identical styling utilized.
We will wash-rinse-repeat with the label too!
# /app/lib/form_builders/tailwind_form_builder.rb
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
def text_field(technique, choices = {})
default_style = "mt-1 block w-full rounded-md shadow-sm
focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
tremendous(technique, choices.merge({class: default_style}))
finish
def label(technique, textual content = nil, choices = {})
default_style = "block text-gray-500 font-bold md:text-right
mb-1 md:mb-0 pr-4"
tremendous(technique, textual content, choices.merge({class: default_style}))
finish
finish
finish
Be aware that Labels have a barely totally different set of parameters than text_field
. Now our view is tidy:
form_with mannequin: @adventurer do |f|
f.label :adventurer_name, "Adventurer Identify"
f.text_field :identify
f.label :adventurer_city, "Adventurer Metropolis"
f.text_field :metropolis
finish
This may look like an inexpensive place to cease. Sadly, if we do cease right here, we’re making extra issues.
Our kind is now depending on the TailwindFormBuilder
to create all of the styling. It’s not unreasonable to think about
we may find yourself with a builder per kind. At this stage, we’re including indirection and complexity for much less profit than
simply copy-pasting our styling. To make issues worse, we’re making each text_field
co-dependent.
Adjustments to 1 have an effect on all of them.
So, we should maintain going!
Our present instance has two fundamental dependencies we have to overcome:
- All the styling is within the builder technique. We will’t modify or modify every
text_field
- We’ve got to put in writing a technique for each enter kind we wish to be styled
If we may modify every enter subject in isolation, our builder code would turn into extra reusable.
The second dependency may appear small, however there are 24 enter varieties. Fortuitously, 17 of them act precisely
like a text_field
. We will handle loads of enter varieties in the identical method.
For something distinctive in its behaviours, like a choose
, or a check_box
, we’ll nonetheless lean on manually defining
our strategies. These distinctive fields have their very own api to work together with.
Breaking the primary dependency strikes us to reusable code, and breaking the second helps our builder be concise.
Let’s begin with permitting modifications to particular person text_field
calls. We’ll see how the wrap with construction,
fill with fashion sample in motion.
Later we’ll embrace that behaviour on all 17 text-like fields.
What makes the 2 kinds totally different? What’s the identical between them?
At its base, a kind is only a assortment of inputs that every level to a selected attribute we wish to report.
Whatever the underlying mannequin, there are solely so some ways to seize the info on the web page.
Enter components have loads of overlap. We wish all of our inputs to have a constant look to them so that they’re
intuitive. They need to all have the identical font, border styling, coloration, and so on.
Types will seem in several components of the person stream. A kind with 20 inputs must be organized
otherwise than a 3-input kind. Format is essential for stream and value, so every wants management of its personal
construction.
Look needs to be the identical throughout totally different kinds, whereas the format ought to shift to accommodate
the knowledge being captured. Splitting up these two considerations offers constant branding with dry
CSS lessons, with the flexibleness to construction the format on every kind independently.
We intuitively use views this fashion. We count on a partial to carry the identical form wherever it’s rendered.
A partial innately has duty for construction. Giving the FormBuilder duty for look styling
lets us pair FormBuilders with partial views to hit the candy spot of dry code and performance.
The enter fields seem constantly the identical whatever the kind however can be formed to suit a selected kind’s want.
Our text_field
technique is presently doing each:
# /app/lib/form_builders/tailwind_form_builder.rb
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
def text_field(technique, choices={})
default_style = "mt-1 block w-full rounded-md shadow-sm focus:ring
focus:ring-indigo-200 focus:ring-opacity-50"
tremendous(technique, choices.merge({class: default_style}))
finish
finish
finish
Our construction styling:
mt-1 block w-full
and look styling:
rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50
Our subsequent change: depart the looks styling right here, however carry our construction styling again as much as the view.
Our text_field
interface permits us to move in a mannequin attribute and a hash of all our choices.
Right here’s what our view ought to seem like:
form_with mannequin: @adventurer do |f|
f.text_field :identify, class: "mt-1 block w-full"
finish
On this instance, we’re calling for a subject tied to the identify
attribute on the @adventurer
. All the things handed
after identify
will get bundled right into a hash.
To permit styling to be dealt with from each the view and the builder degree, we have to isolate the class
handed
from the view with our construction styling. Then we have to mix it with the looks styling declared in our builder.
# /app/lib/form_builders/tailwind_form_builder.rb
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
def text_field(technique, choices = {})
style_options, custom_options =
partition_custom_opts(choices)
fashion = "rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
+ " #{style_options[:class]}"
tremendous(technique, choices.merge({class: fashion}))
finish
CUSTOM_OPTS = [:class].freeze
def partition_custom_opts(opts)
opts.partition CUSTOM_OPTS.embrace?(okay) .map(&:to_h)
finish
finish
finish
Partitioning out the :class
from our different choices offers us entry to do exactly that.
I’ve opted to make use of a template literal, so we’ve got some security if no class
is given from the view.
Our ensuing HTML within the browser has each the view-layer construction styling matched with the
look styling from the builder:
<enter class="rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50 mt-1 block w-full"
kind="textual content" worth="Gandalf the Gray" identify="adventurer[name]" id="adventurer_name">
One dependency damaged!
If we apply an identical change to our labels, it permits our view to handle the construction solely:
<%= form_with mannequin: @adventurer do |f| %>
<div class="md:flex md:w-full md:justify-between md:items-center mb-6">
<%= f.text_field :identify, class: "min-w-1/2" %>
<%= f.label :identify, class: "mb-1 md:mb-0 pr-4" %>
</div>
<% finish %>
The pattern repo does some intelligent enterprise to bundle up label creation inside text_field
and different helpers.
Subsequent, we’ll make modifications, so we’re not manually writing all 17 text_like
subject helpers. That permits us to
deal with labels inside the builder.
Onto making a concise implementation of all 17 text_like
fields!
Should you’re snug with ruby metaprogramming, that is the half the place we’re shamelessly stealing from the
Rails supply for producing these strategies.
Should you’re much less snug, let’s speak in regards to the function metaprogramming serves right here and the way we transfer our
implementation towards using it.
Below the hood, the ActionView::Helpers::FormBuilder
makes use of metaprogramming to maintain
itself tidy. The complete record of enter varieties
is right here in case you’d like to have a look at all of them.
The ActionView FormBuilder
bundles the way it defines all of the strategies that behave like a text_field
and, on initialization, generates an occasion technique
to deal with every kind of enter.
Our builder can mimic this behaviour so as to add our styling to all of the text-like fields, then name for a similar technique
on the ActionView FormBuilder
we’re inheriting from to make use of the underlying performance.
We’re going to regulate our implementation to:
- use
class_eval
to generate strategies for us - embrace two paths by each technique; one for making use of styling, one for pointing again to the default behaviour
Right here’s the large hop towards that new method:
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field].every do |field_method|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{field_method}(technique, choices = {})
if choices.delete(:tailwindified)
tremendous
else
text_like_field(#{field_method.examine}, technique, choices)
finish
finish
RUBY_EVAL
finish
def text_like_field(field_method, object_method, choices = {})
style_options, custom_options =
partition_custom_opts(choices)
fashion = "rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
+ " #{style_options[:class]}"
ship(field_method, object_method, {
class: fashion,
}.compact.merge(opts).merge({ tailwindified: true }))
finish
finish
finish
What modified? Right here we:
- name for
field_helpers
, which comes fromActionView::Helpers::FormBuilder
- subtract just a few strategies from
field_helpers
that don’t behave like atext_field
- add our
class_eval
loop, which handles our metaprogramming - rename our
text_field
technique totext_like_field
to point out we’re dealing with many differing kinds - modify
text_like_field
to nameship
as a substitute oftremendous
- introduce an possibility referred to as
tailwindifed
to regulate our stream
class_evals
, how do they work?
This class_eval
technique is sort of lifted straight from the Rails supply. Thanks, Rails gods.
By tapping into the metaprogramming facet of Ruby, we will create all of the strategies that act like a text_field
in a dry method. We’re creating a technique for every text_like
subject that is aware of methods to apply styling, then level to
the default Rails behaviour for that enter kind.
field_helpers
is outlined on the guardian class, ActionView::Helpers::FormBuilder
. Our particular choice right here
field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]
creates an array of all of the field_helpers
that act the identical method as our text_field
enter does.
Then we loop by our record and generate a technique for every on our builder.
When an occasion of the TailwindFormBuilder
is established, the item in reminiscence will seem like this:
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
# the category eval loop is right here
# def text_like_field ...
def text_field(technique, choices={})
if choices.delete(:tailwindified)
tremendous
else
text_like_field(:text_field, technique, choices)
finish
finish
def email_field(technique, choices={})
if choices.delete(:tailwindified)
tremendous
else
text_like_field(:email_field, technique, choices)
finish
finish
# and so forth, for the entire different 15 text-like subject strategies
finish
finish
We’ve captured our earlier code that partitions the class
possibility and combines it with our default styling to reuse
that performance for each text-like subject. We’re additionally letting every textual content subject check with the default Rails behaviour
by calling tremendous
within the technique. For instance, ActionView::Helpers::FormBuilder
has an email_field
we will invoke by
calling tremendous
in an email_field
technique on our personal builder.
The place does tailwindified
come into play?
I pulled a quick one on you. We’re doing a little gentle recursion. Every technique has two flows. The primary invocation
will add our styling and embrace a brand new key within the choices hash referred to as tailwindified
.
As soon as that styling is utilized, it requires the unique performance on the default builder by calling tremendous
.
The stream goes like this:
- We create a
text_field
within the view - Our customized builder’s
text_field
technique known as - The
text_field
technique callstext_like_field
text_like_field
applies our styling and provides thetailwindified
possibilitytext_like_field
makes use ofship
to nametext_field
once more on our personalTailwindFormBuilder
- This time,
text_field
callstremendous
, asking for the default Railstext_field
behaviour and passing the choices alongside.
By abstracting the identify of the enter kind, and the 2 pathways, we’re in a position to tie into the entire enter fields
that behave equally to text_input
, fashion them, and maintain our FormBuilder
pretty concise.
This is perhaps the place you determine to get off the practice! We’ve received the tie into FormBuilders down.
We will leverage the wrap with construction, fill with fashion sample I’ve been encouraging, and get a
respectable quantity of worth in doing a little easy drying up of our Tailwind.
That mentioned, I’ve received a pair extra ideas for tactics you may lean into each this sample and a few intelligent tie-ins to
Rails magic. Validation errors and labels!
The explanation I like this method is we get to make use of our anticipated Rails API. If a person fails a kind
validation, we wish to present them which subject was incorrect with styling and probably labels.
One main benefit of ActionView is attending to work together elegantly with our mannequin and its built-in validations.
Listed below are a pair jumps that can carry our styling a bit nearer to the pattern app and provides us a pleasant
seam to answer mannequin errors.
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
# no modifications to the opposite stuff
def text_like_field(field_method, object_method, choices = {})
style_options, custom_options =
partition_custom_opts(choices)
fashion = "rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
lessons = apply_style_classes(fashion, style_options, object_method)
ship(field_method, object_method, {
class: lessons,
}.compact.merge(opts).merge({ tailwindified: true }))
finish
def apply_style_classes(lessons, style_options, object_method = nil)
lessons + border_color_classes(object_method) + " #{style_options[:class]}"
finish
def border_color_classes(object_method)
if errors_for(object_method).current?
" border-2 border-red-400 focus:border-rose-200"
else
" border border-gray-300 focus:border-yellow-700"
finish
finish
def errors_for(object_method)
return until @object.current? && object_method.current?
@object.errors[object_method]
finish
finish
finish
What modified? Now:
- we retailer our look styling in a variable in
text_like_field
text_like_field
leans on one other technique for composing the entire fashion stringapply_style_classes
took over mixing construction styling from the view into the looks styling
our builder gives- a brand new
border_color_classes
technique provides a crimson border to the enter if errors are current on the mannequin attribute - a helper technique to examine for errors on the item
Rails magic! Should you’re utilizing a flash[:error]
in your kind view, this helps seize the styling on the shape
to point out customers the place your message relates.
Should you poke on the pattern venture, it’s possible you’ll be aware that text_like_field
returns two issues: labels + subject
.
FormBuilder
strategies are solely required to return HTML that attaches to our view, so we will bundle as many collectively
as we’d like. The Rails docs
use computerized labeling for instance for customized kind builders.
We will present wrap with construction, fill with fashion therapy to our label technology by making small changes:
- Enable
text_like_field
to partition out label choices, much like our inline class - Regulate
text_like_field
to return each a label and the enter ingredient.
# /app/lib/form_builders/tailwind_form_builder.rb
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
# ... the opposite issues right here
CUSTOM_OPTS = [:class, :label].freeze
def partition_custom_opts(opts)
opts.partition CUSTOM_OPTS.embrace?(okay) .map(&:to_h)
finish
finish
finish
The partition can seize each class
and label
from the choices hash, handed from the view.
Now we will generate some labels from the choices hash:
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
# ... no modifications to the opposite issues!
def text_like_field(field_method, object_method, choices = {})
custom_opts, opts = partition_custom_opts(choices)
fashion = "rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
lessons = apply_style_classes(fashion, custom_opts, object_method)
subject = ship(field_method, object_method, {
class: lessons,
title: errors_for(object_method)&.be part of(" ")
}.compact.merge(opts).merge({tailwindified: true}))
label = tailwind_label(object_method, custom_opts[:label], choices)
label + subject
finish
def tailwind_label(object_method, label_options, field_options)
textual content, label_opts = if label_options.current?
[label_options[:text], label_options.besides(:textual content)]
else
[nil, {}]
finish
label_classes = label_opts[:class] || "block text-gray-500 font-bold"
label_classes += " text-yellow-800 darkish:text-yellow-400" if field_options[:disabled]
label(object_method, textual content, {
class: label_classes
}.merge(label_opts.besides(:class)))
finish
finish
finish
What modified?
text_like_field
returns each an enter, and a label for the enter- we move any
label
choices supplied to a helper technique,tailwind_label
tailwind_label
chooses defaults, so we safely do nothing if nolabel
is supplied on the view degree- we apply the looks fashion to the label and name for the default
label
technique
Now our view can present a label for enter fields as an possibility.
<%= form_with mannequin: @adventurer, builder: FormBuilders::TailwindFormBuilder, do |f| %>
<div class="md:flex md:w-full md:justify-between md:items-center mb-6">
<%= f.text_field :identify, label: { textual content: "Full Identify" } %>
</div>
<% finish %>
By itself, this looks like a small sidestep in performance from the f.label
method, so I perceive if
it’s not notably interesting to you. One factor it does allow is so as to add error labels to our kind inputs utilizing
the identical API.
module FormBuilders
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
# ... no modifications to the opposite issues!
def text_like_field(field_method, object_method, choices = {})
custom_opts, opts = partition_custom_opts(choices)
fashion = "rounded-md shadow-sm focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
lessons = apply_style_classes(fashion, custom_opts, object_method)
subject = ship(field_method, object_method, {
class: lessons,
title: errors_for(object_method)&.be part of(" ")
}.compact.merge(opts).merge({tailwindified: true}))
labels = labels(object_method, custom_opts[:label], choices)
labels + subject
finish
def labels(object_method, label_options, field_options)
label = tailwind_label(object_method, label_options, field_options)
error_label = error_label(object_method, field_options)
@template.content_tag("div", label + error_label, {class: "flex flex-col items-start"})
finish
def error_label(object_method, choices)
if errors_for(object_method).current?
error_message = @object.errors[object_method].accumulate(&:titleize).be part of(", ")
tailwind_label(object_method, {textual content: error_message, class: " font-bold text-red-500"}, choices)
finish
finish
finish
finish
General this kinda feels intelligent to me. I don’t love being intelligent. I’ve a powerful
choice for writing specific code and have a excessive tolerance for repetitively typing f.label
in my views.
In the end I’ll depart it as an train to you to determine if the cleverness of auto-labeling is a win or not.
A lot of it’s context-dependent.
I’ll additionally depart the caveat that we’d mix look/format considerations with labels. I’m nonetheless reconciling that and
want to do this sample out on some bigger initiatives earlier than I give a last verdict.
Each time I sit down to put in writing, I find yourself with an entire novel. So, thanks for sticking it out.
I’m not going to prescribe the error styling or label technology extensions to your kinds. Your venture will
have its personal distinctive qualities that can decide if these are related to you or not.
I do hope this dive into the best way Rails builds kinds and the way wrap with construction, fill with fashion helps
easy out your expertise constructing kinds. And even evokes you to do some extra cool issues! I haven’t even touched
on issues like Turbo or among the deeper integration between kinds and their underlying fashions. Should you construct a
cool factor with this sample, I’d like to see it!
In the end, I simply need us all to have kinds that serve us, really feel intuitive, and don’t require us to spend extra
time than mandatory on the cruft. This won’t be the sample for you or to your venture, however possibly it’ll assist.