nested_form 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ryan Bates
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,57 @@
1
+ = Nested Form
2
+
3
+ A Rails gem to conveniently manage multiple nested models in a single form. It does so in an unobtrusive way through jQuery.
4
+
5
+ To learn more about how this works under the hood: http://blog.madebydna.com/dynamic-nested-forms-in-rails-3-with-the-nest
6
+
7
+
8
+ == Install
9
+
10
+ Add it to your Gemfile
11
+
12
+ gem "nested_form"
13
+
14
+ Run
15
+
16
+ bundle install
17
+
18
+ Run the generator
19
+
20
+ rails generate nested_form:install
21
+
22
+
23
+ == Usage
24
+
25
+ Running the generator will add a file at public/javascripts/nested_form.js which should be included after the jQuery or Prototype framework.
26
+
27
+ <%= javascript_include_tag :defaults, "nested_form" %>
28
+
29
+ You can then generate a nested form using the nested_form_for helper method.
30
+
31
+ <%= nested_form_for @project do |f| %>
32
+
33
+ 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.
34
+
35
+ <%= f.fields_for :tasks do |task_form| %>
36
+ <%= task_form.text_field :name %>
37
+ <%= task_form.link_to_remove "Remove this task" %>
38
+ <% end %>
39
+ <%= f.link_to_add "Add a task", :tasks %>
40
+
41
+ This generates links which dynamically add and remove fields.
42
+
43
+
44
+ == Partials
45
+
46
+ 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.
47
+
48
+ <%= f.fields_for :tasks %>
49
+
50
+ In this case it will look for a partial called "task_fields" and pass the form builder as an f variable to it.
51
+
52
+
53
+ == Special Thanks
54
+
55
+ This gem was originally based on the solution by Tim Riley in his {complex-form-examples fork}[https://github.com/timriley/complex-form-examples/tree/unobtrusive-jquery-deep-fix2].
56
+
57
+ Thank you Andrew Manshin for the Rails 3 transition, {Andrea Singh}[https://github.com/madebydna] for converting to a gem and {Peter Giacomo Lombardo}[https://github.com/pglombardo] for Prototype support.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Run RSpec"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,17 @@
1
+ module NestedForm
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ def self.source_root
5
+ File.dirname(__FILE__) + "/templates"
6
+ end
7
+
8
+ def copy_jquery_file
9
+ if File.exists?('public/javascripts/prototype.js')
10
+ copy_file 'prototype_nested_form.js', 'public/javascripts/nested_form.js'
11
+ else
12
+ copy_file 'jquery_nested_form.js', 'public/javascripts/nested_form.js'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ $(function() {
2
+ $('form a.add_nested_fields').live('click', function() {
3
+ // Setup
4
+ var assoc = $(this).attr('data-association'); // Name of child
5
+ var content = $('#' + assoc + '_fields_blueprint').html(); // Fields template
6
+
7
+ // Make the context correct by replacing new_<parents> with the generated ID
8
+ // of each of the parent objects
9
+ var context = ($(this).closest('.fields').find('input:first').attr('name') || '').replace(new RegExp('\[[a-z]+\]$'), '');
10
+
11
+ // context will be something like this for a brand new form:
12
+ // project[tasks_attributes][1255929127459][assignments_attributes][1255929128105]
13
+ // or for an edit form:
14
+ // project[tasks_attributes][0][assignments_attributes][1]
15
+ if(context) {
16
+ var parent_names = context.match(/[a-z_]+_attributes/g) || [];
17
+ var parent_ids = context.match(/[0-9]+/g);
18
+
19
+ for(i = 0; i < parent_names.length; i++) {
20
+ if(parent_ids[i]) {
21
+ content = content.replace(
22
+ new RegExp('(\\[' + parent_names[i] + '\\])\\[.+?\\]', 'g'),
23
+ '$1[' + parent_ids[i] + ']'
24
+ )
25
+ }
26
+ }
27
+ }
28
+
29
+ // Make a unique ID for the new child
30
+ var regexp = new RegExp('new_' + assoc, 'g');
31
+ var new_id = new Date().getTime();
32
+ content = content.replace(regexp, new_id);
33
+
34
+ $(this).before(content);
35
+ return false;
36
+ });
37
+
38
+ $('form a.remove_nested_fields').live('click', function() {
39
+ var hidden_field = $(this).prev('input[type=hidden]')[0];
40
+ if(hidden_field) {
41
+ hidden_field.value = '1';
42
+ }
43
+ $(this).closest('.fields').hide();
44
+ return false;
45
+ });
46
+ });
@@ -0,0 +1,48 @@
1
+ document.observe('click', function(e, el) {
2
+ if (el = e.findElement('form a.add_nested_fields')) {
3
+ // Setup
4
+ var assoc = el.readAttribute('data-association'); // Name of child
5
+ var content = $(assoc + '_fields_blueprint').innerHTML; // Fields template
6
+
7
+ // Make the context correct by replacing new_<parents> with the generated ID
8
+ // of each of the parent objects
9
+ var context = (el.getOffsetParent('.fields').firstDescendant().readAttribute('name') || '').replace(new RegExp('\[[a-z]+\]$'), '');
10
+
11
+ // context will be something like this for a brand new form:
12
+ // project[tasks_attributes][1255929127459][assignments_attributes][1255929128105]
13
+ // or for an edit form:
14
+ // project[tasks_attributes][0][assignments_attributes][1]
15
+ if(context) {
16
+ var parent_names = context.match(/[a-z_]+_attributes/g) || [];
17
+ var parent_ids = context.match(/[0-9]+/g);
18
+
19
+ for(i = 0; i < parent_names.length; i++) {
20
+ if(parent_ids[i]) {
21
+ content = content.replace(
22
+ new RegExp('(\\[' + parent_names[i] + '\\])\\[.+?\\]', 'g'),
23
+ '$1[' + parent_ids[i] + ']'
24
+ )
25
+ }
26
+ }
27
+ }
28
+
29
+ // Make a unique ID for the new child
30
+ var regexp = new RegExp('new_' + assoc, 'g');
31
+ var new_id = new Date().getTime();
32
+ content = content.replace(regexp, new_id);
33
+
34
+ el.insert({ before: content });
35
+ return false;
36
+ }
37
+ });
38
+
39
+ document.observe('click', function(e, el) {
40
+ if (el = e.findElement('form a.remove_nested_fields')) {
41
+ var hidden_field = el.previous(0);
42
+ if(hidden_field) {
43
+ hidden_field.value = '1';
44
+ }
45
+ el.ancestors()[0].hide();
46
+ return false;
47
+ }
48
+ });
@@ -0,0 +1,32 @@
1
+ module NestedForm
2
+ class Builder < ::ActionView::Helpers::FormBuilder
3
+ def link_to_add(name, association)
4
+ @fields ||= {}
5
+ @template.after_nested_form(association) do
6
+ model_object = object.class.reflect_on_association(association).klass.new
7
+ output = %Q[<div id="#{association}_fields_blueprint" style="display: none">].html_safe
8
+ output << fields_for(association, model_object, :child_index => "new_#{association}", &@fields[association])
9
+ output.safe_concat('</div>')
10
+ output
11
+ end
12
+ @template.link_to(name, "javascript:void(0)", :class => "add_nested_fields", "data-association" => association)
13
+ end
14
+
15
+ def link_to_remove(name)
16
+ hidden_field(:_destroy) + @template.link_to(name, "javascript:void(0)", :class => "remove_nested_fields")
17
+ end
18
+
19
+ def fields_for_with_nested_attributes(association, args, block)
20
+ @fields ||= {}
21
+ @fields[association] = block
22
+ super
23
+ end
24
+
25
+ def fields_for_nested_model(name, association, args, block)
26
+ output = '<div class="fields">'.html_safe
27
+ output << super
28
+ output.safe_concat('</div>')
29
+ output
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ module NestedForm
2
+ module ViewHelper
3
+ def nested_form_for(*args, &block)
4
+ options = args.extract_options!.reverse_merge(:builder => NestedForm::Builder)
5
+ output = form_for(*(args << options), &block)
6
+ @after_nested_form_callbacks ||= []
7
+ fields = @after_nested_form_callbacks.map do |callback|
8
+ callback.call
9
+ end
10
+ output << fields.join(" ").html_safe
11
+ end
12
+
13
+ def after_nested_form(association, &block)
14
+ @associations ||= []
15
+ @after_nested_form_callbacks ||= []
16
+ unless @associations.include?(association)
17
+ @associations << association
18
+ @after_nested_form_callbacks << block
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ class ActionView::Base
25
+ include NestedForm::ViewHelper
26
+ end
@@ -0,0 +1,2 @@
1
+ require "nested_form/builder"
2
+ require "nested_form/view_helper"
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe NestedForm::Builder do
4
+ describe "with no options" do
5
+ before(:each) do
6
+ @project = Project.new
7
+ @template = ActionView::Base.new
8
+ @template.output_buffer = ""
9
+ @builder = NestedForm::Builder.new(:item, @project, @template, {}, proc {})
10
+ end
11
+
12
+ it "should have an add link" do
13
+ @builder.link_to_add("Add", :tasks).should == '<a href="javascript:void(0)" class="add_nested_fields" data-association="tasks">Add</a>'
14
+ end
15
+
16
+ it "should have a remove link" do
17
+ @builder.link_to_remove("Remove").should == '<input id="item__destroy" name="item[_destroy]" type="hidden" value="false" /><a href="javascript:void(0)" class="remove_nested_fields">Remove</a>'
18
+ end
19
+
20
+ it "should wrap nested fields each in a div with class" do
21
+ 2.times { @project.tasks.build }
22
+ @builder.fields_for(:tasks) do
23
+ "Task"
24
+ end.should == '<div class="fields">Task</div><div class="fields">Task</div>'
25
+ end
26
+
27
+ it "should add task fields to hidden div after form" do
28
+ pending
29
+ output = ""
30
+ mock(@template).after_nested_form(:tasks) { |arg, block| output << block.call }
31
+ @builder.fields_for(:tasks) { "Task" }
32
+ @builder.link_to_add("Add", :tasks)
33
+ output.should == '<div id="tasks_fields_blueprint" style="display: none"><div class="fields">Task</div></div>'
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe NestedForm::ViewHelper do
4
+ before(:each) do
5
+ @template = ActionView::Base.new
6
+ @template.output_buffer = ""
7
+ @template.stubs(:url_for).returns("")
8
+ @template.stubs(:projects_path).returns("")
9
+ @template.stubs(:protect_against_forgery?).returns(false)
10
+ end
11
+
12
+ it "should pass nested form builder to form_for along with other options" do
13
+ pending
14
+ mock.proxy(@template).form_for(:first, :as => :second, :other => :arg, :builder => NestedForm::Builder) do |form_html|
15
+ form_html
16
+ end
17
+ @template.nested_form_for(:first, :as => :second, :other => :arg) {"form"}
18
+ end
19
+
20
+ it "should pass instance of NestedForm::Builder to nested_form_for block" do
21
+ @template.nested_form_for(Project.new) do |f|
22
+ f.should be_instance_of(NestedForm::Builder)
23
+ end
24
+ end
25
+
26
+ it "should append content to end of nested form" do
27
+ @template.after_nested_form(:tasks) { @template.concat("123") }
28
+ @template.after_nested_form(:milestones) { @template.concat("456") }
29
+ @template.nested_form_for(Project.new) {}
30
+ @template.output_buffer.should include("123456")
31
+ end
32
+ end
33
+
@@ -0,0 +1,55 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ require "action_view"
5
+ require "active_record"
6
+
7
+ Bundler.require(:default)
8
+
9
+ # require 'active_model'
10
+ # require 'active_record'
11
+ # require 'action_controller'
12
+ # require 'action_view'
13
+ # require 'action_view/template'
14
+ # require "active_support/all"
15
+
16
+ # require 'nested_form/view_helper'
17
+ # require 'nested_form/builder'
18
+
19
+ RSpec.configure do |config|
20
+ config.mock_with :mocha
21
+ end
22
+
23
+ class TablelessModel < ActiveRecord::Base
24
+ def self.columns() @columns ||= []; end
25
+
26
+ def self.column(name, sql_type = nil, default = nil, null = true)
27
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
28
+ end
29
+
30
+ def self.quoted_table_name
31
+ name.pluralize.underscore
32
+ end
33
+
34
+ def quoted_id
35
+ "0"
36
+ end
37
+ end
38
+
39
+ class Project < TablelessModel
40
+ column :name, :string
41
+ has_many :tasks
42
+ accepts_nested_attributes_for :tasks
43
+ end
44
+
45
+ class Task < TablelessModel
46
+ column :project_id, :integer
47
+ column :name, :string
48
+ belongs_to :project
49
+ end
50
+
51
+ class Milestone < TablelessModel
52
+ column :task_id, :integer
53
+ column :name, :string
54
+ belongs_to :task
55
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nested_form
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Bates
9
+ - Andrea Singh
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2011-02-15 00:00:00 -08:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: rspec
19
+ prerelease: false
20
+ requirement: &id001 !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ~>
24
+ - !ruby/object:Gem::Version
25
+ version: 2.1.0
26
+ type: :development
27
+ version_requirements: *id001
28
+ - !ruby/object:Gem::Dependency
29
+ name: mocha
30
+ prerelease: false
31
+ requirement: &id002 !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: "0"
37
+ type: :development
38
+ version_requirements: *id002
39
+ - !ruby/object:Gem::Dependency
40
+ name: rails
41
+ prerelease: false
42
+ requirement: &id003 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0
48
+ type: :development
49
+ version_requirements: *id003
50
+ description: Gem to conveniently handle multiple models in a single form with Rails 3 and jQuery or Prototype.
51
+ email: ryan@railscasts.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - lib/generators/nested_form/install_generator.rb
60
+ - lib/generators/nested_form/templates/jquery_nested_form.js
61
+ - lib/generators/nested_form/templates/prototype_nested_form.js
62
+ - lib/nested_form/builder.rb
63
+ - lib/nested_form/view_helper.rb
64
+ - lib/nested_form.rb
65
+ - spec/nested_form/builder_spec.rb
66
+ - spec/nested_form/view_helper_spec.rb
67
+ - spec/spec_helper.rb
68
+ - Gemfile
69
+ - LICENSE
70
+ - Rakefile
71
+ - README.rdoc
72
+ has_rdoc: true
73
+ homepage: http://github.com/ryanb/nested_form
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.3.4
93
+ requirements: []
94
+
95
+ rubyforge_project: nested_form
96
+ rubygems_version: 1.5.0
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Gem to conveniently handle multiple models in a single form.
100
+ test_files: []
101
+