# Gemfile
source '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.rb
module 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.rb
module 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.rb
module 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