diff --git a/Gemfile b/Gemfile index f5191458..c185f2e0 100644 --- a/Gemfile +++ b/Gemfile @@ -2,4 +2,4 @@ gemspec :path => '.' instance_eval File.read(File.expand_path('../gemfiles/Gemfile.base', __FILE__)) -gem 'rails', '~> 3.2.0' +gem 'rails', '~> 4.0.0' diff --git a/lib/nested_form/builder_mixin.rb b/lib/nested_form/builder_mixin.rb index b572cf07..8df7fe7d 100644 --- a/lib/nested_form/builder_mixin.rb +++ b/lib/nested_form/builder_mixin.rb @@ -1,22 +1,22 @@ module NestedForm 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. + # Adds a button 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) + # f.button_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 %> + # <%= f.button_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')) %> + # <%= f.button_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) + def button_to_add(*args, &block) options = args.extract_options!.symbolize_keys association = args.pop @@ -31,8 +31,8 @@ def link_to_add(*args, &block) options[:class] = [options[:class], "add_nested_fields"].compact.join(" ") options["data-association"] = association + options["type"] = "button" options["data-blueprint-id"] = fields_blueprint_id = fields_blueprint_id_for(association) - args << (options.delete(:href) || "javascript:void(0)") args << options @fields ||= {} @@ -43,21 +43,21 @@ def link_to_add(*args, &block) blueprint[:"data-blueprint"] = fields_for(association, model_object, options, &block).to_str @template.content_tag(:div, nil, blueprint) end - @template.link_to(*args, &block) + @template.button_tag(*args, &block) end - # Adds a link to remove the associated record. The first argment is the name of the link. + # Adds a button to remove the associated record. The first argment is the name of the link. # - # f.link_to_remove("Remove Task") + # f.button_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 %> + # <%= f.button_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) + def button_to_remove(*args, &block) options = args.extract_options!.symbolize_keys options[:class] = [options[:class], "remove_nested_fields"].compact.join(" ") @@ -65,10 +65,24 @@ def link_to_remove(*args, &block) md = object_name.to_s.match /(\w+)_attributes\]\[[\w\d]+\]$/ association = md && md[1] options["data-association"] = association + options["type"] = "button" - args << (options.delete(:href) || "javascript:void(0)") args << options - hidden_field(:_destroy) << @template.link_to(*args, &block) + hidden_field(:_destroy) << @template.button_tag(*args, &block) + end + + def button_to_diable(*args, &block) + options = args.extract_options!.symbolize_keys + options[:class] = [options[:class], "remove_nested_fields"].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 + options["type"] = "button" + + args << options + hidden_field(:disabled) << @template.button_tag(*args, &block) end def fields_for_with_nested_attributes(association_name, *args) diff --git a/spec/dummy/app/views/companies/new.html.erb b/spec/dummy/app/views/companies/new.html.erb index 682e2f80..6d985c65 100644 --- a/spec/dummy/app/views/companies/new.html.erb +++ b/spec/dummy/app/views/companies/new.html.erb @@ -6,11 +6,11 @@ <%= tf.text_field :name %> <%= tf.fields_for :milestones do |mf| %> <%= mf.text_field :name %> - <%= mf.link_to_remove 'Remove milestone' %> + <%= mf.button_to_remove 'Remove milestone' %> <% end %> - <%= tf.link_to_add 'Add new milestone', :milestones %> - <%= tf.link_to_remove 'Remove' %> + <%= tf.button_to_add 'Add new milestone', :milestones %> + <%= tf.button_to_remove 'Remove' %> <% end -%> - <%= pf.link_to_add 'Add new task', :tasks %> + <%= pf.button_to_add 'Add new task', :tasks %> <% end -%> <% end -%> diff --git a/spec/dummy/app/views/projects/new.html.erb b/spec/dummy/app/views/projects/new.html.erb index bf87f630..8b062741 100644 --- a/spec/dummy/app/views/projects/new.html.erb +++ b/spec/dummy/app/views/projects/new.html.erb @@ -4,10 +4,10 @@ <%= tf.text_field :name %> <%= tf.fields_for :milestones do |mf| %> <%= mf.text_field :name %> - <%= mf.link_to_remove 'Remove milestone' %> + <%= mf.button_to_remove 'Remove milestone' %> <% end %> - <%= tf.link_to_add 'Add new milestone', :milestones %> - <%= tf.link_to_remove 'Remove' %> + <%= tf.button_to_add 'Add new milestone', :milestones %> + <%= tf.button_to_remove 'Remove' %> <% end -%> - <%= f.link_to_add 'Add new task', :tasks %> + <%= f.button_to_add 'Add new task', :tasks %> <% end -%> diff --git a/spec/dummy/app/views/projects/without_intermediate_inputs.html.erb b/spec/dummy/app/views/projects/without_intermediate_inputs.html.erb index c3db2e8e..e2be571c 100644 --- a/spec/dummy/app/views/projects/without_intermediate_inputs.html.erb +++ b/spec/dummy/app/views/projects/without_intermediate_inputs.html.erb @@ -2,10 +2,10 @@ <%= f.fields_for :tasks do |tf| -%> <%= tf.fields_for :milestones do |mf| %> <%= mf.text_field :name %> - <%= mf.link_to_remove 'Remove milestone' %> + <%= mf.button_to_remove 'Remove milestone' %> <% end %> - <%= tf.link_to_add 'Add new milestone', :milestones %> - <%= tf.link_to_remove 'Remove' %> + <%= tf.button_to_add 'Add new milestone', :milestones %> + <%= tf.button_to_remove 'Remove' %> <% end -%> - <%= f.link_to_add 'Add new task', :tasks %> + <%= f.button_to_add 'Add new task', :tasks %> <% end -%> diff --git a/spec/events_spec.rb b/spec/events_spec.rb index bad25c9c..df3478db 100644 --- a/spec/events_spec.rb +++ b/spec/events_spec.rb @@ -14,19 +14,19 @@ context 'when field was added' do it 'emits general add event' do visit url - click_link 'Add new task' + click_button 'Add new task' page.should have_content 'Added some field' end it 'emits add event for current association' do visit url - click_link 'Add new task' + click_button 'Add new task' page.should have_content 'Added task field' page.should_not have_content 'Added milestone field' - click_link 'Add new milestone' + click_button 'Add new milestone' page.should have_content 'Added milestone field' end @@ -35,26 +35,26 @@ context 'when field was removed' do it 'emits general remove event' do visit url - click_link 'Add new task' - click_link 'Remove' + click_button 'Add new task' + click_button 'Remove' page.should have_content 'Removed some field' end it 'emits remove event for current association' do visit url - 2.times { click_link 'Add new task' } - click_link 'Remove' + 2.times { click_button 'Add new task' } + click_button 'Remove' page.should have_content 'Removed task field' page.should_not have_content 'Removed milestone field' - click_link 'Add new milestone' - click_link 'Remove milestone' + click_button 'Add new milestone' + click_button 'Remove milestone' page.should have_content 'Removed milestone field' end end end end -end \ No newline at end of file +end diff --git a/spec/form_spec.rb b/spec/form_spec.rb index 2701051b..502db409 100644 --- a/spec/form_spec.rb +++ b/spec/form_spec.rb @@ -5,17 +5,17 @@ def check_form page.should have_no_css('form .fields input[id$=name]') - click_link 'Add new task' + click_button 'Add new task' page.should have_css('form .fields input[id$=name]', :count => 1) find('form .fields input[id$=name]').should be_visible find('form .fields input[id$=_destroy]').value.should == 'false' - click_link 'Remove' + click_button 'Remove' find('form .fields input[id$=_destroy]').value.should == '1' find('form .fields input[id$=name]').should_not be_visible - click_link 'Add new task' - click_link 'Add new task' + click_button 'Add new task' + click_button 'Add new task' fields = all('form .fields') fields.select { |field| field.visible? }.count.should == 2 fields.reject { |field| field.visible? }.count.should == 1 @@ -33,32 +33,32 @@ def check_form it 'works when there are no inputs for intermediate association', :js => true do visit '/projects/without_intermediate_inputs' - click_link 'Add new task' - click_link 'Add new milestone' - click_link 'Add new milestone' + click_button 'Add new task' + click_button 'Add new milestone' + click_button 'Add new milestone' inputs = all('.fields .fields input[id$=name]') inputs.first[:name].should_not eq(inputs.last[:name]) end it 'generates correct name for the nested input', :js => true do visit '/projects/new?type=jquery' - click_link 'Add new task' - click_link 'Add new milestone' + click_button 'Add new task' + click_button 'Add new milestone' name = find('.fields .fields input[id$=name]')[:name] name.should match(/\Aproject\[tasks_attributes\]\[\d+\]\[milestones_attributes\]\[\d+\]\[name\]\z/) end it 'generates correct name for the nested input (has_one => has_many)', :js => true do visit '/companies/new?type=jquery' - click_link 'Add new task' + click_button 'Add new task' name = find('.fields .fields input[id$=name]')[:name] name.should match(/\Acompany\[project_attributes\]\[tasks_attributes\]\[\d+\]\[name\]\z/) end it 'generates correct name for the nested input (has_one => has_many => has_many)', :js => true do visit '/companies/new?type=jquery' - click_link 'Add new task' - click_link 'Add new milestone' + click_button 'Add new task' + click_button 'Add new milestone' name = find('.fields .fields .fields input[id$=name]')[:name] name.should match(/\Acompany\[project_attributes\]\[tasks_attributes\]\[\d+\]\[milestones_attributes\]\[\d+\]\[name\]\z/) end diff --git a/spec/nested_form/builder_spec.rb b/spec/nested_form/builder_spec.rb index 22bdde0d..ed968a0d 100644 --- a/spec/nested_form/builder_spec.rb +++ b/spec/nested_form/builder_spec.rb @@ -17,46 +17,46 @@ builder.new(:item, project, template, {}, proc {}) end - describe '#link_to_add' do - it "behaves similar to a Rails link_to" do - subject.link_to_add("Add", :tasks).should == 'Add' - subject.link_to_add("Add", :tasks, :class => "foo", :href => "url").should == 'Add' - subject.link_to_add(:tasks) { "Add" }.should == 'Add' + describe '#button_to_add' do + it "behaves similar to a Rails button_to" do + subject.button_to_add("Add", :tasks).should == '' + subject.button_to_add("Add", :tasks, :class => "foo").should == '' + subject.button_to_add(:tasks) { "Add" }.should == '' end it 'raises ArgumentError when missing association is provided' do expect { - subject.link_to_add('Add', :bugs) + subject.button_to_add('Add', :bugs) }.to raise_error(ArgumentError) end it 'raises ArgumentError when accepts_nested_attributes_for is missing' do expect { - subject.link_to_add('Add', :not_nested_tasks) + subject.button_to_add('Add', :not_nested_tasks) }.to raise_error(ArgumentError) end end - describe '#link_to_remove' do - it "behaves similar to a Rails link_to" do - subject.link_to_remove("Remove").should == 'Remove' - subject.link_to_remove("Remove", :class => "foo", :href => "url").should == 'Remove' - subject.link_to_remove { "Remove" }.should == 'Remove' + describe '#button_to_remove' do + it "behaves similar to a Rails button_to" do + subject.button_to_remove("Remove").should == '' + subject.button_to_remove("Remove", :class => "foo").should == '' + subject.button_to_remove { "Remove" }.should == '' end it 'has data-association attribute' do project.tasks.build subject.fields_for(:tasks, :builder => builder) do |tf| - tf.link_to_remove 'Remove' - end.should match 'Remove' + tf.button_to_remove 'Remove' + end.should match 'Remove' end context 'when association is declared in a model by the class_name' do it 'properly detects association name' do project.assignments.build subject.fields_for(:assignments, :builder => builder) do |tf| - tf.link_to_remove 'Remove' - end.should match 'Remove' + tf.button_to_remove 'Remove' + end.should match 'Remove' end end @@ -66,9 +66,9 @@ task.milestones.build subject.fields_for(:tasks, :builder => builder) do |tf| tf.fields_for(:milestones, :builder => builder) do |mf| - mf.link_to_remove 'Remove' + mf.button_to_remove 'Remove' end - end.should match 'Remove' + end.should match 'Remove' end end end @@ -98,7 +98,7 @@ task = project.tasks.build task.mark_for_destruction subject.fields_for(:tasks) { 'Task' } - subject.link_to_add('Add', :tasks) + subject.button_to_add('Add', :tasks) output = template.send(:after_nested_form_callbacks) expected = ERB::Util.html_escape '
Task
' output.should match(/div.+data-blueprint="#{expected}"/) @@ -109,7 +109,7 @@ task.milestones.build subject.fields_for(:tasks, :builder => builder) do |tf| tf.fields_for(:milestones, :builder => builder) { 'Milestone' } - tf.link_to_add('Add', :milestones) + tf.button_to_add('Add', :milestones) end output = template.send(:after_nested_form_callbacks) output.should match(/div.+id="tasks_milestones_fields_blueprint"/) @@ -121,7 +121,7 @@ fields.should eq('Task') - subject.link_to_add 'Add', :tasks + subject.button_to_add 'Add', :tasks output = template.send(:after_nested_form_callbacks) output.should match(/div.+data-blueprint="Task"/) @@ -133,7 +133,7 @@ fields.should eq('Task') - subject.link_to_add 'Add', :tasks + subject.button_to_add 'Add', :tasks output = template.send(:after_nested_form_callbacks) output.should match(/div.+data-blueprint="Task"/) @@ -145,7 +145,7 @@ fields.should eq('Task') - subject.link_to_add 'Add', :tasks + subject.button_to_add 'Add', :tasks output = template.send(:after_nested_form_callbacks) output.should match(/div.+data-blueprint="Task"/) @@ -158,7 +158,7 @@ context "when model_object given" do it "should use it instead of new generated" do subject.fields_for(:tasks) {|f| f.object.name } - subject.link_to_add("Add", :tasks, :model_object => Task.new(:name => 'for check')) + subject.button_to_add("Add", :tasks, :model_object => Task.new(:name => 'for check')) output = template.send(:after_nested_form_callbacks) expected = ERB::Util.html_escape '
for check
' output.should match(/div.+data-blueprint="#{expected}"/) diff --git a/spec/nested_form/view_helper_spec.rb b/spec/nested_form/view_helper_spec.rb index d1f0b2e2..e7fcd722 100644 --- a/spec/nested_form/view_helper_spec.rb +++ b/spec/nested_form/view_helper_spec.rb @@ -58,7 +58,7 @@ f.fields_for(:tasks) do |t| t.file_field :file end - f.link_to_add "Add", :tasks + f.button_to_add "Add", :tasks end.should include(" enctype=\"multipart/form-data\" ") end end diff --git a/vendor/assets/javascripts/jquery_nested_form.js b/vendor/assets/javascripts/jquery_nested_form.js index b9225b14..f12eb5c8 100644 --- a/vendor/assets/javascripts/jquery_nested_form.js +++ b/vendor/assets/javascripts/jquery_nested_form.js @@ -85,8 +85,8 @@ window.nestedFormEvents = new NestedFormEvents(); $(document) - .delegate('form a.add_nested_fields', 'click', nestedFormEvents.addFields) - .delegate('form a.remove_nested_fields', 'click', nestedFormEvents.removeFields); + .delegate('form button.add_nested_fields', 'click', nestedFormEvents.addFields) + .delegate('form button.remove_nested_fields', 'click', nestedFormEvents.removeFields); })(jQuery); // http://plugins.jquery.com/project/closestChild diff --git a/vendor/assets/javascripts/prototype_nested_form.js b/vendor/assets/javascripts/prototype_nested_form.js index 26733a69..8c16ab36 100644 --- a/vendor/assets/javascripts/prototype_nested_form.js +++ b/vendor/assets/javascripts/prototype_nested_form.js @@ -1,5 +1,5 @@ document.observe('click', function(e, el) { - if (el = e.findElement('form a.add_nested_fields')) { + if (el = e.findElement('form button.add_nested_fields')) { // Setup var assoc = el.readAttribute('data-association'); // Name of child var target = el.readAttribute('data-target'); @@ -55,7 +55,7 @@ document.observe('click', function(e, el) { }); document.observe('click', function(e, el) { - if (el = e.findElement('form a.remove_nested_fields')) { + if (el = e.findElement('form button.remove_nested_fields')) { var hidden_field = el.previous(0), assoc = el.readAttribute('data-association'); // Name of child to be removed if(hidden_field) {