module ActiveScaffold
module Helpers
# Helpers that assist with the rendering of a Form Column
module SearchColumnHelpers
# This method decides which input to use for the given column.
# It does not do any rendering. It only decides which method is responsible for rendering.
def active_scaffold_search_for(column, options = nil)
options ||= active_scaffold_search_options(column)
record = options[:object]
if column.delegated_association
record = record.send(column.delegated_association.name) || column.active_record_class.new
options[:object] = record
end
# first, check if the dev has created an override for this specific field for search
if (method = override_search_field(column))
send(method, record, options)
# second, check if the dev has specified a valid search_ui for this column, using specific ui for searches
elsif column.search_ui && (method = override_search(column.search_ui))
send(method, column, options)
# third, check if the dev has specified a valid search_ui for this column, using generic ui for forms
elsif column.search_ui && (method = override_input(column.search_ui))
send(method, column, options)
# fourth, check if the dev has created an override for this specific field
elsif (method = override_form_field(column))
send(method, record, options)
# fallback: we get to make the decision
else
if column.association || column.virtual?
active_scaffold_search_text(column, options)
else # regular model attribute column
# if we (or someone else) have created a custom render option for the column type, use that
if (method = override_search(column.column.type))
send(method, column, options)
# if we (or someone else) have created a custom render option for the column type, use that
elsif (method = override_input(column.column.type))
send(method, column, options)
# final ultimate fallback: use rails' generic input method
else
# for textual fields we pass different options
text_types = %i[text string integer float decimal]
options = active_scaffold_input_text_options(options) if text_types.include?(column.column.type)
text_field(:record, column.name, options.merge(column.options))
end
end
end
rescue StandardError => e
logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column = :#{column.name} in #{controller.class}"
raise e
end
# the standard active scaffold options used for class, name and scope
def active_scaffold_search_options(column)
{:name => "search[#{column.name}]", :class => "#{column.name}-input", :id => "search_#{column.name}", :value => field_search_params[column.name.to_s]}
end
def search_attribute(column, record)
column_options = active_scaffold_search_options(column).merge(:object => record)
field = active_scaffold_search_for column, column_options
%(
- #{label_tag search_label_for(column, column_options), search_column_label(column, record)}
- #{field}
).html_safe
end
def search_label_for(column, options)
options[:id] unless %i[range integer decimal float string date_picker datetime_picker calendar_date_select].include? column.search_ui
end
##
## Search input methods
##
def active_scaffold_search_multi_select(column, options)
record = options.delete(:object)
associated = options.delete :value
associated = [associated].compact unless associated.is_a? Array
if column.association
associated.collect!(&:to_i)
method = column.options[:label_method] || :to_label
select_options = sorted_association_options_find(column.association, nil, record).collect do |r|
[r.send(method), r.id]
end
else
select_options = column.options[:options].collect do |text, value|
active_scaffold_translated_option(column, text, value)
end
end
return as_(:no_options) if select_options.empty?
active_scaffold_checkbox_list(column, select_options, associated, options)
end
def active_scaffold_search_select(column, html_options, options = {})
record = html_options.delete(:object)
associated = html_options.delete :value
if column.association
associated = associated.is_a?(Array) ? associated.map(&:to_i) : associated.to_i unless associated.nil?
method = column.association.belongs_to? ? column.association.foreign_key : column.name
select_options = sorted_association_options_find(column.association, false, record)
else
method = column.name
select_options = active_scaffold_enum_options(column, record).collect do |text, value|
active_scaffold_translated_option(column, text, value)
end
end
options = options.merge(:selected => associated).merge column.options
html_options.merge! column.options[:html_options] || {}
if html_options[:multiple]
active_scaffold_select_name_with_multiple html_options
else
options[:include_blank] ||= as_(:_select_)
active_scaffold_translate_select_options(options)
end
if (optgroup = options.delete(:optgroup))
select(:record, method, active_scaffold_grouped_options(column, select_options, optgroup), options, html_options)
elsif column.association
collection_select(:record, method, select_options, :id, column.options[:label_method] || :to_label, options, html_options)
else
select(:record, method, select_options, options, html_options)
end
end
def active_scaffold_search_text(column, options)
text_field :record, column.name, active_scaffold_input_text_options(options)
end
# we can't use active_scaffold_input_boolean because we need to have a nil value even when column can't be null
# to decide whether search for this field or not
def active_scaffold_search_boolean(column, options)
select_options = []
select_options << [as_(:_select_), nil]
select_options << [as_(:true), true]
select_options << [as_(:false), false]
select_tag(options[:name], options_for_select(select_options, ActiveScaffold::Core.column_type_cast(options[:value], column.column)), :id => options[:id])
end
# we can't use checkbox ui because it's not possible to decide whether search for this field or not
alias active_scaffold_search_checkbox active_scaffold_search_boolean
def active_scaffold_group_search_column(record, options)
select_tag 'search[active_scaffold_group]', options_for_select(active_scaffold_group_search_options, selected: field_search_params['active_scaffold_group'])
end
def active_scaffold_group_search_options
options = active_scaffold_config.field_search.group_options.collect do |text, value|
active_scaffold_translated_option(active_scaffold_group_column, text, value)
end
[[as_(:no_group), '']].concat options
end
def active_scaffold_group_column
return if active_scaffold_config.field_search.group_options.blank?
@_active_scaffold_group_column ||= begin
column = ActiveScaffold::DataStructures::Column.new(:active_scaffold_group, active_scaffold_config.model)
column.label = :group_by
column
end
end
def active_scaffold_search_null(column, options)
select_options = []
select_options << [as_(:_select_), nil]
select_options.concat ActiveScaffold::Finder::NULL_COMPARATORS.collect { |comp| [as_(comp), comp] }
select_tag(options[:name], options_for_select(select_options, options[:value]), :id => options[:id])
end
def field_search_params_range_values(column)
values = field_search_params[column.name.to_s]
return nil unless values.is_a? Hash
[values['opt'], values['from'].presence, values['to'].presence]
end
def active_scaffold_search_range_string?(column)
column.text? || column.search_ui == :string
end
def active_scaffold_search_range_comparator_options(column)
select_options = ActiveScaffold::Finder::NUMERIC_COMPARATORS.collect { |comp| [as_(comp.downcase.to_sym), comp] }
if active_scaffold_search_range_string?(column)
comparators = ActiveScaffold::Finder::STRING_COMPARATORS.collect { |title, comp| [as_(title), comp] }
select_options.unshift(*comparators)
end
if include_null_comparators? column
select_options.concat ActiveScaffold::Finder::NULL_COMPARATORS.collect { |comp| [as_(comp), comp] }
end
select_options
end
def include_null_comparators?(column)
return column.options[:null_comparators] if column.options.key? :null_comparators
if column.association
!column.association.belongs_to? || active_scaffold_config.columns[column.association.foreign_key].column&.null
else
column.column&.null
end
end
def active_scaffold_search_range(column, options, input_method = :text_field_tag, input_options = {})
opt_value, from_value, to_value = field_search_params_range_values(column)
select_options = active_scaffold_search_range_comparator_options(column)
if active_scaffold_search_range_string?(column)
text_field_size = 15
else
text_field_size = 10
end
opt_value ||= select_options[0][1]
from_value = controller.class.condition_value_for_numeric(column, from_value)
to_value = controller.class.condition_value_for_numeric(column, to_value)
from_value = format_number_value(from_value, column.options) if from_value.is_a?(Numeric)
to_value = format_number_value(to_value, column.options) if to_value.is_a?(Numeric)
html = select_tag("#{options[:name]}[opt]", options_for_select(select_options, opt_value),
:id => "#{options[:id]}_opt", :class => 'as_search_range_option')
from_options = active_scaffold_input_text_options(input_options.merge(:id => options[:id], :size => text_field_size))
to_options = from_options.merge(:id => "#{options[:id]}_to")
html << content_tag('span', :id => "#{options[:id]}_numeric", :style => ActiveScaffold::Finder::NULL_COMPARATORS.include?(opt_value) ? 'display: none' : nil) do
send(input_method, "#{options[:name]}[from]", from_value, input_options) <<
content_tag(
:span,
safe_join([' - ', send(input_method, "#{options[:name]}[to]", to_value, to_options)]),
:id => "#{options[:id]}_between", :class => 'as_search_range_between', :style => ('display: none' unless opt_value == 'BETWEEN')
)
end
content_tag :span, html, :class => 'search_range'
end
alias active_scaffold_search_string active_scaffold_search_range
def active_scaffold_search_integer(column, options)
active_scaffold_search_range(column, options, :number_field_tag, step: '1')
end
def active_scaffold_search_decimal(column, options)
active_scaffold_search_range(column, options, :number_field_tag, step: :any)
end
alias active_scaffold_search_float active_scaffold_search_decimal
def field_search_datetime_value(value)
Time.zone.local(value[:year].to_i, value[:month].to_i, value[:day].to_i, value[:hour].to_i, value[:minute].to_i, value[:second].to_i) unless value.nil? || value[:year].blank?
end
def active_scaffold_search_datetime(column, options)
_, from_value, to_value = field_search_params_range_values(column)
options = column.options.merge(options)
type = "#{'date' unless options[:discard_date]}#{'time' unless options[:discard_time]}"
use_select = options.delete(:use_select)
helper = use_select ? "select_#{type}" : "#{type}#{'_local' if type == 'datetime'}_field"
if use_select
default_from_options = {include_blank: true, prefix: "#{options[:name]}[from]"}
default_to_options = {include_blank: true, prefix: "#{options[:name]}[to]"}
end
safe_join [
send(helper, field_search_datetime_value(from_value), options.reverse_merge(default_from_options || {})),
send(helper, field_search_datetime_value(to_value), options.reverse_merge(default_to_options || {}))
], ' - '
end
def active_scaffold_search_date(column, options)
active_scaffold_search_datetime(column, options.merge!(:discard_time => true))
end
def active_scaffold_search_time(column, options)
active_scaffold_search_datetime(column, options.merge!(:discard_date => true))
end
alias active_scaffold_search_timestamp active_scaffold_search_datetime
##
## Search column override signatures
##
def search_column_label(column, record)
column.label
end
def override_search_field(column)
override_helper column, 'search_column'
end
# the naming convention for overriding search input types with helpers
def override_search(form_ui)
method = "active_scaffold_search_#{form_ui}"
method if respond_to? method
end
def visibles_and_hiddens(search_config)
visibles = []
hiddens = []
search_config.columns.each_column do |column|
next unless column.search_sql
if search_config.optional_columns.include?(column.name) && !searched_by?(column)
hiddens << column
else
visibles << column
end
end
if active_scaffold_group_column
columns = grouped_search? || search_config.optional_columns.empty? ? visibles : hiddens
columns << active_scaffold_group_column
end
[visibles, hiddens]
end
def searched_by?(column)
value = field_search_params[column.name.to_s]
case value
when Hash
value['from'].present?
when String
value.present?
else
false
end
end
end
end
end