class ActionPresenter::Base

Constants

BOOTSTRAP_SIZES
PRESENTER_ATTRIBUTES
PRESENTER_CLASSES

inheritance tracking

Attributes

form_builder[R]
record[RW]
view[RW]

Public Class Methods

define_extension(base_method, new_method, *extension_params) click to toggle source
# File lib/action_presenter/base.rb, line 117
def define_extension(base_method, new_method, *extension_params)
  define_method(new_method) do |*params, &content_block|
    send(base_method, *params, *extension_params.deep_dup, &content_block)
  end
end
generate_bootstrap_presenter_methods!() click to toggle source
# File lib/action_presenter/base/bootstrap.rb, line 4
def self.generate_bootstrap_presenter_methods!
  ["", "offset"].each do |namespace|
    define_method("col_#{namespace}_class".squeeze("_")) do |*hashes, &content_block|
      classes = []
      BOOTSTRAP_SIZES.each do |col_size|
        col_width = extract("col_#{col_size}", *hashes).to_i.nonzero?
        if col_width
          col_width += 1 if extract("#{col_size}_expand", *hashes) == true && col_width < 11
          col_width -= 1 if extract("#{col_size}_compact", *hashes) == true && col_width > 2
          raise ArgumentError, "col_width must be between 1 and 12" if !col_width.between?(1,12)
          # col-sm-3
          classes << "col-#{col_size}-#{namespace}-#{col_width}".squeeze("-")
        end
      end
      [extract_html(*hashes)[:class], *classes].join(" ")
    end
  end

  define_method(:col_div) do |*hashes, &content_block|
    classes = []
    BOOTSTRAP_SIZES.each do |col_size|
      col_width = extract("col_#{col_size}", *hashes).to_i.nonzero?
      col_offset_width = extract("col_#{col_size}_offset", *hashes).to_i.nonzero?
      if col_width
        col_width += 1 if extract("#{col_size}_expand", *hashes) == true && col_width < 11
        col_width -= 1 if extract("#{col_size}_compact", *hashes) == true && col_width > 2
        classes << col_class("col_#{col_size}" => col_width)
      end
      if col_offset_width
        col_offset_width += 1 if extract("#{col_size}_offset_expand", *hashes) == true && col_offset_width < 11
        col_offset_width -= 1 if extract("#{col_size}_offset_compact", *hashes) == true && col_offset_width > 2
        raise ArgumentError, "col_width must be between 1 and 12" if !col_offset_width.between?(1,12)
        classes << col_offset_class("col_#{col_size}" => col_offset_width)
      end
    end
    div_tag(*hashes, add_class: classes.join(" "), &content_block)
  end

  define_method(:check_box_div_label) do |*hashes, &content_block|
    content = extract_content(*hashes, &content_block)
    check_box_div(*hashes) { label_tag(content: content) }
  end

  define_method(:label_span) do |label, *hashes, &content_block|
    span_tag(*hashes, add_class: "label label-#{label}", &content_block)
  end

  define_method(:alert_div) do |alert, *hashes, &content_block|
    div_tag(*hashes, class: "alert alert-#{alert} alert-dismissible", role: "alert", &content_block)
  end

  define_method(:dismiss_button) do |target, *hashes, &content_block|
    button_tag(*hashes, "aria-label" => "Dismiss", data: { dismiss: target.to_s }, &content_block)
  end

  define_method(:dropdown_toggle_btn) do |*hashes, &content_block|
    btn = extract(:btn, *hashes, btn: "default")
    button_tag(*hashes, class: "btn btn-#{btn} dropdown-toggle", data: { toggle: "dropdown" }, &content_block)
  end

  define_method(:glyphicon) do |glyphicon, *hashes, &content_block|
    span_tag(*hashes, add_class: "glyphicon glyphicon-#{glyphicon}", &content_block)
  end

  # override core classes
  {
    core_label_class:     -> { "control-label" },
    core_select_class:    -> { "form-control" },
    core_field_class:     -> { "form-control" },
    core_text_area_class: -> { "form-control" },
  }.each do |method, block|
    define_method(method, &block)
  end

  # div
  define_extension(:div_tag, :row_div,         class: "row")
  define_extension(:div_tag, :form_group_div,  class: "form-group")
  define_extension(:div_tag, :dropdown_div,    class: "dropdown")
  define_extension(:div_tag, :check_box_div,   class: "checkbox")
  # span
  define_extension(:span_tag, :caret_span, class: "caret")
  # ul
  define_extension(:ul_tag, :dropdown_menu_ul, class: "dropdown-menu", role: "menu")
  # label
  define_extension(:label_tag, :radio_label,    class: "radio")
  define_extension(:label_tag, :check_box_label, class: "checkbox")
end
inherited(new_presenter_class) click to toggle source
Calls superclass method
# File lib/action_presenter/base.rb, line 6
def self.inherited(new_presenter_class)
  PRESENTER_CLASSES << new_presenter_class
  super
end
new() click to toggle source

core methods

# File lib/action_presenter/base.rb, line 12
def initialize
end
presenter_options() click to toggle source
# File lib/action_presenter/base.rb, line 123
def presenter_options
  @presenter_options ||= ActiveSupport::OrderedOptions.new.merge(
    verify_content: true,
    presents: name.underscore.remove(/_presenter\z/).singularize.to_sym,
  )
end

Public Instance Methods

button_tag(*hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 33
def button_tag(*hashes, &content_block)
  default_html = { type: "button" }
  html_tag(:button, *hashes, default_html, &content_block)
end
check_box(name, *hashes) click to toggle source
# File lib/action_presenter/base/form.rb, line 76
def check_box(name, *hashes)
  default_html = { class: check_box_class }
  checked_value   = extract(:checked_value, *hashes, checked_value: "true")
  unchecked_value = extract(:unchecked_value, *hashes, unchecked_value: "false")
  form_builder.check_box(name, extract_html(*hashes, default_html), checked_value, unchecked_value)
end
check_box_tag(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 92
def check_box_tag(name, *hashes, &content_block)
  default_html    = { id: name.to_s.slugify_html, name: name, class: check_box_class }
  checked_value   = extract_content(*hashes, content: "true").presence
  unchecked_value = extract(:unchecked_value, *hashes).presence
  checked         = extract(:checked, *hashes, checked: view.params.to_unsafe_h.dig_html(name).presence == checked_value).present?
  check_box_html  = extract_html(*hashes, default_html)
  content = "".html_safe
  if unchecked_value.present?
    content += view.check_box_tag(nil, unchecked_value, true, default_html.merge(id: nil, class: nil, style: "display: none;"))
  end
  content + view.check_box_tag(nil, checked_value, checked, check_box_html)
end
date_select(name, *hashes) click to toggle source
# File lib/action_presenter/base/form.rb, line 54
def date_select(name, *hashes)
  default_html = { class: date_select_class }
  hashes << { order: %i[ month day year ] }
  select_options = {}
  %i[
    include_blank prompt
    order default start_year end_year add_month_numbers use_short_month
  ].each { |key| select_options[key] = extract(key, *hashes) }
  form_builder.date_select(name, select_options.compact, extract_html(*hashes, default_html))
end
datetime_select(name, *hashes) click to toggle source
# File lib/action_presenter/base/form.rb, line 65
def datetime_select(name, *hashes)
  default_html = { class: datetime_select_class }
  hashes << { order: %i[ month day year ], ampm: true }
  select_options = {}
  %i[
    include_blank prompt
    ampm order default start_year end_year
  ].each { |key| select_options[key] = extract(key, *hashes) }
  form_builder.datetime_select(name, select_options, extract_html(*hashes, class: datetime_select_class))
end
extract(key, *hashes) click to toggle source
# File lib/action_presenter/base.rb, line 28
def extract(key, *hashes)
  hashes.compact.map do |hash|
    begin
      hash.delete(key.to_sym) || hash.delete(key.to_s)
    rescue StandardError => error
      raise ArgumentError, "#{error.class} while trying to extract key #{key.inspect} from #{hash.inspect}: #{error.message}"
    end
  end.compact.first
end
extract_content(*hashes, &content_block) click to toggle source
# File lib/action_presenter/base.rb, line 85
def extract_content(*hashes, &content_block)
  content = extract(:content, *hashes)
  # block has highest precedence
  content = view.capture(&content_block) if content_block.is_a?(Proc)
  content = content.to_s if content.is_a?(Numeric)
  # check for escaped HTML
  if presenter_options.verify_content && !content.is_a?(ActiveSupport::SafeBuffer)
    if content =~ /<[a-zA-Z]+( |>)/ || content =~ /&nbsp/
      warning_message = "Escaped HTML in #{self.class} content: #{content}"
      if Rails.env.production?
        Rails.logger.warn { warning_message }
      else
        raise ArgumentError, warning_message
      end
    end
  end
  content
end
extract_content_block(*hashes, &content_block) click to toggle source
# File lib/action_presenter/base.rb, line 104
def extract_content_block(*hashes, &content_block)
  content_block.is_a?(Proc) ? content_block : proc { extract_content(*hashes) }
end
extract_html(*hashes) click to toggle source
# File lib/action_presenter/base.rb, line 38
def extract_html(*hashes)
  html_attributes = {}
  add_classes = []
  remove_classes = []
  merge_data = {}
  reverse_merge_data = {}
  hashes.each do |hash|
    next if hash.blank?
    # custom class attributes
    add_class = extract(:add_class, hash)
    add_classes << add_class if add_class.present?
    remove_class = extract(:remove_class, hash)
    remove_classes << remove_class if remove_class.present?
    # custom data attributes
    merging_data = extract(:merge_data, hash)
    merge_data.merge!(merging_data.to_h.symbolize_keys) if merging_data.present?
    reverse_merging_data = extract(:reverse_merge_data, hash)
    reverse_merge_data.merge!(reverse_merging_data.to_h.symbolize_keys) if reverse_merging_data.present?
    # rest
    html_attributes.reverse_merge!(hash.symbolize_keys)
  end
  # custom class attributes
  if add_classes.present? || remove_classes.present?
    classes_to_a = -> (value) do
      case value
      when Symbol then classes_to_a.call(send(value))
      when Array then value.map { |v| classes_to_a.call(v) }
      else value.to_s.split(/\s/)
      end.flatten
    end
    classes = classes_to_a.call(html_attributes[:class])
    classes += classes_to_a.call(add_classes)
    classes -= classes_to_a.call(remove_classes)
    # reverse so classes appear to append in order
    html_attributes[:class] = classes.uniq.join(" ").presence
  end
  # custom data attributes
  if merge_data.present? || reverse_merge_data.present?
    html_attributes[:data] ||= {}
    html_attributes[:data].merge!(merge_data) if merge_data.present?
    html_attributes[:data].reverse_merge!(reverse_merge_data) if reverse_merge_data.present?
  end
  html_attributes.select do |html_attribute, value|
    !html_attribute.in?(PRESENTER_ATTRIBUTES)
  end
end
extract_query_params(*hashes) click to toggle source
# File lib/action_presenter/base.rb, line 112
def extract_query_params(*hashes)
  extract(:query_params, *hashes, query_params: {})
end
extract_record(*hashes) click to toggle source
# File lib/action_presenter/base.rb, line 108
def extract_record(*hashes)
  extract(:record, *hashes, record: record)
end
file_field_tag(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 70
def file_field_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: file_field_class }
  view.file_field_tag(name, extract_html(*hashes, default_html))
end
form_builder=(new_form_builder) click to toggle source
# File lib/action_presenter/base/form.rb, line 20
def form_builder=(new_form_builder)
  @form_builder = new_form_builder
  self.record = form_builder.try(:object)
  @form_builder
end
form_for(*hashes) { |form_builder| ... } click to toggle source
# File lib/action_presenter/base/form.rb, line 12
def form_for(*hashes, &content_block)
  record = extract_record(*hashes)
  view.form_for(record, extract_html(*hashes)) do |form_builder|
    self.form_builder = form_builder
    yield(form_builder)
  end
end
form_tag(path, *hashes) { |form_builder| ... } click to toggle source
# File lib/action_presenter/base/form.rb, line 5
def form_tag(path, *hashes, &content_block)
  view.form_tag(path, extract_html(*hashes)) do |form_builder|
    self.form_builder = form_builder
    yield(form_builder)
  end
end
hidden_field(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/form.rb, line 37
def hidden_field(name, *hashes, &content_block)
  default_html = { value: extract_content(*hashes, &content_block) }.compact
  form_builder.hidden_field(name, extract_html(*hashes, default_html))
end
hidden_field_tag(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 75
def hidden_field_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name }
  content = extract_content(*hashes, content: view.params.to_unsafe_h.dig_html(name), &content_block)
  view.hidden_field_tag(name, content, extract_html(*hashes, default_html))
end
html_tag(tag, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 4
def html_tag(tag, *hashes, &content_block)
  view.content_tag(tag, extract_html(*hashes), &extract_content_block(*hashes, &content_block))
end
image_tag(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 46
def image_tag(name, *hashes, &content_block)
  view.image_tag(name, extract_html(*hashes), &extract_content_block(*hashes, &content_block))
end
inline_html_tag(tag, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 8
def inline_html_tag(tag, *hashes, &content_block)
  view.tag(tag, extract_html(*hashes), &extract_content_block(*hashes, &content_block))
end
label(name, *hashes, &content_block) click to toggle source

form elements

# File lib/action_presenter/base/form.rb, line 27
def label(name, *hashes, &content_block)
  default_html = { class: label_class }
  form_builder.label(name, extract_html(*hashes, default_html), &extract_content_block(*hashes, &content_block))
end
label_for(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 87
def label_for(name, *hashes, &content_block)
  default_html = { class: label_for_class }
  view.label_tag(name, extract_html(*hashes, default_html), &extract_content_block(*hashes, &content_block))
end
method_field(*hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 42
def method_field(*hashes, &content_block)
  hidden_field_tag("_method", *hashes, id: nil, value: "post", &content_block)
end
presenter_options() click to toggle source
# File lib/action_presenter/base.rb, line 24
def presenter_options
  self.class.presenter_options.dup
end
radio_button(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/form.rb, line 83
def radio_button(name, *hashes, &content_block)
  default_html = { class: radio_button_class }
  content = extract_content(*hashes, &content_block).to_s
  form_builder.radio_button(name, content, extract_html(*hashes, default_html))
end
radio_button_tag(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 105
def radio_button_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: radio_button_class }
  content = extract_content(*hashes, &content_block).presence
  active_content = view.params.to_unsafe_h.dig_html(name).presence
  checked = extract(:checked, *hashes)
  checked = checked.in?([true, false]) ? checked : active_content == content
  view.radio_button_tag(nil, content, checked, extract_html(*hashes, default_html))
end
select(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/form.rb, line 47
def select(name, *hashes, &content_block)
  default_html = { class: select_class }
  content = extract_content(*hashes, &content_block) || {}
  select_options = %i[ include_blank prompt ].map { |key| [key, extract(key, *hashes)] }.to_h
  form_builder.select(name, content, select_options, extract_html(*hashes, default_html))
end
select_tag(name, *hashes, &content_block) click to toggle source

form elements

# File lib/action_presenter/base/html.rb, line 55
def select_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: select_class }
  content = extract_content(*hashes, &content_block)
  if !content.is_a?(String)
    content = view.options_for_select(content, extract(:selected, *hashes, selected: view.params.to_unsafe_h.dig_html(name)))
  end
  view.select_tag(name, content, extract_html(*hashes, default_html))
end
set(options) click to toggle source
# File lib/action_presenter/base.rb, line 19
def set(options)
  options.each { |method, value| send "#{method}=", value }
  self
end
submit_tag(*hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 38
def submit_tag(*hashes, &content_block)
  view.submit_tag(extract_content(*hashes, content: "Submit", &content_block), extract_html(*hashes))
end
text_area(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/form.rb, line 42
def text_area(name, *hashes, &content_block)
  default_html = { class: text_area_class, value: extract_content(*hashes, &content_block) }.compact
  form_builder.text_area(name, extract_html(*hashes, default_html))
end
text_area_tag(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 81
def text_area_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: text_area_class }
  content = extract_content(*hashes, content: view.params.to_unsafe_h.dig_html(name), &content_block)
  view.text_area_tag(name, content, extract_html(*hashes, default_html))
end
text_field(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/form.rb, line 32
def text_field(name, *hashes, &content_block)
  default_html = { class: text_field_class, value: extract_content(*hashes, &content_block) }.compact
  form_builder.text_field(name, extract_html(*hashes, default_html))
end
text_field_tag(name, *hashes, &content_block) click to toggle source
# File lib/action_presenter/base/html.rb, line 64
def text_field_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: text_field_class }
  content = extract_content(*hashes, content: view.params.to_unsafe_h.dig_html(name), &content_block)
  view.text_field_tag(name, content, extract_html(*hashes, default_html))
end