The project is in a healthy, maintained state
Merge hashes as HTML attributes, accounting for the specifics of `class`,`data-controller`, `data-action`
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 2.0.0
>= 0
~> 10.0
~> 3.0
~> 4.0.0

Runtime

>= 6.1.0
 Project Readme

MergeAttributes

Merge hashes of HTML attributes, properly aggregating class and other DOMTokenList-like fields like Stimulus' data-controller or data-action (or also ARIA's aria-labelledby or aria-describedby if you so chose).

The resulting Hash can then be provided to the tag.<tag name> helper to generate an HTML tag with the corresponding attributes (or to content_tag, or as HAML attributes with a double splat, or whatever needs a hash of attributes)

This allows finer control of the provenance on the attributes assigned to a given elements, like splitting its "own" attributes vs. those coming from the parent it's rendered in:

merge_attributes(
  # Styles responsible for the component's look itself
  {class: 'my-component'}, 
  {
    # Styles responsible for adjusting the component 
    # because it's rendered inside `parent-component`
    class: 'parent-component__child', 
    # Extra action that the element would not usually have
    data: {action: 'stimulus-controller#action'}
  }
)
# Creates: {class: 'my-component parent_component__child',data: {action: 'stimulus-controller#action'}}

This also opens the door to abstracting specific sets of components in their own helpers to provide specific vocabulary, say for configuring specific Stimulus controllers

merge_attributes(
  {class: 'my-component'},
  # Returns the right Stimulus controller/actions/values
  # to properly wire the element to open the given dialog
  dialog_trigger(dialog_id: 'my-dialog') 
)

Installation

Requirements

The helper delegate some of its behaviour to Rails 6.1 token_list helper.

If you're running on an older version of Rails, your could alias Primer ViewComponent's class_names helper, which replicates the same feature.

Installing the Gem

# Ideally replace the `branch: "main"` with a commit reference (`ref: COMMIT_HASH`)
# https://bundler.io/guides/git.html
gem "merge_attributes", github: "Amba-Health/merge_attributes", branch: "main"
Coming soon!

Add this line to your application's Gemfile:

gem 'merge_attributes'

And then execute:

bundle

Or install it yourself as:

gem install merge_attributes

Usage

 Providing the attributes

The method is flexible in the attributes it accepts and can be provided. All the examples bellow will generate the same final Hash of attributes:

{
    id: 'the-id',
    class: 'a-class',
    data:{ 
        dialog_id: 'confirm'
    }
}

The method supports:

  • Hashes

    merge_attributes({
        id: 'the-id'
    }, {
        class: 'a-class'
    },{
        data: {
            dialog_id: 'confirm'
        }
    })
  • an Array or Arrays of Hashes

    Nested arrays will be flattened, allowing you to directly pass a list of attributes you'd collected in another part of your app.

    merge_attributes([{
        id: 'the-id'
    }, [{
        class: 'a-class'
    }]],[{
        data: {
            dialog_id: 'confirm'
        }
    }])
  • Keyword arguments

    Any keyword argument (aside from token_list_attributes, see below) is treated as a final Hash of attributes

    merge_attributes({
        id: 'the-id',
        class: 'a-class'
    }, 
        data: {
            dialog_id: 'confirm'
        }
    )
  • Mix'n'match

    You can mix the different kind of attributes in a single call

    merge_attributes({
        id: 'the-id'
    }, [[{
        class: 'a-class'
    }]],
        data: {
            dialog_id: 'confirm'
        }
    )

Any .blank? value (after pre-processing, see below) will be ignored.

Token list attributes

Attributes will generally be deep_merged, the value of the latest one in the list replacing any existing ones.

This model doesn't really work for the class attribute, where it's preferable that the values get concatenated with a space. Same goes for other attributes, like data-action or data-controller from Stimulus.

Out of the box, merge_attributes will concatenate the values of these attributes rather than replace them:

merge_attributes({
    class: 'my-component'
}, {
    class: 'parent-component__child'
})

generates

{
    class: 'my-component parent-component__child'
}

Attributes format

The concatenation is done using Rails's token_list helper. This means it accepts not only Strings, but Arrays of Strings or Hashes with true or false values:

merge_attributes({
    class: 'my-component'
}, {
    class: ['parent-component__child','my-component--variation']
}, {
    class: {
        active: true
    }
})

 Adding other attributes

You may want to treat other attributes that way. ARIA's aria-labelledby and aria-describedby would be great candidates for it for example.

The method accepts the token_list_attributes keyword argument for providing a list of attributes to concatenate:

merge_attributes({
    'aria-labelledby': 'delete_action'
}, {
    'aria-labelledby': 'user_1'
},
    token_list_attributes: [
        *MergeAttributes::DEFAULT_TOKEN_LIST_ATTRIBUTES,
        'aria-labelledby'
    ]
)

As illustrated in the example, the MergeAttributes::DEFAULT_TOKEN_LIST_ATTRIBUTES will help you add to the default list.

 Pre-processing

The method accepts a block that'll let you process the attributes prior to their merging (but after the different arguments have been collected and flattened into a single Array).

This is the perfect time to call to_h if some of the attributes are not hashes already. Or resolve any conflicts between attributes in the data hash and as data-... keys.

The block will be provided:

  • the current Hash being pre-processed
  • its index in the whole attribute list
  • the attribute list itself

It is expected to return the transformed list of attributes (or a .blank? value if you want to filter it out)

merge_attributes({
    class: 'my-component'
}, class: {
    class: 'parent-component__child'
}) do |attributes, index, attributes_list|
    attributes.merge({
        # Dummy example
        "data-item-#{index}": index
    })
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Amba-Health/merge_attributes. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

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

Code of Conduct

Everyone interacting in the MergeAttributes project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.