Skip to content

feat(AnalyticalTable - useIndeterminateRowSelection): add onIndeterminateChange param #3797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 24, 2022
Merged
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import { AnalyticalTable, Button, Input } from '../..';
import { AnalyticalTable, AnalyticalTableHooks, Button, Input } from '../..';
import { TableSelectionMode, TableVisibleRowCountMode, ValueState } from '../../enums';

const generateMoreData = (count) => {
Expand Down Expand Up @@ -431,6 +431,130 @@ describe('AnalyticalTable', () => {
cy.findByTestId('selectedFlatRowsLength').should('have.text', '1');
cy.findByTestId('isSelected').should('have.text', 'false');
});

it('useIndeterminateRowSelection - select subRows', () => {
const indeterminateChange = cy.spy().as('onIndeterminateChangeSpy');
cy.mount(
<AnalyticalTable
selectionMode={TableSelectionMode.MultiSelect}
data={dataTree}
columns={columns}
isTreeTable
tableHooks={[AnalyticalTableHooks.useIndeterminateRowSelection(indeterminateChange)]}
reactTableOptions={{ selectSubRows: true }}
/>
);

// select all
cy.get('#__ui5wcr__internal_selection_column').click();

// expand
cy.get('[aria-rowindex="2"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
cy.get('[aria-rowindex="3"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
cy.get('[aria-rowindex="4"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();

// deselect row
cy.findByText('Wiggins Cotton').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 1);

cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');

// deselect all
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 2);

// select leaf row
cy.findByText('Wiggins Cotton').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 3);

cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');

// deselect all
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 4);

// select row with subRows
cy.findByText('Diann Alvarado').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 5);

cy.get('[aria-rowindex="4"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
cy.get('[aria-rowindex="5"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
cy.get('[aria-rowindex="6"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
cy.get('[aria-rowindex="7"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
cy.get('[aria-rowindex="8"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
});

it('useIndeterminateRowSelection', () => {
const indeterminateChange = cy.spy().as('onIndeterminateChangeSpy');
cy.mount(
<AnalyticalTable
selectionMode={TableSelectionMode.MultiSelect}
data={dataTree}
columns={columns}
isTreeTable
tableHooks={[AnalyticalTableHooks.useIndeterminateRowSelection(indeterminateChange)]}
/>
);
// select all
cy.get('#__ui5wcr__internal_selection_column').click();

// expand
cy.get('[aria-rowindex="2"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
cy.get('[aria-rowindex="3"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
cy.get('[aria-rowindex="4"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();

// deselect row
cy.findByText('Wiggins Cotton').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 1);

cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');

// deselect all
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 2);

// select leaf row
cy.findByText('Wiggins Cotton').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 3);

cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');

// deselect all
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('#__ui5wcr__internal_selection_column').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 4);

// select row with subRows
cy.findByText('Diann Alvarado').click();
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 5);

cy.get('[aria-rowindex="4"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
cy.get('[aria-rowindex="5"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
cy.get('[aria-rowindex="6"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
cy.get('[aria-rowindex="7"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
cy.get('[aria-rowindex="8"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
});
});

const columns = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@ const data = [
The `useIndeterminateRowSelection` plugin hook allows marking parent rows as indeterminate when a child row is selected.
When using this hook, it is recommended to also select all sub-rows when selecting a row. (`reactTableOptions={{ selectSubRows: true }}`)

<MessageStrip hideCloseButton>
The indeterminate state has a higher priority than the selected state. Therefore, a row can be selected and
indeterminate at the same time. This can for example happen, if `selectSubRows: true` is set and a row with sub-rows
is selected and then a sub-row is unselected.
</MessageStrip>

_ES6 Import: `import { AnalyticalTableHooks } from '@ui5/webcomponents-react';`_

<Canvas withSource="none">
Expand Down Expand Up @@ -519,6 +525,17 @@ _ES6 Import: `import { AnalyticalTableHooks } from '@ui5/webcomponents-react';`_
/>
```

#### Optional function parameter

The plugin hook allows passing a callback as parameter with the following structure:

```js
({indeterminateRowsById, tableInstance}) => void;
```

The callback is fired, every time the internal `indeterminateRows` state is changed.
The event parameter is an object, with all indeterminate rows by id (e.g. `{"0.1":true}`) and the table instance.

### Manual control of selected lines

The `useManualRowSelect` plugin hook allows controlling the selected rows manually. It accepts a parameter (`manualRowSelectedKey`) which defaults to `isSelected`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import React, { createRef } from 'react';
import { TableSelectionBehavior, TableSelectionMode, ValueState } from '../../enums';
import { Button } from '../../webComponents';
import { AnalyticalTable } from './index';
import {
useIndeterminateRowSelection,
useManualRowSelect,
useRowDisableSelection
} from './pluginHooks/AnalyticalTableHooks';
import { useManualRowSelect, useRowDisableSelection } from './pluginHooks/AnalyticalTableHooks';

const columns = [
{
Expand Down Expand Up @@ -153,101 +149,6 @@ const moreData = [
}
];

const dataTreeUnique = [
{
name: 'Fra',
age: 40,
friend: {
name: 'MAR',
age: 28
},
subRows: [
{
name: 'asd',
age: 40,
friend: {
name: 'longlonglong',
age: 28
},
subRows: [
{
name: 'ABC',
age: 40,
friend: {
name: 'DEF',
age: 28
},
subRows: [
{
name: 'GHijkl',
age: 40,
friend: {
name: 'mnop',
age: 28
},
subRows: [
{
name: 'Marc',
age: 40,
friend: {
name: 'Peter',
age: 28
},
subRows: [
{
name: 'Paula',
age: 40,
friend: {
name: 'May',
age: 28
}
}
]
}
]
}
]
}
]
},
{
name: 'Charles',
age: 40,
friend: {
name: 'Leela',
age: 28
}
},
{
name: 'Farnsworth',
age: 40,
friend: {
name: 'Fry',
age: 28
},
subRows: [
{
name: 'Zoidberg',
age: 40,
friend: {
name: 'Bender',
age: 28
}
}
]
}
]
},
{
name: 'Amy',
age: 20,
friend: {
name: 'Philip',
age: 50
}
}
];

const dataTree = [
{
name: 'Fra',
Expand Down Expand Up @@ -1048,86 +949,6 @@ describe('AnalyticalTable', () => {
expect(asFragment()).toMatchSnapshot();
});

test('plugin hook: useIndeterminateRowSelection', async () => {
const cb = jest.fn();
const { rerender, getAllByTitle, getByTitle, getByText, container, unmount, asFragment } = render(
<AnalyticalTable
data={dataTreeUnique}
columns={columns}
selectionMode={TableSelectionMode.MultiSelect}
isTreeTable={true}
tableHooks={[useIndeterminateRowSelection()]}
onRowSelect={cb}
selectedRowIds={{ '1': true }}
/>
);
const checkboxes = container.querySelectorAll('ui5-checkbox');

expect(checkboxes[0]).toHaveAttribute('indeterminate', 'true');
expect(checkboxes[1]).not.toHaveAttribute('indeterminate', 'true');
expect(checkboxes[1]).not.toHaveAttribute('checked', 'true');
expect(checkboxes[2]).not.toHaveAttribute('indeterminate', 'true');
expect(checkboxes[2]).toHaveAttribute('checked', 'true');

fireEvent.click(getByText('Amy'));
expect(cb).toHaveBeenCalled();

expect(checkboxes[0]).not.toHaveAttribute('indeterminate', 'true');
expect(checkboxes[1]).not.toHaveAttribute('indeterminate', 'true');
expect(checkboxes[2]).not.toHaveAttribute('indeterminate', 'true');
expect(checkboxes[0]).not.toHaveAttribute('checked', 'true');
expect(checkboxes[1]).not.toHaveAttribute('checked', 'true');
expect(checkboxes[2]).not.toHaveAttribute('checked', 'true');

// expand all rows
fireEvent.click(getByTitle('Expand Node').querySelector('[ui5-icon]'));
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));

fireEvent.click(getByText('GHijkl'));

Array.from(container.querySelectorAll('ui5-checkbox'))
.slice(0, 4)
.forEach((item, index) => {
expect(item).toHaveAttribute('indeterminate', 'true');
});
expect(container.querySelectorAll('ui5-checkbox')[4]).not.toHaveAttribute('indeterminate', 'true');
expect(container.querySelectorAll('ui5-checkbox')[4]).toHaveAttribute('checked', 'true');

Array.from(container.querySelectorAll('ui5-checkbox'))
.slice(5)
.forEach((item, index) => {
expect(item).not.toHaveAttribute('indeterminate', 'true');
expect(item).not.toHaveAttribute('checked', 'true');
});

expect(asFragment()).toMatchSnapshot();

unmount();
const { container: newContainer } = render(
<AnalyticalTable
data={dataTreeUnique}
columns={columns}
selectionMode={TableSelectionMode.MultiSelect}
isTreeTable={true}
tableHooks={[useIndeterminateRowSelection()]}
onRowSelect={cb}
selectedRowIds={{
'0.0.0.0.0': true,
'1': true
}}
/>
);

expect(newContainer.querySelectorAll('ui5-checkbox')[0]).toHaveAttribute('indeterminate', 'true');
expect(newContainer.querySelectorAll('ui5-checkbox')[1]).toHaveAttribute('indeterminate', 'true');
expect(newContainer.querySelectorAll('ui5-checkbox')[2]).not.toHaveAttribute('indeterminate', 'true');
expect(newContainer.querySelectorAll('ui5-checkbox')[2]).toHaveAttribute('checked', 'true');
});

test('body scroll', () => {
const data100 = new Array(100).fill({
name: 'Chris P.',
Expand Down
Loading