Skip to content

Commit

Permalink
feat(table): support sticky headers, footers, and columns (#11483)
Browse files Browse the repository at this point in the history
* feat(table): support sticky headers, footers, and columns

* review

* support rtl sticky columns

* move sticky to mixin

* add bidi to bazel BUILD for spec

* fix prerender; reverse rtl

* minor revisions

* minor changes for g3 internal tests
  • Loading branch information
andrewseguin authored Jun 7, 2018
1 parent 966910a commit edbbc1b
Show file tree
Hide file tree
Showing 34 changed files with 1,745 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projects": {
"staging": "material2-dev"
}
}
2 changes: 2 additions & 0 deletions src/cdk/table/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ng_module(
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
module_name = "@angular/cdk/table",
deps = [
"//src/cdk/bidi",
"//src/cdk/collections",
"//src/cdk/coercion",
"@rxjs",
Expand All @@ -21,6 +22,7 @@ ts_library(
srcs = glob(["**/*.spec.ts"]),
deps = [
":table",
"//src/cdk/bidi",
"//src/cdk/collections",
"@rxjs",
"@rxjs//operators"
Expand Down
68 changes: 68 additions & 0 deletions src/cdk/table/can-stick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {coerceBooleanProperty} from '@angular/cdk/coercion';

/** @docs-private */
export type Constructor<T> = new(...args: any[]) => T;

/**
* Interface for a mixin to provide a directive with a function that checks if the sticky input has
* been changed since the last time the function was called. Essentially adds a dirty-check to the
* sticky value.
* @docs-private
*/
export interface CanStick {
/** Whether sticky positioning should be applied. */
sticky: boolean;

/** Whether the sticky input has changed since it was last checked. */
_hasStickyChanged: boolean;

/** Whether the sticky value has changed since this was last called. */
hasStickyChanged(): boolean;

/** Resets the dirty check for cases where the sticky state has been used without checking. */
resetStickyChanged(): void;
}

/**
* Mixin to provide a directive with a function that checks if the sticky input has been
* changed since the last time the function was called. Essentially adds a dirty-check to the
* sticky value.
*/
export function mixinHasStickyInput<T extends Constructor<{}>>(base: T):
Constructor<CanStick> & T {
return class extends base {
/** Whether sticky positioning should be applied. */
get sticky(): boolean { return this._sticky; }
set sticky(v: boolean) {
const prevValue = this._sticky;
this._sticky = coerceBooleanProperty(v);
this._hasStickyChanged = prevValue !== this._sticky;
}
_sticky: boolean = false;

/** Whether the sticky input has changed since it was last checked. */
_hasStickyChanged: boolean = false;

/** Whether the sticky value has changed since this was last called. */
hasStickyChanged(): boolean {
const hasStickyChanged = this._hasStickyChanged;
this._hasStickyChanged = false;
return hasStickyChanged;
}

/** Resets the dirty check for cases where the sticky state has been used without checking. */
resetStickyChanged() {
this._hasStickyChanged = false;
}

constructor(...args: any[]) { super(...args); }
};
}
28 changes: 26 additions & 2 deletions src/cdk/table/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/

import {ContentChild, Directive, ElementRef, Input, TemplateRef} from '@angular/core';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {CanStick, mixinHasStickyInput} from './can-stick';

/** Base interface for a cell definition. Captures a column's cell template definition. */
export interface CellDef {
Expand Down Expand Up @@ -40,12 +42,20 @@ export class CdkFooterCellDef implements CellDef {
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
}

// Boilerplate for applying mixins to CdkColumnDef.
/** @docs-private */
export class CdkColumnDefBase {}
export const _CdkColumnDefBase = mixinHasStickyInput(CdkColumnDefBase);

/**
* Column definition for the CDK table.
* Defines a set of cells available for a table column.
*/
@Directive({selector: '[cdkColumnDef]'})
export class CdkColumnDef {
@Directive({
selector: '[cdkColumnDef]',
inputs: ['sticky']
})
export class CdkColumnDef extends _CdkColumnDefBase implements CanStick {
/** Unique name for this column. */
@Input('cdkColumnDef')
get name(): string { return this._name; }
Expand All @@ -59,6 +69,20 @@ export class CdkColumnDef {
}
_name: string;

/**
* Whether this column should be sticky positioned on the end of the row. Should make sure
* that it mimics the `CanStick` mixin such that `_hasStickyChanged` is set to true if the value
* has been changed.
*/
@Input('stickyEnd')
get stickyEnd(): boolean { return this._stickyEnd; }
set stickyEnd(v: boolean) {
const prevValue = this._stickyEnd;
this._stickyEnd = coerceBooleanProperty(v);
this._hasStickyChanged = prevValue !== this._stickyEnd;
}
_stickyEnd: boolean = false;

/** @docs-private */
@ContentChild(CdkCellDef) cell: CdkCellDef;

Expand Down
2 changes: 2 additions & 0 deletions src/cdk/table/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export * from './table';
export * from './cell';
export * from './row';
export * from './table-module';
export * from './sticky-styler';
export * from './can-stick';

/** Re-export DataSource for a more intuitive experience for users of just the table. */
export {DataSource} from '@angular/cdk/collections';
50 changes: 33 additions & 17 deletions src/cdk/table/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ViewEncapsulation,
} from '@angular/core';
import {CdkCellDef, CdkColumnDef} from './cell';
import {CanStick, mixinHasStickyInput} from './can-stick';

/**
* The row template that can be used by the mat-table. Should not be used outside of the
Expand All @@ -44,8 +45,8 @@ export abstract class BaseRowDef implements OnChanges {
ngOnChanges(changes: SimpleChanges): void {
// Create a new columns differ if one does not yet exist. Initialize it based on initial value
// of the columns property or an empty array if none is provided.
const columns = changes['columns'].currentValue || [];
if (!this._columnsDiffer) {
const columns = (changes['columns'] && changes['columns'].currentValue) || [];
this._columnsDiffer = this._differs.find(columns).create();
this._columnsDiffer.diff(columns);
}
Expand All @@ -60,44 +61,64 @@ export abstract class BaseRowDef implements OnChanges {
}

/** Gets this row def's relevant cell template from the provided column def. */
abstract extractCellTemplate(column: CdkColumnDef): TemplateRef<any>;
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
if (this instanceof CdkHeaderRowDef) {
return column.headerCell.template;
} if (this instanceof CdkFooterRowDef) {
return column.footerCell.template;
} else {
return column.cell.template;
}
}
}

// Boilerplate for applying mixins to CdkHeaderRowDef.
/** @docs-private */
export class CdkHeaderRowDefBase extends BaseRowDef {}
export const _CdkHeaderRowDefBase = mixinHasStickyInput(CdkHeaderRowDefBase);

/**
* Header row definition for the CDK table.
* Captures the header row's template and other header properties such as the columns to display.
*/
@Directive({
selector: '[cdkHeaderRowDef]',
inputs: ['columns: cdkHeaderRowDef'],
inputs: ['columns: cdkHeaderRowDef', 'sticky: cdkHeaderRowDefSticky'],
})
export class CdkHeaderRowDef extends BaseRowDef {
export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements CanStick, OnChanges {
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
super(template, _differs);
}

/** Gets this row def's relevant cell template from the provided column def. */
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
return column.headerCell.template;
// Prerender fails to recognize that ngOnChanges in a part of this class through inheritance.
// Explicitly define it so that the method is called as part of the Angular lifecycle.
ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
}
}

// Boilerplate for applying mixins to CdkFooterRowDef.
/** @docs-private */
export class CdkFooterRowDefBase extends BaseRowDef {}
export const _CdkFooterRowDefBase = mixinHasStickyInput(CdkFooterRowDefBase);

/**
* Footer row definition for the CDK table.
* Captures the footer row's template and other footer properties such as the columns to display.
*/
@Directive({
selector: '[cdkFooterRowDef]',
inputs: ['columns: cdkFooterRowDef'],
inputs: ['columns: cdkFooterRowDef', 'sticky: cdkFooterRowDefSticky'],
})
export class CdkFooterRowDef extends BaseRowDef {
export class CdkFooterRowDef extends _CdkFooterRowDefBase implements CanStick, OnChanges {
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
super(template, _differs);
}

/** Gets this row def's relevant cell template from the provided column def. */
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
return column.footerCell.template;
// Prerender fails to recognize that ngOnChanges in a part of this class through inheritance.
// Explicitly define it so that the method is called as part of the Angular lifecycle.
ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
}
}

Expand All @@ -124,11 +145,6 @@ export class CdkRowDef<T> extends BaseRowDef {
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
super(template, _differs);
}

/** Gets this row def's relevant cell template from the provided column def. */
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
return column.cell.template;
}
}

/** Context provided to the row cells when `multiTemplateDataRows` is false */
Expand Down
Loading

0 comments on commit edbbc1b

Please sign in to comment.