Template editing in React TreeGrid component
8 Oct 202524 minutes to read
Dialog template
Dialog template editing customizes the default dialog editing behavior. Using the dialog template, custom editors can be rendered by setting editSettings.mode to Dialog and template to a SCRIPT element ID or an HTML string that defines the template.
Some scenarios require adding field editors that are not present in the column model. In such cases, the dialog template provides full control to extend the default edit dialog.
The following video provides a quick overview:
The following sample enables dialog template editing in the TreeGrid.
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>);
    }
}Template form editors must include a name attribute.
Template context
The template is a React component class. Row information is available via props, enabling binding of attributes and values based on the current row context.
The following property is available during template execution.
| Property Name | Usage | 
|---|---|
| isAdd | A Boolean value that indicates whether the current row is a new record. | 
In the following code example, the OrderID textbox is disabled based on 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
Read, format, and update the current editor value in the actionBegin event when requestType is save.
In the following code example, the progress value is 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 receives focus when the dialog opens. If the first input is disabled or hidden, move focus to a valid input in the actionComplete event when requestType is 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
When additional fields not defined in the column model are used, add validation rules for 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});
       }
    }