Creating Bootstrap Helpers
Ruby on Rails Software Development Learn Bootstrap
August 19, 2022 - Robert Cotey

Alerting with Bootstrap Helpers

The next set of helpers that I use a lot is the bootstrap Alerts classes. They are usually very simple, but a lot of times you want to write 2-3 lines of text but the normal features in use with an alert message requires quite a bit markup.

As a bonus, Rails also has flash messages. There not really the same thing as alerts but they are often displayed in alerts. In this article were going to add helpers for the alert class, then add in support for flash messages.

Again the goal is not to enforce a way of using the classes, just to create a few helpers to use the classes.

Looking at the normal use case

The simplest use case is just a div with some text in it. But normally, we want some colors, we want to be able to dismiss the alert. We will also want to support the heading option, though it’s not often used there is a really good use case for it that we will talk about in the next post. And finally, it would be great to also include an icon to help things stand out. But again, we don’t want to force the issue.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
  </symbol>
  <symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
  </symbol>
  <symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
  </symbol>
</svg>

<div class="alert alert-warning alert-dismissible fade show" role="alert">
  <h4 class="alert-heading">
    <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>
    Well done!
  </h4>
  <p><strong>I will be bold!</strong> You should really read this alert.</p>
  <hr/>
  <p>It's important and it will effect you</p>
  <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>

That’s quite a bit of markup. I don’t want to have to write that every time. Also because of the nature of the gem, I don’t want to assume that the svg sprite is available, nor do I want to manage including it. At least not at this point. So lets setup the helper and class.

Creating our AlertHelper Class

Again were going to try to keep things uniform, which means making a helper, and then passing around the object. Alerts can be very complex, but most of the time we don’t want them to be. The least we can do is hide the complexity in the helper, it’s class, and the view.

# lib/bootstrap_helpers/helpers/alert_helper.rb
module Bootstrap
  module AlertHelper
    def alert_message(text = '', level: 'warning', header: nil, icon: true, dismissible: true, &block)
      alert = BootstrapHelpers::Alert.new(self)
      icon = alert.generate_icon(icon, level)
      icon = nil unless block.nil?
      content = capture(alert, &block) unless block.nil?
      bs_render template: 'alert/alert', content: content, locals: { icon: icon, dismissible: dismissible, level: level, header: header, content: content, text: text }
    end
  end
end

As we can see here, we called the method alert_message. I would have preferred to use alert, but that method already exists. This is going to cause a problem later. I don’t want to prefix all the methods with bs_ or something like that, but I may end up not having a choice. For now we will call this alert_message and move on. You should also notice the call to alert.generate_icon. This returns either your custom icon, the svg for the level of alert, or nothing if you don’t want icons. Also, you can take a look at the method signature, you can see we have set some defaults. By default, we have icons, and were dismissible. This is what is most often wanted, but they can be overridden.

Take a look at the standard and named variables. This makes the call very similar to link_to where you can pass in a string or call a block.

# lib/bootstrap_helpers/alert.rb
def generate_icon(icon, level)
  return '' unless icon
  return icon.html_safe if icon.is_a? String

  case level.to_s
  when 'success'
    '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle-fill" viewBox="0 0 16 16">
      <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
    </svg>'.html_safe
  when 'info'
    '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle-fill" viewBox="0 0 16 16">
      <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
    </svg>'.html_safe
  else
    '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
      <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
    </svg>'.html_safe
  end
end

I’m not going to include all the methods in the Alert class, but did want to talk about generate_icon. It’s primary job is to render the svg for the icon, or if a custom icon is passed in, to just pass that along.

<!-- app/views/helpers/bootstrap/alert/_alert.html.erb -->
<div class="alert alert-<%= level %> <%= dismissible ? 'alert-dismissible fade show' : '' %>" role="alert">
  <% unless header.blank? %>
    <h4 class="alert-heading">
      <%= icon %> <%= header %>
    </h4>
  <% end %>
  <% if content.blank? %>
    <p>
      <% if header.blank? %><%= icon %><% end %>
      <%= text %>
    </p>
  <% else %>
    <% if header.blank? %><%= icon %><% end %>
    <%= content %>
  <% end %>
  <% if dismissible %>
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
  <% end %>
</div>

Again, I am not including all the views, but I did want to talk about this one. As you can see a lot of the mark up is here. Keeping this out of a top level project will add a lot of value. Let’s look at some good examples of how to use it.

  <%= alert_message 'You should look at this.' %>

This is the most basic example. It uses the defaults but makes a good alert box with a good set of features.

  <%= alert_message 'Good Job!', level: :success %>

You can also change levels and colors. If we need something more complex, then we can use more options.

  <%= alert_message 'You passed the test!', heading: 'Good Job', level: :success %>

Finally, if you need something really fancy, you can use the object and pass around some blocks.

  <%= alert_message do |a| %>
    <%= a.heading do |h| %>
      <%= h.icon level: 'success' %>
      I am the very model of a modern major general.
    <% end %>
    <p>
      You can find out more information
      <%= a.link 'by clicking this link', 'https://www.youtube.com/watch?v=zSGWoXDFM64' %>
    </p>
  <% end %>

Finally, lets go for the bonus. Lets add in some flash messages. Using our new alert_message helper.

<%= flash_messages %>

That’s all we need to do and we can handle, all of the normal flash messages.

flash[:success] = 'You have thirty minutes to move your car.'
flash[:warning] = 'Your car has been impounded,'
flash[:danger] = 'Your car has been crushed into a cube.'
flash[:info] = 'You have thirty minutes to move your cube.'

They do assume that you will be using the standard bootstrap colors but there’s no reason you can’t use your own symbols.

Under the hood we have some logic and markup:

<% flash.each do |key, value| %>
  <% next if value.blank? %>
  <%= row id: 'flash-row' do %>
    <%= col size: 12, md: { size: 8, offset:1 }, lg: { size: 6, offset: 3 } do %>
      <%= alert_message value.try(:html_safe), level: key.to_s %>
    <% end %>
  <% end %>
<% end %>

The Takeaway

It was very easy to extend the bootstrap helper gem to an area where we can really shorten the code were responsible for by using a few helpers. By added to the helper project we have also continued to highlight some areas that need attention in order to achieve better code reuse.