dynamic_nested
Advanced tools
Comparing version 0.1.0 to 0.1.1
51
index.js
@@ -17,4 +17,4 @@ /** | ||
* <%= content_tag :div, dynamic_nested_index: c.index do %> | ||
* # PS: generate hidden fields inside rows group to handle them easily. | ||
* = for {key, value} <- row.hidden do | ||
* # PS: generate hidden fields manually for handling them easily. | ||
* = for {key, value} <- c.hidden do | ||
* = hidden_input c, key, value: value, dynamic_nested_field_id: true | ||
@@ -30,4 +30,4 @@ * <%= text_input c, :name %> | ||
* | ||
* Everytime a User adds a new row, it is going to generate a new index for that row incrementing | ||
* +1 from the last row on the page. As soon as an User removes a row, all indexes will be updated | ||
* Everytime a User adds a new nested, it is going to generate a new index for that nested incrementing | ||
* +1 from the last nested on the page. As soon as an User removes one, all indexes will be updated | ||
* accordingly to reflect their position on the page. | ||
@@ -43,2 +43,16 @@ * | ||
* | ||
* It supports the following callbacks: | ||
* | ||
* * beforeClone - You might want to do something before cloning the element. | ||
* * afterAdd - You might want to do something after adding the new element. | ||
* * afterRemove - You might want to do something after removing the element. | ||
* | ||
* ```JS | ||
* const beforeClone = (element) => { ... } | ||
* const afterAdd = (element, newElement) => { ... } | ||
* const afterRemove = (elements) => { ... } | ||
* | ||
* new DynamicNested(element, { beforeClone, afterAdd, afterRemove }) | ||
* ``` | ||
* | ||
* ## Know caveats | ||
@@ -49,7 +63,7 @@ * | ||
* * You must be using the last version of `Phoenix.HTML` that supports `skip_hidden` fields. | ||
* * There is no way to whether execute callbacks before/after cloning rows or removing them. | ||
**/ | ||
class DynamicNested { | ||
constructor(element) { | ||
constructor(element, options = {}) { | ||
this.element = element | ||
this.options = options | ||
@@ -92,3 +106,4 @@ this.toggleRemoveButtonDisplay() | ||
// TODO: provide callback to do something before cloning nested. | ||
if (this.options.beforeClone) { this.options.beforeClone($lastNested) } | ||
const $newNested = $lastNested.cloneNode(true) | ||
@@ -115,4 +130,4 @@ | ||
// When editing the form, the cloned row will have hidden ids. They must be removed from the new | ||
// row. | ||
// When editing the form, the cloned element will have hidden ids. They must be removed from | ||
// the new element. | ||
const hiddenId = $newNested.querySelector('[dynamic-nested-field-id]') | ||
@@ -125,6 +140,6 @@ if (hiddenId) { $newNested.removeChild(hiddenId) } | ||
// Add new row on the page. | ||
// Add new nested on the page. | ||
this.element.appendChild($newNested) | ||
// TODO: provide callback to something after adding new nested on the page. | ||
if (this.options.afterAdd) { this.options.afterAdd($lastNested, $newNested) } | ||
} | ||
@@ -139,9 +154,9 @@ | ||
this.replaceIndex($nested, index) | ||
}) | ||
// TODO: provide callback to something after removing nested on the page. | ||
}) | ||
if (this.options.afterRemove) { this.options.afterRemove($allNested) } | ||
} | ||
/** | ||
* Replace indexes in `id`, `name` and `for` for all children from the given row element. | ||
* Replace indexes in `id`, `name` and `for` for all children from the given nested element. | ||
* | ||
@@ -188,7 +203,7 @@ * @param {object} element - DOM element. | ||
* | ||
* newAttributeName(<input name="invoice[invoice_rows][0][amount]">, "name", 1) | ||
* => "invoice[invoice_rows][1][amount]" | ||
* newAttributeName(<input name="user[categories][0][name]">, "name", 1) | ||
* => "user[categories][1][name]" | ||
* | ||
* newAttributeName(<input id="invoice_invoice_rows_0">, "id", 1) | ||
* => "invoice_invoice_rows_1" | ||
* newAttributeName(<input id="user_categories_0">, "id", 1) | ||
* => "user_categories_1" | ||
**/ | ||
@@ -195,0 +210,0 @@ newAttributeName(element, attribute, index) { |
{ | ||
"name": "dynamic_nested", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Add/Remove nested associations dynamically in forms that generates markup like Phoenix Framework.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -5,2 +5,4 @@ # Dynamic Nested | ||
 | ||
## Examples | ||
@@ -41,2 +43,18 @@ | ||
It supports the following callbacks: | ||
* beforeClone - You might want to do something before cloning the element. | ||
* afterAdd - You might want to do something after adding the new element. | ||
* afterRemove - You might want to do something after removing the element. | ||
```JS | ||
const beforeClone = (element) => { ... } | ||
const afterAdd = (element, newElement) => { ... } | ||
const afterRemove = (elements) => { ... } | ||
document | ||
.querySelectorAll('[dynamic-nested]') | ||
.forEach(element => DynamicNested(element, { beforeClone, afterAdd, afterRemove })) | ||
``` | ||
Everytime a User adds a new row, it is going to generate a new index for that row incrementing | ||
@@ -43,0 +61,0 @@ +1 from the last row on the page. As soon as an User removes a row, all indexes will be updated |
import DynamicNested from '../index' | ||
import '@testing-library/jest-dom/extend-expect' | ||
beforeAll(() => { | ||
beforeEach(() => { | ||
document.body.innerHTML = | ||
@@ -27,2 +27,6 @@ `<div dynamic-nested> | ||
afterEach(() => { | ||
document.body.innerHTML = '' | ||
}) | ||
test('adds a new nested association markup incrementing +1 from the previous index', () => { | ||
@@ -63,1 +67,26 @@ const element = document.querySelector('[dynamic-nested]') | ||
test('supports callbacks beforeClone and afterAdd', () => { | ||
const element = document.querySelector('[dynamic-nested]') | ||
const beforeClone = (element) => { | ||
element.setAttribute("data-before-clone", true) | ||
} | ||
const afterAdd = (element, newElement) => { | ||
element.setAttribute("data-after-added", true) | ||
newElement.setAttribute("data-after-added", true) | ||
} | ||
new DynamicNested(element, { beforeClone, afterAdd }) | ||
// Simulates a User clicks on add button. | ||
document.querySelector('[dynamic-nested-add]').click() | ||
const clonedElement = element.querySelector('[dynamic-nested-index="0"]') | ||
const newElement = element.querySelector('[dynamic-nested-index="1"]') | ||
expect(clonedElement).toHaveAttribute('data-before-clone', 'true') | ||
expect(clonedElement).toHaveAttribute('data-after-added', 'true') | ||
expect(newElement).toHaveAttribute('data-after-added', 'true') | ||
}) | ||
import DynamicNested from '../index' | ||
import '@testing-library/jest-dom/extend-expect' | ||
beforeAll(() => { | ||
beforeEach(() => { | ||
document.body.innerHTML = | ||
@@ -24,25 +24,25 @@ `<div dynamic-nested> | ||
<button type="button" dynamic-nested-add>Add more</button> | ||
` | ||
`.trim() | ||
}) | ||
describe("removing nested associations", () => { | ||
test('removes the clicked nested and rebuild indexes', () => { | ||
const element = document.querySelector('[dynamic-nested]') | ||
const dynamicNested = new DynamicNested(element) | ||
test('removes the clicked nested and rebuild indexes and support afterRemove callback', () => { | ||
const element = document.querySelector('[dynamic-nested]') | ||
// Simulates a User clicks on second nested association remove's button. | ||
element | ||
.querySelector('[dynamic-nested-index="1"]') | ||
.querySelector('[dynamic-nested-remove]') | ||
.click() | ||
const afterRemove = (elements) => { | ||
Array.from(elements).forEach(element => element.setAttribute('data-after-remove', true)) | ||
} | ||
expect(element.querySelectorAll('[dynamic-nested-index]').length).toBe(2) | ||
new DynamicNested(element, { afterRemove }) | ||
expect(element).toContainHTML( | ||
'<div dynamic-nested-index="0">' | ||
) | ||
expect(element).toContainHTML( | ||
'<div dynamic-nested-index="1">' | ||
) | ||
// Simulates a User clicks on second nested association remove's button. | ||
element | ||
.querySelector('[dynamic-nested-index="1"]') | ||
.querySelector('[dynamic-nested-remove]') | ||
.click() | ||
expect(element.querySelectorAll('[dynamic-nested-index]').length).toBe(2) | ||
Array.from(element.querySelectorAll('[dynamic-nested-index]')).forEach(element => { | ||
expect(element).toHaveAttribute('data-after-remove', 'true') | ||
}) | ||
}) |
130976
7
303
73