Skip to content

Commit

Permalink
Merge pull request #167 from komarovalexander/feature/tree-mode
Browse files Browse the repository at this point in the history
Tree Mode
  • Loading branch information
komarovalexander authored Jul 6, 2021
2 parents 3344af0 + ce4a243 commit a9ac860
Show file tree
Hide file tree
Showing 26 changed files with 850 additions and 33 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ka-table",
"version": "6.10.0",
"version": "6.11.0",
"license": "MIT",
"repository": "github:komarovalexander/ka-table",
"homepage": "https://komarovalexander.github.io/ka-table/#/overview",
Expand Down
2 changes: 2 additions & 0 deletions src/Demos/Demos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import SortingModesDemo from './SortingModesDemo/SortingModesDemo';
import StateStoringDemo from './StateStoringDemo/StateStoringDemo';
import SummaryDemo from './SummaryDemo/SummaryDemo';
import TabIndexDemo from './TabIndexDemo/TabIndexDemo';
import TreeModeDemo from './TreeModeDemo/TreeModeDemo';
import ValidationDemo from './ValidationDemo/ValidationDemo';

initializeGA();
Expand Down Expand Up @@ -135,6 +136,7 @@ const demos: Demo[] = [
new Demo(StateStoringDemo, '/state-storing', 'State Storing', 'StateStoringDemo', 'https://stackblitz.com/edit/table-state-storing-js', 'https://stackblitz.com/edit/table-state-storing-ts', 'Miscellaneous'),
new Demo(SummaryDemo, '/summary', 'Summary', 'SummaryDemo', 'https://stackblitz.com/edit/table-summary-js', 'https://stackblitz.com/edit/table-summary-ts', 'Miscellaneous'),
new Demo(TabIndexDemo, '/tab-index', 'Tab Index', 'TabIndexDemo', 'https://stackblitz.com/edit/table-tab-index-js', 'https://stackblitz.com/edit/table-tab-index-ts', 'Miscellaneous'),
new Demo(TreeModeDemo, '/tree-mode', 'Tree Mode', 'TreeModeDemo', 'https://stackblitz.com/edit/table-tree-mode-js', 'https://stackblitz.com/edit/table-tree-mode-ts', ''),
new Demo(ValidationDemo, '/validation', 'Validation', 'ValidationDemo', 'https://stackblitz.com/edit/table-validation-js', 'https://stackblitz.com/edit/table-validation-ts', 'Editing'),
];

Expand Down
2 changes: 1 addition & 1 deletion src/Demos/MenuItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class MenuItem {
public isActive?: boolean;
}

const newItems: string[] = ['SummaryDemo', 'GroupingSummaryDemo'];
const newItems: string[] = ['SummaryDemo', 'GroupingSummaryDemo', 'TreeModeDemo'];
const updateItems: string[] = ['Miscellaneous', 'Grouping'];

const MenuItems: React.FC<{ items: MenuItem[] }> = ({ items }) => {
Expand Down
10 changes: 10 additions & 0 deletions src/Demos/TreeModeDemo/TreeModeDemo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';

import TreeModeDemo from './TreeModeDemo';

it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<TreeModeDemo />, div);
ReactDOM.unmountComponentAtNode(div);
});
56 changes: 56 additions & 0 deletions src/Demos/TreeModeDemo/TreeModeDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState } from 'react';

import { ITableProps, kaReducer, Table } from '../../lib';
import { DataType, EditingMode, FilteringMode, SortingMode } from '../../lib/enums';
import { DispatchFunc } from '../../lib/types';

const data = [
{ treeGroupId: null, id: 1, name: 'Department A', productivity: 5 },
{ treeGroupId: 1, id: 2, name: 'Mike Wazowski', productivity: 2 },
{ treeGroupId: 1, id: 3, name: 'Billi Bob', productivity: 3 },
{ treeGroupId: null, id: 4, name: 'Department B', productivity: 7 },
{ treeGroupId: 4, id: 5, name: 'Tom Williams', productivity: 2 },
{ treeGroupId: 4, id: 6, name: 'Kurt Cobain', productivity: 5 },
{ treeGroupId: null, id: 7, name: 'Department C', productivity: 11 },
{ treeGroupId: 10, id: 8, name: 'Sunny Fox', productivity: 2 },
{ treeGroupId: 10, id: 9, name: 'Marshall Bruce', productivity: 5 },
{ treeGroupId: 7, id: 10, name: 'Squad A', productivity: 7 },
{ treeGroupId: 7, id: 11, name: 'Squad B', productivity: 4 },
{ treeGroupId: 11, id: 12, name: 'Alex Thomson', productivity: 1 },
{ treeGroupId: 11, id: 13, name: 'Mike Griffinson', productivity: 3 },
];

const tablePropsInit: ITableProps = {
columns: [
{ key: 'name', title: 'Name', dataType: DataType.String },
{ key: 'productivity', title: 'Productivity', dataType: DataType.Number },
],
data,
filteringMode: FilteringMode.FilterRow,
treeGroupKeyField: 'treeGroupId',
editingMode: EditingMode.Cell,
treeGroupsExpanded: [7, 11],
rowKeyField: 'id',
sortingMode: SortingMode.Single,
};

const TreeModeDemo: React.FC = () => {
const [tableProps, changeTableProps] = useState(tablePropsInit);
const dispatch: DispatchFunc = (action) => {
changeTableProps((prevState: ITableProps) => kaReducer(prevState, action));
};

return (
<Table
{...tableProps}
dispatch={dispatch}
childComponents={{
noDataRow: {
content: () => 'No Data Found'
}
}}
/>
);
};

export default TreeModeDemo;
25 changes: 15 additions & 10 deletions src/lib/Components/CellComponent/CellComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,33 @@ import CellText from '../CellText/CellText';

const CellComponent: React.FunctionComponent<ICellProps> = (props) => {
const {
treeDeep,
treeArrowElement,
childComponents,
column: { style },
isEditableCell,
} = props;

const { elementAttributes, content } = getElementCustomization({
className: defaultOptions.css.cell,
className: `${defaultOptions.css.cell} ${treeDeep != null ? defaultOptions.css.treeCell : ''}`,
style
}, props, childComponents.cell);

return (
<td {...elementAttributes}>
{treeDeep ? Array(treeDeep).fill(undefined).map((_, index) => <div key={index} className={defaultOptions.css.treeCellEmptySpace}/>) : null}
{ content ||
(
isEditableCell ?
(
<CellEditor {...props} />
)
:
(
<CellText {...props} />
)
<>
{treeArrowElement}
{isEditableCell ?
(
<CellEditor {...props} />
)
:
(
<CellText {...props} />
)}
</>
)
}
</td>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Components/DataRow/DataRow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ beforeEach(() => {
jest.clearAllMocks();
});

describe('CellEditorBoolean', () => {
describe('DataRow', () => {
it('renders without crashing', () => {
const element = document.createElement('tbody');
ReactDOM.render(<DataRow {...props} />, element);
Expand Down
27 changes: 22 additions & 5 deletions src/lib/Components/DataRowContent/DataRowContent.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import Enzyme, { mount } from 'enzyme';
import React from 'react';
import ReactDOM from 'react-dom';

import Adapter from '@wojtekmaj/enzyme-adapter-react-17';

import { DataType, EditingMode } from '../../enums';
import DataRowContent, { IDataRowProps } from './DataRowContent';
import { IDataRowProps } from '../../props';
import DataRowContent from './DataRowContent';

Enzyme.configure({ adapter: new Adapter() });

const props: IDataRowProps = {
childComponents: {},
Expand All @@ -20,8 +26,19 @@ const props: IDataRowProps = {
selectedRows: [],
};

it('renders without crashing', () => {
const element = document.createElement('tr');
ReactDOM.render(<DataRowContent {...props} />, element);
ReactDOM.unmountComponentAtNode(element);
describe('DataRowContent', () => {
it('renders without crashing', () => {
const element = document.createElement('tr');
ReactDOM.render(<DataRowContent {...props} />, element);
ReactDOM.unmountComponentAtNode(element);
});
it('click by first row dispatches action', () => {
const dispatch = jest.fn();
const wrapper = mount(<DataRowContent {...props} dispatch={dispatch} isTreeGroup={true} />, {
attachTo: document.createElement('tr'),
});
expect(dispatch).toHaveBeenCalledTimes(0);
wrapper.find('.ka-icon-tree-arrow').simulate('click');
expect(dispatch).toHaveBeenCalledTimes(1);
});
});
17 changes: 16 additions & 1 deletion src/lib/Components/DataRowContent/DataRowContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';

import { updateTreeGroupsExpanded } from '../../actionCreators';
import defaultOptions from '../../defaultOptions';
import { IDataRowProps } from '../../props';
import { getEditableCell } from '../../Utils/CellUtils';
import { getField } from '../../Utils/ColumnUtils';
Expand All @@ -9,28 +11,41 @@ import CellComponent from '../CellComponent/CellComponent';
const DataRowContent: React.FunctionComponent<IDataRowProps> = ({
childComponents,
columns,
treeDeep,
dispatch,
editingMode,
format,
isDetailsRowShown,
isSelectedRow,
isTreeExpanded,
isTreeGroup,
rowData,
rowEditableCells,
rowKeyField,
rowKeyValue,
selectedRows,
validation,
}) => {
const arrow = isTreeGroup ? [(
<div
onClick={() => dispatch(updateTreeGroupsExpanded(rowKeyValue))}
className={isTreeExpanded
? defaultOptions.css.iconTreeArrowExpanded : defaultOptions.css.iconTreeArrowCollapsed}
/>
)] : undefined;
return (
<>
{columns.map((column) => {
{columns.map((column, index) => {
const editableCell = getEditableCell(column, rowEditableCells);
const hasEditorValue = editableCell && editableCell.hasOwnProperty('editorValue');
const editorValue = editableCell && editableCell.editorValue;
const value = hasEditorValue ? editorValue : getValueByColumn(rowData, column);
const cellDeep = treeDeep != null && index === 0 ? treeDeep : undefined;
return (
<CellComponent
treeArrowElement={arrow?.pop()}
childComponents={childComponents}
treeDeep={cellDeep}
column={column}
dispatch={dispatch}
editingMode={editingMode}
Expand Down
11 changes: 11 additions & 0 deletions src/lib/Components/Rows/Rows.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,15 @@ describe('Rows', () => {
});
expect(wrapper.find('.ka-group-text').text()).toBe('formatted: 1');
});

it('does not add ka-tree-cell classes', () => {
const wrapper = mount((
<Rows {...props}/>
), {
attachTo: document.createElement('tbody'),
});
expect(wrapper.find('.ka-tree-cell').length).toBe(0);
expect(wrapper.find('.ka-tree-empty-space').length).toBe(0);
expect(wrapper.find('.ka-icon-tree-arrow').length).toBe(0);
});
});
15 changes: 13 additions & 2 deletions src/lib/Components/Rows/Rows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { ITableBodyProps } from '../../props';
import { getValueByField } from '../../Utils/DataUtils';
import { getRowEditableCells } from '../../Utils/FilterUtils';
import { getGroupMark, getGroupText, groupSummaryMark } from '../../Utils/GroupUtils';
import { treeDataMark, treeGroupMark } from '../../Utils/TreeUtils';
import DataAndDetailsRows from '../DataAndDetailsRows/DataAndDetailsRows';
import GroupRow from '../GroupRow/GroupRow';
import { GroupSummaryRow } from '../GroupSummaryRow/GroupSummaryRow';

export interface IRowsProps extends ITableBodyProps {
onFirstRowRendered: (firstRowRef: RefObject<HTMLElement>) => any;
treeGroupsExpanded?: any[];
}

const Rows: React.FunctionComponent<IRowsProps> = (props) => {
Expand All @@ -25,6 +27,7 @@ const Rows: React.FunctionComponent<IRowsProps> = (props) => {
groups = [],
groupsExpanded = [],
onFirstRowRendered,
treeGroupsExpanded,
rowKeyField,
rowReordering,
selectedRows,
Expand Down Expand Up @@ -61,7 +64,12 @@ const Rows: React.FunctionComponent<IRowsProps> = (props) => {
} else if (d.groupSummaryMark === groupSummaryMark) {
return <GroupSummaryRow {...props} groupData={d.groupData} key={d.key} groupIndex={d.groupIndex} />;
} else {
const rowKeyValue = getValueByField(d, rowKeyField);
const isTreeGroup = d.treeGroupMark === treeGroupMark;
const isTreeData = d.treeDataMark === treeDataMark;
const isTreeRow = isTreeGroup || isTreeData;
const rowData = isTreeRow ? d.rowData : d;
const rowKeyValue = getValueByField(rowData, rowKeyField);
const isTreeExpanded = isTreeGroup && (!treeGroupsExpanded || treeGroupsExpanded.includes(rowKeyValue));
const isSelectedRow = selectedRows.some((s) => s === rowKeyValue);
const isDetailsRowShown = detailsRows.some((r) => r === rowKeyValue);
const rowEditableCells = getRowEditableCells(rowKeyValue, editableCells);
Expand All @@ -72,12 +80,15 @@ const Rows: React.FunctionComponent<IRowsProps> = (props) => {
dispatch={dispatch}
editableCells={props.editableCells}
editingMode={props.editingMode}
isTreeGroup={isTreeGroup}
isTreeExpanded={isTreeExpanded}
treeDeep={isTreeRow === true ? d.treeDeep : undefined}
format={format}
groupColumnsCount={props.groupColumnsCount}
isDetailsRowShown={isDetailsRowShown}
isSelectedRow={isSelectedRow}
key={rowKeyValue}
rowData={d}
rowData={rowData}
rowEditableCells={rowEditableCells}
rowKeyField={props.rowKeyField}
rowKeyValue={rowKeyValue}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/Components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface ITableProps {
loading?: ILoadingProps;
paging?: PagingOptions;
rowKeyField: string;
treeGroupKeyField?: string;
treeGroupsExpanded?: any[];
rowReordering?: boolean;
search?: SearchFunc;
searchText?: string;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/Models/CssClasses.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export class CssClasses {
public root = 'ka';
public cell = 'ka-cell';
public treeCell = 'ka-tree-cell';
public treeCellEmptySpace = 'ka-tree-empty-space';
public cellEditor: string = 'ka-cell-editor';
public cellText = 'ka-cell-text';
public groupCell = 'ka-group-cell';
Expand Down Expand Up @@ -39,6 +41,8 @@ export class CssClasses {
public iconClose = 'ka-icon ka-icon-close';
public iconGroupArrowCollapsed = 'ka-icon ka-icon-group-arrow ka-icon-group-arrow-collapsed';
public iconGroupArrowExpanded = 'ka-icon ka-icon-group-arrow ka-icon-group-arrow-expanded';
public iconTreeArrowCollapsed = 'ka-icon ka-icon-tree-arrow ka-icon-tree-arrow-collapsed';
public iconTreeArrowExpanded = 'ka-icon ka-icon-tree-arrow ka-icon-tree-arrow-expanded';
public iconSortArrowDown = 'ka-icon ka-icon-sort ka-icon-sort-arrow-down';
public iconSortArrowUp = 'ka-icon ka-icon-sort ka-icon-sort-arrow-up';

Expand Down
35 changes: 35 additions & 0 deletions src/lib/Reducers/kaReducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,46 @@ import {
clearSingleAction, deleteRow, deselectAllFilteredRows, deselectAllRows, deselectAllVisibleRows,
deselectRow, loadData, reorderColumns, reorderRows, selectAllFilteredRows, selectAllRows,
selectAllVisibleRows, selectRowsRange, selectSingleRow, setSingleAction, updateData,
updateTreeGroupsExpanded,
} from '../actionCreators';
import { ActionType, FilterOperatorName } from '../enums';
import { kaReducer } from './kaReducer';

describe('kaReducer', () => {
describe('UpdateTreeGroupsExpanded ', () => {
const intialState: ITableProps = {
data: [
{ id: 1, treeGroupId: null },
{ id: 2, treeGroupId: 1 },
{ id: 3, treeGroupId: 2 },
{ id: 4, treeGroupId: 3 }
],
columns: [{ key: 'id' }],
rowKeyField: 'id',
treeGroupKeyField: 'treeGroupId'
};
it('collapse tree group, if treeGroupsExpanded contains it', () => {
const result: ITableProps = kaReducer({
...intialState,
treeGroupsExpanded: [1, 2, 3]
}, updateTreeGroupsExpanded(2));
expect(result.treeGroupsExpanded).toEqual([1, 3]);
});
it('collapse tree group, if treeGroupsExpanded is undefined', () => {
const result: ITableProps = kaReducer({
...intialState,
treeGroupsExpanded: undefined
}, updateTreeGroupsExpanded(2));
expect(result.treeGroupsExpanded).toEqual([1, 3]);
});
it('expands tree group, if treeGroupsExpanded does not contain it', () => {
const result: ITableProps = kaReducer({
...intialState,
treeGroupsExpanded: [1, 3]
}, updateTreeGroupsExpanded(2));
expect(result.treeGroupsExpanded).toEqual([1, 3, 2]);
});
});
describe('SelectRowsRange', () => {
const intialState = {
data: [
Expand Down
Loading

0 comments on commit a9ac860

Please sign in to comment.