nested_fields_rails 0.0.1
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +104 -0
- data/Rakefile +3 -0
- data/lib/nested_fields_rails/engine.rb +4 -0
- data/lib/nested_fields_rails/form_builder.rb +62 -0
- data/lib/nested_fields_rails/version.rb +3 -0
- data/lib/nested_fields_rails.rb +3 -0
- data/nested_fields_rails.gemspec +18 -0
- data/vendor/assets/javascripts/jquery_nested_fields.js +97 -0
- metadata +56 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 gabriel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# NestedFieldsRails
|
2
|
+
|
3
|
+
Manage multiple models in the same form. Inspired on [nested_form gem](https://github.com/ryanb/nested_form)
|
4
|
+
|
5
|
+
Tested with Rails 3.2.x and Ruby 1.9.3
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'nested_fields_rails'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install nested_fields_rails
|
20
|
+
|
21
|
+
And then add it to the Asset Pipeline in the application.js file:
|
22
|
+
|
23
|
+
//= require jquery_nested_fields
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Model definition
|
28
|
+
|
29
|
+
class Person
|
30
|
+
has_many :phones
|
31
|
+
attr_accessible :phones_attributes
|
32
|
+
accepts_nested_attributes_for :phones, :allow_destroy => true
|
33
|
+
end
|
34
|
+
|
35
|
+
### Form specification
|
36
|
+
|
37
|
+
= form_for @person do |f|
|
38
|
+
...
|
39
|
+
%strong Phones
|
40
|
+
= f.link_to_add_nested_fields 'Add', :phones
|
41
|
+
= f.nested_fields_for :phones do |phones_form|
|
42
|
+
= phones_form.link_to_remove_nested_fields 'Remove'
|
43
|
+
= phones_form.text_field :number
|
44
|
+
|
45
|
+
### Partials support
|
46
|
+
|
47
|
+
Main form
|
48
|
+
|
49
|
+
= form_for @person do |f|
|
50
|
+
...
|
51
|
+
%strong Phones
|
52
|
+
= f.link_to_add_nested_fields 'Add', :phones
|
53
|
+
= f.nested_fields_for :phones
|
54
|
+
|
55
|
+
Partial for item
|
56
|
+
|
57
|
+
= f.link_to_remove_nested_fields 'Remove'
|
58
|
+
= f.text_field :number
|
59
|
+
|
60
|
+
### Html wrappers
|
61
|
+
|
62
|
+
Table wrapper
|
63
|
+
|
64
|
+
= form_for @person do |f|
|
65
|
+
...
|
66
|
+
%table
|
67
|
+
%thead
|
68
|
+
%tr
|
69
|
+
%td f.link_to_add_nested_fields 'Add', :phones
|
70
|
+
%td Phones
|
71
|
+
= f.nested_fields_for :phones, nil, :wrapper => :table do |phones_form|
|
72
|
+
%td= phones_form.link_to_remove_nested_fields 'Remove'
|
73
|
+
%td= phones_form.text_field :number
|
74
|
+
|
75
|
+
Custom wrapper
|
76
|
+
|
77
|
+
= form_for @person do |f|
|
78
|
+
...
|
79
|
+
%strong Phones
|
80
|
+
= f.link_to_add_nested_fields 'Add', :phones
|
81
|
+
= f.nested_fields_for :phones, nil, :collection_wrapper => :ul, :element_wrapper => :li do |phones_form|
|
82
|
+
= phones_form.link_to_remove_nested_fields 'Remove'
|
83
|
+
= phones_form.text_field :number
|
84
|
+
|
85
|
+
### Order support
|
86
|
+
|
87
|
+
= form_for @person do |f|
|
88
|
+
...
|
89
|
+
%strong Phones
|
90
|
+
= f.link_to_add_nested_fields 'Add', :phones
|
91
|
+
= f.nested_fields_for :phones do |phones_form|
|
92
|
+
= phones_form.link_to_remove_nested_fields 'Remove'
|
93
|
+
= phones_form.link_to_up_nested_fields 'Up'
|
94
|
+
= phones_form.link_to_down_nested_fields 'Down'
|
95
|
+
= phones_form.text_field :number
|
96
|
+
= phones_form.index_nested_fields :order
|
97
|
+
|
98
|
+
## Contributing
|
99
|
+
|
100
|
+
1. Fork it
|
101
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
102
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
103
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
104
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module NestedFieldsRails
|
2
|
+
class ::ActionView::Helpers::FormBuilder
|
3
|
+
|
4
|
+
def link_to_add_nested_fields(*args, &block)
|
5
|
+
options = args.extract_options!.symbolize_keys
|
6
|
+
association = args.pop
|
7
|
+
options[:class] = [options[:class], 'add-nested-fields'].compact.join(' ')
|
8
|
+
options['data-association'] = association
|
9
|
+
args << (options.delete(:href) || 'javascript:void(0)')
|
10
|
+
args << options
|
11
|
+
@template.link_to(*args, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def link_to_remove_nested_fields(*args, &block)
|
15
|
+
(hidden_field(:_destroy, :class => 'destroy') << link_to_action_nested_fields(:remove, *args, &block)).html_safe
|
16
|
+
end
|
17
|
+
|
18
|
+
def link_to_up_nested_fields(*args, &block)
|
19
|
+
link_to_action_nested_fields(:up, *args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def link_to_down_nested_fields(*args, &block)
|
23
|
+
link_to_action_nested_fields(:down, *args, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def index_nested_fields(attribute_name)
|
27
|
+
hidden_field attribute_name, :class => 'index'
|
28
|
+
end
|
29
|
+
|
30
|
+
def nested_fields_for(association_name, record_object = nil, fields_options = {}, &block)
|
31
|
+
collection_wrapper = fields_options[:collection_wrapper] || (fields_options[:wrapper] && fields_options[:wrapper].to_sym == :table ? :tbody : nil) || :div
|
32
|
+
element_wrapper = fields_options[:element_wrapper] || (fields_options[:wrapper] && fields_options[:wrapper].to_sym == :table ? :tr : nil) || :div
|
33
|
+
|
34
|
+
fields_block = block || Proc.new { |fields| @template.render(:partial => "#{association_name.to_s.singularize}_fields", :locals => {:f => fields}) }
|
35
|
+
fields = @template.content_tag(collection_wrapper, fields_for(association_name, record_object, fields_options, &fields_block), :id => association_name, :class => 'nested-fields')
|
36
|
+
|
37
|
+
model_object = object.class.reflect_on_association(association_name).klass.new
|
38
|
+
template = fields_for(association_name, model_object, :element_wrapper => element_wrapper, :child_index => "new_#{association_name}", &fields_block)
|
39
|
+
template_options = {:id => "#{association_name}_fields_template", :type => 'text/x-jquery-tmpl'}
|
40
|
+
fields_template = @template.content_tag(:script, template, template_options)
|
41
|
+
|
42
|
+
fields + fields_template
|
43
|
+
end
|
44
|
+
|
45
|
+
alias default_fields_for_nested_model fields_for_nested_model
|
46
|
+
def fields_for_nested_model(name, object, options, block)
|
47
|
+
tag = options[:element_wrapper] || (options[:wrapper] && options[:wrapper].to_sym == :table ? :tr : nil) || :div
|
48
|
+
@template.content_tag(tag, default_fields_for_nested_model(name, object, options, block), :class => 'fields', 'data-object-id' => object.id || options[:child_index])
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def link_to_action_nested_fields(action, *args, &block)
|
54
|
+
options = args.extract_options!.symbolize_keys
|
55
|
+
options[:class] = [options[:class], "#{action}-nested-fields"].compact.join(" ")
|
56
|
+
args << (options.delete(:href) || 'javascript:void(0)')
|
57
|
+
args << options
|
58
|
+
@template.link_to(*args, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/nested_fields_rails/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'nested_fields_rails'
|
6
|
+
s.version = NestedFieldsRails::VERSION
|
7
|
+
|
8
|
+
s.authors = ["gabriel"]
|
9
|
+
s.email = ["gnaiman@keepcon.com"]
|
10
|
+
s.homepage = 'https://github.com/gabynaiman/nested_fields_rails'
|
11
|
+
s.summary = 'Manage multiple models the same form'
|
12
|
+
s.description = 'Manage multiple models the same form'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
jQuery(function($) {
|
2
|
+
|
3
|
+
NestedFields = function(associationName) {
|
4
|
+
this.associationName = associationName;
|
5
|
+
|
6
|
+
this.add = function() {
|
7
|
+
var template = $('#' + this.associationName + '_fields_template').html();
|
8
|
+
var container = $('#' + this.associationName + '.nested-fields');
|
9
|
+
var regexp = new RegExp("new_" + this.associationName, "g");
|
10
|
+
var newId = new Date().getTime();
|
11
|
+
container.append(template.replace(regexp, newId))
|
12
|
+
this.updateIndex();
|
13
|
+
};
|
14
|
+
|
15
|
+
this.remove = function(objectId) {
|
16
|
+
var fields = this.findFields(objectId);
|
17
|
+
var destroyField = fields.find('.destroy');
|
18
|
+
destroyField.val('true');
|
19
|
+
fields.hide();
|
20
|
+
this.updateIndex();
|
21
|
+
};
|
22
|
+
|
23
|
+
this.up = function(objectId) {
|
24
|
+
var fields = this.findFields(objectId);
|
25
|
+
var container = fields.closest('.nested-fields');
|
26
|
+
var index = fields.index() - 1;
|
27
|
+
|
28
|
+
if (index >= 0) {
|
29
|
+
this.findFields(objectId).remove();
|
30
|
+
container.children('.fields').eq(index).before(fields);
|
31
|
+
this.updateIndex();
|
32
|
+
}
|
33
|
+
};
|
34
|
+
|
35
|
+
this.down = function(objectId) {
|
36
|
+
var fields = this.findFields(objectId);
|
37
|
+
var container = fields.closest('.nested-fields');
|
38
|
+
var index = fields.index();
|
39
|
+
|
40
|
+
if (fields.index() < container.children('.fields').size()) {
|
41
|
+
this.findFields(objectId).remove();
|
42
|
+
container.children('.fields').eq(index).after(fields);
|
43
|
+
this.updateIndex();
|
44
|
+
}
|
45
|
+
};
|
46
|
+
|
47
|
+
this.findFields = function(objectId) {
|
48
|
+
return $('#' + this.associationName + '.nested-fields .fields[data-object-id=' + objectId + ']');
|
49
|
+
};
|
50
|
+
|
51
|
+
this.updateIndex = function() {
|
52
|
+
var container = $('#' + this.associationName + '.nested-fields');
|
53
|
+
container.find('.index').each(function(index, element) {
|
54
|
+
$(element).val(index);
|
55
|
+
});
|
56
|
+
}
|
57
|
+
|
58
|
+
this.ids = function() {
|
59
|
+
var ids = [];
|
60
|
+
$('#' + this.associationName + '.nested-fields .fields').each(function(index, element) {
|
61
|
+
ids.push($(element).attr('data-object-id'));
|
62
|
+
});
|
63
|
+
return ids;
|
64
|
+
};
|
65
|
+
};
|
66
|
+
|
67
|
+
function nestedFieldContext(element) {
|
68
|
+
var link = $(element);
|
69
|
+
var fields = link.closest('.fields');
|
70
|
+
var objectId = fields.attr('data-object-id');
|
71
|
+
var container = fields.closest('.nested-fields');
|
72
|
+
var associationName = container.attr('id');
|
73
|
+
return {'associationName': associationName, 'objectId': objectId};
|
74
|
+
};
|
75
|
+
|
76
|
+
$('.add-nested-fields').live('click', function() {
|
77
|
+
var link = $(this);
|
78
|
+
var associationName = link.attr('data-association');
|
79
|
+
new NestedFields(associationName).add();
|
80
|
+
});
|
81
|
+
|
82
|
+
$('.remove-nested-fields').live('click', function() {
|
83
|
+
var context = nestedFieldContext(this);
|
84
|
+
new NestedFields(context.associationName).remove(context.objectId);
|
85
|
+
});
|
86
|
+
|
87
|
+
$('.up-nested-fields').live('click', function() {
|
88
|
+
var context = nestedFieldContext(this);
|
89
|
+
new NestedFields(context.associationName).up(context.objectId);
|
90
|
+
});
|
91
|
+
|
92
|
+
$('.down-nested-fields').live('click', function() {
|
93
|
+
var context = nestedFieldContext(this);
|
94
|
+
new NestedFields(context.associationName).down(context.objectId);
|
95
|
+
});
|
96
|
+
|
97
|
+
});
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nested_fields_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- gabriel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-30 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Manage multiple models the same form
|
15
|
+
email:
|
16
|
+
- gnaiman@keepcon.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- lib/nested_fields_rails.rb
|
27
|
+
- lib/nested_fields_rails/engine.rb
|
28
|
+
- lib/nested_fields_rails/form_builder.rb
|
29
|
+
- lib/nested_fields_rails/version.rb
|
30
|
+
- nested_fields_rails.gemspec
|
31
|
+
- vendor/assets/javascripts/jquery_nested_fields.js
|
32
|
+
homepage: https://github.com/gabynaiman/nested_fields_rails
|
33
|
+
licenses: []
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.16
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: Manage multiple models the same form
|
56
|
+
test_files: []
|