diff --git a/README.rdoc b/README.rdoc index 26e43435..99d231bf 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,14 +1,14 @@ = Nested Form -A Rails gem to conveniently manage multiple nested models in a single form. It does so in an unobtrusive way through jQuery. +A Rails gem to conveniently manage multiple nested models in a single simple_form. It does so in an unobtrusive way through jQuery. -To learn more about how this works under the hood: http://blog.madebydna.com/dynamic-nested-forms-in-rails-3-with-the-nest +Note: this gem extends the simple_form gem (https://github.com/plataformatec/simple_form) == Install Add it to your Gemfile - gem "nested_form", :git => "git://github.com/madebydna/nested_form.git" + gem "nested_form", :git => "git://github.com/binarycode/nested_form.git" Run @@ -28,10 +28,10 @@ You can then generate a nested form using the nested_form_for helper method. <%= nested_form_for @project do |f| %> -Use this form just like normal, including the fields_for helper method for nesting models. The benefit of this plugin comes from the link_to_add and link_to_remove helper methods on the form builder. +Use this form just like normal, including the simple_fields_for helper method for nesting models. The benefit of this plugin comes from the link_to_add and link_to_remove helper methods on the form builder. - <%= f.fields_for :tasks do |task_form| %> - <%= task_form.text_field :name %> + <%= f.simple_fields_for :tasks do |task_form| %> + <%= task_form.input :name %> <%= task_form.link_to_remove "Remove this task" %> <% end %> <%= f.link_to_add "Add a task", :tasks %> @@ -40,12 +40,22 @@ This generates links which dynamically add and remove fields. == Partials +[Somehow this was not working with Ryan Bates's initial gem but should work ok with this fork]. + It is often desirable to move the nested fields into a partial to keep things organized. If you don't supply a block to fields_for it will look for a partial and use that. - <%= f.fields_for :tasks %> + <%= f.simple_fields_for :tasks %> In this case it will look for a partial called "task_fields" and pass the form builder as an f variable to it. +== Additional Options + +You can limit how many children a user can add by setting the :limit option : + + <%= f.link_to_add "Add a task", :tasks, :limit => 2 %> + + + == Special Thanks This gem is based on the plugin by Ryan Bates: http://github.com/ryanb/nested_form diff --git a/lib/generators/nested_form/templates/nested_form.js b/lib/generators/nested_form/templates/nested_form.js index 5a805f11..93713105 100644 --- a/lib/generators/nested_form/templates/nested_form.js +++ b/lib/generators/nested_form/templates/nested_form.js @@ -1,46 +1,59 @@ $(function() { -$('form a.add_nested_fields').live('click', function() { - // Setup - var assoc = $(this).attr('data-association'); // Name of child - var content = $('#' + assoc + '_fields_blueprint').html(); // Fields template + $('form a.add_nested_fields').live('click', function() { + var $this = $(this); + // Setup + var assoc = $this.attr('data-association'); // Name of child + var content = $('#' + assoc + '_fields_blueprint').html(); // Fields template - // Make the context correct by replacing new_ with the generated ID - // of each of the parent objects - var context = ($(this).closest('.fields').find('input:first').attr('name') || '').replace(new RegExp('\[[a-z]+\]$'), ''); + // Make the context correct by replacing new_ with the generated ID + // of each of the parent objects + var context = ($this.closest('.fields').find('input:first').attr('name') || '').replace(new RegExp('\[[a-z]+\]$'), ''); + + // context will be something like this for a brand new form: + // project[tasks_attributes][1255929127459][assignments_attributes][1255929128105] + // or for an edit form: + // project[tasks_attributes][0][assignments_attributes][1] + if (context) { + var parent_names = context.match(/[a-z_]+_attributes/g) || []; + var parent_ids = context.match(/[0-9]+/g); - // context will be something like this for a brand new form: - // project[tasks_attributes][1255929127459][assignments_attributes][1255929128105] - // or for an edit form: - // project[tasks_attributes][0][assignments_attributes][1] - if(context) { - var parent_names = context.match(/[a-z_]+_attributes/g) || []; - var parent_ids = context.match(/[0-9]+/g); - - for(i = 0; i < parent_names.length; i++) { - if(parent_ids[i]) { - content = content.replace( - new RegExp('(\\[' + parent_names[i] + '\\])\\[.+?\\]', 'g'), - '$1[' + parent_ids[i] + ']' - ) + for (i = 0; i < parent_names.length; i++) { + if (parent_ids[i]) { + content = content.replace( + new RegExp('(\\[' + parent_names[i] + '\\])\\[.+?\\]', 'g'), + '$1[' + parent_ids[i] + ']' + ) + } } } - } - // Make a unique ID for the new child - var regexp = new RegExp('new_' + assoc, 'g'); - var new_id = new Date().getTime(); - content = content.replace(regexp, new_id); + // Make a unique ID for the new child + var regexp = new RegExp('new_' + assoc, 'g'); + var new_id = new Date().getTime(); + content = content.replace(regexp, new_id); - $(this).before(content); - return false; -}); + $this.before(content); + $this.closest(".fields").hide().fadeIn(500); + + return false; + }); + + $("form a.remove_nested_fields").live("click", function() { + var + $this = $(this), + $hidden_field = $this.prev("input[type=hidden]")[0], + $fields = $this.closest(".fields"); + + if ($hidden_field) { + $hidden_field.value = "1"; + } + + // delete all wrapped inputs + // to properly handle required fields + $fields.fadeOut(300, function(){ + $fields.children("div.input").remove(); + }); -$('form a.remove_nested_fields').live('click', function() { - var hidden_field = $(this).prev('input[type=hidden]')[0]; - if(hidden_field) { - hidden_field.value = '1'; - } - $(this).closest('.fields').hide(); - return false; + return false; + }); }); -}); \ No newline at end of file diff --git a/lib/nested_form/builder.rb b/lib/nested_form/builder.rb index ca370d0a..c9f8eeff 100644 --- a/lib/nested_form/builder.rb +++ b/lib/nested_form/builder.rb @@ -1,34 +1,42 @@ -module NestedForm - class Builder < ActionView::Helpers::FormBuilder - def link_to_add(name, association) - @fields ||= {} - @template.after_nested_form(association) do - model_object = object.class.reflect_on_association(association).klass.new - output = %Q[') - output - end - @template.link_to(name, "javascript:void(0)", :class => "add_nested_fields", "data-association" => association) - end - - def link_to_remove(name) - hidden_field(:_destroy) + @template.link_to(name, "javascript:void(0)", :class => "remove_nested_fields") - end - - - def fields_for_with_nested_attributes(association, args, block) - @fields ||= {} - @fields[association] = block - super - end - - - def fields_for_nested_model(name, association, args, block) - output = '
'.html_safe - output << super +class SimpleForm::FormBuilder + def link_to_add(name, association, html_options = {}) + association = association.to_sym + html_options.merge!(:class => "add_nested_fields", "data-association" => association) + @fields ||= {} + @template.after_nested_form(association) do + model_object = object.class.reflect_on_association(association).klass.new + output = %Q[') output end + @template.link_to(name, "javascript:void(0)", html_options) + end + + def link_to_remove(name) + hidden_field(:_destroy) + @template.link_to(name, "javascript:void(0)", :class => "remove_nested_fields") + end + + + def fields_for_with_nested_attributes(association, args, block) + @fields ||= {} + @fields[association] = block + super + end + + + def fields_for_nested_model(name, association, args, block = nil) + output = '
'.html_safe + if block.nil? + block = lambda do |f| + klass_name = association.class.name.downcase + contents = @template.render :partial => "#{klass_name.pluralize}/#{klass_name}_fields", :locals => {:f => f} + template.concat(contents) + contents + end + end + output << super + output.safe_concat('
') + output end -end \ No newline at end of file +end diff --git a/lib/nested_form/view_helper.rb b/lib/nested_form/view_helper.rb index 0eae5fda..8080766c 100644 --- a/lib/nested_form/view_helper.rb +++ b/lib/nested_form/view_helper.rb @@ -1,8 +1,8 @@ module NestedForm module ViewHelper def nested_form_for(*args, &block) - options = args.extract_options!.reverse_merge(:builder => NestedForm::Builder) - output = form_for(*(args << options), &block) + options = args.extract_options!.reverse_merge(:builder => SimpleForm::FormBuilder) + output = simple_form_for(*(args << options), &block) @after_nested_form_callbacks ||= [] fields = @after_nested_form_callbacks.map do |callback| callback.call diff --git a/nested_form.gemspec b/nested_form.gemspec index b773047f..eedf3bc9 100644 --- a/nested_form.gemspec +++ b/nested_form.gemspec @@ -6,9 +6,9 @@ Gem::Specification.new do |s| s.name = "nested_form" s.version = NestedForm::VERSION s.platform = Gem::Platform::RUBY - s.authors = ["Ryan Bates", "Andrea Singh"] + s.authors = ["Ryan Bates", "Andrea Singh", "Jean-Dominique Morani"] s.email = ["info@madebydna.com"] - s.homepage = "http://github.com/madebydna/nested_form" + s.homepage = "http://github.com/jdmorani/nested_form" s.summary = "Gem to conveniently handle multiple models in a single form." s.description = "Gem to conveniently handle multiple models in a single form. Rails 2 plugin by Ryan Bates converted into a gem." @@ -23,4 +23,6 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/} s.require_path = 'lib' + + s.add_dependency('simple_form', '>= 1.3.0') end \ No newline at end of file