Skip to content

seanpdoyle/attributes_and_token_lists

Repository files navigation

AttributesAndTokenLists

Transform Hash arguments into composable groups of HTML attributes


Usage

tag.attributes

Installing AttributesAndTokenLists extends Action View's tag.attributes to return an instance of ActionView::Attributes.

ActionView::Attributes are Hash-like objects with two distinguishing abilities:

  1. They know how to serialize themselves into valid HTML attributes:

    attributes = tag.attributes class: "border rounded-full p-4 aria-[expanded=false]:text-gray-500",
                                aria: {
                                  controls: "a_disclosure",
                                  expanded: false
                                },
                                data: {
                                  controller: "disclosure",
                                  action: "click->disclosure#toggle",
                                  disclosure_element_outlet: "#a_disclosure"
                                }
    
    attributes.to_s
    # => class="border rounded-full p-4 aria-[expanded=false]:text-gray-500" aria-controls="a_disclosure" aria-expanded="false" data-controller="disclosure" data-action="click->disclosure#toggle" data-disclosure-element-outlet="#a_disclosure"
  2. They know how to deeply merge into attributes that are token lists:

    border = tag.attribute class: "border rounded-full"
    padding = tag.attribute class: "p-4"
    
    attributes = border.merge(padding).merge(class: "a-special-class")
    
    attributes.to_h
    # => { class: "border rounded-full p-4 a-special-class" }

with_options

Since ActionView::Attributes instances are Hash-like, they're compatible with Object#with_options. Compose instances together to

def focusable
  tag.attributes class: "focus:outline-none focus:ring-2"
end

def button
  tag.attributes class: "py-2 px-4 font-semibold shadow-md"
end

def primary
  tag.attributes class: "bg-black rounded-lg text-white hover:bg-yellow-300 focus:ring-yellow-300 focus:ring-opacity-75"
end

def primary_button(...)
  tag.with_options([focusable, button, primary].reduce(:merge))
end

primary_button.button "Save", class: "uppercase"
#=> <button class="py-2 px-4 font-semibold shadow-md focus:outline-none focus:ring-2 bg-black rounded-lg text-white hover:bg-yellow-300 focus:ring-yellow-300 focus:ring-opacity-75 uppercase">
#=>   Save
#=> </button>

primary_button.a "Cancel", href: "/", class: "uppercase"
#=> <a href="/" class="py-2 px-4 font-semibold shadow-md focus:outline-none focus:ring-2 bg-black rounded-lg text-white hover:bg-yellow-300 focus:ring-yellow-300 focus:ring-opacity-75 uppercase">
#=>   Cancel
#=> </a>

Attribute support isn't limited to class:. Declare composable ARIA- and Stimulus-aware attributes with aria: and data: keys:

def disclosure(controls: nil, expanded: false)
  tag.attributes aria: {controls:, expanded:},
                 data: {controller: "disclosure", action: "click->disclosure#toggle", disclosure_element_outlet: ("#" + controls if controls)}
end

def primary_disclosure_button(controls: nil, expanded: false)
  tag.with_options([focusable, button, primary, disclosure(controls:, expanded:)].reduce(:merge))
end

primary_disclosure_button.button "Toggle", controls: "a_disclosure", expanded: true
#=> <button class="py-2 px-4 font-semibold shadow-md focus:outline-none focus:ring-2 bg-black rounded-lg text-white hover:bg-yellow-300 focus:ring-yellow-300 focus:ring-opacity-75"
#=>         aria-controls="a_disclosure" aria-expanded="true"
#=>         data-controller="disclosure" data-action="click->disclosure#toggle" data-disclosure-element-outlet="#a_disclosure">
#=>   Toggle
#=> </button>

primary_disclosure_button.summary "Toggle"
#=> <summary class="py-2 px-4 font-semibold shadow-md focus:outline-none focus:ring-2 bg-black rounded-lg text-white hover:bg-yellow-300 focus:ring-yellow-300 focus:ring-opacity-75"
#=>          data-controller="disclosure" data-action="click->disclosure#toggle">
#=>   Toggle
#=> </summary>

#with_attributes

Inspired by #with_options, the #with_attributes is a short-hand method that combines any number of Hash-like arguments into an ActionView::Attributes instance, then passes that along as an argument to #with_options.

The #with_attributes method available both as an Action View helper method and as tag instance method.

with_attributes {class: "border rounded-full"}, {class: "p-4"}, class: "focus:outline-none focus:ring" do |styled|
  styled.link_to "A link", "/a-link"
  # => <a class="border rounded-full p-4 focus:outline-none focus:ring" href="/a-link">A link</a>

  styled.button_tag "A button", type: "button"
  # => <button class="border rounded-full p-4 focus:outline-none focus:ring" name="button" type="button">A button</button>
end

builder = tag.with_attributes {class: "border rounded-full"}, {class: "p-4"}, class: "focus:outline-none focus:ring"

builder.a "A link", href: "/a-link"
# => <a class="border rounded-full p-4 focus:outline-none focus:ring" href="/a-link">A link</a>

builder.button_tag "A button", type: "button"
# => <button class="border rounded-full p-4 focus:outline-none focus:ring" type="button">A button</button>

Installation

Add this line to your application's Gemfile:

gem 'attributes_and_token_lists', github: "seanpdoyle/attributes_and_token_lists", branch: "main"

And then execute:

$ bundle

Or install it yourself as:

$ gem install attributes_and_token_lists

Contributing

Contribution directions go here.

License

The gem is available as open source under the terms of the MIT License.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •