cocooned 1.3.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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +194 -0
- data/History.md +253 -0
- data/LICENSE +13 -0
- data/README.md +293 -0
- data/Rakefile +44 -0
- data/app/assets/javascripts/cocoon.js +14 -0
- data/app/assets/javascripts/cocooned/core.js +284 -0
- data/app/assets/javascripts/cocooned/jquery/onload.js +10 -0
- data/app/assets/javascripts/cocooned/jquery/plugin.js +20 -0
- data/app/assets/javascripts/cocooned/plugins/limit.js +22 -0
- data/app/assets/javascripts/cocooned/plugins/reorderable.js +101 -0
- data/app/assets/javascripts/cocooned.js +3 -0
- data/app/assets/stylesheets/cocoon.css +3 -0
- data/app/assets/stylesheets/cocooned.css +9 -0
- data/cocooned.gemspec +37 -0
- data/config/linters/js.json +50 -0
- data/config/linters/ruby.yml +16 -0
- data/gemfiles/Gemfile.rails-4 +8 -0
- data/gemfiles/Gemfile.rails-5 +8 -0
- data/lib/cocooned/association_builder.rb +69 -0
- data/lib/cocooned/helpers/cocoon_compatibility.rb +27 -0
- data/lib/cocooned/helpers/deprecate.rb +49 -0
- data/lib/cocooned/helpers.rb +331 -0
- data/lib/cocooned/railtie.rb +14 -0
- data/lib/cocooned/version.rb +5 -0
- data/lib/cocooned.rb +6 -0
- data/package.json +24 -0
- data/yarn.lock +1052 -0
- metadata +183 -0
@@ -0,0 +1,331 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cocooned/helpers/deprecate'
|
4
|
+
require 'cocooned/helpers/cocoon_compatibility'
|
5
|
+
require 'cocooned/association_builder'
|
6
|
+
|
7
|
+
module Cocooned
|
8
|
+
# TODO: Remove in 2.0 (Only Cocoon class names).
|
9
|
+
HELPER_CLASSES = {
|
10
|
+
add: ['cocooned-add', 'add_fields'],
|
11
|
+
remove: ['cocooned-remove', 'remove_fields'],
|
12
|
+
up: ['cocooned-move-up'],
|
13
|
+
down: ['cocooned-move-down']
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
module Helpers
|
17
|
+
# Create aliases to old Cocoon method name
|
18
|
+
# TODO: Remove in 2.0
|
19
|
+
include Cocooned::Helpers::CocoonCompatibility
|
20
|
+
|
21
|
+
# Output an action link to add an item in a nested form.
|
22
|
+
#
|
23
|
+
# ==== Signatures
|
24
|
+
#
|
25
|
+
# cocooned_add_item_link(label, form, association, options = {})
|
26
|
+
# # Explicit name
|
27
|
+
#
|
28
|
+
# cocooned_add_item_link(form, association, options = {}) do
|
29
|
+
# # Name as a block
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# cocooned_add_item_link(form, association, options = {})
|
33
|
+
# # Use default name
|
34
|
+
#
|
35
|
+
# ==== Parameters
|
36
|
+
#
|
37
|
+
# `label` is the text to be used as the link label.
|
38
|
+
# Just as when you use the Rails builtin helper +link_to+, you can give an explicit
|
39
|
+
# label to your link or use a block to build it.
|
40
|
+
# If you provide neither an explicit label nor a block, Cocooned will try to use I18n
|
41
|
+
# to find a suitable label. This lookup will check the following keys:
|
42
|
+
#
|
43
|
+
# - `cocooned.{association}.add`
|
44
|
+
# - `cocooned.defaults.add`
|
45
|
+
#
|
46
|
+
# If none of these translation exist, it will fallback to 'Add'.
|
47
|
+
#
|
48
|
+
#
|
49
|
+
# `form` is your form builder. Can be a SimpleForm::Builder, Formtastic::Builder
|
50
|
+
# or a standard Rails FormBuilder.
|
51
|
+
#
|
52
|
+
# `association` is the name of the nested association.
|
53
|
+
# Ex: cocooned_add_item_link "Add an item", form, :items
|
54
|
+
#
|
55
|
+
# ==== Options
|
56
|
+
#
|
57
|
+
# `options` can be any of the following.
|
58
|
+
#
|
59
|
+
# Rendering options:
|
60
|
+
#
|
61
|
+
# - **partial**: the nested form partial.
|
62
|
+
# Defaults to `{association.singular_name}_fields`.
|
63
|
+
# - **form_name**: name used to access the form builder in the nested form partial.
|
64
|
+
# Defaults to `:f`.
|
65
|
+
# - **form_options**: options to be passed to the nested form builder. Can be used
|
66
|
+
# to specify a wrapper for simple_form_fields if you use its Bootstrap setup, for
|
67
|
+
# example.
|
68
|
+
# No defaults.
|
69
|
+
# - **locals**: a hash of local variables, will be forwarded to the partial.
|
70
|
+
# No default.
|
71
|
+
#
|
72
|
+
# Association options:
|
73
|
+
#
|
74
|
+
# - **insertion_method** : the jQuery method to be used to insert new items.
|
75
|
+
# Can be any of `before`, `after`, `append`, `prepend`, `replaceWith`.
|
76
|
+
# Defaults to `before`
|
77
|
+
# - **insertion_traversal** and **insertion_node** : respectively the jQuery
|
78
|
+
# traversal method and the jQuery compatible selector that will be used to find
|
79
|
+
# the insertion node, relative to the generated link.
|
80
|
+
# When both are specified, `$(addLink).{traversal}({node})` will be used.
|
81
|
+
# When only **insertion_node** is specified, `$({node})` will be used.
|
82
|
+
# When only **insertion_traversal** is specified, it will be ignored.
|
83
|
+
# When none is specified, `$(addLink).parent()` will be used.
|
84
|
+
# - **count**: how many item will be inserted on click.
|
85
|
+
# Defaults to 1.
|
86
|
+
# - **wrap_object**: anything responding to `call` to be used to wrap the newly build
|
87
|
+
# item instance. Can be useful with decorators or special initialisations.
|
88
|
+
# Ex: cocooned_add_item_link "Add an item", form, :items,
|
89
|
+
# wrap_object: Proc.new { |comment| CommentDecorator.new(comment) })
|
90
|
+
# No default.
|
91
|
+
# - **force_non_association_create**: force to build instances of the nested model
|
92
|
+
# outside association (i.e. without calling `build_{association}` or `{association}.build`)
|
93
|
+
# Can be usefull if, for some specific reason, you need an object to _not_ be created
|
94
|
+
# on the association, for example if you did not want `after_add` callbacks to be triggered.
|
95
|
+
# Defaults to false.
|
96
|
+
#
|
97
|
+
# Link HTML options:
|
98
|
+
#
|
99
|
+
# You can pass any option supported by +link_to+. It will be politely forwarded.
|
100
|
+
# See the documentation of +link_to+ for more informations.
|
101
|
+
#
|
102
|
+
# Compatibility options:
|
103
|
+
#
|
104
|
+
# These options are supported for backward compatibility with the original Cocoon.
|
105
|
+
# **Support for these options will be removed in the next major release !**.
|
106
|
+
#
|
107
|
+
# - **render_options**: A nested Hash originaly used to pass locals and form builder
|
108
|
+
# options.
|
109
|
+
#
|
110
|
+
def cocooned_add_item_link(*args, &block)
|
111
|
+
if block_given?
|
112
|
+
cocooned_add_item_link(capture(&block), *args)
|
113
|
+
|
114
|
+
elsif args.first.respond_to?(:object)
|
115
|
+
association = args.second
|
116
|
+
cocooned_add_item_link(cocooned_default_label(:add, association), *args)
|
117
|
+
|
118
|
+
else
|
119
|
+
name, form, association, html_options = *args
|
120
|
+
html_options ||= {}
|
121
|
+
html_options = html_options.with_indifferent_access
|
122
|
+
|
123
|
+
builder_options = cocooned_extract_builder_options!(html_options)
|
124
|
+
render_options = cocooned_extract_render_options!(html_options)
|
125
|
+
|
126
|
+
builder = Cocooned::AssociationBuilder.new(form, association, builder_options)
|
127
|
+
rendered = cocooned_render_association(builder, render_options)
|
128
|
+
|
129
|
+
data = cocooned_extract_data!(html_options).merge!(
|
130
|
+
association: builder.singular_name,
|
131
|
+
associations: builder.plural_name,
|
132
|
+
association_insertion_template: CGI.escapeHTML(rendered.to_str).html_safe
|
133
|
+
)
|
134
|
+
|
135
|
+
html_options[:data] = (html_options[:data] || {}).merge(data)
|
136
|
+
html_options[:class] = [Array(html_options.delete(:class)).collect { |k| k.to_s.split(' ') },
|
137
|
+
Cocooned::HELPER_CLASSES[:add]].flatten.compact.uniq.join(' ')
|
138
|
+
|
139
|
+
link_to(name, '#', html_options)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Output an action link to remove an item (and an hidden field to mark
|
144
|
+
# it as destroyed if it has already been persisted).
|
145
|
+
#
|
146
|
+
# ==== Signatures
|
147
|
+
#
|
148
|
+
# cocooned_remove_item_link(label, form, html_options = {})
|
149
|
+
# # Explicit name
|
150
|
+
#
|
151
|
+
# cocooned_remove_item_link(form, html_options = {}) do
|
152
|
+
# # Name as a block
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# cocooned_remove_item_link(form, html_options = {})
|
156
|
+
# # Use default name
|
157
|
+
#
|
158
|
+
# See the documentation of +link_to+ for valid options.
|
159
|
+
def cocooned_remove_item_link(name, form = nil, html_options = {}, &block)
|
160
|
+
# rubocop:disable Style/ParallelAssignment
|
161
|
+
html_options, form = form, nil if form.is_a?(Hash)
|
162
|
+
form, name = name, form if form.nil?
|
163
|
+
# rubocop:enable Style/ParallelAssignment
|
164
|
+
|
165
|
+
return cocooned_remove_item_link(capture(&block), form, html_options) if block_given?
|
166
|
+
|
167
|
+
association = form.object.class.to_s.tableize
|
168
|
+
return cocooned_remove_item_link(cocooned_default_label(:remove, association), form, html_options) if name.nil?
|
169
|
+
|
170
|
+
html_options[:class] = [html_options[:class], Cocooned::HELPER_CLASSES[:remove]].flatten.compact
|
171
|
+
html_options[:class] << (form.object.new_record? ? 'dynamic' : 'existing')
|
172
|
+
html_options[:class] << 'destroyed' if form.object.marked_for_destruction?
|
173
|
+
|
174
|
+
hidden_field_tag("#{form.object_name}[_destroy]", form.object._destroy) <<
|
175
|
+
link_to(name, '#', html_options)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Output an action link to move an item up.
|
179
|
+
#
|
180
|
+
# ==== Signatures
|
181
|
+
#
|
182
|
+
# cocooned_move_item_up_link(label, form, html_options = {})
|
183
|
+
# # Explicit name
|
184
|
+
#
|
185
|
+
# cocooned_move_item_up_link(form, html_options = {}) do
|
186
|
+
# # Name as a block
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# cocooned_move_item_up_link(form, html_options = {})
|
190
|
+
# # Use default name
|
191
|
+
#
|
192
|
+
# See the documentation of +link_to+ for valid options.
|
193
|
+
def cocooned_move_item_up_link(name, form = nil, html_options = {}, &block)
|
194
|
+
cocooned_move_item_link(:up, name, form, html_options, &block)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Output an action link to move an item down.
|
198
|
+
#
|
199
|
+
# ==== Signatures
|
200
|
+
#
|
201
|
+
# cocooned_move_item_down_link(label, html_options = {})
|
202
|
+
# # Explicit name
|
203
|
+
#
|
204
|
+
# cocooned_move_item_down_link(html_options = {}) do
|
205
|
+
# # Name as a block
|
206
|
+
# end
|
207
|
+
#
|
208
|
+
# cocooned_move_item_down_link(html_options = {})
|
209
|
+
# # Use default name
|
210
|
+
#
|
211
|
+
# See the documentation of +link_to+ for valid options.
|
212
|
+
def cocooned_move_item_down_link(name, form = nil, html_options = {}, &block)
|
213
|
+
cocooned_move_item_link(:down, name, form, html_options, &block)
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
|
218
|
+
def cocooned_move_item_link(direction, name, form = nil, html_options = {}, &block)
|
219
|
+
form, name = name, form if form.nil?
|
220
|
+
return cocooned_move_item_link(direction, capture(&block), form, html_options) if block_given?
|
221
|
+
return cocooned_move_item_link(direction, cocooned_default_label(direction), form, html_options) if name.nil?
|
222
|
+
|
223
|
+
html_options[:class] = [html_options[:class], Cocooned::HELPER_CLASSES[direction]].flatten.compact.join(' ')
|
224
|
+
link_to name, '#', html_options
|
225
|
+
end
|
226
|
+
|
227
|
+
def cocooned_default_label(action, association = nil)
|
228
|
+
# TODO: Remove in 2.0
|
229
|
+
if I18n.exists?(:cocoon)
|
230
|
+
msg = Cocooned::Helpers::Deprecate.deprecate_release_message('the :cocoon i18n scope', ':cocooned')
|
231
|
+
warn msg
|
232
|
+
end
|
233
|
+
|
234
|
+
keys = ["cocooned.defaults.#{action}", "cocoon.defaults.#{action}"]
|
235
|
+
keys.unshift("cocooned.#{association}.#{action}", "cocoon.#{association}.#{action}") unless association.nil?
|
236
|
+
keys.collect!(&:to_sym)
|
237
|
+
keys << action.to_s.humanize
|
238
|
+
|
239
|
+
I18n.translate(keys.take(1).first, default: keys.drop(1))
|
240
|
+
end
|
241
|
+
|
242
|
+
def cocooned_render_association(builder, render_options = {})
|
243
|
+
locals = (render_options.delete(:locals) || {}).symbolize_keys
|
244
|
+
partial = render_options.delete(:partial) || builder.singular_name + '_fields'
|
245
|
+
form_name = render_options.delete(:form_name)
|
246
|
+
form_options = (render_options.delete(:form_options) || {}).symbolize_keys
|
247
|
+
form_options.reverse_merge!(child_index: "new_#{builder.association}")
|
248
|
+
|
249
|
+
builder.form.send(cocooned_form_method(builder.form),
|
250
|
+
builder.association,
|
251
|
+
builder.build_object,
|
252
|
+
form_options) do |form_builder|
|
253
|
+
|
254
|
+
partial_options = { form_name.to_sym => form_builder, :dynamic => true }.merge(locals)
|
255
|
+
render(partial, partial_options)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def cocooned_form_method(form)
|
260
|
+
ancestors = form.class.ancestors.map(&:to_s)
|
261
|
+
if ancestors.include?('SimpleForm::FormBuilder')
|
262
|
+
:simple_fields_for
|
263
|
+
elsif ancestors.include?('Formtastic::FormBuilder')
|
264
|
+
:semantic_fields_for
|
265
|
+
else
|
266
|
+
:fields_for
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def cocooned_extract_builder_options!(html_options)
|
271
|
+
%i[wrap_object force_non_association_create].each_with_object({}) do |option_name, opts|
|
272
|
+
opts[option_name] = html_options.delete(option_name) if html_options.key?(option_name)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def cocooned_extract_render_options!(html_options)
|
277
|
+
render_options = { form_name: :f }
|
278
|
+
|
279
|
+
# TODO: Remove in 2.0
|
280
|
+
if html_options.key?(:render_options)
|
281
|
+
msg = Cocooned::Helpers::Deprecate.deprecate_release_message(':render_options', :none)
|
282
|
+
warn msg
|
283
|
+
|
284
|
+
options = html_options.delete(:render_options)
|
285
|
+
render_options[:locals] = options.delete(:locals) if options.key?(:locals)
|
286
|
+
render_options[:form_options] = options
|
287
|
+
end
|
288
|
+
|
289
|
+
%i[locals partial form_name form_options].each_with_object(render_options) do |option_name, opts|
|
290
|
+
opts[option_name] = html_options.delete(option_name) if html_options.key?(option_name)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def cocooned_extract_data!(html_options)
|
295
|
+
data = {
|
296
|
+
count: [
|
297
|
+
html_options.delete(:count).to_i,
|
298
|
+
(html_options.key?(:data) ? html_options[:data].delete(:count) : 0).to_i,
|
299
|
+
1
|
300
|
+
].compact.max,
|
301
|
+
association_insertion_node: cocooned_extract_single_data!(html_options, :insertion_node),
|
302
|
+
association_insertion_method: cocooned_extract_single_data!(html_options, :insertion_method),
|
303
|
+
association_insertion_traversal: cocooned_extract_single_data!(html_options, :insertion_traversal)
|
304
|
+
}
|
305
|
+
|
306
|
+
# Compatibility with the old way to pass data attributes to Rails view helpers
|
307
|
+
# Has we build a :data key, they will not be looked up.
|
308
|
+
html_options.keys.select { |k| k.to_s.match?(/data[_-]/) }.each_with_object(data) do |data_key, d|
|
309
|
+
key = data_key.to_s.gsub(/^data[_-]/, '')
|
310
|
+
d[key] = html_options.delete(data_key)
|
311
|
+
end
|
312
|
+
data.compact
|
313
|
+
end
|
314
|
+
|
315
|
+
def cocooned_extract_single_data!(h, key)
|
316
|
+
k = key.to_s
|
317
|
+
return h.delete(k) if h.key?(k)
|
318
|
+
|
319
|
+
# Compatibility alternatives
|
320
|
+
# TODO: Remove in 2.0
|
321
|
+
return h.delete("association_#{k}") if h.key?("association_#{k}")
|
322
|
+
return h.delete("data_association_#{k}") if h.key?("data_association_#{k}")
|
323
|
+
return h.delete("data-association-#{k.tr('_', '-')}") if h.key?("data-association-#{k.tr('_', '-')}")
|
324
|
+
return nil unless h.key?(:data)
|
325
|
+
d = h[:data].with_indifferent_access
|
326
|
+
return d.delete("association_#{k}") if d.key?("association_#{k}")
|
327
|
+
return d.delete("association-#{k.tr('_', '-')}") if d.key?("association-#{k.tr('_', '-')}")
|
328
|
+
nil
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
require 'cocooned/helpers'
|
5
|
+
|
6
|
+
module Cocooned
|
7
|
+
class Railtie < ::Rails::Engine
|
8
|
+
initializer 'cocooned.initialize' do |_app|
|
9
|
+
ActiveSupport.on_load :action_view do
|
10
|
+
ActionView::Base.send :include, Cocooned::Helpers
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/cocooned.rb
ADDED
data/package.json
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"name": "cocooned",
|
3
|
+
"version": "1.2.11",
|
4
|
+
"private": true,
|
5
|
+
"license": "APACHE-2.0",
|
6
|
+
"description": "Unobtrusive nested forms handling using jQuery.",
|
7
|
+
"homepage": "https://github.com/notus-sh/cocooned#readme",
|
8
|
+
"repository": "https://github.com/notus-sh/cocooned",
|
9
|
+
"author": "Gaël-Ian Havard <gael-ian@notus.sh>",
|
10
|
+
"main": "app/assets/javascripts/cocooned.js",
|
11
|
+
"files": ["app/assets/javascripts"],
|
12
|
+
"bugs": { "url": "https://github.com/notus-sh/cocooned/issues" },
|
13
|
+
"dependencies": {
|
14
|
+
"jquery": "^3.3.1"
|
15
|
+
},
|
16
|
+
"devDependencies": {
|
17
|
+
"eslint": "^4.19.1",
|
18
|
+
"eslint-config-standard": "^11.0.0",
|
19
|
+
"eslint-plugin-import": "^2.8.0",
|
20
|
+
"eslint-plugin-node": "^5.2.1",
|
21
|
+
"eslint-plugin-promise": "^3.6.0",
|
22
|
+
"eslint-plugin-standard": "^3.0.1"
|
23
|
+
}
|
24
|
+
}
|