Template editing in React Treegrid component

27 Jan 202324 minutes to read

Dialog template

The dialog template editing provides an option to customize the default behavior of dialog editing. Using the dialog template, you can render your own editors by defining the editSettings.mode as Dialog and template as SCRIPT element ID or HTML string which holds the template.

In some cases, you need to add the new field editors in the dialog which are not present in the column model. In that situation, the dialog template will help you to customize the default edit dialog.

For quick details with dialog template, you can check on this video:

In the following sample, treegrid enabled with dialog template editing.

import { setValue } from '@syncfusion/ej2-base';
import { ColumnDirective, ColumnsDirective, Inject, TreeGridComponent } from '@syncfusion/ej2-react-treegrid';
import { Edit, Toolbar } from '@syncfusion/ej2-react-treegrid';
import * as React from 'react';
import { sampleData } from './datasource';
import { DialogFormTemplate } from './template';
function App() {
    let treegrid;
    const toolbarOptions = ['Add', 'Edit', 'Delete', 'Update', 'Cancel'];
    const dialogTemplate = (props) => {
        const obj = [props, treegrid];
        return (<DialogFormTemplate {...obj}/>);
    };
    const editOptions = {
        allowAdding: true,
        allowDeleting: true,
        allowEditing: true,
        mode: 'Dialog',
        template: dialogTemplate
    };
    const actionComplete = (args) => {
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
            // Add Validation Rules
            const form = args.form;
            form.ej2_instances[0].addRules('progress', { max: 100 });
            if (args.requestType === 'beginEdit') {
                form.elements.namedItem('taskName').focus();
            }
        }
    };
    const actionBegin = (args) => {
        if (args.requestType === 'save') {
            // cast string to integer value.
            const val = args.form
                .querySelector("#progress").value;
            setValue('data.progress', parseFloat(val), args);
        }
    };
    return <TreeGridComponent dataSource={sampleData} treeColumnIndex={1} childMapping='subtasks' height='270' editSettings={editOptions} toolbar={toolbarOptions} ref={g => treegrid = g} actionBegin={actionBegin} actionComplete={actionComplete}>
        <ColumnsDirective>
            <ColumnDirective field='taskID' headerText='Task ID' width='90' textAlign='Right' isPrimaryKey={true}/>
            <ColumnDirective field='taskName' headerText='Task Name' width='180'/>
            <ColumnDirective field='startDate' headerText='Start Date' width='130' format='yMd' textAlign='Right' type='date' editType='datepickeredit'/>
            <ColumnDirective field='duration' headerText='Duration' width='80' textAlign='Right'/>
        </ColumnsDirective>
        <Inject services={[Edit, Toolbar]}/>
    </TreeGridComponent>;
}
;
export default App;
import { setValue } from '@syncfusion/ej2-base';
import { DialogEditEventArgs } from '@syncfusion/ej2-grids';
import { ColumnDirective, ColumnsDirective, Inject, TreeGridComponent } from '@syncfusion/ej2-react-treegrid';
import { Edit, EditSettingsModel, Toolbar, ToolbarItems, TreeGrid } from '@syncfusion/ej2-react-treegrid';
import * as React from 'react';
import { sampleData } from './datasource';
import { IOrderModel } from './orderModel';
import { DialogFormTemplate } from './template';

function App() {
    let treegrid: TreeGridComponent | null;
    const toolbarOptions: ToolbarItems[] = ['Add', 'Edit', 'Delete', 'Update', 'Cancel'];

    const dialogTemplate = (props: IOrderModel): any => {
        const obj = [props, treegrid];
        return (<DialogFormTemplate {...obj} />);
    }
    const editOptions: EditSettingsModel = {
        allowAdding: true,
        allowDeleting: true,
        allowEditing: true,
        mode: 'Dialog',
        template: dialogTemplate
    };
    const actionComplete = (args: DialogEditEventArgs) => {
       if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
           // Add Validation Rules
            const form: HTMLFormElement = args.form as HTMLFormElement;
            form.ej2_instances[0].addRules('progress', {max: 100});
            if (args.requestType === 'beginEdit') {
                (form.elements.namedItem('taskName') as HTMLInputElement).focus();
            }
       }
    }

    const actionBegin = (args: DialogEditEventArgs) => {
        if (args.requestType === 'save') {
            // cast string to integer value.
            const val: string = ((args.form as HTMLFormElement)
                .querySelector("#progress") as HTMLInputElement).value;
            setValue('data.progress', parseFloat(val), args);
        }
    }

    return <TreeGridComponent dataSource={sampleData} treeColumnIndex={1} childMapping='subtasks' height='270'
            editSettings={editOptions} toolbar={toolbarOptions} ref={g => treegrid = g}
            actionBegin={actionBegin} actionComplete={actionComplete}>
        <ColumnsDirective>
            <ColumnDirective field='taskID' headerText='Task ID' width='90' textAlign='Right' isPrimaryKey={true}/>
            <ColumnDirective field='taskName' headerText='Task Name' width='180'/>
            <ColumnDirective field='startDate' headerText='Start Date' width='130' format='yMd' textAlign='Right' type='date' editType='datepickeredit' />
            <ColumnDirective field='duration' headerText='Duration' width='80' textAlign='Right' />
        </ColumnsDirective>
        <Inject services={[Edit,Toolbar]}/>
    </TreeGridComponent>
};
export default App;
export {};
export interface IOrderModel {
    taskID?: number;
    taskName?: string;
    priority?: string;
    progress?: number;
    startDate?: Date;
    endDate?: Date;
    isAdd?: boolean;
    approved?: boolean;
}
import { DataUtil } from '@syncfusion/ej2-data';
import { CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
import { DatePickerComponent } from '@syncfusion/ej2-react-calendars';
import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import { NumericTextBoxComponent } from '@syncfusion/ej2-react-inputs';
import * as React from 'react';
import { sampleData as orderData } from './datasource';
export class DialogFormTemplate extends React.Component {
    priorityData = DataUtil.distinct(orderData, 'priority', true);
    constructor(props) {
        super(props);
        this.state = Object.assign({}, props[0]);
    }
    onChange(args) {
        this.setState({ [args.target.name]: args.target.value });
    }
    render() {
        this.onChange = this.onChange.bind(this);
        const data = this.state;
        return (<div>
            <div className="form-row">
                <div className="form-group col-md-6">
                <div className="e-float-input e-control-wrapper">
                        <input id="taskID" name="taskID" disabled={!data.isAdd} type="text" value={data.taskID} onChange={this.onChange}/>
                        <span className="e-float-line"/>
                        <label className="e-float-text e-label-top">Task ID</label>
                    </div>

                </div>
                <div className="form-group col-md-6">
                <div className="e-float-input e-control-wrapper">
                        <input id="taskName" name="taskName" type="text" value={data.taskName} onChange={this.onChange}/>
                        <span className="e-float-line"/>
                        <label className="e-float-text e-label-top">Task Name</label>
                    </div>
                </div>
            </div>
            <div className="form-row">
                <div className="form-group col-md-6">
                <DropDownListComponent id="priority" value={data.priority} dataSource={this.priorityData} fields={{ text: 'priority', value: 'priority' }} placeholder="Priority" popupHeight='300px' floatLabelType='Always'/>
                </div>
                <div className="form-group col-md-6">
                   <NumericTextBoxComponent id="progress" format='n' value={data.progress} placeholder="Progress" floatLabelType='Always'/>
                </div>
            </div>
            <div className="form-row">
                <div className="form-group col-md-6">
                    <div className="e-float-input e-control-wrapper">
                        <DatePickerComponent value={data.startDate} floatLabelType='Always' placeholder="Start Date"/>
                        <span className="e-float-line"/>
                    </div>
                </div>
                <div className="form-group col-md-6">
                    <div className="e-float-input e-control-wrapper">
                    <DatePickerComponent value={data.endDate} floatLabelType='Always' placeholder="End Date"/>
                        <span className="e-float-line"/>
                    </div>
                </div>
            </div>
            <div className="form-row">
                <div className="form-group col-md-6">
                    <div className="e-float-input e-control-wrapper">
                    <CheckBoxComponent checked={data.approved} label='Approved'/>
                        <span className="e-float-line"/>
                    </div>
                </div>
            </div>
        </div>);
    }
}
import { DataUtil } from '@syncfusion/ej2-data';
import { CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
import { DatePickerComponent } from '@syncfusion/ej2-react-calendars';
import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import { NumericTextBoxComponent } from '@syncfusion/ej2-react-inputs';
import * as React from 'react';
import { sampleData as orderData } from './datasource';
import { IOrderModel } from './orderModel';

export class DialogFormTemplate extends React.Component<{}, {}> {
    private priorityData: any = DataUtil.distinct(orderData, 'priority', true );
    constructor(props: any) {
        super(props);
        this.state = Object.assign({}, props[0]);
    }

    public onChange(args: React.ChangeEvent) {
        this.setState({[(args.target as HTMLInputElement).name]: (args.target as HTMLInputElement).value});
    }

    public render(): any {
        this.onChange = this.onChange.bind(this);
        const data: IOrderModel = this.state;
        return (<div>
            <div className="form-row">
                <div className="form-group col-md-6">
                <div className="e-float-input e-control-wrapper">
                        <input id="taskID" name="taskID" disabled={!data.isAdd} type="text" value={data.taskID} onChange={this.onChange} />
                        <span className="e-float-line"/>
                        <label className="e-float-text e-label-top">Task ID</label>
                    </div>

                </div>
                <div className="form-group col-md-6">
                <div className="e-float-input e-control-wrapper">
                        <input id="taskName" name="taskName" type="text" value={data.taskName} onChange={this.onChange} />
                        <span className="e-float-line"/>
                        <label className="e-float-text e-label-top">Task Name</label>
                    </div>
                </div>
            </div>
            <div className="form-row">
                <div className="form-group col-md-6">
                <DropDownListComponent id="priority" value={data.priority} dataSource={this.priorityData}
                        fields={{text: 'priority', value: 'priority' }} placeholder="Priority"
                        popupHeight='300px' floatLabelType='Always'/>
                </div>
                <div className="form-group col-md-6">
                   <NumericTextBoxComponent id="progress" format='n' value={data.progress}
                    placeholder="Progress" floatLabelType='Always'/>
                </div>
            </div>
            <div className="form-row">
                <div className="form-group col-md-6">
                    <div className="e-float-input e-control-wrapper">
                        <DatePickerComponent value={data.startDate} floatLabelType='Always' placeholder="Start Date"/>
                        <span className="e-float-line"/>
                    </div>
                </div>
                <div className="form-group col-md-6">
                    <div className="e-float-input e-control-wrapper">
                    <DatePickerComponent value={data.endDate} floatLabelType='Always' placeholder="End Date" />
                        <span className="e-float-line"/>
                    </div>
                </div>
            </div>
            <div className="form-row">
                <div className="form-group col-md-6">
                    <div className="e-float-input e-control-wrapper">
                    <CheckBoxComponent checked={data.approved} label='Approved'/>
                        <span className="e-float-line"/>
                    </div>
                </div>
            </div>
        </div>);
    }
}

The template form editors should have name attribute.

Template context

The template should be a React Component class. You can access the row information inside the Component class using props, you can bind the attribute or value based on this row information.

The following properties will be available at the time of template execution.

Property Name Usage
isAdd A Boolean property; it defines whether the current row should be a new record or not.

In the following code example, the OrderID textbox has been disabled by using the isAdd property.

// The disabled attributes will be added based on the isAdd property.
<input id="taskID" name="taskID" type="text" disabled={!data.isAdd} value={data.taskID} onChange={this.onChange} />

The following code example illustrates rendering the taskID textbox, when a new record is added.

Get value from editor

You can read, format, and update the current editor value in the actionBegin event at the time of setting requestType to save.

In the following code example, the progress value has been formatted and updated.

    const actionBegin = (args: DialogEditEventArgs) => {
        if (args.requestType === 'save') {
            // cast string to integer value.
            const val: string = ((args.form as HTMLFormElement)
                .querySelector("#progress") as HTMLInputElement).value;
            setValue('data.progress', parseFloat(val), args);
        }
    }

Set focus to editor

By default, the first input element in the dialog will be focused while opening the dialog. If the first input element is in disabled or hidden state, focus the valid input element in the actionComplete event based on requestType as beginEdit.

    const actionComplete = (args: DialogEditEventArgs) => {
       if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
            const form: HTMLFormElement = args.form as HTMLFormElement;
            if (args.requestType === 'beginEdit') {
                (form.elements.namedItem('taskName') as HTMLInputElement).focus();
            }
       }
    }

Adding validation rules for custom editors

If you have used additional fields that are not present in the column model, then add the validation rules to them in the actionComplete event.

    const actionComplete = (args: DialogEditEventArgs) => {
       if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
           // Add Validation Rules
            const form: HTMLFormElement = args.form as HTMLFormElement;
            form.ej2_instances[0].addRules('progress', {max: 100});
       }
    }