erb_component

React-style front-end components but for ERB?


Keywords
ruby, erb, erb-component
License
MIT
Install
gem install erb_component -v 0.2.1

Documentation

ErbComponent

Why not have view components and write them in OOP style? It hides complexity. DRYes views.

FEEL FREE TO OPEN AN ISSUE OR HAVE SOME DISCUSSION.

USE WITH CAUTION! This is more like an experiment.

Installation

Add this line to your application's Gemfile:

gem 'erb_component'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install erb_component

Usage

Easiest example

It's important to define template method

class ImgTag < ErbComponent
  attr_reader :src

  # req is Rack::Request or ActionDispatch::Request (for Rails)
  def initialize(req, opts)
    @src = opts[:src]
    super
  end

  def template
    <<ERB
<img src="<%= src %>" />
ERB
  end
end

# Somewhere in views:
url = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Ruby_logo.svg/1920px-Ruby_logo.svg.png" 
ImgTag.(request, src: url) # or ImgTag.new(request, src: url).call
# => "<img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Ruby_logo.svg/1920px-Ruby_logo.svg.png\" />\n"

You can use more elegant Rails content_tag

class ImgContentTag < ErbComponent
  attr_reader :src

  def initialize(req, opts)
    @src = opts[:src]
    super
  end

  def template
    content_tag :img, nil, src: src
  end
end

More advanced nested example

Here TabNavs.(request, tab_list: %w(overview photos contact accommodations)) will generate Bootstrap's tab navigation, like here: https://getbootstrap.com/docs/5.0/components/navs-tabs/

class TabNavs < ErbComponent
  attr_reader :tab_list

  def initialize(req, tab_list:)
    @tab_list = tab_list
    super
  end

  def template
    <<ERB
<ul class="nav nav-tabs" id="myTab" role="tablist">
  <%= tab_list_rendered %>
</ul>
ERB
  end

  def tab_list_rendered
    active_found = false
    opts = tab_list.map do |i|
      if params[:active_tab] == i
        active_found = true
        {target: i, active: true}
      else
        {target: i, active: false}
      end
    end

    opts[0][:active] = true unless active_found

    opts.map do |i|
      NavItem.(req, **i)
    end.join("\n")
  end
end

class NavItem < ErbComponent
  attr_reader :target

  def initialize(req_params, active: false, target:)
    @req_params = req_params
    @active = active
    @target = target
    super
  end

  def template
    <<ERB
  <li class="nav-item" role="presentation">
    <button class="<%= button_class %>" id="<%= id %>" data-bs-toggle="tab" data-bs-target="#<%= target %>" type="button" role="tab" aria-controls="<%= target %>" aria-selected="true">
      <%= tab_name %>
    </button>
  </li>
ERB
  end

  def id
    "#{target}-tab"
  end

  def tab_name
    target.humanize
  end

  def button_class
    res = "nav-link"
    res += " active" if active?
    res
  end

  def active?
    @active
  end
end

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/arthurkarganyan/erb_component.

License

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