awesome_nested_fields 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
data/README.md
CHANGED
@@ -26,7 +26,7 @@ Installation
|
|
26
26
|
|
27
27
|
gem 'awesome_nested_fields'
|
28
28
|
|
29
|
-
2. Copy the javascript dependency to `public
|
29
|
+
2. Copy the javascript dependency to `public/javascripts` by using the generator.
|
30
30
|
|
31
31
|
rails generate awesome_nested_fields:install
|
32
32
|
|
@@ -44,62 +44,144 @@ Basic Usage
|
|
44
44
|
|
45
45
|
First, make sure the object that has the `has_many` or `has_and_belongs_to_many` relation accepts nested attributes for the collection you want. For example, if a person _has_many_ phones, we'll have a model like this:
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
class Person < ActiveRecord::Base
|
48
|
+
has_many :phones
|
49
|
+
accepts_nested_attributes_for :phones, allow_destroy: true
|
50
|
+
end
|
51
51
|
|
52
52
|
The `accepts_nested_attributes_for` is a method from Active Record that allows you to pass attributes of nested models directly to its parent, instead of instantiate each child object separately. In this case, `Person` gains a method called `phones_attributes=`, that accepts data for new and existing phones of a given person. The `allow_destroy` option enables us to also delete child objects. To know more about nested attributes, check out the [ActiveRecord::NestedAttribute](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L1) class.
|
53
53
|
|
54
54
|
### View
|
55
55
|
|
56
|
-
The next step is set up the form view
|
57
|
-
|
56
|
+
The next step is set up the form view with the `nested_fields_for` method. It receives the association/collection name, an optional hash of options (humm, a pun) and a block with the nested fields. Proceeding with the person/phones example, we can have a form like this:
|
57
|
+
|
58
|
+
<%= form_for(@person) do |f| %>
|
59
|
+
<% # person fields... %>
|
60
|
+
|
61
|
+
<h2>Phones</h2>
|
62
|
+
<div class="container">
|
63
|
+
<%= f.nested_fields_for :phones do |f| %>
|
64
|
+
<fieldset class="item">
|
65
|
+
<%= f.label :number %>
|
66
|
+
<%= f.text_field :number %>
|
67
|
+
|
68
|
+
<a href="#" class="remove">remove</a>
|
69
|
+
|
70
|
+
<%= f.hidden_field :id %>
|
71
|
+
<%= f.hidden_field :_destroy %>
|
72
|
+
</fieldset>
|
73
|
+
<% end %>
|
74
|
+
</div>
|
75
|
+
<a href="#" class="add">add phone</a>
|
76
|
+
|
77
|
+
<% # more person fields... %>
|
78
|
+
<% end %>
|
58
79
|
|
59
|
-
|
60
|
-
<% # person fields... %>
|
80
|
+
The `nested_fields_for` method lists the phones this person has and also adds an empty template to the page for creating new phones. (Actually, there is too much code inside the block. If you're not working with a simple example like this you better extract this code into a partial and call just `render :phones` inside the block. Good coding practices, you know.)
|
61
81
|
|
62
|
-
|
63
|
-
<div class="container">
|
64
|
-
<%= nested_fields(f, :phones) %>
|
65
|
-
</div>
|
66
|
-
<a href="#" class="add">add phone</a>
|
82
|
+
If you're paying attention, you noticed the key elements are marked with special class names. We *need* this for the javascript code, so it knows what to do with each HTML element: the one that have the children must have the class `container`; each child must be marked with the class `item`; inside an item, the link for removal must have the class `remove`; and the link to add new items must have the class `add`. We can change the names later, but these are the default choices. Finally, don't forget to add the `id` field, as it is needed by AR to identify whether this is an existing or a new element, and the `_destroy` field to activate deletion when the user clicks on the remove link.
|
67
83
|
|
68
|
-
|
69
|
-
<% end %>
|
84
|
+
### Javascript
|
70
85
|
|
71
|
-
|
86
|
+
This is the easiest part: just activate the nested fields actions when the page loads. We can put this in the `application.js` file (or in any other place that gets executed in the page):
|
72
87
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
<%= f.label :number %>
|
78
|
-
<%= f.text_field :number %>
|
88
|
+
$(document).ready(function(e) {
|
89
|
+
$('FORM').nestedFields();
|
90
|
+
});
|
79
91
|
|
80
|
-
|
81
|
-
|
82
|
-
<%= f.hidden_field :id %>
|
83
|
-
<%= f.hidden_field :_destroy %>
|
84
|
-
</fieldset>
|
92
|
+
Now enjoy your new nested model form!
|
85
93
|
|
86
|
-
If you're paying attention, you noticed the key elements are marked with a special class name. We need this for the javascript code, so it knows what to do with each HTML element: the one that have the children must have the class `container`; each child must be marked with the class `item`; inside an item, the link for removal must have the class `remove`; and the link to add new items must have the class `add`. We can change the names later, but these are the default choices. Finally, don't forget to add the `id` field, as it is needed by AR to identify if this is an existing or a new element, and the `_destroy` field to activate deletion when the user clicks on the remove link.
|
87
94
|
|
88
|
-
|
95
|
+
Reference
|
96
|
+
---------
|
89
97
|
|
90
|
-
|
98
|
+
### View Options
|
99
|
+
|
100
|
+
There are some view options, but most are internal. There is just one you really need to know about; for the others, go to the code.
|
101
|
+
|
102
|
+
#### show_empty
|
103
|
+
|
104
|
+
Sometimes you want to show something when the collection is empty. Just set `show_empty` to `true` and prepare the block to receive `nil` when the collection is empty. Awesome nested fields will take care to show the empty message when there are no elements and remove it when one is added.
|
105
|
+
To implement this on the basic example, do something like:
|
106
|
+
|
107
|
+
<%= f.nested_fields_for :phones, show_empty: true do |f| %>
|
108
|
+
<% if f %>
|
109
|
+
<% fields code... %>
|
110
|
+
<% else %>
|
111
|
+
<p class="empty">There are no phones.</p>
|
112
|
+
<% end %>
|
113
|
+
<% end %>
|
114
|
+
|
115
|
+
And yeah, you need to mark it with the class `empty` or any other selector configured via javascript.
|
91
116
|
|
92
|
-
|
93
|
-
|
117
|
+
### Javascript Options
|
118
|
+
|
119
|
+
#### Selectors
|
120
|
+
|
121
|
+
To make nested fields work dynamically, the JS code needs to know what elements to use. By default, this is made by marking key elements with CSS classes, but you can use other selectors (any valid jQuery selector will do). The available options are shown below.
|
122
|
+
|
123
|
+
* `itemSelector` marks each item from the collection (`.item` by default)
|
124
|
+
* `containerSelector` marks the element that contains the items (`.container` by default)
|
125
|
+
* `addSelector` marks the element that will add a new item to the container when clicked (`.add` by default)
|
126
|
+
* `removeSelector` marks the element inside an item that will remove it when clicked (`.remove` by default)
|
127
|
+
* `emptySelector` marks the element that is shown when there are no items; used in conjunction with `show_empty` option (`.empty` by default)
|
128
|
+
|
129
|
+
For example, if you are using nested fields inside a table, you can do:
|
130
|
+
|
131
|
+
element.nestedFields({
|
132
|
+
containerSelector: 'tbody',
|
133
|
+
itemSelector: 'tr'
|
134
|
+
});
|
135
|
+
|
136
|
+
|
137
|
+
#### Callbacks
|
138
|
+
|
139
|
+
Actions can be executed before or after items get inserted or removed. There are four callbacks available: `beforeInsert`, `afterInsert`, `beforeRemove` and `afterRemove`. All of them receive the item as the first parameter, so you can query or modify it before the operation.
|
140
|
+
|
141
|
+
element.nestedFields({
|
142
|
+
beforeInsert: function(item) {
|
143
|
+
item.css('color', 'red'); // Make some operation
|
144
|
+
console.log(item + ' will be inserted.')
|
145
|
+
},
|
146
|
+
afterRemove: function(item) {
|
147
|
+
console.log(item + ' was removed.');
|
148
|
+
}
|
149
|
+
});
|
150
|
+
|
151
|
+
The before callbacks also allow you to control when the element will be inserted or removed, so you can perform async operations (ajax, of course!) or choose to not insert or remove the element at all if some condition is not met. Just receive a second parameter as the handler function.
|
152
|
+
|
153
|
+
element.nestedFields({
|
154
|
+
beforeInsert: function(item, insert) {
|
155
|
+
$.get('/ajax_function', function() {
|
156
|
+
insert();
|
94
157
|
});
|
158
|
+
}
|
159
|
+
});
|
95
160
|
|
96
|
-
|
161
|
+
|
162
|
+
### Javascript API
|
163
|
+
|
164
|
+
It is possible to control nested fields programmatically using a jQuery-style API.
|
165
|
+
|
166
|
+
element.nestedFields('insert', function(item) {
|
167
|
+
// Make some operation with item
|
168
|
+
}, {skipBefore: true});
|
169
|
+
|
170
|
+
The code above inserts a new item and does not execute the `beforeInsert` callback function. The complete list of available methods is shown below.
|
171
|
+
|
172
|
+
* `insert(callback, options)` inserts a new item in the container. The `callback` function is executed just before the item is inserted. There are two available options: `skipBefore` and `skipAfter`. Both arguments are optional.
|
173
|
+
* `remove(element, options)` removes `element` from the container. There are two available options: `skipBefore` and `skipAfter`. The last argument is optional.
|
174
|
+
* `removeAll(options)` removes all elements from the container. There are two available options: `skipBefore` and `skipAfter`. The argument is optional.
|
175
|
+
* `items()` returns a list of items on the container.
|
176
|
+
* `destroy()` deactivates nested fields for the element.
|
177
|
+
|
178
|
+
These methods can be called from the element where nested fields are applied (e.g. a form) or from any element inside it (e.g. an input or the container itself).
|
97
179
|
|
98
180
|
|
99
181
|
Compatibility
|
100
182
|
-------------
|
101
183
|
|
102
|
-
awesome_nested_fields works only with
|
184
|
+
awesome_nested_fields works only with jQuery and Rails 3.x. Sorry, Rails 2.x users.
|
103
185
|
|
104
186
|
|
105
187
|
TODO
|
@@ -108,8 +190,6 @@ TODO
|
|
108
190
|
* Write tests
|
109
191
|
* Write awesome demos
|
110
192
|
* Make sure it can degrade gracefully
|
111
|
-
* Return and API object on JS to make interaction easier
|
112
|
-
* Make `nested_fields` call compatible with Rails `fields_for`
|
113
193
|
|
114
194
|
|
115
195
|
Copyleft
|
@@ -0,0 +1,34 @@
|
|
1
|
+
ActionView::Helpers::FormBuilder.class_eval do
|
2
|
+
def nested_fields_for(association, options={}, &block)
|
3
|
+
raise ArgumentError, 'Missing block to nested_fields_for' unless block_given?
|
4
|
+
|
5
|
+
options[:new_item_index] ||= 'new_nested_item'
|
6
|
+
options[:new_object] ||= self.object.class.reflect_on_association(association).klass.new
|
7
|
+
options[:item_template_class] ||= 'template item'
|
8
|
+
options[:empty_template_class] ||= 'template empty'
|
9
|
+
options[:show_empty] ||= false
|
10
|
+
|
11
|
+
output = @template.capture { fields_for(association, &block) }
|
12
|
+
|
13
|
+
if options[:show_empty] and self.object.send(association).empty?
|
14
|
+
output.safe_concat @template.capture { yield nil }
|
15
|
+
end
|
16
|
+
|
17
|
+
output.safe_concat nested_fields_templates(association, options, &block)
|
18
|
+
|
19
|
+
output
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def nested_fields_templates(association, options, &block)
|
24
|
+
templates = @template.content_tag(:script, type: 'text/html', class: options[:item_template_class]) do
|
25
|
+
fields_for(association, options[:new_object], child_index: options[:new_item_index], &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
if options[:show_empty]
|
29
|
+
templates.safe_concat @template.content_tag(:script, type: 'text/html', class: options[:empty_template_class], &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
templates
|
33
|
+
end
|
34
|
+
end
|
@@ -13,13 +13,13 @@
|
|
13
13
|
afterInsert: function(item) {},
|
14
14
|
beforeRemove: function(item, callback) { callback() },
|
15
15
|
afterRemove: function(item) {},
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
itemTemplateSelector: '.item.template',
|
17
|
+
emptyTemplateSelector: '.empty.template',
|
18
|
+
containerSelector: '.container',
|
19
|
+
itemSelector: '.item',
|
20
|
+
emptySelector: '.empty',
|
21
|
+
addSelector: '.add',
|
22
|
+
removeSelector: '.remove',
|
23
23
|
newItemIndex: 'new_nested_item'
|
24
24
|
};
|
25
25
|
|
@@ -36,35 +36,26 @@
|
|
36
36
|
}
|
37
37
|
|
38
38
|
options = $.extend({}, defaultSettings, options);
|
39
|
-
options.itemTemplate = $(options.
|
40
|
-
options.
|
41
|
-
options.container = $(options.
|
42
|
-
options.add = $(options.
|
39
|
+
options.itemTemplate = $(options.itemTemplateSelector, $this);
|
40
|
+
options.emptyTemplate = $(options.emptyTemplateSelector, $this);
|
41
|
+
options.container = $(options.containerSelector, $this);
|
42
|
+
options.add = $(options.addSelector, $this);
|
43
43
|
$this.data('nested-fields.options', options);
|
44
44
|
|
45
|
-
options
|
46
|
-
|
47
|
-
var newItem = prepareTemplate(options);
|
48
|
-
insertItemWithCallbacks(newItem, null, options);
|
49
|
-
});
|
50
|
-
|
51
|
-
$(options.item, options.container).each(function(i, item) {
|
52
|
-
bindRemoveEvent(item, options);
|
53
|
-
});
|
45
|
+
bindInsertToAdd(options);
|
46
|
+
bindRemoveToItems(options);
|
54
47
|
|
55
48
|
return $this;
|
56
49
|
},
|
57
50
|
|
58
51
|
insert: function(callback, options) {
|
59
52
|
options = $.extend({}, getOptions(this), options);
|
60
|
-
|
61
|
-
|
62
|
-
insertItemWithCallbacks(newItem, callback, options);
|
53
|
+
return insertItemWithCallbacks(callback, options);
|
63
54
|
},
|
64
55
|
|
65
56
|
remove: function(element, options) {
|
66
57
|
options = $.extend({}, getOptions(this), options);
|
67
|
-
return
|
58
|
+
return removeItemWithCallbacks(element, options);
|
68
59
|
},
|
69
60
|
|
70
61
|
removeAll: function(options) {
|
@@ -94,6 +85,8 @@
|
|
94
85
|
}
|
95
86
|
};
|
96
87
|
|
88
|
+
// Initialization functions
|
89
|
+
|
97
90
|
function getOptions(element) {
|
98
91
|
element = $(element);
|
99
92
|
while(element.length > 0) {
|
@@ -107,6 +100,21 @@
|
|
107
100
|
return null;
|
108
101
|
}
|
109
102
|
|
103
|
+
function bindInsertToAdd(options) {
|
104
|
+
options.add.bind('click.nested-fields', function(e) {
|
105
|
+
e.preventDefault();
|
106
|
+
insertItemWithCallbacks(null, options);
|
107
|
+
});
|
108
|
+
}
|
109
|
+
|
110
|
+
function bindRemoveToItems(options) {
|
111
|
+
$(options.itemSelector, options.containerSelector).each(function(i, item) {
|
112
|
+
bindRemoveToItem(item, options);
|
113
|
+
});
|
114
|
+
}
|
115
|
+
|
116
|
+
// Insertion functions
|
117
|
+
|
110
118
|
function prepareTemplate(options) {
|
111
119
|
var regexp = new RegExp(options.newItemIndex, 'g');
|
112
120
|
var newId = new Date().getTime();
|
@@ -116,26 +124,27 @@
|
|
116
124
|
newItem.attr('data-new-record', true);
|
117
125
|
newItem.attr('data-record-id', newId);
|
118
126
|
|
119
|
-
|
127
|
+
bindRemoveToItem(newItem, options);
|
120
128
|
|
121
129
|
return newItem;
|
122
130
|
}
|
123
131
|
|
124
|
-
function
|
125
|
-
|
126
|
-
|
127
|
-
}
|
128
|
-
|
129
|
-
function insertItemWithCallbacks(newItem, onInsertCallback, options) {
|
132
|
+
function insertItemWithCallbacks(onInsertCallback, options) {
|
133
|
+
var newItem = prepareTemplate(options);
|
134
|
+
|
130
135
|
function insert() {
|
131
136
|
if(onInsertCallback) {
|
132
137
|
onInsertCallback(newItem);
|
133
138
|
}
|
134
|
-
|
139
|
+
removeEmpty(options);
|
140
|
+
options.container.append(newItem);
|
135
141
|
}
|
136
142
|
|
137
|
-
if(!options.skipBefore) {
|
143
|
+
if(!options.skipBefore) {
|
138
144
|
options.beforeInsert(newItem, insert);
|
145
|
+
if(options.beforeInsert.length <= 1) {
|
146
|
+
insert();
|
147
|
+
}
|
139
148
|
} else {
|
140
149
|
insert();
|
141
150
|
}
|
@@ -147,7 +156,13 @@
|
|
147
156
|
return newItem;
|
148
157
|
}
|
149
158
|
|
150
|
-
function
|
159
|
+
function removeEmpty(options) {
|
160
|
+
findEmpty(options).remove();
|
161
|
+
}
|
162
|
+
|
163
|
+
// Removal functions
|
164
|
+
|
165
|
+
function removeItemWithCallbacks(element, options) {
|
151
166
|
function remove() {
|
152
167
|
if($element.attr('data-new-record')) { // record is new
|
153
168
|
$element.remove();
|
@@ -155,12 +170,15 @@
|
|
155
170
|
$element.find("INPUT[name$='[_destroy]']").val('true');
|
156
171
|
$element.hide();
|
157
172
|
}
|
158
|
-
|
173
|
+
insertEmpty(options);
|
159
174
|
}
|
160
175
|
|
161
176
|
var $element = $(element);
|
162
177
|
if(!options.skipBefore) {
|
163
178
|
options.beforeRemove($element, remove);
|
179
|
+
if(options.beforeRemove.length <= 1) {
|
180
|
+
insert();
|
181
|
+
}
|
164
182
|
} else {
|
165
183
|
remove();
|
166
184
|
}
|
@@ -172,35 +190,33 @@
|
|
172
190
|
return $element;
|
173
191
|
}
|
174
192
|
|
175
|
-
function
|
176
|
-
|
193
|
+
function insertEmpty(options) {
|
194
|
+
if(findItems(options).length === 0) {
|
195
|
+
options.container.append(options.emptyTemplate.html());
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
function bindRemoveToItem(item, options) {
|
200
|
+
var removeHandler = $(item).find(options.removeSelector);
|
177
201
|
var needsConfirmation = removeHandler.attr('data-confirm');
|
178
202
|
|
179
203
|
var event = needsConfirmation ? 'confirm:complete' : 'click';
|
180
204
|
removeHandler.bind(event + '.nested-fields', function(e, confirmed) {
|
181
205
|
e.preventDefault();
|
182
206
|
if(confirmed === undefined || confirmed === true) {
|
183
|
-
|
207
|
+
removeItemWithCallbacks(item, options);
|
184
208
|
}
|
185
209
|
});
|
186
210
|
}
|
187
211
|
|
188
|
-
|
189
|
-
if(findItems(options).length === 0) {
|
190
|
-
options.container.append(options.noneTemplate.html());
|
191
|
-
}
|
192
|
-
}
|
193
|
-
|
194
|
-
function removeNone(options) {
|
195
|
-
findNone(options).remove();
|
196
|
-
}
|
212
|
+
// Find functions
|
197
213
|
|
198
214
|
function findItems(options) {
|
199
|
-
return options.container.find(options.
|
215
|
+
return options.container.find(options.itemSelector + ':visible');
|
200
216
|
}
|
201
217
|
|
202
|
-
function
|
203
|
-
return options.container.find(options.
|
218
|
+
function findEmpty(options) {
|
219
|
+
return options.container.find(options.emptySelector);
|
204
220
|
}
|
205
221
|
|
206
222
|
})(jQuery);
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awesome_nested_fields
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-07-
|
12
|
+
date: 2011-07-11 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
|
-
requirement: &
|
16
|
+
requirement: &2155985320 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.0.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2155985320
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rails
|
27
|
-
requirement: &
|
27
|
+
requirement: &2155984440 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 3.0.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2155984440
|
36
36
|
description: Awesome dynamic nested fields for Rails and jQuery
|
37
37
|
email: lailson@guava.com.br
|
38
38
|
executables: []
|
@@ -45,13 +45,13 @@ files:
|
|
45
45
|
- LICENSE
|
46
46
|
- README.md
|
47
47
|
- Rakefile
|
48
|
-
- app/helpers/awesome_nested_fields_helper.rb
|
49
48
|
- awesome_nested_fields.gemspec
|
50
49
|
- lib/awesome_nested_fields.rb
|
51
50
|
- lib/awesome_nested_fields/engine.rb
|
52
51
|
- lib/awesome_nested_fields/railtie.rb
|
53
52
|
- lib/awesome_nested_fields/version.rb
|
54
53
|
- lib/generators/awesome_nested_fields/install/install_generator.rb
|
54
|
+
- lib/rails/form_helper.rb
|
55
55
|
- vendor/assets/javascripts/jquery.nested-fields.js
|
56
56
|
homepage: http://rubygems.org/gems/awesome_nested_fields
|
57
57
|
licenses: []
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module AwesomeNestedFieldsHelper
|
2
|
-
|
3
|
-
def nested_fields(builder, association, options={})
|
4
|
-
nested_fields_items(builder, association, options) <<
|
5
|
-
nested_fields_template(builder, association, options)
|
6
|
-
end
|
7
|
-
|
8
|
-
def nested_fields_items(builder, association, options={})
|
9
|
-
options = nested_fields_process_default_options(options, builder, association)
|
10
|
-
|
11
|
-
items = ''
|
12
|
-
builder.fields_for(association) do |f|
|
13
|
-
items << render(options[:partial], options[:builder_local] => f)
|
14
|
-
end
|
15
|
-
|
16
|
-
if options[:none_partial] and builder.object.send(association).empty?
|
17
|
-
items << render(options[:none_partial])
|
18
|
-
end
|
19
|
-
|
20
|
-
items.html_safe
|
21
|
-
end
|
22
|
-
|
23
|
-
def nested_fields_template(builder, association, options={})
|
24
|
-
options = nested_fields_process_default_options(options, builder, association)
|
25
|
-
|
26
|
-
templates = content_tag(:script, type: 'text/html', class: options[:item_template_class]) do
|
27
|
-
builder.fields_for(association, options[:new_object], child_index: options[:new_item_index]) do |f|
|
28
|
-
render(options[:partial], options[:builder_local] => f)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
if options[:none_partial]
|
33
|
-
templates << content_tag(:script, type: 'text/html', class: options[:none_template_class]) do
|
34
|
-
builder.fields_for(association, options[:new_object], child_index: options[:new_item_index]) do |f|
|
35
|
-
render(options[:none_partial], options[:builder_local] => f)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
templates.html_safe
|
41
|
-
end
|
42
|
-
|
43
|
-
protected
|
44
|
-
def nested_fields_process_default_options(options, builder, association)
|
45
|
-
options[:new_object] ||= builder.object.class.reflect_on_association(association).klass.new
|
46
|
-
options[:partial] ||= association.to_s.singularize
|
47
|
-
options[:builder_local] ||= :f
|
48
|
-
options[:item_template_class] ||= 'template item'
|
49
|
-
options[:none_template_class] ||= 'template none'
|
50
|
-
options[:new_item_index] ||= 'new_nested_item'
|
51
|
-
options
|
52
|
-
end
|
53
|
-
end
|