All Components

Focus Clicked Cells while Editing Rows

By default, when the user opens a row for editing, the editable Grid focuses the first available input.

In scenarios that require you to focus another cell when a row is in editing mode—for example, scenarios which are similar to the inline editing on row click—you can programmatically focus the desired cell in the edit event handler of the Grid. You have to wrap the operation in a setTimeout() call to ensure that the respective inputs are rendered.

The following example demonstrates how to focus the input which corresponds to the clicked cell in the Grid.

import { Component, OnInit, Inject, ElementRef, ViewChild, Renderer2 } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ProductsService } from './products.service';
import { AddEvent, EditEvent, GridComponent } from '@progress/kendo-angular-grid';
import { groupBy, GroupDescriptor } from '@progress/kendo-data-query';

const formGroup = dataItem => new FormGroup({
    'Discontinued': new FormControl(dataItem.Discontinued),
    'ProductID': new FormControl(dataItem.ProductID),
    'ProductName': new FormControl(dataItem.ProductName, Validators.required),
    'UnitPrice': new FormControl(dataItem.UnitPrice),
    'UnitsInStock': new FormControl(dataItem.UnitsInStock, Validators.compose([Validators.required, Validators.pattern('^[0-9]{1,3}')]))
});

const hasClass = (el, className) => new RegExp(className).test(el.className);

const isChildOf = (el, className) => {
    while (el && el.parentElement) {
        if (hasClass(el.parentElement, className)) {
            return true;
        }
        el = el.parentElement;
    }
    return false;
};

@Component({
    selector: 'my-app',
    template: `
        <kendo-grid
            (cellClick)="editClick($event)"
            [groupable]="true"
            [group]="groups"
            (groupChange)="groupChange($event)"
            [data]="view"
            height="500"
            (add)="addHandler($event)"
            >
            <ng-template kendoGridToolbarTemplate>
                <button kendoGridAddCommand>Add new</button>
                <button *ngIf="isInEditingMode"
                    (click)="cancelHandler()"
                    class="k-button k-primary">Cancel</button>
            </ng-template>
            <kendo-grid-column field="ProductName" title="Product Name"></kendo-grid-column>
            <kendo-grid-column field="UnitPrice" editor="numeric" title="Price"></kendo-grid-column>
            <kendo-grid-column field="Discontinued" editor="boolean" title="Discontinued"></kendo-grid-column>
            <kendo-grid-column field="UnitsInStock" editor="numeric" title="Units In Stock"></kendo-grid-column>
        </kendo-grid>
    `
})
export class AppComponent implements OnInit {
    public formGroup: FormGroup;
    public groups: GroupDescriptor[] = [];
    public view: any[];
    @ViewChild(GridComponent) private grid: GridComponent;
    private editedRowIndex: number;
    private isNew = false;

    public get isInEditingMode(): boolean {
        return this.editedRowIndex !== undefined || this.isNew;
    }

    public groupChange(groups: GroupDescriptor[]): void {
        this.groups = groups;
        this.view = groupBy(this.service.products(), this.groups);
    }

    constructor(private service: ProductsService, private renderer: Renderer2) { }

    public ngOnInit(): void {
      this.view = this.service.products();
      this.renderer.listen(
          "document",
          "click",
          ({ target }) => {
              if (!isChildOf(target, "k-grid")) {
                  this.saveClick();
              }
          });
    }

    public addHandler({ sender }: AddEvent): void {
        this.closeEditor(sender);

        this.formGroup = formGroup({
            'Discontinued': false,
            'ProductName': "",
            'UnitPrice': 0,
            'UnitsInStock': ""
        });

        this.isNew = true;
        sender.addRow(this.formGroup);
    }

    public editHandler({ sender, colIndex, rowIndex, dataItem }: EditEvent): void {
        if (this.formGroup && !this.formGroup.valid) {
            return;
        }

        this.saveRow();
        this.formGroup = formGroup(dataItem);
        this.editedRowIndex = rowIndex;
        sender.editRow(rowIndex, this.formGroup);
        setTimeout(() => {
          console.log(colIndex);
          document.querySelector(`.k-grid-edit-row > td:nth-child(${colIndex + 1}) input`).focus();
        });
    }

    public cancelHandler(): void {
        this.closeEditor(this.grid, this.editedRowIndex);
    }

    public editClick({ dataItem, rowIndex, columnIndex }: any): void {
        this.editHandler({
            dataItem: dataItem,
            rowIndex: rowIndex,
            colIndex: columnIndex,
            sender: this.grid
        });
    }

    public saveClick(): void {
        if (this.formGroup && !this.formGroup.valid) {
            return;
        }

        this.saveRow();
    }

    private closeEditor(grid: GridComponent, rowIndex: number = this.editedRowIndex): void {
        this.isNew = false;
        grid.closeRow(rowIndex);
        this.editedRowIndex = undefined;
        this.formGroup = undefined;
    }

    private saveRow(): void {
        if (this.isInEditingMode) {
            this.service.save(this.formGroup.value, this.isNew);
        }

        this.closeEditor(this.grid);
    }
}
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

enableProdMode();

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';

import { GridModule } from '@progress/kendo-angular-grid';
import { DropDownListModule } from '@progress/kendo-angular-dropdowns';

import { AppComponent } from './app.component';
import { ProductsService } from './products.service';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        ReactiveFormsModule,
        GridModule,
        DropDownListModule
    ],
    providers: [
        ProductsService
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}
export const products = [{
    "ProductID" : 1,
    "ProductName" : "Chai",
    "SupplierID" : 1,
    "CategoryID" : 1,
    "QuantityPerUnit" : "10 boxes x 20 bags",
    "UnitPrice" : 18.0000,
    "UnitsInStock" : 39,
    "UnitsOnOrder" : 0,
    "ReorderLevel" : 10,
    "Discontinued" : false

}, {
    "ProductID" : 2,
    "ProductName" : "Chang",
    "SupplierID" : 1,
    "CategoryID" : 1,
    "QuantityPerUnit" : "24 - 12 oz bottles",
    "UnitPrice" : 19.0000,
    "UnitsInStock" : 17,
    "UnitsOnOrder" : 40,
    "ReorderLevel" : 25,
    "Discontinued" : false
}, {
    "ProductID" : 3,
    "ProductName" : "Aniseed Syrup",
    "SupplierID" : 1,
    "CategoryID" : 2,
    "QuantityPerUnit" : "12 - 550 ml bottles",
    "UnitPrice" : 10.0000,
    "UnitsInStock" : 13,
    "UnitsOnOrder" : 70,
    "ReorderLevel" : 25,
    "Discontinued" : false
}, {
    "ProductID" : 4,
    "ProductName" : "Chef Anton's Cajun Seasoning",
    "SupplierID" : 2,
    "CategoryID" : 2,
    "QuantityPerUnit" : "48 - 6 oz jars",
    "UnitPrice" : 22.0000,
    "UnitsInStock" : 53,
    "UnitsOnOrder" : 0,
    "ReorderLevel" : 0,
    "Discontinued" : false
}, {
    "ProductID" : 5,
    "ProductName" : "Chef Anton's Gumbo Mix",
    "SupplierID" : 2,
    "CategoryID" : 2,
    "QuantityPerUnit" : "36 boxes",
    "UnitPrice" : 21.3500,
    "UnitsInStock" : 0,
    "UnitsOnOrder" : 0,
    "ReorderLevel" : 0,
    "Discontinued" : true
}, {
    "ProductID" : 6,
    "ProductName" : "Grandma's Boysenberry Spread",
    "SupplierID" : 3,
    "CategoryID" : 2,
    "QuantityPerUnit" : "12 - 8 oz jars",
    "UnitPrice" : 25.0000,
    "UnitsInStock" : 120,
    "UnitsOnOrder" : 0,
    "ReorderLevel" : 25,
    "Discontinued" : false
}, {
    "ProductID" : 7,
    "ProductName" : "Uncle Bob's Organic Dried Pears",
    "SupplierID" : 3,
    "CategoryID" : 7,
    "QuantityPerUnit" : "12 - 1 lb pkgs.",
    "UnitPrice" : 30.0000,
    "UnitsInStock" : 15,
    "UnitsOnOrder" : 0,
    "ReorderLevel" : 10,
    "Discontinued" : false
}, {
    "ProductID" : 8,
    "ProductName" : "Northwoods Cranberry Sauce",
    "SupplierID" : 3,
    "CategoryID" : 2,
    "QuantityPerUnit" : "12 - 12 oz jars",
    "UnitPrice" : 40.0000,
    "UnitsInStock" : 6,
    "UnitsOnOrder" : 0,
    "ReorderLevel" : 0,
    "Discontinued" : false
}];
import { Injectable } from '@angular/core';
import { products } from './products';

@Injectable()
export class ProductsService {
    private data: any[] = products;
    private counter: number = products.length;

    public products(): any[] {
        return this.data;
    }

    public remove(product: any): void {
        const index = this.data.findIndex(({ ProductID }) => ProductID === product.ProductID);
        this.data.splice(index, 1);
    }

    public save(product: any, isNew: boolean): void {
        if (isNew) {
            product.ProductID = this.counter++;
            this.data.splice(0, 0, product);
        } else {
            Object.assign(
                this.data.find(({ ProductID }) => ProductID === product.ProductID),
                product
            );
        }
    }
}
In this article