#81 Bootstrap Framework and Ruby on Rails

Summary

Using Bootstrap in your application has become much more simple. Also learn to create some helper methods to make working with Bootstrap much easier.
rails view css 9:18

Summary

Gemfilesource 'https://rails-assets.org' do
  gem 'rails-assets-jquery'
  gem 'rails-assets-bootstrap', '~> 4.0.0.alpha.6'
  gem 'rails-assets-tether'
end
application.js//= require jquery
...
//= require tether
//= require bootstrap
application.css*= require bootstrap
helpers/bootstrap/common_helper.rbmodule Bootstrap::CommonHelper
  ArgumentError = Class.new(::ArgumentError)
  
  # Returns a new Hash with:
  # * keys converted to Symbols
  # * the +:class+ key has its value converted to an Array of String
  # @example
  # canonicalize_options("id" => "ID", "class" => "CLASS") # => {:id=>"ID", :class=>["CLASS"]} 
  # canonicalize_options(:class => 'one two') # => {:class=>["one", "two"]}
  # canonicalize_options("class" => [:one, 2]) # => {:class=>["one", "2"]} 
  # @param [Hash] hash typically an +options+ param to a method call
  # @raise [ArgumentError] if _hash_ is not a Hash
  # @return [Hash]
  def canonicalize_options(hash)
    raise ArgumentError.new("expected a Hash, got #{hash.inspect}") unless hash.is_a?(Hash)

    hash.symbolize_keys.tap do |h|
      h[:class] = arrayify_and_stringify_elements(h[:class])
    end
  end

  # Returns a new Array of String from _arg_.
  # @example
  # arrayify_and_stringify_elements(nil) #=> [] 
  # arrayify_and_stringify_elements('foo') #=> ["foo"]
  # arrayify_and_stringify_elements('foo bar') #=> ["foo", "bar"]
  # arrayify_and_stringify_elements([:foo, 'bar']) #=> ["foo", "bar"]
  # @param [String, Array] arg
  # @return [Array of String]
  def arrayify_and_stringify_elements(arg)
    return false if arg == false
    
    case
    when arg.blank? then []
    when arg.is_a?(Array) then arg
    else arg.to_s.strip.split(/\s/)
    end.map(&:to_s)
  end
  
  # Returns down-caret character used in various dropdown menus.
  # @param [Hash] options html options for 
  # @example
  # caret(id: 'my-id') #=> 
  # @return [String]
  def caret(options={})
    options= canonicalize_options(options)
    options = ensure_class(options, 'caret')
    content_tag(:span, nil, options)
  end
  
  # Returns new (canonicalized) Hash where :class value includes _klasses_.
  # 
  # @example
  # ensure_class({class: []}, 'foo') #=> {class: 'foo'}
  # ensure_class({class: ['bar'], id: 'my-id'}, ['foo', 'foo2']) #=> {:class=>["bar", "foo", "foo2"], :id=>"my-id"}
  # @param [Hash] hash
  # @param [String, Array] klasses one or more classes to add to the +:class+ key of _hash_
  # @return [Hash]
  def ensure_class(hash, klasses)
    canonicalize_options(hash)
    
    hash.dup.tap do |h|
      Array(klasses).map(&:to_s).each do |k|
        h[:class] << k unless h[:class].include?(k)
      end
    end
  end

  # Returns extra arguments that are Bootstrap modifiers. Basically 2nd argument
  # up to (not including) the last (Hash) argument.
  #
  # @example
  # extract_extras('text') #=> []
  # extract_extras('text', :small, :info, id: 'foo') #=> [:small, :info]
  # @return [Array]
  def extract_extras(*args)
    args.extract_options!
    args.shift
    args
  end
  
  def bootstrap_generator(*args, bs_class, element, &block)
    options = canonicalize_options(args.extract_options!)
    options = ensure_class(options, bs_class)
  
    content = block_given? ? capture(&block) : args.shift
  
    content_tag(element.to_sym, options) do
      content
    end
  end
end
helpers/bootstrap/modal_helper.rbmodule Bootstrap::ModalHelper
  ArgumentError = Class.new(StandardError)
  def modal_trigger(text, options={})
    options = canonicalize_options(options)
    href = options.delete(:href) or raise(ArgumentError, 'missing :href option')
    options.merge!(role: 'button', href: href, data: { toggle: 'modal'})
    options = ensure_class(options, 'btn')
    
    content_tag(:a, text, options)
  end

  def modal(options={})
    options = canonicalize_options(options)
    options.has_key?(:id) or raise(ArgumentError, "missing :id option")
    options = ensure_class(options, %w(modal fade))
    content_tag(:div, options) do
      content_tag(:div, class: 'modal-dialog', role: :document) do
        content_tag(:div, class: 'modal-content') do
          yield
        end
      end
    end
  end
  
  def modal_header(*args, &block)
    options = canonicalize_options(args.extract_options!)
    options = ensure_class(options, 'modal-header')
  
    content = block_given? ? capture(&block) : args.shift

    button_content = content_tag(:button, class: :close, data: { dismiss: :modal }, aria: { label: 'Close' }) do
      content_tag(:span, "×".html_safe, aria: { hidden: true }).html_safe + content_tag(:span, "Close", class: 'sr-only')
    end

    content_tag(:div, options) do
      content_tag(:h4, content, class: 'modal-title') + button_content.html_safe
    end.html_safe

  end

  def modal_title(*args, &block)
    bootstrap_generator(*args, 'modal-title', :h4, &block)
  end
  
  def modal_body(*args, &block)
    bootstrap_generator(*args, 'modal-body', :div, &block)
  end

  def modal_footer(*args, &block)
    options = canonicalize_options(args.extract_options!)
    options = ensure_class(options, 'modal-footer')
  
    content = block_given? ? capture(&block) : args.shift
    button_close_content = content_tag(:button, 'Close', type: :button, class: 'btn btn-secondary', data: { dismiss: :modal })
    content_tag(:div, options) do
      button_close_content + content
    end
  end
end
helpers/bootstrap/card_helper.rbmodule Bootstrap::CardHelper
  def card(options={})
    options = canonicalize_options(options)
    options = ensure_class(options, %w(card))
    content_tag(:div, options) do
      content_tag(:div, class: 'card-block') do
        yield
      end
    end
  end
  
  def card_header(*args, &block)
    bootstrap_generator(*args, 'card-header', :h5, &block)
  end

  def card_title(*args, &block)
    bootstrap_generator(*args, 'card-title', :h5, &block)
  end
  
  def card_subtitle(*args, &block)
    bootstrap_generator(*args, 'card-subtitle mb-2 text-muted', :h6, &block)
  end
  
  def card_body(*args, &block)
    bootstrap_generator(*args, 'card-text', :p, &block)
  end
  
  def card_list(*args, &block)
    bootstrap_generator(*args, 'list-group list-group-flush', :ul, &block)
  end  

  def card_list_item(*args, &block)
    bootstrap_generator(*args, 'list-group-item', :li, &block)
  end
end


ninoM said over 1 year ago:

Would definitely love a separate more detailed video about creating helpers. Pretty please!

kobaltz PRO said over 1 year ago:

I've been thinking about making a helper gem for bootstrap. Waiting for the official release of v4 or at least a beta ( and time to actually do it ).

claudiob said over 1 year ago:

You might want to check out Bh http://fullscreen.github.io/bh -- a popular library (800 GitHub stars) that provides Bootstrap helpers for Ruby.

kobaltz PRO said over 1 year ago:

I saw that during my research. However, it didn't seem to support Bootstrap 4 yet. For < 4, it would definitely be a great option!

StupidLaptop said over 1 year ago:

Great episode! Thanks so much for all these, have been a massive help. Looking fwd to that gem!

Login to Comment