cocooned 1.4.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,77 +1,105 @@
1
1
  # Cocooned
2
2
 
3
- [![Build Status](https://travis-ci.org/notus-sh/cocooned.png?branch=master)](https://travis-ci.org/notus-sh/cocooned)
3
+ [![Unit tests](https://github.com/notus-sh/cocooned/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/notus-sh/cocooned/actions/workflows/unit-tests.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/composite_content.svg)](https://badge.fury.io/rb/cocooned)
4
5
 
5
- Cocooned makes it easier to handle nested forms in a Rails project.
6
+ Cocooned makes it easier to handle nested forms in Rails.
6
7
 
7
- Cocooned is form builder-agnostic: it works with standard Rails (>= 5.0, < 7.0) form helpers, [Formtastic](https://github.com/justinfrench/formtastic) or [SimpleForm](https://github.com/plataformatec/simple_form).
8
+ Cocooned is form builder-agnostic: it works with standard Rails (>= 6.0, < 7.1) form helpers, [Formtastic](https://github.com/justinfrench/formtastic) or [SimpleForm](https://github.com/plataformatec/simple_form).
9
+
10
+ 1. [Background](#some-background)
11
+ 2. [Installation](#installation)
12
+ 3. [Getting started](#getting-started)
13
+ 4. [Going further with plugins](#plugins)
14
+ 5. [Links or buttons ?](#links-or-buttons)
15
+ 5. [I18n integration](#internationalisation)
16
+ 6. [JavaScript](#javascript)
17
+ 7. [Styling](#styling-forms)
18
+ 8. [Migration from a previous version](#migration-from-a-previous-version) or from Cocoon
8
19
 
9
20
  ## Some Background
10
21
 
11
22
  Cocooned is a fork of [Cocoon](https://github.com/nathanvda/cocoon) by [Nathan Van der Auwera](https://github.com/nathanvda). He and all Cocoon contributors did a great job to maintain it for years. Many thanks to them!
12
23
 
13
- However, the project seems to have only received minimal fixes since 2018 and many pull requests, even simple ones, have been on hold for a long time. In 2019, as I needed a more than what Cocoon provided at this time, I had the choice to either maintain an extension or to fork it and integrate everything that was waiting and more.
14
-
15
- Cocooned is almost a complete rewrite of Cocoon, with more functionnalities, a more fluent API (I hope) and integration with modern toolchains (including webpacker).
24
+ However, the project seems to have only received minimal fixes since 2018 and many pull requests, even simple ones, have been on hold for a long time. In 2019, as I needed more than what Cocoon provided at this time, I had the choice to either maintain an extension or to fork it and integrate everything that was waiting and more.
16
25
 
17
- **For now, Cocooned is completely compatible with Cocoon and can be used as a drop-in replacement** as long as we talk about Ruby code. Just change the name of the gem in your Gemfile and you're done. It will work the same (but will add a bunch of deprecation warning to your logs).
26
+ Over the time, Cocooned turned into an almost complete rewrite of Cocoon with more functionnalities, a more fluent API (I hope) and integration with modern toolchains. Still, **Cocooned is completely compatible with Cocoon and can be used as a drop-in replacement** as long as we talk about Ruby code. Change the name of the gem in your Gemfile and you're done. **This compatibility layer with the original Cocoon API will be dropped in the next major release.**
18
27
 
19
- **This compatibility layer with the original Cocoon API will be dropped in Cocooned 3.0.**
20
-
21
- On the JavaScript side, Cocoon 1.2.13 introduced the original browser event as a third parameter to all event handlers. Meanwhile, Cocooned already started to use this positional parameter to pass the Cocooned object instance (since 1.3.0). To get access to the original event, [you'll have to change your handlers and use `event.originalEvent`](#javascript-callbacks).
28
+ On the JavaScript side, Cocooned 2.0 removed the dependency to jQuery (Yeah! :tada:). See [JavaScript](#javascript) for details.
22
29
 
23
30
  ## Installation
24
31
 
25
- Inside your `Gemfile` add the following:
32
+ Add `cocooned` to your `Gemfile`:
26
33
 
27
34
  ```ruby
28
- gem "cocooned"
35
+ gem 'cocooned'
36
+ ```
37
+
38
+ ### Load Cocooned JavaScript
39
+
40
+ Cocooned comes with an NPM companion package: [`@notus.sh/cocooned`](https://www.npmjs.com/package/@notus.sh/cocooned).
41
+ It bundles JavaScript files to handles in-browser interactions with your nested forms.
42
+
43
+ If you use import maps (Rails 7.0+ default), add it with:
44
+
45
+ ```shell
46
+ $ bin/importmap pin @notus.sh/cocooned
47
+ ```
48
+
49
+ If you use Yarn and Webpack (Rails 5.1+ default), add it with:
50
+
51
+ ```shell
52
+ $ yarn add @notus.sh/cocooned
53
+ ```
54
+
55
+ **Note:** To ensure you will always get the version of the companion package that match with the gem version, you should specify the same version constraint you used for the `cocooned` gem in your `Gemfile`.
56
+
57
+ Once installed, load it into your application with:
58
+
59
+ ```javascript
60
+ import Cocooned from '@notus.sh/cocooned'
61
+ Cocooned.start()
29
62
  ```
30
63
 
31
- ### Load Cocooned styles and scripts
64
+ If you still use Sprockets to bundle your javascripts (Rails 3.1+ default), you can either install the companion package from npmjs.org with the package manager of your choice, configure Sprockets to look for files in your application's `/node_modules` directory and load it as above (recommended) or require `cocooned` in your `application.js` with:
32
65
 
33
- If you use Sprockets, you have to require `cocooned` in your `application.js` and `application.css`, so it compiles with the asset pipeline.
66
+ ```javascript
67
+ //= require 'cocooned'
68
+ ```
34
69
 
35
- If you use Yarn to manage your non-Ruby dependencies and/or Webpack to build your assets, you can install the [`@notus.sh/cocooned` companion package](https://www.npmjs.com/package/@notus.sh/cocooned).
70
+ **This compatibility with aging Rails assets pipelines will be removed in the next major release.**
36
71
 
37
- ## Usage
72
+ ## Getting started
38
73
 
39
74
  For all the following examples, we will consider modelisation of an administrable list with items.
40
75
  Here are the two ActiveRecord models : `List` and `Item`:
41
76
 
42
77
  ```ruby
43
- class Item < ApplicationRecord
78
+ # == Schema Info
79
+ #
80
+ # Table name: lists
81
+ #
82
+ # id :integer(11) not null, primary key
83
+ # name :string
84
+ class List < ApplicationRecord
44
85
  has_many :items, inverse_of: :list
45
86
  accepts_nested_attributes_for :items, reject_if: :all_blank, allow_destroy: true
46
87
  end
47
88
 
89
+ # == Schema Info
90
+ #
91
+ # Table name: items
92
+ #
93
+ # id :integer(11) not null, primary key
94
+ # list_id :integer(11) not null
95
+ # description :text
96
+ # done :bool not null, default(false)
48
97
  class Item < ApplicationRecord
49
98
  belongs_to :list
50
99
  end
51
100
  ```
52
101
 
53
- We will build a form where we can dynamically add and remove items to a list.
54
-
55
- ### Strong Parameters Gotcha
56
-
57
- To destroy nested models, Rails uses a virtual attribute called `_destroy`.
58
- When `_destroy` is set, the nested model will be deleted. If the record has previously been persisted, Rails generate and use an automatic `id` field to fetch the wannabe destroyed record.
59
-
60
- When using Rails > 4.0 (or strong parameters), you need to explicitly add both `:id` and `:_destroy` to the list of permitted parameters.
61
-
62
- E.g. in your `ListsController`:
63
-
64
- ```ruby
65
- def list_params
66
- params.require(:list).permit(:name, tasks_attributes: [:id, :description, :done, :_destroy])
67
- end
68
- ```
69
-
70
- ### Has One Gotcha
71
-
72
- If you have a `has_one` association, then you (probably) need to set `force_non_association_create: true` on `link_to_add_association` or the associated object will be destroyed every time the edit form is rendered (which is probably not what you expect).
73
-
74
- See the [original merge request](https://github.com/nathanvda/cocoon/pull/247) for more details.
102
+ We will build a form where we can dynamically add items to a list, remove or reorder them.
75
103
 
76
104
  ### Basic form
77
105
 
@@ -79,48 +107,54 @@ See the [original merge request](https://github.com/nathanvda/cocoon/pull/247) f
79
107
 
80
108
  ```erb
81
109
  <% # `app/views/lists/_form.html.erb` %>
82
- <%= form_for @list do |f| %>
83
- <%= f.input :name %>
110
+ <%= form_for @list do |form| %>
111
+ <%= form.text_field :name %>
84
112
 
85
113
  <h3>Items</h3>
86
- <%= f.fields_for :tasks do |item_form| %>
87
- <% # This block is repeated for every task in @list.items %>
114
+ <%= form.fields_for :items do |item_form| %>
88
115
  <%= item_form.label :description %>
89
116
  <%= item_form.text_field :description %>
90
117
  <%= item_form.check_box :done %>
91
118
  <% end %>
92
119
 
93
- <%= f.submit "Save" %>
120
+ <%= form.submit "Save" %>
94
121
  <% end %>
95
122
  ```
96
123
 
97
124
  To enable Cocooned on this form, we need to:
98
125
 
99
126
  1. Move the nested form to a partial
100
- 2. Add a way to add a new item to the collection
101
- 3. Add a way to remove an item from the collection
102
- 4. Initialize Cocooned to handle this form
127
+ 2. Signal to Cocooned it should handle your form
128
+ 3. Add a way to add a new item to the list
129
+ 4. Add a way to remove an item from the collection
103
130
 
104
131
  Let's do it.
105
132
 
106
- #### 1. Move the nested form to a partial
133
+ **Note:** In this example, we will use Cocooned helpers named with a `_link` suffix. If you want to use buttons in your forms instead, the same helpers exist with a `_button` suffix.
107
134
 
108
- We now have two files:
135
+ ### 1. Move the nested form to a partial
109
136
 
110
- ```erb
137
+ Change your main form as follow:
138
+
139
+ ```diff
111
140
  <% # `app/views/lists/_form.html.erb` %>
112
141
  <%= form_for @list do |form| %>
113
- <%= form.input :name %>
142
+ <%= form.text_field :name %>
114
143
 
115
144
  <h3>Items</h3>
116
145
  <%= form.fields_for :items do |item_form|
117
- <%= render 'item_fields', f: item_form %>
146
+ - <%= item_form.label :description %>
147
+ - <%= item_form.text_field :description %>
148
+ - <%= item_form.check_box :done %>
149
+ + <%= render 'item_fields', f: item_form %>
118
150
  <% end %>
119
151
 
120
152
  <%= form.submit "Save" %>
121
153
  <% end %>
122
154
  ```
123
155
 
156
+ And create a new file where items fields are defined:
157
+
124
158
  ```erb
125
159
  <% # `app/views/lists/_item_fields.html.erb` %>
126
160
  <%= f.label :description %>
@@ -128,196 +162,234 @@ We now have two files:
128
162
  <%= f.check_box :done %>
129
163
  ```
130
164
 
131
- #### 2. Add a way to add a new item to the collection
165
+ ### 2. Signal to Cocooned it should handle your form
132
166
 
133
- ```erb
167
+ Change your main form as follow:
168
+
169
+ ```diff
134
170
  <% # `app/views/lists/_form.html.erb` %>
135
171
  <%= form_for @list do |form| %>
136
172
  <%= form.input :name %>
137
173
 
138
174
  <h3>Items</h3>
139
- <div id="items">
140
- <%= form.fields_for :tasks do |item_form| %>
175
+ + <%= cocooned_container do %>
176
+ <%= form.fields_for :items do |item_form| %>
141
177
  <%= render 'item_fields', f: item_form %>
142
178
  <% end %>
143
-
144
- <div class="links">
145
- <%= cocooned_add_item_link 'Add an item', form, :items %>
146
- </div>
147
- </div>
179
+ + <% end %>
148
180
 
149
181
  <%= form.submit "Save" %>
150
182
  <% end %>
151
183
  ```
152
184
 
153
- By default, a new item will be inserted just before the immediate parent of the 'Add an item' link. You can have a look at the documentation of `cocooned_add_item_link` for more information about how to change that but we'll keep it simple for now.
185
+ And your sub form as follow:
154
186
 
155
- #### 3. Add a way to remove an item from the collection
156
-
157
- ```erb
187
+ ```diff
158
188
  <% # `app/views/lists/_item_fields.html.erb` %>
159
- <div class="cocooned-item">
160
- <%= f.label :description %>
161
- <%= f.text_field :description %>
162
- <%= f.check_box :done %>
163
- <%= cocooned_remove_item_link 'Remove', f %>
164
- </div>
189
+ + <%= cocooned_item do %>
190
+ <%= f.label :description %>
191
+ <%= f.text_field :description %>
192
+ <%= f.check_box :done %>
193
+ + <% end %>
165
194
  ```
166
195
 
167
- The `cocooned-item` class is required for the `cocooned_remove_item_link` to work correctly.
196
+ The `cocooned_container` and `cocooned_item` helpers will set for you the HTML attributes the JavaScript part of Cocooned expect to find to hook on. They will forward any option supported by ActionView's `content_tag`.
168
197
 
169
- #### 4. Initialize Cocooned to handle this form
198
+ ### 3. Add a way to add a new item to the list
170
199
 
171
- Cocooned will detect on page load forms it should handle and initialize itself.
172
- This detection is based on the presence of a `data-cocooned-options` attribute on the nested forms container.
200
+ Change your main form as follow:
173
201
 
174
- ```erb
202
+ ```diff
175
203
  <% # `app/views/lists/_form.html.erb` %>
176
204
  <%= form_for @list do |form| %>
177
205
  <%= form.input :name %>
178
206
 
179
207
  <h3>Items</h3>
180
- <div id="items" data-cocooned-options="<%= {}.to_json %>">
181
- <%= form.fields_for :tasks do |item_form| %>
208
+ <%= cocooned_container do %>
209
+ <%= form.fields_for :items do |item_form| %>
182
210
  <%= render 'item_fields', f: item_form %>
183
211
  <% end %>
184
-
185
- <div class="links">
186
- <%= cocooned_add_item_link 'Add an item', form, :items %>
187
- </div>
188
- </div>
212
+ +
213
+ + <p><%= cocooned_add_item_link 'Add an item', form, :items %></p>
214
+ <% end %>
189
215
 
190
216
  <%= form.submit "Save" %>
191
- </div>
192
- ```
217
+ <% end %>
218
+ ```
193
219
 
194
- You're done!
220
+ By default, new items will be inserted just before the immediate parent of the 'Add an item' link. You can have a look at the documentation of `cocooned_add_item_link` for more information about how to change that but we'll keep it simple for now.
195
221
 
196
- ### Wait, what's the point of `data-cocooned-options` if it's to be empty?
222
+ ### 4. Add a way to remove an item from the collection
197
223
 
198
- For simple use cases as the one we just demonstrated, the `data-cocooned-options` attributes only triggers the Cocooned initialization on page load. But you can use it to pass additional options to the Cocooned javascript and enable plugins.
224
+ Change your sub form as follow:
199
225
 
200
- For now, Cocooned supports two plugins:
226
+ ```diff
227
+ <% # `app/views/lists/_item_fields.html.erb` %>
228
+ <%= cocooned_item do %>
229
+ <%= f.label :description %>
230
+ <%= f.text_field :description %>
231
+ <%= f.check_box :done %>
232
+ + <%= cocooned_remove_item_link 'Remove', f %>
233
+ <% end %>
234
+ ```
201
235
 
202
- * **Limit**, to set a maximum limit of items that can be added to the association
203
- * **Reorderable**, that will automatically update `position` fields when you add or remove an item or when you reorder associated items.
236
+ You're done!
204
237
 
205
- #### The limit plugin
238
+ ### Gotchas
206
239
 
207
- The limit plugin is autoloaded when needed and does not require anything more than you specifiying the maximum number of items allowed in the association.
240
+ #### Strong Parameters Gotcha
208
241
 
209
- ```erb
210
- <% # `app/views/lists/_form.html.erb` %>
211
- <%= form_for @list do |form| %>
212
- <%= form.input :name %>
213
-
214
- <h3>Items</h3>
215
- <div id="items" data-cocooned-options="<%= { limit: 12 }.to_json %>">
216
- <%= form.fields_for :tasks do |item_form| %>
217
- <%= render 'item_fields', f: item_form %>
218
- <% end %>
219
-
220
- <div class="links">
221
- <%= cocooned_add_item_link 'Add an item', form, :items %>
222
- </div>
223
- </div>
224
-
225
- <%= form.submit "Save" %>
226
- <% end %>
242
+ To destroy nested models, Rails uses a virtual attribute called `_destroy`. When `_destroy` is set, the nested model will be deleted. If a record has previously been persisted, Rails generates and uses an additional `id` field.
243
+
244
+ When using Rails > 4.0 (or strong parameters), you need to explicitly add both `:id` and `:_destroy` to the list of permitted parameters in your controller.
245
+
246
+ In our example:
247
+
248
+ ```ruby
249
+ def list_params
250
+ params.require(:list).permit(:name, tasks_attributes: [:id, :description, :done, :_destroy])
251
+ end
227
252
  ```
228
253
 
229
- #### The reorderable plugin
254
+ #### Has One Gotcha
255
+
256
+ If you have a `has_one` association, then you (probably) need to set `force_non_association_create: true` on `cocooned_add_item_link` or the associated object will be destroyed every time the edit form is rendered (which is probably not what you expect).
257
+
258
+ See the [original merge request](https://github.com/nathanvda/cocoon/pull/247) for more details.
230
259
 
231
- The reorderable plugin is autoloaded when activated and does not support any particular options.
260
+ ## Plugins
261
+
262
+ Cocooned comes with two built-in plugins:
263
+
264
+ * **Limit**, to set a maximum limit of items the association can contain
265
+ * **Reorderable**, that will automatically update a `position` field in each of your sub forms when you add, remove or move an item.
266
+
267
+ ### The limit plugin
268
+
269
+ The limit plugin requires you specify the maximum number of items allowed in the association. To do so, pass a `:limit` option to the `cocooned_container` helper:
232
270
 
233
271
  ```erb
234
- <% # `app/views/lists/_form.html.haml` %>
235
- <%= form_for @list do |form| %>
236
- <%= form.input :name %>
237
-
238
- <h3>Items</h3>
239
- <div id="items" data-cocooned-options="<%= { reorderable: true }.to_json %>">
240
- <%= form.fields_for :tasks do |item_form| %>
241
- <%= render 'item_fields', f: item_form %>
242
- <% end %>
243
-
244
- <div class="links">
245
- <%= cocooned_add_item_link 'Add an item', form, :items %>
246
- </div>
247
- </div>
248
-
249
- <%= form.submit "Save" %>
272
+ <%= cocooned_container limit: 12 do %>
273
+ <% # […] %>
250
274
  <% end %>
251
275
  ```
252
276
 
253
- However, you need to edit your nested partial to add the links that allow your users to move an item up or down in the collection and to add a `position` field.
277
+ ### The reorderable plugin
254
278
 
255
- ```erb
279
+ **Important:** To use the reorderable plugin, your model must have a `position` numeric attribute you use to order collections (as in [acts_as_list](https://rubygems.org/gems/acts_as_list)).
280
+
281
+ The reorderable plugin can be activated in two ways through the `cocooned_container` helper:
282
+
283
+ - With a boolean: `cocooned_container reorderable: true`
284
+ Will use plugin's defaults (and start counting positions at 1)
285
+ - With a configuration hash: `cocooned_container reorderable: { startAt: 0 }`
286
+ Will use given `:startAt` as base position
287
+
288
+ To be able to move items up and down in your form and for positions to be saved, you need to change your sub form as follow:
289
+
290
+ ```diff
256
291
  <% # `app/views/lists/_item_fields.html.erb` %>
257
- <div class="cocooned-item">
292
+ <%= cocooned_item do %>
258
293
  <%= f.label :description %>
259
294
  <%= f.text_field :description %>
260
295
  <%= f.check_box :done %>
261
- <%= f.hidden_field :position %>
262
- <%= cocooned_move_item_up_link 'Up', f %>
263
- <%= cocooned_move_item_down_link 'Down', f %>
296
+ + <%= f.hidden_field :position %>
297
+ + <%= cocooned_move_item_up_link 'Up', f %>
298
+ + <%= cocooned_move_item_down_link 'Down', f %>
264
299
  <%= cocooned_remove_item_link 'Remove', f %>
265
- </div>
300
+ <% end %>
266
301
  ```
267
302
 
268
- Also, remember the strong parameters gotcha we mentioned earlier.
303
+ Remember to add `:position` as a permitted parameter in your controller.
269
304
 
270
- Of course, it means your model must have a `position` attribute you will use to sort collections.
305
+ ## Links or buttons?
271
306
 
272
- ## How it works
307
+ Each helper provided by Cocooned with a name ending with `_link` has its `_button` equivalent, to generate a `<button type="button" />` instead of a `<a href="#" />`:
273
308
 
274
- Cocooned defines some helper functions:
309
+ - `cocooned_add_item_link` <=> `cocooned_add_item_button` ([Documentation](https://github.com/notus-sh/cocooned/blob/master/lib/cocooned/helpers/tags/add.rb))
310
+ - `cocooned_remove_item_link` <=> `cocooned_remove_item_button` ([Documentation](https://github.com/notus-sh/cocooned/blob/master/lib/cocooned/helpers/tags/remove.rb))
311
+ - `cocooned_move_item_up_link` <=> `cocooned_move_item_up_button` ([Documentation](https://github.com/notus-sh/cocooned/blob/master/lib/cocooned/helpers/tags/up.rb))
312
+ - `cocooned_move_item_down_link` <=> `cocooned_move_item_down_button` ([Documentation](https://github.com/notus-sh/cocooned/blob/master/lib/cocooned/helpers/tags/down.rb))
275
313
 
276
- * `cocooned_add_item_link` will build a link that, when clicked, dynamically adds a new partial form for the given association. [Have a look at the documentation for available options](https://github.com/notus-sh/cocooned/blob/master/lib/cocooned/helpers.rb#L21).
277
- * `cocooned_remove_item_link` will build a link that, when clicked, dynamically removes the surrounding partial form. [Have a look at the documentation for available options](https://github.com/notus-sh/cocooned/blob/master/lib/cocooned/helpers.rb#L143).
278
- * `cocooned_move_item_up_link` and `cocooned_move_item_down_link` will build links that, when clicked, will move the surrounding partial form one step up or down in the collection. [Have a look at the documentation for available options](https://github.com/notus-sh/cocooned/blob/master/lib/cocooned/helpers.rb#L178).
314
+ While all `_link` helpers accept and will politely forward any option supported by ActionView's `link_to`, `_button` helpers will do the same with options supported by ActionView's `button_tag`.
279
315
 
280
- ### Javascript callbacks
316
+ ## Internationalisation
281
317
 
282
- When your collection is modified, the following events can be triggered:
318
+ The label of any action trigger can be given explicitly as helper's first argument or as a block, just as you can do with ActionView's `link_to` or `button_to`.
283
319
 
284
- * `cocooned:before-insert`: called before inserting a new nested child, can be [canceled](#canceling-an-action)
285
- * `cocooned:after-insert`: called after inserting
286
- * `cocooned:before-remove`: called before removing the nested child, can be [canceled](#canceling-an-action)
287
- * `cocooned:after-remove`: called after removal
320
+ Additionally, Cocooned helpers will lookup I18n translations for a default label based on the action name (`add`, `remove`, `up`, `down`) and the association name. For `add` triggers, the association name used is the same as passed as argument. Other triggers extract the association name from form's `#object_name`.
288
321
 
289
- The limit plugin can trigger its own event:
322
+ You can declare default labels in your translation files with following keys:
290
323
 
291
- * `cocooned:limit-reached`: called when the limit is reached (before a new item will be inserted)
324
+ - `cocooned.{association}.{action}` (Ex: `cocooned.items.add`)
325
+ - `cocooned.defaults.{action}`
292
326
 
293
- And so does the reorderable plugin:
327
+ If no translation is found, the default label will be the humanized action name.
294
328
 
295
- * `cocooned:before-move`: called before moving the nested child, can be [canceled](#canceling-an-action)
296
- * `cocooned:after-move`: called after moving
297
- * `cocooned:before-reindex`: called before updating the `position` fields of nested items, can be [canceled](#canceling-an-action) (even if I honestly don't know why you would)
298
- * `cocooned:after-reindex`: called after `position` fields update
329
+ ## Javascript
299
330
 
300
- To listen to the events in your JavaScript:
331
+ For more documentation about the JavaScript bundled in the companion package, please refer to [its own documentation](https://github.com/notus-sh/cocooned/blob/master/npm/README.md).
301
332
 
302
- ```javascript
303
- $('#container').on('cocooned:before-insert', function(event, node, cocoonedInstance) {
304
- /* Do something */
305
- });
333
+ ## Styling forms
334
+
335
+ Cocooned now uses exclusively data-attribute to hook JavaScript methods on but usual classes are still here and will stay so you can style your forms:
336
+
337
+ - `.cocooned-container` on a container
338
+ - `.cocooned-item` on an item
339
+ - `.cocooned-add` on an add trigger (link or button)
340
+ - `.cocooned-remove` on a remove trigger (link or button)
341
+ - `.cocooned-move-up` on a move up trigger (link or button)
342
+ - `.cocooned-move-down` on a move down trigger (link or button)
343
+
344
+ ## Migration from a previous version
345
+
346
+ These migrations steps only highlight major changes. When upgrading from a previous version, always refer to [the CHANGELOG](https://github.com/notus-sh/cocooned/blob/main/CHANGELOG.md) for new features and breaking changes.
347
+
348
+ ### From Cocooned ~1.0
349
+
350
+ #### Forms markup
351
+
352
+ Cocooned 2.0 introduced the `cocooned_container` and `cocooned_item` helpers to respectively wrap the container where items will be added and each of the items.
353
+
354
+ If you used Cocooned ~1.0, you should modify your main forms as follow:
355
+
356
+ ```diff
357
+ - <div data-cocooned-options="<%= {}.to_json %>">
358
+ + <%= cocooned_container do %>
359
+ <% # […] %>
360
+ + <% end %>
361
+ - </div>
306
362
  ```
307
363
 
308
- An event handler is called with 3 arguments:
364
+ And your nested partials:
365
+
366
+ ```diff
367
+ - <div class="cocooned-item">
368
+ + <%= cocooned_item do %>
369
+ <% # […] %>
370
+ + <% end %>
371
+ - </div>
372
+ ```
373
+
374
+ Support for the `data-cocooned-options` attribute to identify a container and the `.cocooned-item` class to identify an item is still here but it is not the recommended way to tag your containers and items anymore.
375
+
376
+ **Compatibility with older markup will be dropped in the next major release.**
377
+
378
+ #### Bundled styles
379
+
380
+ Cocooned ~2.0 does not provide any styles anymore. If you used to require (with Sprockets) or import the cocooned stylesheets into your application, you need to remove it.
381
+
382
+ **Empty files are included to not break your assets pipeline but will be removed in the next major release.**
309
383
 
310
- The event `event` is an instance of `jQuery.Event` and carry some additional data:
384
+ ### From Cocoon (any version)
311
385
 
312
- * `event.link`, the clicked link
313
- * `event.node`, the nested item that will be added, removed or moved, as a jQuery object. This is null for `cocooned:limit-reached` and `cocooned:*-reindex` events
314
- * `event.nodes`, the nested items that will be or just have been reindexed on `cocooned:*-reindex` events, as a jQuery object. Null otherwise.
315
- * `event.cocooned`, the Cocooned javascript object instance handling the nested association.
316
- * `event.originalEvent`, the original (browser) event.
386
+ Cocoon uses a `.nested_fields` class to identify items in a nested form and nothing to identify containers new items will be added to.
317
387
 
318
- The `node` argument is the same jQuery object as `event.node`.
319
- The `cocooned` argument is the same as `event.cocooned`.
388
+ If you used Cocoon, you should:
320
389
 
321
- #### Canceling an action
390
+ 1. Modify your forms and sub forms to use the `cocooned_container` and `cocooned_item` helpers. (See above for examples)
391
+ 2. Replace calls to `link_to_add_association` by `cocooned_add_item_link`
392
+ 3. Replace calls to `link_to_remove_association` by `cocooned_remove_item_link`
393
+ 4. Rename your I18n keys to use the `cocooned` namespace instead of `cocoon`
322
394
 
323
- You can cancel an action within the `cocooned:before-<action>` callback by calling `event.preventDefault()` or `event.stopPropagation()`.
395
+ **Compatibility with the original Cocoon API will be dropped in the next major release.**
@@ -1,15 +1,3 @@
1
- /* globals Cocooned */
2
1
  //= require 'cocooned'
3
2
 
4
- // Compatibility with the original Cocoon
5
- // TODO: Remove in 3.0
6
- function initCocoon () {
7
- $(Cocooned.prototype.selector('add')).each(function (_i, addLink) {
8
- var container = Cocooned.prototype.findContainer(addLink);
9
- var limit = parseInt($(addLink).data('limit'), 10) || false;
10
-
11
- container.cocooned({ limit: limit });
12
- });
13
- }
14
-
15
- $(initCocoon);
3
+ /* TODO: Remove in 3.0 */