cocooned 1.4.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +178 -0
- data/README.md +244 -172
- data/app/assets/javascripts/cocoon.js +1 -13
- data/app/assets/javascripts/cocooned.js +929 -399
- data/app/assets/stylesheets/cocooned.css +1 -9
- data/cocooned.gemspec +27 -18
- data/lib/cocooned/association/builder.rb +66 -0
- data/lib/cocooned/association/renderer.rb +53 -0
- data/lib/cocooned/association.rb +8 -0
- data/lib/cocooned/deprecation.rb +105 -0
- data/lib/cocooned/helpers/containers.rb +72 -0
- data/lib/cocooned/helpers/tags/add.rb +136 -0
- data/lib/cocooned/helpers/tags/down.rb +76 -0
- data/lib/cocooned/helpers/tags/remove.rb +78 -0
- data/lib/cocooned/helpers/tags/up.rb +76 -0
- data/lib/cocooned/helpers/tags.rb +60 -0
- data/lib/cocooned/helpers.rb +3 -329
- data/lib/cocooned/railtie.rb +7 -2
- data/lib/cocooned/tags/add.rb +61 -0
- data/lib/cocooned/tags/base.rb +61 -0
- data/lib/cocooned/tags/down.rb +19 -0
- data/lib/cocooned/tags/remove.rb +35 -0
- data/lib/cocooned/tags/up.rb +19 -0
- data/lib/cocooned/tags.rb +12 -0
- data/lib/cocooned/tags_helper.rb +83 -0
- data/lib/cocooned/version.rb +1 -1
- data/lib/cocooned.rb +6 -1
- metadata +51 -86
- data/History.md +0 -283
- data/Rakefile +0 -113
- data/lib/cocooned/association_builder.rb +0 -68
- data/lib/cocooned/helpers/cocoon_compatibility.rb +0 -27
- data/lib/cocooned/helpers/deprecate.rb +0 -47
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
module Helpers
|
5
|
+
module Tags
|
6
|
+
# Output an action link to move an item up.
|
7
|
+
#
|
8
|
+
# = Signatures
|
9
|
+
#
|
10
|
+
# cocooned_move_item_up_link(label, form, options = {})
|
11
|
+
# # Explicit name
|
12
|
+
#
|
13
|
+
# cocooned_move_item_up_link(form, options = {}) do
|
14
|
+
# # Name as a block
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# cocooned_move_item_up_link(form, options = {})
|
18
|
+
# # Use default name
|
19
|
+
#
|
20
|
+
# = Parameters
|
21
|
+
#
|
22
|
+
# `label` is the text to be used as the link label. See the main
|
23
|
+
# documentation for Cocooned::Helpers::Tags for more about labelling.
|
24
|
+
#
|
25
|
+
# `form` is your form builder. Can be a SimpleForm::Builder,
|
26
|
+
# Formtastic::Builder or a standard Rails FormBuilder.
|
27
|
+
module Up
|
28
|
+
# Output a link to move an item up.
|
29
|
+
#
|
30
|
+
# = Signatures
|
31
|
+
#
|
32
|
+
# cocooned_move_item_up_link(label, form, options = {})
|
33
|
+
# # Explicit name
|
34
|
+
#
|
35
|
+
# cocooned_move_item_up_link(form, options = {}) do
|
36
|
+
# # Name as a block
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# cocooned_move_item_up_link(form, options = {})
|
40
|
+
# # Use default name
|
41
|
+
#
|
42
|
+
# See Cocooned::Helpers::Tags::Up main documentation for a reference of
|
43
|
+
# supported parameters.
|
44
|
+
#
|
45
|
+
# See the documentation of +ActionView::Base#link_to+ for additional
|
46
|
+
# options.
|
47
|
+
def cocooned_move_item_up_link(*args, &block)
|
48
|
+
cocooned_link(Cocooned::Tags::Up, *args, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Output a button to move an item up.
|
52
|
+
#
|
53
|
+
# = Signatures
|
54
|
+
#
|
55
|
+
# cocooned_move_item_up_button(label, form, options = {})
|
56
|
+
# # Explicit name
|
57
|
+
#
|
58
|
+
# cocooned_move_item_up_button(form, options = {}) do
|
59
|
+
# # Name as a block
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# cocooned_move_item_up_button(form, options = {})
|
63
|
+
# # Use default name
|
64
|
+
#
|
65
|
+
# See Cocooned::Helpers::Tags::Up main documentation for a reference of
|
66
|
+
# supported parameters.
|
67
|
+
#
|
68
|
+
# See the documentation of +ActionView::Helpers::FormBuilder#button+ for
|
69
|
+
# valid options.
|
70
|
+
def cocooned_move_item_up_button(*args, &block)
|
71
|
+
cocooned_button(Cocooned::Tags::Up, *args, &block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
module Helpers
|
5
|
+
# Cocooned tags helpers output action link to interact with a dynamic nested form.
|
6
|
+
#
|
7
|
+
# Each action has its dedicated helpers:
|
8
|
+
#
|
9
|
+
# - `cocooned_add_item_link` / `cocooned_add_item_button` to add an item
|
10
|
+
# - `cocooned_remove_item_link` / `cocooned_remove_item_button` to remove
|
11
|
+
# an item
|
12
|
+
# - `cocooned_move_item_up_link` / `cocooned_move_item_up_button` to move
|
13
|
+
# an item up (in a reorderable form)
|
14
|
+
# - `cocooned_move_item_down_link` / `cocooned_move_item_down_link` to move
|
15
|
+
# an item down (in a reorderable form)
|
16
|
+
#
|
17
|
+
# Labelling action links
|
18
|
+
#
|
19
|
+
# The label of any action links can be given explicitly as helper's first
|
20
|
+
# argument or as a block, just as you can do on ActionView's `link_to` or
|
21
|
+
# similar helpers.
|
22
|
+
#
|
23
|
+
# Additionally, Cocooned helpers will lookup I18n translations for a default
|
24
|
+
# label based on the action name (`add`, `remove`, `up`, `down`) and the
|
25
|
+
# association name.
|
26
|
+
#
|
27
|
+
# For `add` action links, the association name used is the same as passed as
|
28
|
+
# argument. On other action triggers, it is extracted from nested form
|
29
|
+
# #object_name.
|
30
|
+
#
|
31
|
+
# You can declare default labels in your translation files with following
|
32
|
+
# keys:
|
33
|
+
#
|
34
|
+
# - `cocooned.{association}.{action}` (Ex: `cocooned.items.add`)
|
35
|
+
# - `cocooned.defaults.{action}`
|
36
|
+
#
|
37
|
+
# If no translation is found, the default label will be the humanized action
|
38
|
+
# name.
|
39
|
+
module Tags
|
40
|
+
autoload :Add, 'cocooned/helpers/tags/add'
|
41
|
+
autoload :Down, 'cocooned/helpers/tags/down'
|
42
|
+
autoload :Remove, 'cocooned/helpers/tags/remove'
|
43
|
+
autoload :Up, 'cocooned/helpers/tags/up'
|
44
|
+
|
45
|
+
include Cocooned::Deprecated::Helpers::Tags
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def cocooned_link(klass, *args, &block)
|
50
|
+
options = args.extract_options!
|
51
|
+
klass.create(self, *args, **options, &block).render(as: :link)
|
52
|
+
end
|
53
|
+
|
54
|
+
def cocooned_button(klass, *args, &block)
|
55
|
+
options = args.extract_options!
|
56
|
+
klass.create(self, *args, **options, &block).render(as: :button)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/cocooned/helpers.rb
CHANGED
@@ -1,334 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'cocooned/helpers/deprecate'
|
4
|
-
require 'cocooned/helpers/cocoon_compatibility'
|
5
|
-
require 'cocooned/association_builder'
|
6
|
-
|
7
3
|
module Cocooned
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
remove: %w[cocooned-remove remove_fields],
|
12
|
-
up: %w[cocooned-move-up],
|
13
|
-
down: %w[cocooned-move-down]
|
14
|
-
}.freeze
|
15
|
-
|
16
|
-
module Helpers # rubocop:disable Metrics/ModuleLength
|
17
|
-
# Create aliases to old Cocoon method name
|
18
|
-
# TODO: Remove in 3.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.dup.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
|
-
link_options = html_options.dup
|
171
|
-
link_options[:class] = [html_options[:class], Cocooned::HELPER_CLASSES[:remove]].flatten.compact
|
172
|
-
link_options[:class] << (form.object.new_record? ? 'dynamic' : 'existing')
|
173
|
-
link_options[:class] << 'destroyed' if form.object.marked_for_destruction?
|
174
|
-
|
175
|
-
form.hidden_field(:_destroy, value: form.object._destroy) << link_to(name, '#', link_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
|
-
link_options = html_options.dup
|
224
|
-
link_options[:class] = [html_options[:class], Cocooned::HELPER_CLASSES[direction]].flatten.compact.join(' ')
|
225
|
-
link_to name, '#', link_options
|
226
|
-
end
|
227
|
-
|
228
|
-
def cocooned_default_label(action, association = nil)
|
229
|
-
# TODO: Remove in 3.0
|
230
|
-
if I18n.exists?(:cocoon)
|
231
|
-
msg = Cocooned::Helpers::Deprecate.deprecate_release_message('the :cocoon i18n scope', ':cocooned')
|
232
|
-
warn msg
|
233
|
-
end
|
234
|
-
|
235
|
-
keys = ["cocooned.defaults.#{action}", "cocoon.defaults.#{action}"]
|
236
|
-
keys.unshift("cocooned.#{association}.#{action}", "cocoon.#{association}.#{action}") unless association.nil?
|
237
|
-
keys.collect!(&:to_sym)
|
238
|
-
keys << action.to_s.humanize
|
239
|
-
|
240
|
-
I18n.translate(keys.take(1).first, default: keys.drop(1))
|
241
|
-
end
|
242
|
-
|
243
|
-
def cocooned_render_association(builder, options = {})
|
244
|
-
render_options = options.dup
|
245
|
-
locals = (render_options.delete(:locals) || {}).symbolize_keys
|
246
|
-
partial = render_options.delete(:partial) || "#{builder.singular_name}_fields"
|
247
|
-
form_name = render_options.delete(:form_name)
|
248
|
-
form_options = (render_options.delete(:form_options) || {}).symbolize_keys
|
249
|
-
form_options.reverse_merge!(child_index: "new_#{builder.association}")
|
250
|
-
|
251
|
-
builder.form.send(cocooned_form_method(builder.form),
|
252
|
-
builder.association,
|
253
|
-
builder.build_object,
|
254
|
-
form_options) do |form_builder|
|
255
|
-
partial_options = { form_name.to_sym => form_builder, :dynamic => true }.merge(locals)
|
256
|
-
render(partial, partial_options)
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
def cocooned_form_method(form)
|
261
|
-
ancestors = form.class.ancestors.map(&:to_s)
|
262
|
-
if ancestors.include?('SimpleForm::FormBuilder')
|
263
|
-
:simple_fields_for
|
264
|
-
elsif ancestors.include?('Formtastic::FormBuilder')
|
265
|
-
:semantic_fields_for
|
266
|
-
else
|
267
|
-
:fields_for
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
def cocooned_extract_builder_options!(html_options)
|
272
|
-
%i[wrap_object force_non_association_create].each_with_object({}) do |option_name, opts|
|
273
|
-
opts[option_name] = html_options.delete(option_name) if html_options.key?(option_name)
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def cocooned_extract_render_options!(html_options)
|
278
|
-
render_options = { form_name: :f }
|
279
|
-
|
280
|
-
# TODO: Remove in 2.0
|
281
|
-
if html_options.key?(:render_options)
|
282
|
-
msg = Cocooned::Helpers::Deprecate.deprecate_release_message(':render_options', :none)
|
283
|
-
warn msg
|
284
|
-
|
285
|
-
options = html_options.delete(:render_options)
|
286
|
-
render_options[:locals] = options.delete(:locals) if options.key?(:locals)
|
287
|
-
render_options[:form_options] = options
|
288
|
-
end
|
289
|
-
|
290
|
-
%i[locals partial form_name form_options].each_with_object(render_options) do |option_name, opts|
|
291
|
-
opts[option_name] = html_options.delete(option_name) if html_options.key?(option_name)
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
def cocooned_extract_data!(html_options)
|
296
|
-
data = {
|
297
|
-
count: [
|
298
|
-
html_options.delete(:count).to_i,
|
299
|
-
(html_options.key?(:data) ? html_options[:data].delete(:count) : 0).to_i,
|
300
|
-
1
|
301
|
-
].compact.max,
|
302
|
-
association_insertion_node: cocooned_extract_single_data!(html_options, :insertion_node),
|
303
|
-
association_insertion_method: cocooned_extract_single_data!(html_options, :insertion_method),
|
304
|
-
association_insertion_traversal: cocooned_extract_single_data!(html_options, :insertion_traversal)
|
305
|
-
}
|
306
|
-
|
307
|
-
# Compatibility with the old way to pass data attributes to Rails view helpers
|
308
|
-
# Has we build a :data key, they will not be looked up.
|
309
|
-
html_options.keys.select { |k| k.to_s.match?(/data[_-]/) }.each_with_object(data) do |data_key, d|
|
310
|
-
key = data_key.to_s.gsub(/^data[_-]/, '')
|
311
|
-
d[key] = html_options.delete(data_key)
|
312
|
-
end
|
313
|
-
data.compact
|
314
|
-
end
|
315
|
-
|
316
|
-
def cocooned_extract_single_data!(hash, key)
|
317
|
-
k = key.to_s
|
318
|
-
return hash.delete(k) if hash.key?(k)
|
319
|
-
|
320
|
-
# Compatibility alternatives
|
321
|
-
# TODO: Remove in 2.0
|
322
|
-
return hash.delete("association_#{k}") if hash.key?("association_#{k}")
|
323
|
-
return hash.delete("data_association_#{k}") if hash.key?("data_association_#{k}")
|
324
|
-
return hash.delete("data-association-#{k.tr('_', '-')}") if hash.key?("data-association-#{k.tr('_', '-')}")
|
325
|
-
return nil unless hash.key?(:data)
|
326
|
-
|
327
|
-
d = hash[:data].with_indifferent_access
|
328
|
-
return d.delete("association_#{k}") if d.key?("association_#{k}")
|
329
|
-
return d.delete("association-#{k.tr('_', '-')}") if d.key?("association-#{k.tr('_', '-')}")
|
330
|
-
|
331
|
-
nil
|
332
|
-
end
|
4
|
+
module Helpers # :nodoc:
|
5
|
+
autoload :Containers, 'cocooned/helpers/containers'
|
6
|
+
autoload :Tags, 'cocooned/helpers/tags'
|
333
7
|
end
|
334
8
|
end
|
data/lib/cocooned/railtie.rb
CHANGED
@@ -4,10 +4,15 @@ require 'rails'
|
|
4
4
|
require 'cocooned/helpers'
|
5
5
|
|
6
6
|
module Cocooned
|
7
|
-
class Railtie < ::Rails::Engine
|
7
|
+
class Railtie < ::Rails::Engine # :nodoc:
|
8
8
|
initializer 'cocooned.initialize' do |_app|
|
9
9
|
ActiveSupport.on_load :action_view do
|
10
|
-
|
10
|
+
include Cocooned::Helpers::Tags,
|
11
|
+
Cocooned::Helpers::Tags::Add,
|
12
|
+
Cocooned::Helpers::Tags::Down,
|
13
|
+
Cocooned::Helpers::Tags::Remove,
|
14
|
+
Cocooned::Helpers::Tags::Up,
|
15
|
+
Cocooned::Helpers::Containers
|
11
16
|
end
|
12
17
|
end
|
13
18
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
module Tags
|
5
|
+
class Add < Base # :nodoc:
|
6
|
+
module AssociationOptions # :nodoc:
|
7
|
+
protected
|
8
|
+
|
9
|
+
def association_options
|
10
|
+
# TODO: Replace with compact_blank when dropping support for Rails 6.0
|
11
|
+
{
|
12
|
+
association: association,
|
13
|
+
template: html_template_name,
|
14
|
+
association_insertion_count: [options.delete(:count).to_i, 1].compact.max,
|
15
|
+
association_insertion_node: options.delete(:insertion_node),
|
16
|
+
association_insertion_method: options.delete(:insertion_method),
|
17
|
+
association_insertion_traversal: options.delete(:insertion_traversal)
|
18
|
+
}.reject { |_, value| value.blank? }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
include Cocooned::TagsHelper::AssociationLabel
|
23
|
+
|
24
|
+
include Cocooned::TagsHelper::Renderer
|
25
|
+
include Cocooned::Deprecated::TagsHelper::Renderer
|
26
|
+
include AssociationOptions
|
27
|
+
include Cocooned::Deprecated::TagsHelper::AssociationOptions
|
28
|
+
|
29
|
+
def initialize(template, form, association, **options, &block)
|
30
|
+
@association = association
|
31
|
+
super(template, form, **options, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def render(as: :link)
|
35
|
+
template.safe_join([super(as: as), html_template])
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
attr_reader :association
|
41
|
+
|
42
|
+
def html_template
|
43
|
+
template.content_tag(:template, data: { name: html_template_name }) do
|
44
|
+
renderer.render.html_safe # rubocop:disable Rails/OutputSafety
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def html_template_name
|
49
|
+
@html_template_name ||= SecureRandom.uuid
|
50
|
+
end
|
51
|
+
|
52
|
+
def html_classes
|
53
|
+
super + %w[cocooned-add add_fields]
|
54
|
+
end
|
55
|
+
|
56
|
+
def html_data
|
57
|
+
super.merge(association_options, cocooned_trigger: :add)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
module Tags
|
5
|
+
class Base # :nodoc:
|
6
|
+
include Cocooned::TagsHelper::DefaultLabel
|
7
|
+
include Cocooned::Deprecated::TagsHelper::DefaultLabel
|
8
|
+
|
9
|
+
include Cocooned::TagsHelper::DataAttributes
|
10
|
+
include Cocooned::Deprecated::TagsHelper::DataAttributes
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def create(template, *args, **kwargs, &block)
|
14
|
+
return new(template, *args, **kwargs, &block) if block
|
15
|
+
return new(template, *args, **kwargs) unless args.first.is_a?(String)
|
16
|
+
|
17
|
+
label = args.shift
|
18
|
+
new(template, *args, **kwargs) { label }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(template, form, **options, &block)
|
23
|
+
@template = template
|
24
|
+
@form = form
|
25
|
+
@options = options.dup.symbolize_keys
|
26
|
+
@label_block = block if block
|
27
|
+
end
|
28
|
+
|
29
|
+
def render(as: :link)
|
30
|
+
return template.link_to('#', html_options) { label } if as == :link
|
31
|
+
return template.button_tag(type: :button, **html_options) { label } if as == :button
|
32
|
+
|
33
|
+
raise ArgumentError, "Unsupported value for :as. Expected :link or :button, got #{as}."
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
attr_reader :template, :form, :options, :label_block
|
39
|
+
|
40
|
+
def label
|
41
|
+
return default_label if label_block.blank?
|
42
|
+
|
43
|
+
label_block.call
|
44
|
+
end
|
45
|
+
|
46
|
+
def html_options
|
47
|
+
@html_options ||= begin
|
48
|
+
options[:class] = html_classes
|
49
|
+
options[:data] = html_data
|
50
|
+
# TODO: Replace with compact_blank when dropping support for Rails 6.0
|
51
|
+
options.reject { |_, value| value.blank? }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def html_classes
|
56
|
+
# TODO: Replace with compact_blank when dropping support for Rails 6.0
|
57
|
+
Array.wrap(options.delete(:class)).flat_map { |k| k.to_s.split }.reject(&:blank?)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
module Tags
|
5
|
+
class Down < Base # :nodoc:
|
6
|
+
include Cocooned::TagsHelper::AssociationLabel
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def html_classes
|
11
|
+
super + %w[cocooned-move-down]
|
12
|
+
end
|
13
|
+
|
14
|
+
def html_data
|
15
|
+
super.merge(cocooned_trigger: :down)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
module Tags
|
5
|
+
class Remove < Base # :nodoc:
|
6
|
+
include Cocooned::TagsHelper::AssociationLabel
|
7
|
+
|
8
|
+
def render(as: :link)
|
9
|
+
template.safe_join([hidden_field, super(as: as)])
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def hidden_field
|
15
|
+
form.hidden_field(:_destroy, value: marked_for_destruction?)
|
16
|
+
end
|
17
|
+
|
18
|
+
def html_classes
|
19
|
+
super + %w[cocooned-remove remove_fields]
|
20
|
+
end
|
21
|
+
|
22
|
+
def html_data
|
23
|
+
super.merge(cocooned_trigger: :remove, cocooned_persisted: persisted?)
|
24
|
+
end
|
25
|
+
|
26
|
+
def persisted?
|
27
|
+
!!form.object.try(:persisted?)
|
28
|
+
end
|
29
|
+
|
30
|
+
def marked_for_destruction?
|
31
|
+
!!form.object.try(:marked_for_destruction?)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
module Tags
|
5
|
+
class Up < Base # :nodoc:
|
6
|
+
include Cocooned::TagsHelper::AssociationLabel
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def html_classes
|
11
|
+
super + %w[cocooned-move-up]
|
12
|
+
end
|
13
|
+
|
14
|
+
def html_data
|
15
|
+
super.merge(cocooned_trigger: :up)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|