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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: support updating checkbox state by record index",
"type": "patch",
"packageName": "@visactor/vtable"
}
],
"packageName": "@visactor/vtable",
"email": "github@visactor.io"
}
41 changes: 40 additions & 1 deletion docs/assets/api/en/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -2107,14 +2107,21 @@ Get the selection state of all checkbox data under a field, the order correspond
getCheckboxState(field?: string | number): Array
```

- field: Optional checkbox field. If omitted, returns checkbox states for all fields
- return: Checkbox state array. Tree data keeps state by children path

## getCellCheckboxState(Function)

Get the state of a checkbox in a specific cell

```
getCellCheckboxState(col: number, row: number): Array
getCellCheckboxState(col: number, row: number): boolean | 'indeterminate' | undefined
```

- col: Column number
- row: Row number
- return: Checkbox state of the cell

## getRadioState(Function)

Get the selection state of all radio data under a field, the order corresponds to the original input data records, not the state value of the table display row
Expand Down Expand Up @@ -2143,6 +2150,38 @@ setCellCheckboxState(col: number, row: number, checked: boolean) => void
- row: Row number
- checked: Whether selected

## setCellCheckboxStateByRecordIndex(Function)

Set the checkbox state by source records index and field. For tree tables, pass a children path such as `[0, 1]` for the second child of the first root record. The state is updated even when the target node is collapsed and not currently visible.

```
setCellCheckboxStateByRecordIndex(recordIndex: number | number[], field: string | number, checked: boolean | 'indeterminate') => void
```

- recordIndex: Source data index; number for normal tables, number[] for tree tables
- field: Field of the checkbox column
- checked: Checkbox state, including `'indeterminate'`

## clearCheckboxState(Function)

Clear all checkbox checked states under the specified field. `clearAllCheckboxState(field)` is an alias of this method.

```
clearCheckboxState(field: string | number) => void
```

- field: Field of the checkbox column

## clearAllCheckboxState(Function)

Alias of `clearCheckboxState(field)`. Clear all checkbox checked states under the specified field.

```
clearAllCheckboxState(field: string | number) => void
```

- field: Field of the checkbox column

## setCellRadioState(Function)

Set the radio state of the cell to selected state
Expand Down
41 changes: 40 additions & 1 deletion docs/assets/api/zh/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -2106,14 +2106,21 @@ arrangeCustomCellStyle: (cellPosition: { col?: number; row?: number; range?: Cel
getCheckboxState(field?: string | number): Array
```

- field: 可选,checkbox 所在字段;不传时返回所有字段的 checkbox 状态
- 返回值: checkbox 状态数组,树形数据会按 children 路径组织状态

## getCellCheckboxState(Function)

获取某个单元格 checkbox 的状态

```
getCellCheckboxState(col: number, row: number): Array
getCellCheckboxState(col: number, row: number): boolean | 'indeterminate' | undefined
```

- col: 列号
- row: 行号
- 返回值: 当前单元格 checkbox 状态

## getRadioState(Function)

获取某个字段下 radio 全部数据的选中状态 顺序对应原始传入数据 records 不是对应表格展示 row 的状态值
Expand Down Expand Up @@ -2142,6 +2149,38 @@ setCellCheckboxState(col: number, row: number, checked: boolean) => void
- row: 行号
- checked: 是否选中

## setCellCheckboxStateByRecordIndex(Function)

根据源数据 records 的 index 和 field 设置 checkbox 状态。树形表格可传入 children 路径,例如 `[0, 1]` 表示第 1 条根节点下第 2 条子节点;即使该节点当前处于折叠不可见状态,也会更新其 checkbox 状态。

```
setCellCheckboxStateByRecordIndex(recordIndex: number | number[], field: string | number, checked: boolean | 'indeterminate') => void
```

- recordIndex: 源数据索引;普通表格为 number,树形表格为 number[]
- field: checkbox 所在字段
- checked: 是否选中,支持半选状态 `'indeterminate'`

## clearCheckboxState(Function)

清除指定 field 下所有 checkbox 的选中状态。`clearAllCheckboxState(field)` 是该方法的别名。

```
clearCheckboxState(field: string | number) => void
```

- field: checkbox 所在字段

## clearAllCheckboxState(Function)

`clearCheckboxState(field)` 的别名,用于清除指定 field 下所有 checkbox 的选中状态。

```
clearAllCheckboxState(field: string | number) => void
```

- field: checkbox 所在字段

## setCellRadioState(Function)

将单元格的 radio 状态设置为选中状态
Expand Down
166 changes: 166 additions & 0 deletions packages/vtable/__tests__/listTable-checkbox-record-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// @ts-nocheck
import { ListTable, TYPES } from '../src';
import { createDiv } from './dom';

global.__VERSION__ = 'none';

describe('ListTable checkbox record index api', () => {
let table: ListTable;

afterEach(() => {
table?.release();
document.body.innerHTML = '';
});

test('sets collapsed tree child checkbox state by record index path', () => {
table = new ListTable({
container: createDiv(),
columns: [
{
field: 'task',
title: 'Task',
tree: true,
cellType: 'checkbox',
headerType: 'checkbox'
}
],
records: [
{
task: { text: 'parent', checked: true },
hierarchyState: TYPES.HierarchyState.collapse,
children: [{ task: { text: 'child', checked: true } }]
}
],
enableCheckboxCascade: false
});
const renderAsync = jest.spyOn(table, 'renderAsync').mockImplementation(() => Promise.resolve());

table.setCellCheckboxStateByRecordIndex([0, 0], 'task', false);

expect(table.stateManager.checkedState.get('0,0').task).toBe(false);
expect(renderAsync).toHaveBeenCalledTimes(1);
});

test('clears all checkbox states for a field', () => {
table = new ListTable({
container: createDiv(),
columns: [
{
field: 'task',
title: 'Task',
tree: true,
cellType: 'checkbox',
headerType: 'checkbox'
}
],
records: [
{
task: { text: 'parent', checked: true },
hierarchyState: TYPES.HierarchyState.collapse,
children: [{ task: { text: 'child', checked: true } }]
},
{ task: { text: 'sibling', checked: true } }
],
enableCheckboxCascade: false
});
const renderAsync = jest.spyOn(table, 'renderAsync').mockImplementation(() => Promise.resolve());

table.clearAllCheckboxState('task');

expect(table.stateManager.checkedState.get('0').task).toBe(false);
expect(table.stateManager.checkedState.get('0,0').task).toBe(false);
expect(table.stateManager.checkedState.get('1').task).toBe(false);
expect(table.stateManager.headerCheckedState.task).toBe(false);
expect(renderAsync).toHaveBeenCalledTimes(1);
});

test('keeps header indeterminate when checkedState is sparse', () => {
table = new ListTable({
container: createDiv(),
columns: [
{
field: 'task',
title: 'Task',
cellType: 'checkbox',
headerType: 'checkbox'
}
],
records: [{ task: { text: 'checked', checked: true } }, { task: { text: 'unchecked' } }],
enableCheckboxCascade: false
});
table.stateManager.checkedState.delete('1');

table.setCellCheckboxStateByRecordIndex(0, 'task', true);

expect(table.stateManager.headerCheckedState.task).toBe('indeterminate');
});

test('does not create false state for records that are already unchecked when clearing', () => {
table = new ListTable({
container: createDiv(),
columns: [
{
field: 'task',
title: 'Task',
tree: true,
cellType: 'checkbox',
headerType: 'checkbox'
}
],
records: [
{
task: { text: 'parent', checked: true },
hierarchyState: TYPES.HierarchyState.collapse,
children: [{ task: { text: 'child', checked: true } }]
},
{ task: { text: 'already unchecked', checked: false } }
],
enableCheckboxCascade: false
});
table.stateManager.checkedState.delete('1');

table.clearAllCheckboxState('task');

expect(table.stateManager.checkedState.get('0').task).toBe(false);
expect(table.stateManager.checkedState.get('0,0').task).toBe(false);
expect(table.stateManager.checkedState.has('1')).toBe(false);
});

test('updates children and parent state when cascade is enabled', () => {
table = new ListTable({
container: createDiv(),
columns: [
{
field: 'task',
title: 'Task',
tree: true,
cellType: 'checkbox',
headerType: 'checkbox'
}
],
records: [
{
task: { text: 'parent', checked: true },
hierarchyState: TYPES.HierarchyState.collapse,
children: [
{ task: { text: 'child 1', checked: true } },
{ task: { text: 'child 2', checked: true } }
]
}
],
enableCheckboxCascade: true
});

table.setCellCheckboxStateByRecordIndex([0, 0], 'task', false);

expect(table.stateManager.checkedState.get('0').task).toBe('indeterminate');
expect(table.stateManager.checkedState.get('0,0').task).toBe(false);
expect(table.stateManager.checkedState.get('0,1').task).toBe(true);

table.setCellCheckboxStateByRecordIndex(0, 'task', false);

expect(table.stateManager.checkedState.get('0').task).toBe(false);
expect(table.stateManager.checkedState.get('0,0').task).toBe(false);
expect(table.stateManager.checkedState.get('0,1').task).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as VTable from '../../src';

const CONTAINER_ID = 'vTable';

export function createTable() {
const records = [
{
task: { text: 'Project A', checked: true },
owner: 'Alice',
status: 'collapsed parent',
hierarchyState: VTable.TYPES.HierarchyState.collapse,
children: [
{
task: { text: 'Hidden task A-1', checked: true },
owner: 'Bob',
status: 'hidden child'
},
{
task: { text: 'Hidden task A-2', checked: true },
owner: 'Cindy',
status: 'hidden child'
}
]
},
{
task: { text: 'Project B', checked: true },
owner: 'David',
status: 'root'
}
];

const option: VTable.ListTableConstructorOptions = {
container: document.getElementById(CONTAINER_ID),
records,
columns: [
{
field: 'task',
title: 'Task',
tree: true,
cellType: 'checkbox',
headerType: 'checkbox',
width: 260
},
{ field: 'owner', title: 'Owner', width: 120 },
{ field: 'status', title: 'Status', width: 180 }
],
defaultRowHeight: 38,
hierarchyIndent: 20,
enableCheckboxCascade: false,
enableHeaderCheckboxCascade: false,
theme: VTable.themes.BRIGHT
};

const tableInstance = new VTable.ListTable(option);
window.tableInstance = tableInstance;

const toolbar = document.createElement('div');
toolbar.style.cssText = 'position:absolute;top:8px;left:8px;z-index:10;display:flex;gap:8px;';

const setHiddenChildButton = document.createElement('button');
setHiddenChildButton.textContent = 'Uncheck hidden child [0,0]';
setHiddenChildButton.onclick = () => {
tableInstance.setCellCheckboxStateByRecordIndex([0, 0], 'task', false);
console.log('checkbox state after set hidden child:', tableInstance.getCheckboxState('task'));
};

const clearAllButton = document.createElement('button');
clearAllButton.textContent = 'Clear task checkbox field';
clearAllButton.onclick = () => {
tableInstance.clearAllCheckboxState('task');
console.log('checkbox state after clear:', tableInstance.getCheckboxState('task'));
};

toolbar.appendChild(setHiddenChildButton);
toolbar.appendChild(clearAllButton);
document.body.appendChild(toolbar);

return tableInstance;
}
4 changes: 4 additions & 0 deletions packages/vtable/examples/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ export const menus = [
path: 'list',
name: 'list-checkbox-tree'
},
{
path: 'list',
name: 'issue-5211-tree-checkbox-record-index'
},
{
path: 'list',
name: 'list-tree-20000'
Expand Down
Loading
Loading