require 'action_view'
require 'action_view/helpers'
require 'action_view/helpers/form_helper'
module ActionView
module Helpers
module FormHelper
def field_set(object_name, name, content = nil, options = {}, &block)
options.delete(:object)
options[:name] ||= name
options[:id] ||= name
content ||= self.capture(&block) if block_given?
content_tag("fieldset", raw(content), options).html_safe
end
protected
def singular_class_name(name)
ActiveModel::Naming.singular(name)
end
def pick_form_builder(name)
name = "#{name.to_s.classify}FormBuilder"
name.constantize
rescue NameError
Object.const_set(name, Class.new(ActionView::Base.default_form_builder)) rescue ActionView::Base.default_form_builder
end
end
end
end
module Adva
class ExtensibleFormBuilder < ActionView::Helpers::FormBuilder
class_attribute :callbacks
self.callbacks = { :before => {}, :after => {} }
class_attribute :tabs
self.tabs = []
class_attribute :options
self.options = { :labels => false, :wrap => false, :default_class_names => {} }
class << self
[:labels, :wrap].each do |option|
define_method(:"#{option}=") { |value| self.options[option] = value }
end
def default_class_names(type = nil)
if type
self.options[:default_class_names][type] ||= []
else
self.options[:default_class_names]
end
end
def before(object_name, method, string = nil, &block)
add_callback(:before, object_name, method, string || block)
end
def after(object_name, method, string = nil, &block)
add_callback(:after, object_name, method, string || block)
end
def tab(name, options = {}, &block)
self.tabs.reject! { |n, b| name == n }
self.tabs += [[name, block]]
end
protected
def add_callback(stage, object_name, method, callback)
method = method.to_sym
callbacks[stage][object_name] ||= { }
callbacks[stage][object_name][method] ||= []
callbacks[stage][object_name][method] << callback
end
end
helpers = field_helpers + %w(select date_select datetime_select time_select time_zone_select collection_select) -
%w(hidden_field label fields_for apply_form_for_options!)
helpers.each do |method_name|
class_eval <<-src, __FILE__, __LINE__
def #{method_name}(*args, &block)
type = #{method_name.to_sym.inspect}
options = args.extract_options!
options = add_default_class_names(options, type)
# options = add_tabindex(options, type)
label, wrap, hint = options.delete(:label), options.delete(:wrap), options.delete(:hint)
name = args.first
hint = I18n.t(hint) if hint.is_a?(Symbol)
options[:title] = hint
with_callbacks(name) do
tag = super(*(args << options), &block)
# remember_tabindex(tag, options)
tag = labelize(type, tag, name, label) if label || self.options[:labels]
tag = wrap(tag) if wrap || self.options[:wrap]
tag
end
end
src
end
def field_set(*args, &block)
options = args.extract_options!
options = add_default_class_names(options, :field_set)
name = args.first
name ||= :default_fields
@template.concat with_callbacks(name) {
legend = options.delete(:legend) || ''
legend = @template.content_tag('legend', legend) unless legend.blank?
@template.field_set(@object_name, name, nil, objectify_options(options)) do
legend.to_s + (block ? block.call.to_s : '')
end
}
end
def tabs
yield if block_given?
assign_ivars!
@template.content_tag(:div, :class => 'tabs') {
self.class.tabs.map.with_index { |(name, _), index|
active = self.class.tabs.first.first == name
%()
}.join.html_safe +
@template.content_tag(:ul) {
self.class.tabs.map.with_index { |(name, _), index|
@template.content_tag(:li) {
title = I18n.t(name, :scope => :'adva.titles')
%().html_safe
}
}.join.html_safe
} +
self.class.tabs.map.with_index { |(name, block), index|
klass = self.class.tabs.first.first == name ? 'tab active' : 'tab'
@template.content_tag 'fieldset', block.call(self), id: "tab_#{name}", class: klass, for: "adva_current_tab_#{index}"
}.join.html_safe
}.html_safe
end
def tab(name, &block)
with_callbacks(:"tab_#{name}") {
self.class.tab(name, &block)
}
end
def buttons(name = :submit_buttons, &block)
@template.concat with_callbacks(name) {
@template.capture { @template.buttons(&block) }
}
end
def render(*args)
@template.send(:render, *args)
end
protected
def labelize(type, tag, method, label = nil)
label = case label
when String then label
when Symbol then I18n.t(label)
when TrueClass then
scope = [:activerecord, :attributes] + object.class.to_s.underscore.split('/')
string = I18n.t(method, :scope => scope)
string.is_a?(String) ? string : method.to_s.titleize
else nil
end
case type
when :check_box, :radio_button
tag + self.label(method, label, :class => 'inline light', :for => extract_id(tag), :id => "#{extract_id(tag)}_label")
else
self.label(method, label) + tag
end
end
def wrap(tag)
@template.content_tag(:p, tag)
end
def hint(tag, hint)
tag + @template.content_tag(:span, "", title: hint, class: 'hint', for: extract_id(tag))
end
def add_default_class_names(options, type)
options[:class] = (Array(options[:class]) + self.class.default_class_names(type)).join(' ')
options.delete(:class) if options[:class].blank?
options
end
def tabindex_increment!
@tabindex_count ||= 0
@tabindex_count += 1
end
def set_tabindex_position(index = nil, position = nil)
position = case position
when :after then tabindexes[index] + 1
when :before then tabindexes[index] - 1
when :same then tabindexes[index]
else tabindex_increment!
end
position
end
def add_tabindex(options, type)
index = options[:tabindex]
if index.is_a?(Hash)
key = index.keys.first
options[:tabindex] = set_tabindex_position(index[key], key)
elsif index.is_a?(Symbol)
options[:tabindex] = set_tabindex_position(index, :same)
elsif index.blank?
options[:tabindex] = set_tabindex_position
end
options
end
def tabindexes
@tabindexes ||= {}
end
def remember_tabindex(tag, options)
id = extract_id(tag)
tabindexes[:"#{id}"] = options[:tabindex] unless id.blank?
end
def with_callbacks(method, &block)
result = ''
result += run_callbacks(:before, method) if method
result += yield.to_s
result += run_callbacks(:after, method) if method
result.html_safe
end
def run_callbacks(stage, method)
if callbacks = callbacks_for(stage, method.to_sym)
callbacks.inject('') do |result, callback|
result + case callback
when Proc
assign_ivars!
instance_eval(&callback)
else
callback
end.to_s
end
end || ''
end
def callbacks_for(stage, method)
object_name = @object_name.try(:to_sym)
self.callbacks[stage][object_name] and
self.callbacks[stage][object_name][method.to_sym]
end
def assign_ivars!
unless @ivars_assigned
@template.assigns.each { |key, value| instance_variable_set("@#{key}", value) }
vars = @template.controller.instance_variable_names
vars.each { |name| instance_variable_set(name, @template.controller.instance_variable_get(name)) }
@ivars_assigned = true
end
end
# yep, we gotta do this crap because there doesn't seem to be a sane way
# to hook into actionview's form_helper methods
def extract_id(tag)
tag =~ /id="([^"]+)"/
$1
end
end
end
ActionView::Base.default_form_builder = Adva::ExtensibleFormBuilder