Skip to content

Commit

Permalink
Merge pull request #3 from stimulus-components/feature/remove-field
Browse files Browse the repository at this point in the history
Adding support to remove field
  • Loading branch information
guillaumebriday authored Oct 31, 2020
2 parents 114180e + 35f6017 commit b5aff53
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## Changed

- Adding `remove` support.

## [1.0.0] - 2020-10-15

### Added
Expand Down
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ To DRY up the code, we extract the fields in a partial called `todo_form` to use

In your view:
```html
<%= form_with model: @user, data: { controller: 'nested-form' } do |f| %>
<%= form_with model: @user, data: { controller: 'nested-form', nested_form_wrapper_selector: '.nested-form-wrapper' } do |f| %>
<template data-target="nested-form.template">
<%= f.fields_for :todos, Todo.new, child_index: 'NEW_RECORD' do |todo_fields| %>
<%= render "todo_form", f: todo_fields %>
Expand All @@ -83,22 +83,37 @@ In your view:
<!-- Inserted elements will be injected before that target. -->
<div data-target="nested-form.target"></div>

<button type="button" data-action="click->nested-form#add">
<button type="button" data-action="nested-form#add">
Add todo
</button>

<%= f.submit 'Save todos' %>
<% end %>
```

In the `todo_form.html.erb` partial:
In the `_todo_form.html.erb` partial:
```html
<%= f.label :description %>
<%= f.text_field :description %>
<div class="nested-form-wrapper" data-new-record="<%= f.object.new_record? %>">
<%= f.label :description %>
<%= f.text_field :description %>

<button type="button" data-action="nested-form#remove">
Remove todo
</button>

<%= f.hidden_field :_destroy %>
</div>
```

As explained in the [documentation](https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for), we need to
specify the `child_index` and replace its value in JavaScript because the index needs to be unique for each fields.
As explained in the [documentation](https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for), we need to specify the `child_index` and replace its value in JavaScript because the index needs to be unique for each fields.

## Configuration

| Attribute | Default | Description | Optional |
| --------- | ------- | ----------- | -------- |
| `data-nested-form-wrapper-selector` | `.nested-form-wrapper` | Selector to find the wrapper. ||

The remove feature is completely optional.

## Extending Controller

Expand Down
44 changes: 33 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,53 @@ <h2 class="text-center text-2xl font-bold my-4">Essential links</h2>
</li>
</ul>

<section data-controller="nested-form" class="mt-16">
<form>
<section class="mt-16">
<form data-controller="nested-form">
<template data-target="nested-form.template">
<div class="mt-4">
<div class="mt-4 nested-form-wrapper" data-new-record="true">
<label for="NEW_RECORD" class="block text-sm font-medium leading-5 text-gray-700">New todo</label>
<div class="mt-1 relative rounded-md shadow-sm">
<div class="mt-1 flex relative rounded-md shadow-sm">
<input
id="NEW_RECORD"
name="user[todos][NEW_RECORD][description]"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
name="user[todos_attributes][NEW_RECORD][description]"
class="appearance-none border w-full py-2 px-3 rounded-l-md text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="Your todo"
/>

<button
class="cursor-pointer inline-flex items-center px-3 rounded-r-md border border-l-0 border-gray-300 bg-gray-100 text-gray-500 sm:text-sm"
type="button"
data-action="nested-form#remove"
title="Remove todo"
>
X
</button>

<input type="hidden" name="user[todos_attributes][NEW_RECORD][_destroy]" />
</div>
</div>
</template>

<div class="mt-4">
<div class="mt-4 nested-form-wrapper" data-new-record="false">
<label for="todo-1" class="block text-sm font-medium leading-5 text-gray-700">Your todo</label>
<div class="mt-1 relative rounded-md shadow-sm">
<div class="mt-1 flex relative rounded-md shadow-sm">
<input
id="todo-1"
name="user[todos][0][description]"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
name="user[todos_attributes][0][description]"
class="appearance-none border w-full py-2 px-3 rounded-l-md text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
value="Pet the cat"
/>

<button
class="cursor-pointer inline-flex items-center px-3 rounded-r-md border border-l-0 border-gray-300 bg-gray-100 text-gray-500 sm:text-sm"
type="button"
data-action="nested-form#remove"
title="Remove todo"
>
X
</button>

<input type="hidden" name="user[todos_attributes][0][_destroy]" />
</div>
</div>

Expand All @@ -125,7 +147,7 @@ <h2 class="text-center text-2xl font-bold my-4">Essential links</h2>
<button
id="nested-form-button"
type="button"
data-action="click->nested-form#add"
data-action="nested-form#add"
class="mt-4 bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow"
>
Add todo
Expand Down
17 changes: 17 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,27 @@ import { Controller } from 'stimulus'
export default class extends Controller {
static targets = ['target', 'template']

initialize () {
this.wrapperSelector = this.data.get('wrapperSelector') || '.nested-form-wrapper'
}

add (e) {
e.preventDefault()

const content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
this.targetTarget.insertAdjacentHTML('beforebegin', content)
}

remove (e) {
e.preventDefault()

const wrapper = e.target.closest(this.wrapperSelector)

if (wrapper.dataset.newRecord === 'true') {
wrapper.remove()
} else {
wrapper.style.display = 'none'
wrapper.querySelector("input[name*='_destroy']").value = 1
}
}
}

0 comments on commit b5aff53

Please sign in to comment.