Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 64 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Example:

![sample-gif](./github-assets/sample.gif)

# Installation
## Installation

Just add it to your gemfile

Expand All @@ -17,17 +17,18 @@ gem 'vanilla_nested'

# or, to use latest code from git:
gem 'vanilla_nested', github: 'arielj/vanilla-nested', branch: :main
# you can use any branch, note just main
```

## Using Sprockets
### Using Sprockets

Require the js:

```
//= require vanilla_nested
```

## Using Webpacker
### Using Webpacker

Add the package too (gem is required for the helper methods) using:

Expand All @@ -36,6 +37,7 @@ yarn add vanilla-nested

# or, to use latest code from git:
yarn add arielj/vanilla-nested
# you can use any branch with `yarn add arielj/vanilla-nested#branch-name`
```

And then use it in your application.js as:
Expand All @@ -44,7 +46,7 @@ And then use it in your application.js as:
import "vanilla-nested";
```

## Using Importmaps in Rails 7
### Using Importmaps in Rails 7

Add the importmap config:

Expand All @@ -58,7 +60,7 @@ And then use it in your application.js as:
import "vanilla-nested";
```

# Updating
## Updating

To update the gem use either:

Expand All @@ -78,9 +80,9 @@ yarn upgrade vanilla-nested

> You can clear the webpacker cache just in case if changes are not reflecting with `rails webpacker:clobber`

# Usage
## Usage

## Backend prerequisites
### Backend prerequisites

```ruby
# models/order.rb
Expand Down Expand Up @@ -135,7 +137,41 @@ Note that:
- `link_to_remove_nested` receives the nested form as a parameter, it adds a hidden `[_destroy]` field
- `link_to_add_nested` expects the form builder, the name of the association and the selector of the container where the gem will insert the new fields

# Customizing link_to_add_nested
### Nested with Nested

In order to support a nested dynamic association which also has nested dynamic associations, some conventions have to be followed. Because the rendering happens inside a partial, the `link_to_add_nested` helper doesn't have enough control over the placeholder used for the nested IDs, so the placeholders used in the ids and selectors have to be defined explicitly:

Given a relationship User has many Pets, and Pet has many Appointments, the appointments form nested inside the Pet form has to define the ids to use:

```erb
# the first level can use the default `_idx_placeholder` or define one

<%= link_to_add_nested(form, :pets, '#pets', idx_placeholder: "_idx1") do %>
```

The second level has to follow more conventions:

- The wrapper id has to include both the `form.object.id` value (to support persisted elements) and `_idx1` (in this example, it's the value used for idx_placeholder in the parent) to support new dynamic elements.

```
<div id="appointments_<%= form.object.id %>_idx1">
<%= form.fields_for :appointments do |app_f| %>
<%= render 'appointment_fields', form: app_f %>
<% end %>
</div>
```

- The call to `link_to_add_nested` we must use the same pattern for the third element (the wrapper's id) and MUST define a new different `idx_placeholder` value:

```
<%= link_to_add_nested(form, :appointments, "#appointments_#{form.object.id}_idx1", idx_placeholder: "_idx2") do %>
<span><span>Add Appointment</span></span>
<% end %>
```

> Note that this feature is tested with 3-level nesting. If more levels are needed, a similar pattern must be used defining new values for ids and indexes (if I'm thought this through correctly), but it's not fully tested. If you end up having a form with 4 levels of nesting, I'd suggest rethinking if it's really necessary to avoid issues.

### Customizing link_to_add_nested

#### Link text

Expand Down Expand Up @@ -230,7 +266,7 @@ If you need html content, you can use a block:
<% end %>
```

# Customizing link_to_remove_nested
### Customizing link_to_remove_nested

#### Link text

Expand Down Expand Up @@ -313,7 +349,7 @@ Options are:
- `undo_link_text`: string with the text of the link, great for internationalization, default: `'Undo'`
- `undo_link_classes`: space separated string, default: `''`

# Events
### Events

There are some events that you can listen to add custom callbacks on different moments. All events bubbles up the dom, so you can listen for them on any ancestor.

Expand Down Expand Up @@ -395,7 +431,9 @@ You can run the tests following these commands:

> If you make changes in the JS files, you have to tell yarn to refresh the code inside the node_modules folder running `./bin/update-gem` (or `yarn upgrade vanilla-nested` and `rails webpacker:clobber`), and then restart the rails server or re-run the tests.

# Version 1.1.0 Changes
## History

### Version 1.1.0 Changes

#### Change the method to infere the name of the partial

Expand Down Expand Up @@ -427,19 +465,19 @@ Mostly on the javascript code

So it can be used as a node module using yarn to integrate it using webpacker.

# Version 1.2.0 Changes
### Version 1.2.0 Changes

#### New event for the "limit" option of `accepts_nested_attributes_for`

You can listen to the `vanilla-nested:fields-limit-reached` event that will fire when container has more or equals the amount of children than the `limit` option set on the `accepts_nested_attributes_for` configuration.

# Version 1.2.1 Changes
### Version 1.2.1 Changes

#### Removed "onclick" attribute for helpers and add event listeners within js

If you were using webpacker, remember to replace the vanilla_nested.js file in your app/javascript folder

# Version 1.2.2 Changes
### Version 1.2.2 Changes

#### Added "link_classes" option to "link_to_remove_nested"

Expand All @@ -449,7 +487,7 @@ You can set multiple classes for the "X" link

You can pass a block to use as the content for the add and remove links

# Version 1.2.3 Changes
### Version 1.2.3 Changes

#### Fix using nested html elements as the content for buttons

Expand All @@ -463,15 +501,15 @@ There was an error when using the helpers with things like:

It would detect the wrong element for the click event, making the JS fail.

# Version 1.2.4 Changes
### Version 1.2.4 Changes

Play nicely with Turbolinks' `turbolinks:load` event.

# Version 1.2.5 Changes
### Version 1.2.5 Changes

License change from GPL to MIT

# Version 1.3.0 Changes
### Version 1.3.0 Changes

#### Custom generated HTML element tag

Expand All @@ -493,7 +531,7 @@ Before, the elements were just hidden using `display: none` on the wrapper. That

If the `accepts_nested_attributes_for` configuration has a limit, this gem was counting the number of children wrong (it was counting removed elements and extra children of the wrapper). This fixes that by only counting the `[_destroy]` hidden fields with value `0`.

# Version 1.4.0 Changes
### Version 1.4.0 Changes

#### Custom HTML attributes for the generated HTML element tag

Expand All @@ -517,19 +555,19 @@ link_to_add_nested(form, :order_items, '#order-items', tag: 'button', tag_attrib

- you can set any valid html attribute accepted by [`content_tag`](https://apidock.com/rails/ActionView/Helpers/TagHelper/content_tag)

# Version 1.5.0 Changes
### Version 1.5.0 Changes

#### Added Integration with the `turbo` Gem

The JavaScript part of the gem now plays nicely with the `turbo` gem by initializing the needed events when the `turbo:load` event is fired.

# Version 1.5.1 Changes
### Version 1.5.1 Changes

#### Yarn/NPM Packages

Node package can be installed using npm or yarn without using the GitHub repo. This improves the size of the bundle and allows version flags.

# Version 1.6.0 Changes
### Version 1.6.0 Changes

#### Fix undeclared variables

Expand All @@ -539,27 +577,27 @@ https://github.com/arielj/vanilla-nested/pull/45 thanks @gmeir.

You can pin the vanilla-nested module. A Rails 7 sample app is added to the test directory.

# Version 1.6.1 Changes
### Version 1.6.1 Changes

#### Fix elements' style after undo

When undoing a removal, the gem was setting `display: initial` to all the elements. Now it sets the `display` value it had before hidding the element.

> Remember to update both gem and package https://github.com/arielj/vanilla-nested#update

# Version 1.6.2 Changes
### Version 1.6.2 Changes

#### Event listeners are now added once to the `document`

Attach the vanilla-nested event listeners to the document object. This fixes [issues](https://github.com/arielj/vanilla-nested/issues/47) with turbo/hotwire where the listener was not being attached to the new elements added to the DOM. Thanks to @lenilsonjr for testing these changes!

# Version 1.7.0 Changes
### Version 1.7.0 Changes

#### Fix initialization of Vanilla Nested when using importmaps and Safari

The shim to support importmaps in Safari was not firing the load events as the code was expecting. This patches that by considering if the DOM is already ready when the code loads.

# Version 1.7.1 Changes
### Version 1.7.1 Changes

#### New classes added

Expand Down
4 changes: 3 additions & 1 deletion app/assets/javascripts/vanilla_nested.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

const data = element.dataset;
const container = document.querySelector(data.containerSelector);
const newHtml = data.html.replace(/_idx_placeholder_/g, Date.now());
const newHtml = data.html
.replace(/\<vanilla-quote\>/g, '"')
.replace(new RegExp(data.idxPlaceholder, "g"), Date.now());

// insert and store reference
let inserted;
Expand Down
9 changes: 5 additions & 4 deletions lib/vanilla_nested/view_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ module ViewHelpers
# @param link_content [Block] block of code for the link content
# @param tag_attributes [Hash<attribute, value>] hash with attribute,value pairs for the html tag
# @return [String] link tag
def link_to_add_nested(form, association, container_selector, link_text: nil, link_classes: '', insert_method: :append, partial: nil, partial_form_variable: :form, partial_locals: {}, tag: 'a', tag_attributes: {}, &link_content)
def link_to_add_nested(form, association, container_selector, link_text: nil, link_classes: '', insert_method: :append, partial: nil, partial_form_variable: :form, partial_locals: {}, tag: 'a', tag_attributes: {}, idx_placeholder: '_idx_placeholder', &link_content)
association_class = form.object.class.reflections[association.to_s].klass
object = association_class.new

partial_name = partial || "#{association_class.name.underscore}_fields"

html = capture do
form.fields_for association, object, child_index: '_idx_placeholder_' do |ff|
form.fields_for association, object, child_index: idx_placeholder do |ff|
render partial: partial_name, locals: { partial_form_variable => ff }.merge(partial_locals)
end
end
Expand All @@ -31,8 +31,9 @@ def link_to_add_nested(form, association, container_selector, link_text: nil, li
classes = "vanilla-nested-add #{link_classes}"
data = {
'container-selector': container_selector,
'html': html,
'method-for-insert': method_for_insert
'html': html.gsub('"', "<vanilla-quote>"),
'method-for-insert': method_for_insert,
'idx-placeholder': idx_placeholder
}

nested_options = form.object.class.nested_attributes_options[association.to_sym]
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vanilla-nested",
"version": "1.7.0",
"version": "1.7.1",
"description": "Dynamic nested form for Ruby On Rails",
"main": "app/assets/javascripts/vanilla_nested.js",
"repository": "git@github.com:arielj/vanilla-nested.git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ def new_using_turbo
render template: 'users/new', layout: "turbo"
end

def create
User.create(user_params)
end

private
def new_user
@user = User.new
@user.pets.build
end

def user_params
params.require(:user).permit(pets_attributes: [:name, :color, {appointments_attributes: [:datetime]}])
end
end
3 changes: 3 additions & 0 deletions test/VanillaNestedTestRails7/app/models/appointment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Appointment < ApplicationRecord
belongs_to :pet
end
2 changes: 2 additions & 0 deletions test/VanillaNestedTestRails7/app/models/pet.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
class Pet < ApplicationRecord
belongs_to :user
has_many :appointments
accepts_nested_attributes_for :appointments
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="appointment-fields">
<%= form.datetime_select :datetime %>

<%= link_to_remove_nested(form) do %>
<span>
<span>X</span>
</span>
<% end %>
</div>
4 changes: 3 additions & 1 deletion test/VanillaNestedTestRails7/app/views/users/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<%= form.label :name %>
<%= form.text_field :name %>

<%= link_to_add_nested(form, :pets, '#pets') do %>
<%= link_to_add_nested(form, :pets, '#pets', idx_placeholder: "_idx1") do %>
<span><span>Add Pet</span></span>
<% end %>
<h1>Pets</h1>
Expand All @@ -12,4 +12,6 @@
<%= render 'pet_fields', form: pet_f %>
<% end %>
</div>

<%= form.submit %>
<% end %>
33 changes: 22 additions & 11 deletions test/VanillaNestedTestRails7/app/views/users/_pet_fields.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
<div class="pet-fields">
<%= form.label :name %>
<%= form.text_field :name %>
<%= form.label :color %>
<%= form.text_field :color %>
<%= link_to_remove_nested(form) do %>
<span>
<span>X</span>
</span>
<% end %>
</div>
<div class="pet-fields">
<%= form.label :name %>
<%= form.text_field :name %>
<%= form.label :color %>
<%= form.text_field :color %>
<%= link_to_remove_nested(form) do %>
<span>
<span>X</span>
</span>
<% end %>

<%= link_to_add_nested(form, :appointments, "#appointments_#{form.object.id}_idx1", idx_placeholder: "_idx2") do %>
<span><span>Add Appointment</span></span>
<% end %>

<h2>Appointments</h1>
<div id="appointments_<%= form.object.id %>_idx1">
<%= form.fields_for :appointments do |app_f| %>
<%= render 'appointment_fields', form: app_f %>
<% end %>
</div>
</div>
Loading