module SimpleAdmin module FilterHelper def filter_fields(attributes) attributes.map do |col| options = col[:options].dup expand_block_options!(options) case col[:kind] when :attribute, :filter filter_for(col[:attribute], @interface.constant, options) when :content instance_exec(self, &col[:data]) when :fieldset content_tag :fieldset, options do content_tag :legend do options[:legend] end unless options[:legend].blank filter_fields(col[:attributes]) end else content_tag :div, options do filter_fields(col[:attributes]) end end end.join.html_safe end def filter_for(method, klass, options={}) options[:as] ||= default_filter_type(klass, method) return "" unless options[:as] field_type = options.delete(:as) wrapper_options = options[:wrapper_html] || {} wrapper_options[:class] = (wrapper_options[:class] || "") + " filter_form_field filter_#{field_type}" content_tag :div, wrapper_options do send("filter_#{field_type}_input", klass, method, options) end end def filter_string_input(klass, method, options = {}) field_name = "#{method}_cont" label_content = options[:title] || options[:label] || "Search #{method.to_s.titlecase}" unless options[:label] == false content = [] content << label(field_name, label_content) unless label_content.blank? content << text_field_tag(field_name, params[field_name] || '', options[:input_html] || {}) content.join("\n").html_safe end def filter_date_range_input(klass, method, options = {}) gt_field_name = "#{method}_gteq" lt_field_name = "#{method}_lteq" label_content = options[:title] || options[:label] || "#{method.to_s.titlecase}" unless options[:label] == false content = [] content << label(gt_field_name, label_content) unless label_content.blank? content << filter_date_text_field(klass, gt_field_name) content << " - " content << filter_date_text_field(klass, lt_field_name) content.join("\n").html_safe end def filter_date_text_field(klass, method) current_value = params[method] || '' text_field_tag(method, current_value.respond_to?(:strftime) ? current_value.strftime("%Y-%m-%d") : current_value, :size => 12, :class => "datepicker", :max => 10) end def filter_numeric_input(klass, method, options = {}) field_name = "#{method}_numeric" filters = numeric_filters_for_method(method, options.delete(:filters) || default_numeric_filters) current_filter = current_numeric_scope(klass, filters) label_content = options[:title] || options[:label] || "#{method.to_s.titlecase}" unless options[:label] == false content = [] content << label(field_name, label_content) unless label_content.blank? content << select_tag('', options_for_select(filters, current_filter), {:onchange => "document.getElementById('#{method}_numeric').name = '' + this.value + '';"}.merge(options[:input_html] || {})) content << " " content << text_field_tag(current_filter, params[current_filter] || '', {:size => 10, :id => field_name}.merge(options[:input_html] || {})) content.join("\n").html_safe end def numeric_filters_for_method(method, filters) filters.collect{|scope| [scope[0], [method,scope[1]].join("_") ] } end # Returns the scope for which we are currently searching. If no search is available # it returns the first scope def current_numeric_scope(klass, filters) filters[1..-1].inject(filters.first){|a,b| params[b[1].to_sym] ? b : a }[1] end def default_numeric_filters [['Equal To', 'eq'], ['Greater Than', 'gt'], ['Less Than', 'lt']] end def filter_select_input(klass, method, options = {}) association_name = method.to_s.gsub(/_id$/, '').to_sym field_name = if reflection = reflection_for(klass, association_name) if [:has_and_belongs_to_many, :has_many].include?(reflection.macro) "#{association_name.to_s.singularize}_ids" else reflection.options[:foreign_key] || "#{association_name}_id" end else association_name end field_name = (field_name.to_s + "_eq") collection = find_collection_for_column(klass, association_name, options) label_content = options[:title] || options[:label] || "#{method.to_s.titlecase}" unless options[:label] == false content = [] content << label(field_name, label_content) unless label_content.blank? content << select_tag(field_name, options_for_select(collection, params[field_name.to_sym]), {:include_blank => options[:include_blank] || 'Any'}.merge(options[:input_html] || {})) content.join("\n").html_safe end def generate_association_input_name(klass, method) #:nodoc: if reflection = reflection_for(klass, method) if [:has_and_belongs_to_many, :has_many].include?(reflection.macro) "#{method.to_s.singularize}_ids" else reflection.options[:foreign_key] || "#{method}_id" end else method end.to_sym end def find_collection_for_column(klass, column, options) #:nodoc: Rails.logger.info("******** find_collection_for_column #{klass}, #{column}, #{options.to_json}") collection = if options[:collection] collection = options.delete(:collection) collection = collection.to_a if collection.is_a?(Hash) collection.map { |o| [pretty_format(o.first), o.last] } elsif reflection = reflection_for(klass, column) options[:find_options] ||= {} if conditions = reflection.options[:conditions] options[:find_options][:conditions] = reflection.klass.merge_conditions(conditions, options[:find_options][:conditions]) end collection = reflection.klass.find(:all, options[:find_options]) collection.map { |o| [pretty_format(o), o.id] } else boolean_collection(klass, column, options) end collection end def boolean_collection(klass, column, options) [['No', false], ['Yes', true]] end def filter_boolean_input(klass, method, options = {}) field_name = options[:name] || (generate_association_input_name(klass, method).to_s + "_in") label_content = options[:title] || options[:label] || "#{method.to_s.titlecase}" unless options[:label] == false content = [] content << check_box_tag(field_name, true, params[field_name.to_sym], options) content << label(field_name, label_content) unless label_content.blank? content.join("\n").html_safe end def filter_check_boxes_input(klass, method, options = {}) field_name = options[:name] || (generate_association_input_name(klass, method).to_s + "_in") collection = find_collection_for_column(klass, method, options) selected_values = params[field_name.to_sym] || [] checkboxes = content_tag :div, :class => "check_boxes_wrapper" do collection.map do |c| label = c.is_a?(Array) ? c.first : c value = c.is_a?(Array) ? c.last : c "" end.join("\n").html_safe end label_content = options[:title] || options[:label] || "#{method.to_s.titlecase}" unless options[:label] == false content = [] content << label(field_name, label_content) unless label_content.blank? content << checkboxes content.join("\n").html_safe end alias_method :filter_checkboxes_input, :filter_check_boxes_input # Returns the default filter type for a given attribute def default_filter_type(klass, method) if column = column_for(klass, method) case column.type when :date, :datetime return :date_range when :string, :text return :string when :integer return :select if reflection_for(klass, method.to_s.gsub('_id','').to_sym) return :numeric when :float, :decimal return :numeric when :boolean return :boolean end end if reflection = reflection_for(klass, method) return :select if reflection.macro == :belongs_to && !reflection.options[:polymorphic] end end # Returns the column for an attribute on the object being searched # if it exists. Otherwise returns nil def column_for(klass, method) klass.columns_hash[method.to_s] if klass.respond_to?(:columns_hash) end # Returns the association reflection for the method if it exists def reflection_for(klass, method) klass.reflect_on_association(method) if klass.respond_to?(:reflect_on_association) end end end