Skip to content

Commit

Permalink
feat(data-table): Foundation changes to support column sorting
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 299861789
  • Loading branch information
abhiomkar authored and copybara-github committed Mar 9, 2020
1 parent 17b9699 commit 6ee0355
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 29 deletions.
9 changes: 9 additions & 0 deletions packages/mdc-data-table/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,13 @@ Method Signature | Description
`setHeaderRowCheckboxChecked(checked: boolean) => void` | Sets header row checkbox checked or unchecked.
`setHeaderRowCheckboxIndeterminate(indeterminate: boolean) => void` | Sets header row checkbox to indeterminate.
`setRowCheckboxCheckedAtIndex(rowIndex: number, checked: boolean) => void` | Sets row checkbox to checked or unchecked at given row index.
`getHeaderCellCount(): number;` | Returns total count of header cells.
`getHeaderCellElements(): Element[];` | Returns array of header cell elements.
`getAttributeByHeaderCellIndex(columnIndex: number, attribute: string) => string` | Returns attribute value for given header cell index.
`setAttributeByHeaderCellIndex(columnIndex: number, attribute: string, value: string) => void` | Sets attribute of a header cell by index.
`setClassNameByHeaderCellIndex(columnIndex: number, className: string) => void` | Sets class name of a header cell by index.
`removeClassNameByHeaderCellIndex(columnIndex: number, className: string) => void` | Removes a class name of a header cell by index.
`notifySortAction(data: SortActionEventDetail) => void` | Notifies when column is sorted.

### `MDCDataTableFoundation`

Expand All @@ -382,3 +389,5 @@ Method Signature | Description
`getSelectedRowIds() => Array<string \| null>` | Returns array of selected row ids.
`handleHeaderRowCheckboxChange() => void` | Handles header row checkbox change event.
`handleRowCheckboxChange(event: Event) => void` | Handles change event originated from row checkboxes.
`getHeaderCells() => Elements[]` | Returns array of header cell elements.
`handleSortAction(eventData: SortActionEventData) => void` | Handles sort action on sortable header cell.
40 changes: 39 additions & 1 deletion packages/mdc-data-table/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md
*/

import {MDCDataTableRowSelectionChangedEventDetail} from './types';
import {MDCDataTableRowSelectionChangedEventDetail, SortActionEventDetail} from './types';

export interface MDCDataTableAdapter {
/**
Expand Down Expand Up @@ -156,4 +156,42 @@ export interface MDCDataTableAdapter {
* @param checked True to set checked.
*/
setRowCheckboxCheckedAtIndex(rowIndex: number, checked: boolean): void;

/**
* @return Total count of header cells.
*/
getHeaderCellCount(): number;

/**
* @return Array of header cell elements.
*/
getHeaderCellElements(): Element[];

/**
* @return Attribute value for given header cell index.
*/
getAttributeByHeaderCellIndex(columnIndex: number, attribute: string): string
|null;

/**
* Sets attribute of a header cell by index.
*/
setAttributeByHeaderCellIndex(
columnIndex: number, attribute: string, value: string): void;

/**
* Sets class name of a header cell by index.
*/
setClassNameByHeaderCellIndex(columnIndex: number, className: string): void;

/**
* Removes a class name of a header cell by index.
*/
removeClassNameByHeaderCellIndex(columnIndex: number, className: string):
void;

/**
* Notifies when column is sorted.
*/
notifySortAction(data: SortActionEventDetail): void;
}
39 changes: 26 additions & 13 deletions packages/mdc-data-table/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,27 @@ export class MDCDataTable extends MDCComponent<MDCDataTableFoundation> {
// DO NOT INLINE this variable. For backward compatibility, foundations take a Partial<MDCFooAdapter>.
// To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable.
// tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface.
const adapter: MDCDataTableAdapter = {
addClassAtRowIndex: (rowIndex: number, className: string) => this.getRows()[rowIndex].classList.add(className),
const adapter: Partial<MDCDataTableAdapter> = {
addClassAtRowIndex: (rowIndex: number, className: string) => {
this.getRows()[rowIndex].classList.add(className);
},
getRowCount: () => this.getRows().length,
getRowElements: () => [].slice.call(this.root_.querySelectorAll(strings.ROW_SELECTOR)),
getRowIdAtIndex: (rowIndex: number) => this.getRows()[rowIndex].getAttribute(strings.DATA_ROW_ID_ATTR),
getRowElements: () => [].slice.call(
this.root_.querySelectorAll(strings.ROW_SELECTOR)),
getRowIdAtIndex: (rowIndex: number) =>
this.getRows()[rowIndex].getAttribute(strings.DATA_ROW_ID_ATTR),
getRowIndexByChildElement: (el: Element) => {
return this.getRows().indexOf((closest(el, strings.ROW_SELECTOR) as HTMLElement));
},
getSelectedRowCount: () => this.root_.querySelectorAll(strings.ROW_SELECTED_SELECTOR).length,
isCheckboxAtRowIndexChecked: (rowIndex: number) => this.rowCheckboxList_[rowIndex].checked,
getSelectedRowCount: () =>
this.root_.querySelectorAll(strings.ROW_SELECTED_SELECTOR).length,
isCheckboxAtRowIndexChecked: (rowIndex: number) =>
this.rowCheckboxList_[rowIndex].checked,
isHeaderRowCheckboxChecked: () => this.headerRowCheckbox_.checked,
isRowsSelectable: () => !!this.root_.querySelector(strings.ROW_CHECKBOX_SELECTOR),
notifyRowSelectionChanged: (data: MDCDataTableRowSelectionChangedEventDetail) => {
isRowsSelectable: () =>
!!this.root_.querySelector(strings.ROW_CHECKBOX_SELECTOR),
notifyRowSelectionChanged: (
data: MDCDataTableRowSelectionChangedEventDetail) => {
this.emit(events.ROW_SELECTION_CHANGED, {
row: this.getRowByIndex_(data.rowIndex),
rowId: this.getRowIdByIndex_(data.rowIndex),
Expand All @@ -121,8 +129,12 @@ export class MDCDataTable extends MDCComponent<MDCDataTableFoundation> {
},
/** shouldBubble */ true);
},
notifySelectedAll: () => this.emit(events.SELECTED_ALL, {}, /** shouldBubble */ true),
notifyUnselectedAll: () => this.emit(events.UNSELECTED_ALL, {}, /** shouldBubble */ true),
notifySelectedAll: () => {
this.emit(events.SELECTED_ALL, {}, /** shouldBubble */ true);
},
notifyUnselectedAll: () => {
this.emit(events.UNSELECTED_ALL, {}, /** shouldBubble */ true);
},
registerHeaderRowCheckbox: () => {
if (this.headerRowCheckbox_) {
this.headerRowCheckbox_.destroy();
Expand All @@ -145,9 +157,10 @@ export class MDCDataTable extends MDCComponent<MDCDataTableFoundation> {
removeClassAtRowIndex: (rowIndex: number, className: string) => {
this.getRows()[rowIndex].classList.remove(className);
},
setAttributeAtRowIndex: (rowIndex: number, attr: string, value: string) => {
this.getRows()[rowIndex].setAttribute(attr, value);
},
setAttributeAtRowIndex:
(rowIndex: number, attr: string, value: string) => {
this.getRows()[rowIndex].setAttribute(attr, value);
},
setHeaderRowCheckboxChecked: (checked: boolean) => {
this.headerRowCheckbox_.checked = checked;
},
Expand Down
45 changes: 44 additions & 1 deletion packages/mdc-data-table/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,72 @@
* THE SOFTWARE.
*/

/**
* CSS class names used in component.
*/
export const cssClasses = {
CELL: 'mdc-data-table__cell',
CELL_NUMERIC: 'mdc-data-table__cell--numeric',
CONTENT: 'mdc-data-table__content',
HEADER_CELL_SORTED: 'mdc-data-table__header-cell--sorted',
HEADER_CELL_SORTED_DESCENDING:
'mdc-data-table__header-cell--sorted-descending',
HEADER_CELL_WITH_SORT: 'mdc-data-table__header-cell--with-sort',
HEADER_ROW: 'mdc-data-table__header-row',
HEADER_ROW_CHECKBOX: 'mdc-data-table__header-row-checkbox',
ROOT: 'mdc-data-table',
ROW: 'mdc-data-table__row',
ROW_CHECKBOX: 'mdc-data-table__row-checkbox',
ROW_SELECTED: 'mdc-data-table__row--selected',
SORT_ICON_BUTTON: 'mdc-data-table__sort-icon-button',
};

/**
* List of data attributes used in component.
*/
export const dataAttributes = {
ROW_ID: 'data-row-id',
COLUMND_ID: 'data-columnd-id',
};

/**
* Attributes and selectors used in component.
*/
export const strings = {
ARIA_SELECTED: 'aria-selected',
DATA_ROW_ID_ATTR: 'data-row-id',
ARIA_SORT: 'aria-sort',
DATA_ROW_ID_ATTR:
dataAttributes.ROW_ID, // deprecated. Moved to `dataAttributes`.
HEADER_ROW_CHECKBOX_SELECTOR: `.${cssClasses.HEADER_ROW_CHECKBOX}`,
ROW_CHECKBOX_SELECTOR: `.${cssClasses.ROW_CHECKBOX}`,
ROW_SELECTED_SELECTOR: `.${cssClasses.ROW_SELECTED}`,
ROW_SELECTOR: `.${cssClasses.ROW}`,
};

/**
* Sort values defined by ARIA.
* See https://www.w3.org/WAI/PF/aria/states_and_properties#aria-sort
*/
export enum SortValue {
// Items are sorted in ascending order by this column.
ASCENDING = 'ascending',

// Items are sorted in descending order by this column.
DESCENDING = 'descending',

// There is no defined sort applied to the column.
NONE = 'none',

// A sort algorithm other than ascending or descending has been applied.
OTHER = 'other',
}

/**
* Event names used in component.
*/
export const events = {
ROW_SELECTION_CHANGED: 'MDCDataTable:rowSelectionChanged',
SELECTED_ALL: 'MDCDataTable:selectedAll',
UNSELECTED_ALL: 'MDCDataTable:unselectedAll',
SORTED: 'MDCDataTable:sorted',
};
75 changes: 74 additions & 1 deletion packages/mdc-data-table/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@
*/

import {MDCFoundation} from '@material/base/foundation';

import {MDCDataTableAdapter} from './adapter';
import {cssClasses, strings} from './constants';
import {cssClasses, SortValue, strings} from './constants';
import {SortActionEventData} from './types';

export class MDCDataTableFoundation extends MDCFoundation<MDCDataTableAdapter> {
static get defaultAdapter(): MDCDataTableAdapter {
return {
addClassAtRowIndex: () => undefined,
getAttributeByHeaderCellIndex: () => '',
getHeaderCellCount: () => 0,
getHeaderCellElements: () => [],
getRowCount: () => 0,
getRowElements: () => [],
getRowIdAtIndex: () => '',
Expand All @@ -39,11 +44,15 @@ export class MDCDataTableFoundation extends MDCFoundation<MDCDataTableAdapter> {
isRowsSelectable: () => false,
notifyRowSelectionChanged: () => undefined,
notifySelectedAll: () => undefined,
notifySortAction: () => undefined,
notifyUnselectedAll: () => undefined,
registerHeaderRowCheckbox: () => undefined,
registerRowCheckboxes: () => undefined,
removeClassAtRowIndex: () => undefined,
removeClassNameByHeaderCellIndex: () => undefined,
setAttributeAtRowIndex: () => undefined,
setAttributeByHeaderCellIndex: () => undefined,
setClassNameByHeaderCellIndex: () => undefined,
setHeaderRowCheckboxChecked: () => undefined,
setHeaderRowCheckboxIndeterminate: () => undefined,
setRowCheckboxCheckedAtIndex: () => undefined,
Expand Down Expand Up @@ -87,6 +96,13 @@ export class MDCDataTableFoundation extends MDCFoundation<MDCDataTableAdapter> {
return this.adapter_.getRowElements();
}

/**
* @return Array of header cell elements.
*/
getHeaderCells(): Element[] {
return this.adapter_.getHeaderCellElements();
}

/**
* Sets selected row ids. Overwrites previously selected rows.
* @param rowIds Array of row ids that needs to be selected.
Expand Down Expand Up @@ -158,6 +174,63 @@ export class MDCDataTableFoundation extends MDCFoundation<MDCDataTableAdapter> {
this.adapter_.notifyRowSelectionChanged({rowId, rowIndex, selected});
}

/**
* Handles sort action on sortable header cell.
*/
handleSortAction(eventData: SortActionEventData) {
const {columnId, columnIndex, headerCell} = eventData;

// Reset sort attributes / classes on other header cells.
for (let index = 0; index < this.adapter_.getHeaderCellCount(); index++) {
if (index === columnIndex) {
continue;
}

this.adapter_.removeClassNameByHeaderCellIndex(
index, cssClasses.HEADER_CELL_SORTED);
this.adapter_.removeClassNameByHeaderCellIndex(
index, cssClasses.HEADER_CELL_SORTED_DESCENDING);
this.adapter_.setAttributeByHeaderCellIndex(
index, strings.ARIA_SORT, SortValue.NONE);
}

// Set appropriate sort attributes / classes on target header cell.
this.adapter_.setClassNameByHeaderCellIndex(
columnIndex, cssClasses.HEADER_CELL_SORTED);

const currentSortValue = this.adapter_.getAttributeByHeaderCellIndex(
columnIndex, strings.ARIA_SORT);
let sortValue = SortValue.NONE;

// Set to descending if sorted on ascending order.
if (currentSortValue === SortValue.ASCENDING) {
this.adapter_.setClassNameByHeaderCellIndex(
columnIndex, cssClasses.HEADER_CELL_SORTED_DESCENDING);
this.adapter_.setAttributeByHeaderCellIndex(
columnIndex, strings.ARIA_SORT, SortValue.DESCENDING);
sortValue = SortValue.DESCENDING;
// Set to ascending if sorted on descending order.
} else if (currentSortValue === SortValue.DESCENDING) {
this.adapter_.removeClassNameByHeaderCellIndex(
columnIndex, cssClasses.HEADER_CELL_SORTED_DESCENDING);
this.adapter_.setAttributeByHeaderCellIndex(
columnIndex, strings.ARIA_SORT, SortValue.ASCENDING);
sortValue = SortValue.ASCENDING;
} else {
// Set to ascending by default when not sorted.
this.adapter_.setAttributeByHeaderCellIndex(
columnIndex, strings.ARIA_SORT, SortValue.ASCENDING);
sortValue = SortValue.ASCENDING;
}

this.adapter_.notifySortAction({
columnId,
columnIndex,
headerCell,
sortValue,
});
}

/**
* Updates header row checkbox state based on number of rows selected.
*/
Expand Down
Loading

0 comments on commit 6ee0355

Please sign in to comment.