Skip to content

Commit

Permalink
Repeater: Clone - Add possibility to exclude some values from being c…
Browse files Browse the repository at this point in the history
…loned
  • Loading branch information
nicolas-jaussaud committed Feb 13, 2025
1 parent 58eb26f commit 8fcff89
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 23 deletions.
38 changes: 19 additions & 19 deletions assets/build/index.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/src/components/repeater/Repeater.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const Repeater = props => {
fields.forEach(field => emptyItem[ field.name ] = '')

const [items, dispatch] = useReducer(
repeaterDispatcher(emptyItem, maxLength),
repeaterDispatcher(emptyItem, maxLength, props),
props.value ?? '',
initDispatcher
)
Expand Down
34 changes: 32 additions & 2 deletions assets/src/components/repeater/dispatcher.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { uniqid } from '../../utils'

const repeaterDispatcher = (emptyItem, maxLength) => (items, action) => {
const repeaterDispatcher = (emptyItem, maxLength, props) => (items, action) => {
if( action.callback ) setTimeout(action.callback) // Trigger callback after state change
switch (action.type) {
case 'add':
Expand Down Expand Up @@ -35,7 +35,7 @@ const repeaterDispatcher = (emptyItem, maxLength) => (items, action) => {
: [
...items,
{
...(action.item),
...(formatClone(action.item, props)),
key: uniqid(),
},
]
Expand Down Expand Up @@ -67,6 +67,36 @@ const repeaterDispatcher = (emptyItem, maxLength) => (items, action) => {
}
}

/**
* If props.clone.exclude is set on a repeater, some fields
* value won't be cloned
*
* Syntax is the following:
* {
* clone: {
* exclude: {
* 'fields-name-1': true,
* 'fields-name-2': { 'sub-value': true },
* }
* }
* }
*/
const formatClone = (initial, props) => {
if ( typeof props?.clone?.exclude !== 'object' ) return initial
const item = { ...initial }
Object.keys(props.clone.exclude)
.map(name => {
const value = props.clone.exclude[ name ]
if ( value === true ) delete item[ name ]
if ( typeof value === 'object' ) item[ name ] = Array.isArray(item[ name ])
? item[ name ].map(
subItem => formatClone(subItem, { clone : { exclude : value } })
)
: formatClone(item[ name ], { clone : { exclude : value } })
})
return item
}

const initDispatcher = value => {
try {
const initialItems = Array.isArray(value) ? value : JSON.parse(value)
Expand Down
128 changes: 127 additions & 1 deletion tests/jest/cases/components/repeaters/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ const commonRepeaterTests = (layout, args = {}) => {
expect(addButton).not.toBeDisabled()

if( layout !== 'bare' ) {
cloneButton = within(container).getByText(config.cloneText)
cloneButton = within(container).getByText(config.cloneText)
expect(cloneButton).not.toBeDisabled()
}

Expand Down Expand Up @@ -482,6 +482,132 @@ const commonRepeaterTests = (layout, args = {}) => {

expect(items.children.length).toBe(2)
})

it('support exclude props when cloning', async () => {

const user = userEvent.setup()
const store = tangibleFields.store

const { container } = render(
fields.render({
type : 'repeater',
layout : layout,
name : 'test',
clone : {
exclude : {
id : true,
name2 : { id: true },
name4 : { id: true },
}
},
value : [
{
key : 'something',
id : 10,
name1 : 'Text value',
name2 : {
id : 20,
name3 : 'Text value 2'
},
name4 : [
{
id : 30,
name5 : 'Text value 3'
},
{
id : 40,
name5 : 'Text value 4'
}
],
}
],
fields : [
{
name : 'id',
type : 'hidden'
},
{
name : 'name1',
label : 'Test 1',
type : 'text'
},
{
name : 'name2',
label : 'Test 2',
type : 'field-group',
fields : [
{
name : 'id',
type : 'hidden'
},
{
name : 'text-name',
label : 'Text',
type : 'text'
},
]
},
{
name : 'name4',
label : 'Test 2',
type : 'repeater',
// To be sure there is only one clone button, easier to test
parts : { actions : { clone : props => <></> } },
fields : [
{
name : 'id',
type : 'hidden'
},
{
name : 'name5',
label : 'Text',
type : 'text'
},
]
},
],
})
)

// Does not have a clone button
if( layout === 'bare' ) return;

const cloneButton = within(container).getByText(config.cloneText)
await user.click(cloneButton)

const repeater = store.getRepeater('test')

expect( repeater.getRowValue('0', 'id') ).toBe(10)
expect( repeater.getRowValue('1', 'id') ).not.toBe(10)

expect( repeater.getRowValue('0', 'name1') ).toBe('Text value')
expect( repeater.getRowValue('1', 'name1') ).toBe('Text value')

const fieldGroup1 = repeater.getRowValue('0', 'name2')
const fieldGroup2 = repeater.getRowValue('1', 'name2')

expect( fieldGroup1.id ).toBe(20)
expect( fieldGroup2.id ).not.toBe(20)

expect( fieldGroup1.name3 ).toBe('Text value 2')
expect( fieldGroup2.name3 ).toBe('Text value 2')

const subRepeater1 = repeater.getRowValue('0', 'name4')
const subRepeater2 = repeater.getRowValue('1', 'name4')

expect( subRepeater1[0].id ).toBe(30)
expect( subRepeater2[0].id ).not.toBe(30)

expect( subRepeater1[1].id ).toBe(40)
expect( subRepeater2[1].id ).not.toBe(40)

expect( subRepeater1[0].name5 ).toBe('Text value 3')
expect( subRepeater2[0].name5 ).toBe('Text value 3')

expect( subRepeater1[1].name5 ).toBe('Text value 4')
expect( subRepeater2[1].name5 ).toBe('Text value 4')
})

}

export { commonRepeaterTests }

0 comments on commit 8fcff89

Please sign in to comment.