jameshuynh/formnestic

View on GitHub
lib/formnestic/form_builder/table_form_builder.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

# rubocop:disable MethodLength, Documentation, ModuleLength, AbcSize
module Formnestic
  module FormBuilder
    module TableFormBuilder
      include Formnestic::FormBuilder::BaseBuilder
      def formnestic_table_semantic_fields_for(
        record_or_name_or_array, *args, &block
      )
        options = args.dup.extract_options!
        formnestic_add_table_headers_form_attributes
        formnestic_add_rows_counter_related_attributes

        child_form_builder = nil
        contents = [formtastic_semantic_fields_for(
          record_or_name_or_array,
          *args,
          &lambda { |nested_form_builder|
            child_form_builder = nested_form_builder
            yield(nested_form_builder)
          }
        )]

        if resolve_row_addable(options[:row_addable])
          contents.push(
            formnestic_add_new_record_button_row_for_table(
              record_or_name_or_array, *args, &block
            )
          )
        end
        options[:class] = [
          'formnestic-table-inputs formnestic-nested-model',
          options[:class]
        ].compact.join(' ')
        options[:min_entry] ||= -1
        options[:max_entry] ||= -1
        options[:min_entry_alert_message] =
          formnestic_min_entry_alert_message(
            record_or_name_or_array, options[:min_entry]
          )

        ### Finally, wrap everything inside a table tag
        template.content_tag(:table, [
          formnestic_table_header(
            options,
            record_or_name_or_array,
            child_form_builder
          ), # table header
          template.content_tag(
            :tbody,
            Formtastic::Util.html_safe(contents.join)
          ) # table body
        ].join.html_safe, options.except(
                            :builder,
                            :parent,
                            :name,
                            :parent_builder,
                            :display_type,
                            :row_removable,
                            :new_record_link_label,
                            :table_headers
                          ))
      end

      def resolve_row_addable(row_addable)
        return row_addable if [true, false].index(row_addable)
        return true if row_addable.nil?

        return row_addable.call if row_addable.is_a?(Proc)

        true
      end

      def formnestic_add_table_headers_form_attributes
        instance_eval do
          instance_variable_set('@table_headers', [])
          def table_headers
            @table_headers
          end

          def add_table_header(column_name, class_name, label_text)
            return if @table_headers.detect do |x|
              x[:attr] == column_name
            end.present?

            @table_headers.push(
              attr: column_name,
              class:
                class_name
                  .to_s
                  .tableize
                  .gsub('formtastic/inputs/', '')
                  .tr('_', '-')
                  .gsub('-inputs', ''),
              label_text: label_text
            )
          end
        end
      end

      def formnestic_add_new_record_button_row_for_table(
        record_or_name_or_array,
        *args,
        &block
      )
        template.content_tag(
          :tr,
          template.content_tag(
            :td,
            formnestic_link_to_add_fields_with_content(
              record_or_name_or_array, *args, &block
            ), colspan: '100%'
          ), class: 'formnestic-table-no-border'
        )
      end

      def formnestic_table_header(
        header_options, record_or_name_or_array, child_form_builder
      )
        thead_contents = if header_options[:table_headers]
                           formnestic_thead_contents_from_arguments(
                             header_options[:table_headers],
                             record_or_name_or_array, child_form_builder
                           )
                         else
                           formnestic_thead_contents_from_inputs(
                             header_options[:row_removable]
                           )
                         end
        template.content_tag(:thead, thead_contents.join.html_safe)
      end

      def formnestic_thead_contents_from_inputs(row_removable)
        tr_content_arr = []
        table_headers.each do |header|
          tr_content_arr.push(
            template.content_tag(
              :th,
              header[:label_text],
              class: header[:class]
            )
          )
        end
        if row_removable != false
          tr_content_arr.push(
            template.content_tag(
              :th, '', class: 'formnestic-minus-thead'
            )
          )
        end
        [template.content_tag(:tr, tr_content_arr.join.html_safe)]
      end

      def formnestic_thead_contents_from_arguments(
        theads,
        record_or_name_or_array,
        child_form_builder
      )
        thead_contents = []
        theads.each do |el_arr|
          tr_content_arr = []
          el_arr.each_with_index do |el, index|
            tr_content_arr
              .push(
                template.content_tag(
                  :th,
                  formnestic_table_thead_th_content(
                    record_or_name_or_array, el, index + 1, child_form_builder
                  ), el[:wrapper_html] || {}
                )
              )
          end
          thead_contents.push(
            template.content_tag(:tr, tr_content_arr.join.html_safe)
          )
        end
        thead_contents
      end

      def formnestic_table_thead_th_content(
        record_or_name_or_array,
        table_header_element_opts,
        position,
        child_form_builder
      )
        if table_header_element_opts[:attr].present?
          input_base =
            Formtastic::Inputs::StringInput.new(
              child_form_builder,
              '',
              record_or_name_or_array,
              child_form_builder.object,
              table_header_element_opts[:attr],
              {}
            )
          input_base.label_text
        elsif table_header_element_opts[:label].present?
          table_header_element_opts[:label].html_safe
        else
          "Column #{position}"
        end
      end
    end

    def resolve_row_removable(row_removable, record)
      if row_removable.is_a?(Proc)
        row_removable.call(record)
      else
        row_removable
      end
    end
  end
end