Inline

The KendoReact TreeList enables you to create, update, and delete data records inline.

Basic Concepts

The edit mode of the TreeList rows is based on the value of the editField property.

import React from 'react';
import ReactDOM from 'react-dom';
import {
    TreeList, TreeListToolbar, mapTree, extendDataItem,
    removeItems, modifySubItems,
    TreeListTextEditor, TreeListBooleanEditor
} from '@progress/kendo-react-treelist';
import MyCommandCell from './my-command-cell.jsx';
import employees from './data';

const subItemsField = 'employees';
const expandField = 'expanded';
const editField = 'inEdit';

class App extends React.Component {
    state = {
        data: employees.slice(),
        expanded: [1, 2, 32],
        inEdit: [ ]
    }

    addChild = (dataItem) => {
        const newRecord = this.createNewItem();

        this.setState({
            inEdit: [ ...this.state.inEdit, newRecord ],
            expanded: [ ...this.state.expanded, dataItem.id ],
            data: modifySubItems(
                this.state.data,
                subItemsField,
                item => item.id === dataItem.id,
                subItems => [ newRecord, ...subItems ]
            )
        });
    }

    enterEdit = (dataItem) => {
        this.setState({
            inEdit: [ ...this.state.inEdit, extendDataItem(dataItem, subItemsField) ]
        });
    }

    save = (dataItem) => {
        const { isNew, inEdit, ...itemToSave } = dataItem;
        this.setState({
            data: mapTree(this.state.data, subItemsField, item => item.id === itemToSave.id ? itemToSave : item),
            inEdit: this.state.inEdit.filter(i => i.id !== itemToSave.id)
        });
    }

    cancel = (editedItem) => {
        const { inEdit, data } = this.state;
        if (editedItem.isNew) {
            return this.remove(editedItem);
        }

        this.setState({
            data: mapTree(data, subItemsField,
                item => item.id === editedItem.id ? inEdit.find(i => i.id === item.id) : item),
            inEdit: inEdit.filter(i => i.id !== editedItem.id)
        });
    }

    remove = (dataItem) => {
        this.setState({
            data: removeItems(this.state.data, subItemsField, i => i.id === dataItem.id),
            inEdit: this.state.inEdit.filter(i => i.id !== dataItem.id)
        });
    }

    CommandCell = MyCommandCell(this.enterEdit, this.remove, this.save, this.cancel, this.addChild, editField);

    onExpandChange = (e) => {
        this.setState({
            expanded: e.value ?
                this.state.expanded.filter(id => id !== e.dataItem.id) :
                [ ...this.state.expanded, e.dataItem.id ]
        });
    }

    onItemChange = (event) => {
        this.setState({
            data: mapTree(
                this.state.data,
                subItemsField,
                item => item.id === event.dataItem.id ?
                    extendDataItem(item, subItemsField, { [event.field]: event.value }) : item
            )
        });
    }

    addRecord = () => {
        const newRecord = this.createNewItem();
        this.setState({
            data: [ newRecord, ...this.state.data ],
            inEdit: [ ...this.state.inEdit, { ...newRecord } ]
        });
    }

    createNewItem = () => {
        const timestamp = new Date().getTime();
        return { id: timestamp, isNew: true };
    }

    render() {
        const { data, expanded, inEdit } = this.state;

        return (
            <TreeList
                style={{ maxHeight: '510px', overflow: 'auto' }}
                data={mapTree(data, subItemsField, item =>
                    extendDataItem(item, subItemsField, {
                        [expandField]: expanded.includes(item.id),
                        [editField]: Boolean(inEdit.find(i => i.id === item.id))
                    }))
                }
                editField={editField}
                expandField={expandField}
                subItemsField={subItemsField}

                onItemChange={this.onItemChange}
                onExpandChange={this.onExpandChange}
                columns={[
                    { field: 'name', title: 'Name', width: 280, editCell: TreeListTextEditor, expandable: true },
                    { field: 'position', title: 'Position', width: 260, editCell: TreeListTextEditor },
                    { field: 'fullTime', title: 'Full Time', width: 160, editCell: TreeListBooleanEditor },
                    { cell: this.CommandCell, width: 360 }
                ]}
                toolbar={
                    <TreeListToolbar>
                        <button
                            title="Add new"
                            className="k-button k-primary"
                            onClick={this.addRecord}
                        >
                            Add new
                        </button>
                    </TreeListToolbar>
                }
            />
        );
    }
}

ReactDOM.render(
    <App />,
    document.querySelector('my-app')
);

Setup

  1. Set the field which will indicate the editable data items by using the editField property. This field is part of the temporary data items which are used during editing.

    <TreeList
        editField="inEdit"
  2. Configure the command column by defining the command buttons inside the TreeListCell component. To render the command cell, use a Higher-Order Component which has to receive all functions that will be executed from the command buttons and the value of the TreeList editField property.

    CommandCell = MyCommandCell(this.enterEdit, this.remove, this.save, this.cancel, this.addChild, 'inEdit');
    export default function MyCommandCell(enterEdit, remove, save, cancel, addChild, editField) {
        return class extends TreeListCell {
            render() {
                const { dataItem } = this.props;
                return dataItem[editField]
                    ? (
                        <td>
                            <button
                                className="k-button"
                                onClick={(e) => save(dataItem)}>
                                {dataItem.isNew
                                    ? 'Add'
                                    : 'Update'}
                            </button>
                            <button
                                className="k-button"
                                onClick={(e) => cancel(dataItem)}>{dataItem.isNew
                                    ? 'Discard'
                                    : 'Cancel'}
                            </button>
                        </td>
                    ) : (
                        <td>
                            <button
                                className="k-button"
                                onClick={(e) => addChild(dataItem)}>
                                Add Child
                            </button>
                            <button
                                className="k-button"
                                onClick={(e) => enterEdit(dataItem)}>
                                Edit
                            </button>
                            <button
                                className="k-button"
                                onClick={(e) => remove(dataItem)}>
                                Remove
                            </button>
                        </td>
                    );
            }
        }
    }
  3. Set the editCell and cell properties per column.

    <TreeList
        columns={[
            { editCell: TreeListTextEditor, ... },
            { editCell: TreeListTextEditor, ... },
            { editCell: TreeListBooleanEditor, ... },
            { cell: this.CommandCell, ... }
        ]}
  4. Define a function for the onItemChange event which will handle any input changes during editing. Inside the event, all relevant data, such as the edited data item, the newly entered value, or the edited field will be available as onItemChange parameters.

    <TreeList onItemChange={this.onItemChange}>
    onItemChange = (event) => {
        this.setState({
            data: mapTree(
                this.state.data,
                subItemsField,
                item => item.id === event.dataItem.id ?
                    extendDataItem(item, subItemsField, { [event.field]: event.value }) : item
            )
        });
    }
  5. Define the functions which will set the item in edit mode and create a new item in edit mode. You can call these functions from the command buttons in the command cell.

    enterEdit = (dataItem) => {
        this.setState({
            inEdit: [ ...this.state.inEdit, extendDataItem(dataItem, subItemsField) ]
        });
    }
    addRecord = () => {
        const newRecord = this.createNewItem();
        this.setState({
            data: [ newRecord, ...this.state.data ],
            inEdit: [ ...this.state.inEdit, { ...newRecord } ]
        });
    }
  6. Define the functions which will handle the save, cancel, remove, and addChild actions. You can call these functions from the command buttons, the toolbar template, or a button which is outside of the TreeList.

    save = (dataItem) => {
        const { isNew, inEdit, ...itemToSave } = dataItem;
        this.setState({
            data: mapTree(this.state.data, subItemsField, item => item.id === itemToSave.id ? itemToSave : item),
            inEdit: this.state.inEdit.filter(i => i.id !== itemToSave.id)
        });
    }
    cancel = (editedItem) => {
        const { inEdit, data } = this.state;
        if (editedItem.isNew) {
            return this.remove(editedItem);
        }
    
        this.setState({
            data: mapTree(data, subItemsField,
                item => item.id === editedItem.id ? inEdit.find(i => i.id === item.id) : item),
            inEdit: inEdit.filter(i => i.id !== editedItem.id)
        });
    }
    remove = (dataItem) => {
        this.setState({
            data: removeItems(this.state.data, subItemsField, i => i.id === dataItem.id),
            inEdit: this.state.inEdit.filter(i => i.id !== dataItem.id)
        });
    }
    addChild = (dataItem) => {
        const newRecord = this.createNewItem();
    
        this.setState({
            inEdit: [ ...this.state.inEdit, newRecord ],
            expanded: [ ...this.state.expanded, dataItem.id ],
            data: modifySubItems(
                this.state.data,
                subItemsField,
                item => item.id === dataItem.id,
                subItems => [ newRecord, ...subItems ]
            )
        });
    }
 /