module FreeForm module BuilderMixin # Adds a link to insert a new associated records. The first argument is the name of the link, the second is the name of the association. # # f.link_to_add("Add Task", :tasks) # # You can pass HTML options in a hash at the end and a block for the content. # # <%= f.link_to_add(:tasks, :class => "add_task", :href => new_task_path) do %> # Add Task # <% end %> # # You can also pass model_object option with an object for use in # the blueprint, e.g.: # # <%= f.link_to_add(:tasks, :model_object => Task.new(:name => 'Task')) %> # # See the README for more details on where to call this method. def link_to_add(*args, &block) options = args.extract_options!.symbolize_keys association = args.pop unless object.respond_to?("#{association}_attributes=") raise ArgumentError, "Invalid association. Make sure that a nested form for #{association.to_s} exists." end model_object = options.delete(:model_object) do object.send("build_#{association.to_s.singularize}") end options[:class] = [options[:class], "add_nested_form"].compact.join(" ") options["data-association"] = association options["data-blueprint-id"] = fields_blueprint_id = fields_blueprint_id_for(association) args << (options.delete(:href) || "javascript:void(0)") args << options @fields ||= {} @template.after_freeform(fields_blueprint_id) do blueprint = {:id => fields_blueprint_id, :style => 'display: none'} block, options = @fields[fields_blueprint_id].values_at(:block, :options) options[:child_index] = "new_#{association}" blueprint[:"data-blueprint"] = fields_for(association, model_object, options, &block).to_str @template.content_tag(:div, nil, blueprint) end @template.link_to(*args, &block) end # Adds a link to remove the associated record. The first argment is the name of the link. # # f.link_to_remove("Remove Task") # # You can pass HTML options in a hash at the end and a block for the content. # # <%= f.link_to_remove(:class => "remove_task", :href => "#") do %> # Remove Task # <% end %> # # See the README for more details on where to call this method. def link_to_remove(*args, &block) options = args.extract_options!.symbolize_keys options[:class] = [options[:class], "remove_nested_form"].compact.join(" ") # Extracting "milestones" from "...[milestones_attributes][...]" md = object_name.to_s.match(/(\w+)_attributes\](?:\[[\w\d]+\])?$/) association = md && md[1] options["data-association"] = association args << (options.delete(:href) || "javascript:void(0)") args << options destroy = if self.object.send(:marked_for_destruction?) "1" else "0" end hidden_field(:_destroy, :value => destroy) << @template.link_to(*args, &block) end def fields_for_with_nested_attributes(association_name, *args) block = args.pop || Proc.new { |fields| @template.render(:partial => "#{association_name.to_s.singularize}_fields", :locals => {:f => fields}) } options = args.dup.extract_options! # Rails 3.0.x if options.empty? && args[0].kind_of?(Array) options = args[0].dup.extract_options! end @fields ||= {} @fields[fields_blueprint_id_for(association_name)] = { :block => block, :options => options } super(association_name, *(args << block)) end def fields_for_nested_model(name, object, options, block) perform_wrap = options.fetch(:nested_wrapper, true) perform_wrap &&= options[:wrapper] != false # wrap even if nil if perform_wrap if object.respond_to?(:marked_for_destruction?) && object.marked_for_destruction? @template.content_tag(:div, super, :class => 'fields marked_for_destruction', :style => "display: none;") else @template.content_tag(:div, super, :class => 'fields') end else super end end private def fields_blueprint_id_for(association) assocs = object_name.to_s.scan(/(\w+)_attributes/).map(&:first) assocs << association assocs.join('_') + '_fields_blueprint' end end end