-
Notifications
You must be signed in to change notification settings - Fork 357
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
feat(data-table): onclick event for datatable rows, select event only on checkboxes, multi shift click and basic a11y (closes #468) (closes #592) #572
Changes from 9 commits
027d50e
a4e4460
cf2d51a
1828a4a
eec687e
632453d
01c777b
c28d148
94e81e9
35b1eb3
3a09de4
8f9a666
365edfd
5409ae9
1ebdd62
cb189ee
ec038aa
1281a51
dd389f1
f453f6e
1117342
565ec7f
15a1a26
b9a0a45
cbd2116
980cbbf
b1bbc98
7cfa95b
411af75
9bd4f9c
5fc7731
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { Component, Input, Output, EventEmitter, forwardRef, ChangeDetectionStrategy, ChangeDetectorRef, | ||
ContentChildren, TemplateRef, AfterContentInit, QueryList } from '@angular/core'; | ||
ContentChildren, TemplateRef, AfterContentInit, QueryList, HostListener } from '@angular/core'; | ||
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; | ||
|
||
import { ITdDataTableSortChangeEvent } from './data-table-column/data-table-column.component'; | ||
|
@@ -42,6 +42,10 @@ export interface ITdDataTableSelectAllEvent { | |
selected: boolean; | ||
} | ||
|
||
export interface ITdDataTableRowClickEvent { | ||
row: any; | ||
} | ||
|
||
@Component({ | ||
providers: [ TD_DATA_TABLE_CONTROL_VALUE_ACCESSOR ], | ||
selector: 'td-data-table', | ||
|
@@ -71,6 +75,9 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI | |
private _sortBy: ITdDataTableColumn; | ||
private _sortOrder: TdDataTableSortingOrder = TdDataTableSortingOrder.Ascending; | ||
|
||
/** shift select */ | ||
private _lastSelectedIndex: number = -1; | ||
|
||
/** template fetching support */ | ||
private _templateMap: Map<string, TemplateRef<any>> = new Map<string, TemplateRef<any>>(); | ||
@ContentChildren(TdDataTableTemplateDirective) _templates: QueryList<TdDataTableTemplateDirective>; | ||
|
@@ -247,6 +254,13 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI | |
*/ | ||
@Output('rowSelect') onRowSelect: EventEmitter<ITdDataTableSelectEvent> = new EventEmitter<ITdDataTableSelectEvent>(); | ||
|
||
/** | ||
* onRowClick?: function | ||
* Event emitted when a row is clicked. | ||
* Emits an [ITdDataTableRowClickEvent] implemented object. | ||
*/ | ||
@Output('rowClick') onRowClick: EventEmitter<ITdDataTableRowClickEvent> = new EventEmitter<ITdDataTableRowClickEvent>(); | ||
|
||
/** | ||
* selectAll?: function | ||
* Event emitted when all rows are selected/deselected by the all checkbox. [selectable] needs to be enabled. | ||
|
@@ -333,32 +347,66 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI | |
} | ||
|
||
/** | ||
* Selects or clears a row depending on 'checked' value | ||
* Selects or clears a row depending on 'checked' value if the row 'isSelectable' | ||
* handles cntrl clicks and shift clicks for multi-select | ||
*/ | ||
select(row: any, checked: boolean, event: Event): void { | ||
event.preventDefault(); | ||
// clears all the fields for the dataset | ||
if (!this._multiple) { | ||
this.clearModel(); | ||
} | ||
|
||
if (checked) { | ||
this._value.push(row); | ||
} else { | ||
// if selection is done by a [uniqueId] it uses it to compare, else it compares by reference. | ||
if (this.uniqueId) { | ||
row = this._value.filter((val: any) => { | ||
return val[this.uniqueId] === row[this.uniqueId]; | ||
})[0]; | ||
if (this.isSelectable) { | ||
event.preventDefault(); | ||
// clears all the fields for the dataset | ||
if (!this._multiple) { | ||
this.clearModel(); | ||
} | ||
let index: number = this._value.indexOf(row); | ||
if (index > -1) { | ||
this._value.splice(index, 1); | ||
let currentSelected: number = this._data.findIndex((d: any) => d === row); | ||
this._doSelection(row); | ||
|
||
// Check to see if Shift key is selected and need to select everything in between | ||
let mouseEvent: MouseEvent = event as MouseEvent; | ||
if (this.isMultiple && mouseEvent && mouseEvent.shiftKey && this._lastSelectedIndex > -1) { | ||
let firstSelected: number = this._data.findIndex((d: any) => this.isRowSelected(d)); | ||
let lastSelected: number = this._data.concat([]).reverse().findIndex((d: any) => this.isRowSelected(d)); | ||
// find the index when not reversed | ||
lastSelected = (this._data.length - 1) - lastSelected; | ||
if (firstSelected > -1 && lastSelected > -1) { | ||
for (let i: number = firstSelected; i < lastSelected; i++) { | ||
if (this._data[i] !== row && i !== this._lastSelectedIndex) { | ||
this._doSelection(this._data[i]); | ||
} | ||
} | ||
} | ||
} | ||
this._lastSelectedIndex = currentSelected; | ||
} | ||
} | ||
|
||
/** | ||
* Overrides the onselectstart method of the document so other text on the page | ||
* doesn't get selected when doing shift selections. | ||
*/ | ||
@HostListener('window:mousedown', ['$event']) | ||
disableOnSelectStart(): void { | ||
if (event.srcElement.tagName === 'MD-PSEUDO-CHECKBOX') { | ||
document.onselectstart = function(): boolean { | ||
return false; | ||
}; | ||
} | ||
this._calculateCheckboxState(); | ||
this.onRowSelect.emit({row: row, selected: checked}); | ||
this.onChange(this._value); | ||
} | ||
|
||
/** | ||
* Resets the original onselectstart method. | ||
*/ | ||
@HostListener('window:mouseup', ['$event']) | ||
reEnableOnSelectStart(): void { | ||
if (event.srcElement.tagName === 'MD-PSEUDO-CHECKBOX') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should avoid using Also i think its better to bind the event to the e.g. /**
* Overrides the onselectstart method of the document so other text on the page
* doesn't get selected when doing shift selections.
*/
disableOnSelectStart(): void {
if (document) {
document.onselectstart = function(): boolean {
return false;
};
}
}
/**
* Resets the original onselectstart method.
*/
enableOnSelectStart(): void {
if (document) {
document.onselectstart = undefined;
}
} <md-pseudo-checkbox
[state]="isRowSelected(row) ? 'checked' : 'unchecked'"
(mouseup)="enableOnSelectStart()"
(mousedown)="disableOnSelectStart()"
(click)="select(row, !isRowSelected(row), $event)">
</md-pseudo-checkbox> Which we can probably create a directive later on for this if we want to reuse the functionality. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also is better to import
|
||
document.onselectstart = undefined; | ||
} | ||
} | ||
|
||
/** | ||
* emits the onRowClickEvent when a row is clicked | ||
*/ | ||
clickRow(row: any): void { | ||
this.onRowClick.emit({row: row}); | ||
} | ||
|
||
/** | ||
|
@@ -405,6 +453,29 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI | |
} | ||
} | ||
|
||
/** | ||
* Does the actual Row Selection | ||
*/ | ||
private _doSelection(row: any): void { | ||
if (!this.isRowSelected(row)) { | ||
this._value.push(row); | ||
} else { | ||
// if selection is done by a [uniqueId] it uses it to compare, else it compares by reference. | ||
if (this.uniqueId) { | ||
row = this._value.filter((val: any) => { | ||
return val[this.uniqueId] === row[this.uniqueId]; | ||
})[0]; | ||
} | ||
let index: number = this._value.indexOf(row); | ||
if (index > -1) { | ||
this._value.splice(index, 1); | ||
} | ||
} | ||
this._calculateCheckboxState(); | ||
this.onRowSelect.emit({row: row, selected: this.isRowSelected(row)}); | ||
this.onChange(this._value); | ||
} | ||
|
||
/** | ||
* Calculate all the state of all checkboxes | ||
*/ | ||
|
@@ -436,5 +507,4 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI | |
} | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🍷