lib/active_admin/form_builder.rb in yousty-activeadmin-1.0.4.pre vs lib/active_admin/form_builder.rb in yousty-activeadmin-1.0.5.pre

- old
+ new

@@ -1,147 +1,136 @@ -# Note for posterity: -# -# Here we have two core customizations on top of Formtastic. First, this allows -# you to build forms in the AA DSL without dealing with the HTML return value of -# individual form methods (hence the +form_buffers+ object). Second, this provides -# an intuitive way to build has_many associated records in the same form. -# +# Provides an intuitive way to build has_many associated records in the same form. +module Formtastic + module Inputs + module Base + def input_wrapping(&block) + html = super + template.concat(html) if template.output_buffer && template.assigns['has_many_block'] + html + end + end + end +end + module ActiveAdmin class FormBuilder < ::Formtastic::FormBuilder + self.input_namespaces = [::Object, ::ActiveAdmin::Inputs, ::Formtastic::Inputs] - attr_reader :form_buffers + # TODO: remove both class finders after formtastic 4 (where it will be default) + self.input_class_finder = ::Formtastic::InputClassFinder + self.action_class_finder = ::Formtastic::ActionClassFinder - def initialize(*args) - @form_buffers = ["".html_safe] - super - end - - def inputs(*args, &block) - @use_form_buffer = block_given? - form_buffers.last << with_new_form_buffer{ super } - end - - # If this `input` call is inside a `inputs` block, add the content - # to the form buffer. Else, return it directly. - def input(method, *args) - content = with_new_form_buffer{ super } - @use_form_buffer ? form_buffers.last << content : content - end - - def cancel_link(url = {:action => "index"}, html_options = {}, li_attrs = {}) + def cancel_link(url = {action: "index"}, html_options = {}, li_attrs = {}) li_attrs[:class] ||= "cancel" li_content = template.link_to I18n.t('active_admin.cancel'), url, html_options - form_buffers.last << template.content_tag(:li, li_content, li_attrs) + template.content_tag(:li, li_content, li_attrs) end - def actions(*args, &block) - form_buffers.last << with_new_form_buffer do - block_given? ? super : super{ commit_action_with_cancel_link } - end - end + attr_accessor :already_in_an_inputs_block - def action(*args) - form_buffers.last << with_new_form_buffer{ super } + def assoc_heading(assoc) + object.class.reflect_on_association(assoc).klass.model_name. + human(count: ::ActiveAdmin::Helpers::I18n::PLURAL_MANY_COUNT) end - def commit_action_with_cancel_link - action(:submit) - cancel_link - end - def has_many(assoc, options = {}, &block) - options = {for: assoc, new_record: true}.merge options - options[:class] ||= "" - options[:class] << "inputs has_many_fields" + # remove options that should not render as attributes + custom_settings = :new_record, :allow_destroy, :heading, :sortable, :sortable_start + builder_options = {new_record: true}.merge! options.slice *custom_settings + options = {for: assoc }.merge! options.except *custom_settings + options[:class] = [options[:class], "inputs has_many_fields"].compact.join(' ') + sortable_column = builder_options[:sortable] + sortable_start = builder_options.fetch(:sortable_start, 0) - # Add Delete Links - form_block = proc do |has_many_form| - index = parent_child_index options[:parent] if options[:parent] - contents = block.call has_many_form, index + if sortable_column + options[:for] = [assoc, sorted_children(assoc, sortable_column)] + end - if has_many_form.object.new_record? - contents << template.content_tag(:li) do - template.link_to I18n.t('active_admin.has_many_remove'), "#", class: 'button has_many_remove' - end - elsif options[:allow_destroy] - has_many_form.input :_destroy, as: :boolean, wrapper_html: {class: 'has_many_delete'}, - label: I18n.t('active_admin.has_many_delete') + html = "".html_safe + unless builder_options.key?(:heading) && !builder_options[:heading] + html << template.content_tag(:h3) do + builder_options[:heading] || assoc_heading(assoc) end - contents end - form_buffers.last << with_new_form_buffer do - template.content_tag :div, class: "has_many #{assoc}" do - unless options.key?(:heading) && !options[:heading] - form_buffers.last << template.content_tag(:h3) do - options[:heading] || object.class.reflect_on_association(assoc).klass.model_name.human(count: 1.1) - end - end + html << template.capture do + contents = "".html_safe + form_block = proc do |has_many_form| + index = parent_child_index options[:parent] if options[:parent] + block.call has_many_form, index + template.concat has_many_actions(has_many_form, builder_options, "".html_safe) + end + + template.assign('has_many_block'=> true) + contents = without_wrapper { inputs(options, &form_block) } - inputs options, &form_block - - form_buffers.last << js_for_has_many(assoc, form_block, template, options[:new_record]) if options[:new_record] + if builder_options[:new_record] + contents << js_for_has_many(assoc, form_block, template, builder_options[:new_record], options[:class]) + else + contents end end - end - def semantic_errors(*args) - form_buffers.last << with_new_form_buffer{ super } + tag = @already_in_an_inputs_block ? :li : :div + html = template.content_tag(tag, html, class: "has_many_container #{assoc}", 'data-sortable' => sortable_column, 'data-sortable-start' => sortable_start) + template.concat(html) if template.output_buffer + html end protected - def active_admin_input_class_name(as) - "ActiveAdmin::Inputs::#{as.to_s.camelize}Input" - end + def has_many_actions(has_many_form, builder_options, contents) + if has_many_form.object.new_record? + contents << template.content_tag(:li) do + template.link_to I18n.t('active_admin.has_many_remove'), "#", class: 'button has_many_remove' + end + elsif builder_options[:allow_destroy] + has_many_form.input(:_destroy, as: :boolean, + wrapper_html: {class: 'has_many_delete'}, + label: I18n.t('active_admin.has_many_delete')) + end - def input_class(as) - @input_classes_cache ||= {} - @input_classes_cache[as] ||= begin - begin - custom_input_class_name(as).constantize - rescue NameError - begin - active_admin_input_class_name(as).constantize - rescue NameError - standard_input_class_name(as).constantize - end + if builder_options[:sortable] + has_many_form.input builder_options[:sortable], as: :hidden + + contents << template.content_tag(:li, class: 'handle') do + Iconic.icon :move_vertical end - rescue NameError - raise Formtastic::UnknownInputError, "Unable to find input class for #{as}" end + + contents end - # This method calls the block it's passed (in our case, the `f.inputs` block) - # and wraps the resulting HTML in a fieldset. If your block doesn't have a - # valid return value but it was otherwise built correctly, we instead use - # the most recent part of the Active Admin form buffer. - def field_set_and_list_wrapping(*args, &block) - block_given? ? super{ - (val = yield).is_a?(String) ? val : form_buffers.last - } : super + def sorted_children(assoc, column) + object.public_send(assoc).sort_by do |o| + attribute = o.public_send column + [attribute.nil? ? Float::INFINITY : attribute, o.id || Float::INFINITY] + end end private - def with_new_form_buffer - form_buffers << ''.html_safe - return_value = (yield || '').html_safe - form_buffers.pop - return_value + def without_wrapper + is_being_wrapped = @already_in_an_inputs_block + @already_in_an_inputs_block = false + + html = yield + + @already_in_an_inputs_block = is_being_wrapped + html end # Capture the ADD JS - def js_for_has_many(assoc, form_block, template, new_record) + def js_for_has_many(assoc, form_block, template, new_record, class_string) assoc_reflection = object.class.reflect_on_association assoc assoc_name = assoc_reflection.klass.model_name - placeholder = "NEW_#{assoc_name.to_s.upcase.split(' ').join('_')}_RECORD" + placeholder = "NEW_#{assoc_name.to_s.underscore.upcase.gsub(/\//, '_')}_RECORD" opts = { - :for => [assoc, assoc_reflection.klass.new], - :class => "inputs has_many_fields", - :for_options => { child_index: placeholder } + for: [assoc, assoc_reflection.klass.new], + class: class_string, + for_options: { child_index: placeholder } } - html = with_new_form_buffer{ inputs_for_nested_attributes opts, &form_block } + html = template.capture{ inputs_for_nested_attributes opts, &form_block } text = new_record.is_a?(String) ? new_record : I18n.t('active_admin.has_many_new', model: assoc_name.human) template.link_to text, '#', class: "button has_many_add", data: { html: CGI.escapeHTML(html).html_safe, placeholder: placeholder }