Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix - Grid - Cannot select deleted row when transactions are in place - master #3456

Merged
merged 14 commits into from
Jan 4, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions projects/igniteui-angular/src/lib/grids/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,4 +599,20 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent> {
const grid = this.get(id);
return grid.primaryKey ? rowData[grid.primaryKey] : rowData;
}

public row_deleted_transaction(id: string, rowID: any): boolean {
const grid = this.get(id);
if (!grid) {
return false;
}
if (!grid.transactions.enabled) {
return false;
}
const state = grid.transactions.getState(rowID);
if (state) {
return state.type === TransactionType.DELETE;
}

return false;
}
}
15 changes: 11 additions & 4 deletions projects/igniteui-angular/src/lib/grids/grid-base.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3315,8 +3315,8 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
* @hidden
*/
public refreshGridState(args?) {
this.endEdit(true);
this.summaryService.clearSummaryCache(args);
this.endEdit(true);
this.summaryService.clearSummaryCache(args);
}

// TODO: We have return values here. Move them to event args ??
Expand Down Expand Up @@ -3795,7 +3795,8 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
protected _disableMultipleSummaries(expressions) {
expressions.forEach((column) => {
const columnName = column && column.fieldName ? column.fieldName : column;
this._summaries(columnName, false); });
this._summaries(columnName, false);
});
}

/**
Expand Down Expand Up @@ -4021,7 +4022,13 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
*/
public selectRows(rowIDs: any[], clearCurrentSelection?: boolean) {
let newSelection: Set<any>;
newSelection = this.selection.add_items(this.id, rowIDs, clearCurrentSelection);
let selectableRows = [];
if (this.transactions.enabled) {
selectableRows = rowIDs.filter(e => !this.gridAPI.row_deleted_transaction(this.id, e));
} else {
selectableRows = rowIDs;
}
newSelection = this.selection.add_items(this.id, selectableRows, clearCurrentSelection);
this.triggerRowSelectionChange(newSelection);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</ng-container>
<ng-container *ngIf="showRowCheckboxes">
<div class="igx-grid__cbx-selection">
<igx-checkbox [checked]="isSelected" (change)="onCheckboxClick($event)" disableRipple="true" [disableTransitions]="grid.disableTransitions" [aria-label]="rowCheckboxAriaLabel"></igx-checkbox>
<igx-checkbox [checked]="isSelected" [disabled]="deleted" (change)="onCheckboxClick($event)" disableRipple="true" [disableTransitions]="grid.disableTransitions" [aria-label]="rowCheckboxAriaLabel"></igx-checkbox>
</div>
</ng-container>
<ng-container *ngIf="pinnedColumns.length > 0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2865,6 +2865,22 @@ describe('IgxGrid Component Tests', () => {
expect(targetRowElement.classList).toContain('igx-grid__tr--edited', 'row does not contain edited class w/ edits');
expect(targetCellElement.classList).toContain('igx-grid__td--edited', 'cell does not contain edited class w/ edits');
}));

it('Should not allow selecting rows that are deleted', fakeAsync(() => {
const fixture = TestBed.createComponent(IgxGridRowEditingTransactionComponent);
fixture.detectChanges();
const grid = fixture.componentInstance.grid;
grid.rowSelectable = true;
fixture.detectChanges();

grid.deleteRowById(2);
grid.deleteRowById(3);

fixture.detectChanges();
grid.selectRows([2, 3, 4]);
fixture.detectChanges();
expect(grid.selectedRows()).toEqual([4]);
}));
});

describe('Row Editing - Grouping', () => {
Expand Down
11 changes: 1 addition & 10 deletions projects/igniteui-angular/src/lib/grids/row.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class IgxRowComponent<T extends IgxGridBaseComponent> implements DoCheck

/** @hidden */
public get deleted(): boolean {
return this.isRowDeleted();
return this.gridAPI.row_deleted_transaction(this.gridID, this.rowID);
}

public get inEditMode(): boolean {
Expand Down Expand Up @@ -320,13 +320,4 @@ export class IgxRowComponent<T extends IgxGridBaseComponent> implements DoCheck
const deletedClass = this.deleted ? 'igx-grid__tr--deleted' : '';
return `${this.defaultCssClass} ${indexClass} ${selectedClass} ${editClass} ${dirtyClass} ${deletedClass}`.trim();
}

protected isRowDeleted(): boolean {
const state: State = this.grid.transactions.getState(this.rowID);
if (state) {
return state.type === TransactionType.DELETE;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ITreeGridRecord } from './tree-grid.interfaces';
import { IRowToggleEventArgs } from './tree-grid.interfaces';
import { IgxColumnComponent } from '../column.component';
import { first } from 'rxjs/operators';
import { HierarchicalTransaction, TransactionType } from '../../services';
import { HierarchicalTransaction, TransactionType, State } from '../../services';
import { mergeObjects } from '../../core/utils';

export class IgxTreeGridAPIService extends GridBaseAPIService<IgxTreeGridComponent> {
Expand Down Expand Up @@ -174,4 +174,26 @@ export class IgxTreeGridAPIService extends GridBaseAPIService<IgxTreeGridCompone
this.get_selected_children(id, child, selectedRowIDs);
}
}

public row_deleted_transaction(id: string, rowID: any): boolean {
return this.row_deleted_parent(id, rowID) || super.row_deleted_transaction(id, rowID);
}

private row_deleted_parent(id: string, rowID: any): boolean {
const grid = this.get(id);
if (!grid) {
return false;
}
if ((grid.cascadeOnDelete && grid.foreignKey) || grid.childDataKey) {
let node = grid.records.get(rowID);
while (node) {
const state: State = grid.transactions.getState(node.rowID);
if (state && state.type === TransactionType.DELETE) {
return true;
}
node = node.parent;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,62 @@ describe('IgxTreeGrid - Integration', () => {
expect(trans.add).toHaveBeenCalledTimes(2);
expect(trans.add).toHaveBeenCalledWith(transPasrams, null);
}));

it('Should NOT select deleted rows through API - Hierarchical DS', fakeAsync(() => {
fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent);
fix.detectChanges();
treeGrid = fix.componentInstance.treeGrid;

treeGrid.rowSelectable = true;
tick();
fix.detectChanges();
/** Select deleted row */
treeGrid.deleteRowById(663);
tick();
fix.detectChanges();
expect(treeGrid.selectedRows()).toEqual([]);
treeGrid.selectRows([663]);
tick();
fix.detectChanges();
expect(treeGrid.selectedRows()).toEqual([]);
/** Select row with deleted parent */
treeGrid.deleteRowById(147);
tick();
fix.detectChanges();
// 147 -> 475
treeGrid.selectRows([475]);
tick();
fix.detectChanges();
expect(treeGrid.selectedRows()).toEqual([]);
}));

it('Should NOT select deleted rows through API - Flat DS', fakeAsync(() => {
fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent);
fix.detectChanges();
treeGrid = fix.componentInstance.treeGrid;

treeGrid.rowSelectable = true;
tick();
fix.detectChanges();
/** Select deleted row */
treeGrid.deleteRowById(6);
tick();
fix.detectChanges();
expect(treeGrid.selectedRows()).toEqual([]);
treeGrid.selectRows([6]);
tick();
fix.detectChanges();
expect(treeGrid.selectedRows()).toEqual([]);
/** Select row with deleted parent */
treeGrid.deleteRowById(10);
tick();
fix.detectChanges();
// 10 -> 9
treeGrid.selectRows([9]);
tick();
fix.detectChanges();
expect(treeGrid.selectedRows()).toEqual([]);
}));
});

describe('Multi-column header', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<ng-container *ngIf="rowSelectable">
<div class="igx-grid__cbx-selection">
<igx-checkbox [checked]="isSelected" (change)="onCheckboxClick($event)" disableRipple="true" [disableTransitions]="grid.disableTransitions" [aria-label]="rowCheckboxAriaLabel"></igx-checkbox>
<igx-checkbox [checked]="isSelected" [disabled]="deleted" (change)="onCheckboxClick($event)" disableRipple="true" [disableTransitions]="grid.disableTransitions" [aria-label]="rowCheckboxAriaLabel"></igx-checkbox>
</div>
</ng-container>
<ng-container *ngIf="pinnedColumns.length > 0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { IgxTreeGridComponent } from './tree-grid.component';
import { IgxRowComponent } from '../row.component';
import { ITreeGridRecord } from './tree-grid.interfaces';
import { IgxTreeGridAPIService } from './tree-grid-api.service';
import { State, TransactionType } from '../../services';
import { GridBaseAPIService } from '../api.service';
import { IgxSelectionAPIService } from '../../core/selection';

Expand Down Expand Up @@ -86,27 +85,4 @@ export class IgxTreeGridRowComponent extends IgxRowComponent<IgxTreeGridComponen
const filteredClass = this.treeRow.isFilteredOutParent ? 'igx-grid__tr--filtered' : '';
return `${classes} ${filteredClass}`;
}

/** @hidden */
public get deleted(): boolean {
return this.hasDeletedParent() || super.isRowDeleted();
}

/**
* Checks if any of its parent rows are in deleted state
* @returns whether any of its parent rows are in deleted state
*/
private hasDeletedParent(): boolean {
if ((this.grid.cascadeOnDelete && this.grid.foreignKey) || this.grid.childDataKey) {
let node = this.grid.records.get(this.rowID);
while (node) {
const state: State = this.grid.transactions.getState(node.rowID);
if (state && state.type === TransactionType.DELETE) {
return true;
}
node = node.parent;
}
}
return false;
}
}