First editable cell is not focused when addin a new row

Dear, when I add a new row, I want the first editable cell to be selected. This happens when trying to update a row, but it doesn't on a new row. I sustect, it has something to do with this:

public actionBegin(args: any) {
    const requestType = args.requestType;
    if (requestType === 'beginEdit') {
      this.setCurrentWcmPillarValues(args.rowData);
      this.setCurrentStrategicalAxises(args.rowData);
    }

    if (requestType === 'save') {
      args.data.wcmPillars = this.selectedWcmPillarIds;
      args.data.strategicalAxises = this.selectedStrategicalAxisIds;
    }

    if (requestType === 'add') {
      args.data.isActive = true;
      args.data.order = ++(this.grid.getPreviousRowData() as any).order;
    }

    if (requestType === 'sorting') {
      this.allowRowDragAndDrop =
        !args.columnName || (args.columnName === 'order' && args.direction === 'Ascending');
    }
  }

4 Replies 1 reply marked as answer

RR Rajapandi Ravi Syncfusion Team September 16, 2024 01:48 PM UTC

Hi Niels,


Greetings from Syncfusion support


After reviewing your shared information, we could see that you are encountering the problem with the first cell is not focused when adding a new row. Based on your shared code information, we have prepared the sample and tried to reproduce your reported problem, however the focus was properly maintained in the first cell, and it was working correctly at our end. Please refer the below code example and sample for more information.


 

actionBegin(args: any): void {

        const requestType = args.requestType;

        if (requestType === 'add') {

            args.data.Verified = true;

            args.data.OrderID = ++(this.grid.getPreviousRowData() as any).OrderID;

          }

    }

 

 


Sample: https://stackblitz.com/edit/angular-8uokfe-xt9n1k?file=src%2Fapp.component.ts,src%2Fapp.component.html


Screenshot:



Since we are not able to identify the Grid initialization from your shared information, to proceed further validation we need some more information for our clarification. So please share the below details that would be helpful for us to provide better solution.


1)         Share your complete Grid rendering code (both TypeScript and HTML files), we would like to check your Grid initialization settings and any customizations you've made in your implementation.

2)         Share your Syncfusion package version.

3)         Please try to reproduce your reported problem in our shared sample that would be helpful for us to provide better solution.


Regards,

Rajapandi R



NV Niels Van Goethem September 17, 2024 09:29 AM UTC

<am-search-bar (valueChanges)="searchList($event)"></am-search-bar>
<ejs-grid
  #grid
  id="grid"
  [pageSettings]="pageSettings"
  [allowPaging]="true"
  [editSettings]="editSettings"
  [toolbar]="toolbar"
  [gridLines]="lines"
  [allowFiltering]="true"
  [filterSettings]="filterSettings"
  [allowSorting]="true"
  [allowMultiSorting]="true"
  showColumnChooser="true"
  [allowReordering]="true"
  [allowResizing]="true"
  [allowRowDragAndDrop]="allowRowDragAndDrop"
  [allowSelection]="true"
  [allowPdfExport]="true"
  [allowExcelExport]="true"
  (actionBegin)="actionBegin($event)"
  (dataBound)="dataBound()"
  (dataStateChange)="dataStateChange($event)"
  (rowDrop)="rowDrop($event)"
  (actionFailure)="onActionFailure($event)"
  (actionComplete)="saveGridState()"
  (resizeStop)="saveGridState()"
>
  <ng-template #emptyRecordTemplate>
    <span class="loader" *ngIf="showLoader; else reallyEmpty">
      <ejs-skeleton
        [width]="'100%'"
        [height]="'28px'"
        *ngFor="let _ of [].constructor(5)"
      ></ejs-skeleton>
    </span>
    <ng-template #reallyEmpty>
      <div class="empty-records">
        <img src="assets/images/empty-records.svg" alt="No records" />
        <span>{{ 'Common.Grid.NoData' | translate }}</span>
      </div>
    </ng-template>
  </ng-template>
  <e-columns>
    <e-column
      [width]="'36px'"
      field="order"
      headerText=""
      [customAttributes]="{ class: 'order-column' }"
      [showInColumnChooser]="false"
      [allowReordering]="false"
      [allowFiltering]="false"
      [allowResizing]="false"
      [allowEditing]="false"
      [filterBarTemplate]="''"
    >
    </e-column>
    <e-column
      type="checkbox"
      [width]="'32px'"
      [showInColumnChooser]="false"
      [allowReordering]="false"
      [allowResizing]="false"
      [allowSorting]="false"
    ></e-column>
    <e-column
      field="id"
      [isPrimaryKey]="true"
      [visible]="false"
      [allowEditing]="false"
      [showInColumnChooser]="false"
    ></e-column>
    <e-column
      field="code"
      [headerText]="'Processes.Headers.Code' | translate"
      width="120"
    ></e-column>
    <e-column
      field="nameEN"
      [visible]="true"
      [headerText]="('Processes.Headers.Name' | translate) + ' (EN)'"
    ></e-column>
    <e-column
      field="nameNL"
      [visible]="currentLanguage === 'nl'"
      [headerText]="('Processes.Headers.Name' | translate) + ' (NL)'"
    ></e-column>
    <e-column
      field="nameFR"
      [visible]="currentLanguage === 'fr'"
      [headerText]="('Processes.Headers.Name' | translate) + ' (FR)'"
    ></e-column>
    <e-column
      field="nameDE"
      [visible]="currentLanguage === 'de'"
      [headerText]="('Processes.Headers.Name' | translate) + ' (DE)'"
    ></e-column>
    <e-column
      field="nameES"
      [visible]="currentLanguage === 'es'"
      [headerText]="('Processes.Headers.Name' | translate) + ' (ES)'"
    ></e-column>
    <e-column
      field="nameSK"
      [visible]="currentLanguage === 'sk'"
      [headerText]="('Processes.Headers.Name' | translate) + ' (SK)'"
    ></e-column>
    <e-column
      field="namePL"
      [visible]="currentLanguage === 'pl'"
      [headerText]="('Processes.Headers.Name' | translate) + ' (PL)'"
    ></e-column>
    <e-column
      field="descriptionEN"
      [visible]="currentLanguage === 'en'"
      [headerText]="('Processes.Headers.Description' | translate) + ' (EN)'"
    ></e-column>
    <e-column
      field="descriptionNL"
      [visible]="currentLanguage === 'nl'"
      [headerText]="('Processes.Headers.Description' | translate) + ' (NL)'"
    ></e-column>
    <e-column
      field="descriptionFR"
      [visible]="currentLanguage === 'fr'"
      [headerText]="('Processes.Headers.Description' | translate) + ' (FR)'"
    ></e-column>
    <e-column
      field="descriptionDE"
      [visible]="currentLanguage === 'de'"
      [headerText]="('Processes.Headers.Description' | translate) + ' (DE)'"
    ></e-column>
    <e-column
      field="descriptionES"
      [visible]="currentLanguage === 'es'"
      [headerText]="('Processes.Headers.Description' | translate) + ' (ES)'"
    ></e-column>
    <e-column
      field="descriptionSK"
      [visible]="currentLanguage === 'sk'"
      [headerText]="('Processes.Headers.Description' | translate) + ' (SK)'"
    ></e-column>
    <e-column
      field="descriptionPL"
      [visible]="currentLanguage === 'pl'"
      [headerText]="('Processes.Headers.Description' | translate) + ' (PL)'"
    ></e-column>
    <e-column
      field="wcmPillarNames"
      [headerText]="'Processes.Headers.WcmPillars' | translate"
      [allowSorting]="false"
    >
      <ng-template #editTemplate let-data>
        <ejs-multiselect
          [dataSource]="wcmPillars$ | async"
          [fields]="wcmPillarFields"
          [(value)]="selectedWcmPillarIds[data.id]"
        ></ejs-multiselect>
      </ng-template>
    </e-column>
    <e-column
      field="strategicalAxisNames"
      [headerText]="'Processes.Headers.StrategicalAxis' | translate"
      [allowSorting]="false"
    >
      <ng-template #editTemplate let-data>
        <ejs-multiselect
          [dataSource]="strategicalAxises$ | async"
          [fields]="strategicalAxisFields"
          [(value)]="selectedStrategicalAxisIds[data.id]"
        ></ejs-multiselect>
      </ng-template>
    </e-column>
  </e-columns>
</ejs-grid>

<am-custom-toolbar
  [filterTypeItems]="filterTypeItems"
  [gridLineItems]="gridLineItems"
  [freezingOptions]="freezingOptions"
  [selectedFrozenColumns]="selectedFrozenColumns"
  [selectedFilterType]="grid.filterSettings.type"
  [selectedGridlineItem]="grid.gridLines"
  (filterChange)="filterChangeHandler($event)"
  (gridLineChange)="gridLinesChangeHandler($event)"
  (freezeChange)="handleFreezeChange($event, $event.field)"
  (clearFiltering)="handleClearFiltering()"
>
</am-custom-toolbar>
import { AfterViewInit, Component, Inject, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { FormArray } from '@angular/forms';
import { APP_CONFIG, CoreConfig, TranslationService } from '@arcelormittal-platform/core';
import { AuthenticationService } from '@arcelormittal-platform/security';
import { SnackBarService, SnackBarType } from '@arcelormittal-platform/ui';
import { StrategicalAxisDropdownItem, WcmPillarDropdownItem } from '@masterdata/models';
import {
  ProcessesGridService,
  StrategicalAxisesService,
  WcmPillarsService
} from '@masterdata/services';
import { TranslateService } from '@ngx-translate/core';
import { GridService, ToolbarConfigService } from '@shared/services';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-dropdowns';
import {
  ColumnChooserService,
  DataResult,
  DataStateChangeEventArgs,
  EditService,
  EditSettingsModel,
  ExcelExportService,
  FailureEventArgs,
  FilterService,
  FilterSettings,
  FreezeService,
  GridComponent,
  GridLine,
  PageService,
  PageSettingsModel,
  PdfExportService,
  ReorderService,
  ResizeService,
  RowDDService,
  RowDropEventArgs,
  SortService,
  ToolbarService
} from '@syncfusion/ej2-angular-grids';
import { MenuEventArgs } from '@syncfusion/ej2-angular-splitbuttons';
import { DataManager, Query, RemoteSaveAdaptor } from '@syncfusion/ej2-data';
import { catchError, finalize, Observable, retry, Subject, takeUntil, tap } from 'rxjs';

@Component({
  selector: 'am-process-tab-item',
  templateUrl: './process-tab-item.component.html',
  styleUrls: ['./process-tab-item.component.scss'],
  providers: [
    PageService,
    EditService,
    ToolbarService,
    ColumnChooserService,
    FilterService,
    FreezeService,
    ReorderService,
    ResizeService,
    SortService,
    RowDDService,
    PdfExportService,
    ExcelExportService,
  ],
})
export class ProcessTabItemComponent implements OnInit, AfterViewInit, OnDestroy {
  @Input() public fetchActive = true;
  @ViewChild('grid') grid: GridComponent | undefined;
  public clientData: DataResult | undefined;

  public showLoader = true;
  public pageSettings: PageSettingsModel;
  public editSettings: EditSettingsModel;
  public toolbar: (string | object)[];

  public currentDescriptionFormArray: FormArray | undefined;
  public descriptionDialogVisible = false;

  public wcmPillars$: Observable<WcmPillarDropdownItem[]> | undefined;
  public selectedWcmPillarIds: number[][] = [];
  public wcmPillarFields: FieldSettingsModel;

  public strategicalAxises$: Observable<StrategicalAxisDropdownItem[]> | undefined;
  public selectedStrategicalAxisIds: number[][] = [];
  public strategicalAxisFields: FieldSettingsModel;

  public freezingOptions: { text: string; field: string }[];

  public filterTypeItems = [
    { text: 'FilterBar', iconCss: 'e-icons e-bullet-5' },
    { text: 'Menu', iconCss: 'e-icons e-bullet-5' },
    { text: 'CheckBox', iconCss: 'e-icons e-bullet-5' },
    { text: 'Excel', iconCss: 'e-icons e-bullet-5' },
  ];

  public gridLineItems = [
    { text: 'None', iconCss: 'e-icons e-bullet-5' },
    { text: 'Default', iconCss: 'e-icons e-bullet-5' },
    { text: 'Both', iconCss: 'e-icons e-bullet-5' },
    { text: 'Horizontal', iconCss: 'e-icons e-bullet-5' },
    { text: 'Vertical', iconCss: 'e-icons e-bullet-5' },
  ];

  public filterSettings: Partial<FilterSettings>;
  public allowRowDragAndDrop = true;
  public currentLanguage: string | undefined;

  private _baseUrl: string | undefined;
  private _accessToken: string | undefined;
  private userSettingsKey: string | undefined;

  private destroy$ = new Subject<void>();

  constructor(
    private readonly processesGridService: ProcessesGridService,
    private readonly wcmPillarsService: WcmPillarsService,
    private readonly strategicalAxisesService: StrategicalAxisesService,
    private readonly toolbarConfigService: ToolbarConfigService,
    private readonly gridService: GridService,
    private readonly translationService: TranslationService,
    @Inject(APP_CONFIG) private readonly appConfig: CoreConfig,
    private readonly snackBarService: SnackBarService,
    private readonly translateService: TranslateService,
    private readonly authService: AuthenticationService
  ) {
    this._baseUrl = `${this.appConfig.api}masterdata/lists/processes/grid`;
    const authValue = JSON.parse(
      sessionStorage.getItem('oidc.user:https://sts.belgium.arcelormittal.com/TokenService:PROMATO')
    );
    this._accessToken = authValue.access_token;
  }

  public get selectedFrozenColumns() {
    return this.gridService.selectedFrozenColumns;
  }

  public get lines() {
    return this.gridService.lines;
  }

  public ngOnInit(): void {
    this.authService.userProfile$
      .pipe(
        tap((userProfile) => {
          this.userSettingsKey = `${userProfile.sid}processes-gridState-${
            this.fetchActive ? 'active' : 'inactive'
          }`;
        }),
        takeUntil(this.destroy$)
      )
      .subscribe();
    this.currentLanguage = this.translationService.getCurrentLanguage();
    this.fetchData();
    this.pageSettings = { pageSize: 20 };
    this.editSettings = {
      allowEditing: true,
      allowAdding: true,
      allowDeleting: true,
      mode: 'Normal',
      newRowPosition: 'Bottom',
    };
    this.setupWcmPillars();
    this.setupStrategicalAxises();
    this.configureToolbar();
  }

  public ngAfterViewInit(): void {
    this.configureToolbar();
    this.gridService.loadGridState(this.userSettingsKey, this.grid);
  }

  public ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  public dataBound() {
    this.configureFreezingOptions();
  }

  public dataStateChange(state: DataStateChangeEventArgs) {
    const query = this.grid.getDataModule().generateQuery().requiresCount();
    if (state.action) {
      this.grid.dataSource = new DataManager(this.clientData.result).executeLocal(query);
      if (state.dataSource) {
        state.dataSource(this.clientData.result);
      }
    } else {
      this.fetchDataFromService(query);
    }
  }

  public actionBegin(args: any) {
    const requestType = args.requestType;
    if (requestType === 'beginEdit') {
      this.setCurrentWcmPillarValues(args.rowData);
      this.setCurrentStrategicalAxises(args.rowData);
    }

    if (requestType === 'save') {
      args.data.wcmPillars = this.selectedWcmPillarIds;
      args.data.strategicalAxises = this.selectedStrategicalAxisIds;
    }

    if (requestType === 'add') {
      args.data.isActive = true;
      args.data.order = ++(this.grid.getPreviousRowData() as any).order;
    }

    if (requestType === 'sorting') {
      this.allowRowDragAndDrop =
        !args.columnName || (args.columnName === 'order' && args.direction === 'Ascending');
    }
  }

  public rowDrop(args: RowDropEventArgs) {
    const data = args.data[0] as any;
    const pageSize = this.grid.pageSettings.pageSize;
    const currentPage = this.grid.pageSettings.currentPage;
    const newIndex = args.dropIndex + pageSize * (currentPage - 1) + 1;
    const oldIndex = args.fromIndex + pageSize * (currentPage - 1) + 1;
    this.processesGridService
      .saveNewOrder$({ id: data.id, newIndex, oldIndex })
      .pipe(
        finalize(() => this.fetchData()),
        takeUntil(this.destroy$)
      )
      .subscribe();
  }

  public filterChangeHandler(args: MenuEventArgs) {
    this.gridService.handleFilterChange(args, this.grid);
    setTimeout(() => {
      this.saveGridState();
    }, 0);
  }

  public gridLinesChangeHandler(args: MenuEventArgs) {
    const type = args.item.text;
    this.gridService.lines = type as GridLine;
    this.saveGridState();
    setTimeout(() => {
      this.grid.refresh();
    }, 0);
  }

  public handleFreezeChange(args: any, fieldColumn: string) {
    this.gridService.selectedFrozenColumns = this.gridService.handleFreezeChange(
      args,
      fieldColumn,
      this.grid
    );
    this.saveGridState();
  }

  public pdfExport() {
    this.grid.pdfExport();
  }

  public excelExport() {
    this.grid.excelExport();
  }

  public addNewRecord() {
    this.grid.addRecord();
  }

  public handleClearFiltering() {
    this.gridService.handleClearFiltering(this.grid);
  }

  public fetchData() {
    const state = { skip: 0, take: 20 };
    const query = new Query().skip(state.skip).take(state.take).requiresCount();
    this.fetchDataFromService(query);
  }

  public searchList(searchTerm: string) {
    const state = { skip: 0, take: 20 };
    const query = new Query().skip(state.skip).take(state.take).requiresCount();
    this.fetchDataFromService(query, searchTerm);
  }

  public onActionFailure(args: FailureEventArgs) {
    this.showLoader = false;
    this.snackBarService.open(
      {
        message: this.translateService.instant('Processes.ErrorMessages.Save'),
      },
      SnackBarType.ERROR
    );
  }

  public saveGridState() {
    this.gridService.saveGridState(this.userSettingsKey, this.grid);
  }

  private fetchDataFromService(query: Query, searchTerm = '') {
    this.processesGridService
      .getAllData$(query, this.fetchActive, searchTerm)
      .pipe(
        tap((response: DataResult | Response) => {
          this.showLoader = false;
          this.clientData = response as DataResult;
          this.grid.dataSource = new DataManager({
            json: this.clientData.result,
            updateUrl: `${this._baseUrl}/update`,
            insertUrl: `${this._baseUrl}/create`,
            adaptor: new RemoteSaveAdaptor(),
            headers: [
              {
                Authorization: `Bearer ${this._accessToken}`,
              },
            ],
          });
        }),
        catchError((err) => {
          this.showLoader = false;
          this.snackBarService.open(
            {
              message: this.translateService.instant('Processes.ErrorMessages.Fetch'),
            },
            SnackBarType.ERROR
          );
          return err;
        }),
        retry(3),
        takeUntil(this.destroy$)
      )
      .subscribe();
  }

  private setCurrentWcmPillarValues(data: any) {
    this.selectedWcmPillarIds[data.id] = data.wcmPillars.map((x) => x.id);
  }

  private setupWcmPillars() {
    this.wcmPillars$ = this.wcmPillarsService.GetDropdownItems();
    this.wcmPillarFields = { value: 'id', text: 'name' };
  }

  private setCurrentStrategicalAxises(data: any) {
    this.selectedStrategicalAxisIds[data.id] = data.strategicalAxises.map((x) => x.id);
  }

  private setupStrategicalAxises() {
    this.strategicalAxises$ = this.strategicalAxisesService.getDropdownItems();
    this.strategicalAxisFields = { value: 'id', text: 'name' };
  }

  private configureFreezingOptions() {
    if (this.grid) {
      this.freezingOptions = this.grid
        ?.getVisibleColumns()
        .map((column) => ({ text: column.headerText, field: column.field }))
        .filter((x) => !!x.field && !!x.text)
        .sort((a, b) => a.text?.localeCompare(b.text));
    }
  }

  private configureToolbar() {
    this.toolbar = this.toolbarConfigService.getConfig();
  }
}

"@syncfusion/ej2-angular-grids": "^26.2.12"


NV Niels Van Goethem September 17, 2024 10:55 AM UTC

I found the problem, the next editable cell was a checkbox, which does not have a visible focus state.
I applied [allowEditing]="true", now the focus is applied to the next cell, which is a textbox, and I can still check records


Marked as answer

AR Aishwarya Rameshbabu Syncfusion Team September 18, 2024 05:38 PM UTC

Hi Niels,


Thank you for sharing the details.


Upon reviewing the provided information and the accompanying code example, it has been observed that within your Grid columns, the initial editable column is designated as a checkbox column. When a new record is added, the focus is automatically directed to the input element of the first editable column in the edit form. Given that the first editable cell in your Grid is a checkbox, the focus will consequently be set to the input element of this checkbox. This configuration allows you to update the state of the checkbox by pressing the spacebar button. Additionally, you can navigate to the subsequent cell by pressing the tab key. We have made adjustments to the sample in accordance with the reported scenario. For a more comprehensive understanding, please consult the provided sample and video demonstration.


Sample: https://stackblitz.com/edit/angular-8uokfe-j1k5ut?file=src%2Fapp.component.ts,src%2Fapp.component.html


If you need any other assistance or have additional questions, please feel free to contact us.


Regards

Aishwarya R


Attachment: 194419Video_22c08d06.zip

Loader.
Up arrow icon