cocooned 1.4.1 → 2.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.
- 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
|