Skip to content

feat(table): add text column for simple columns #14841

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

Merged
merged 8 commits into from
Apr 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions src/cdk/table/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface CellDef {
*/
@Directive({selector: '[cdkCellDef]'})
export class CdkCellDef implements CellDef {
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
constructor(/** @docs-private */ public template: TemplateRef<any>) {}
}

/**
Expand All @@ -31,7 +31,7 @@ export class CdkCellDef implements CellDef {
*/
@Directive({selector: '[cdkHeaderCellDef]'})
export class CdkHeaderCellDef implements CellDef {
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
constructor(/** @docs-private */ public template: TemplateRef<any>) {}
}

/**
Expand All @@ -40,13 +40,13 @@ export class CdkHeaderCellDef implements CellDef {
*/
@Directive({selector: '[cdkFooterCellDef]'})
export class CdkFooterCellDef implements CellDef {
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
constructor(/** @docs-private */ public template: TemplateRef<any>) {}
}

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

/**
Expand All @@ -56,19 +56,20 @@ export const _CdkColumnDefBase: CanStickCtor & typeof CdkColumnDefBase =
@Directive({
selector: '[cdkColumnDef]',
inputs: ['sticky'],
providers: [{
provide: 'MAT_SORT_HEADER_COLUMN_DEF',
useExisting: CdkColumnDef
}],
providers: [{provide: 'MAT_SORT_HEADER_COLUMN_DEF', useExisting: CdkColumnDef}],
})
export class CdkColumnDef extends _CdkColumnDefBase implements CanStick {
/** Unique name for this column. */
@Input('cdkColumnDef')
get name(): string { return this._name; }
get name(): string {
return this._name;
}
set name(name: string) {
// If the directive is set without a name (updated programatically), then this setter will
// trigger with an empty string and should not overwrite the programatically set value.
if (!name) { return; }
if (!name) {
return;
}

this._name = name;
this.cssClassFriendlyName = name.replace(/[^a-z0-9_-]/ig, '-');
Expand All @@ -81,7 +82,9 @@ export class CdkColumnDef extends _CdkColumnDefBase implements CanStick {
* has been changed.
*/
@Input('stickyEnd')
get stickyEnd(): boolean { return this._stickyEnd; }
get stickyEnd(): boolean {
return this._stickyEnd;
}
set stickyEnd(v: boolean) {
const prevValue = this._stickyEnd;
this._stickyEnd = coerceBooleanProperty(v);
Expand Down
1 change: 1 addition & 0 deletions src/cdk/table/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './row';
export * from './table-module';
export * from './sticky-styler';
export * from './can-stick';
export * from './text-column';

/** Re-export DataSource for a more intuitive experience for users of just the table. */
export {DataSource} from '@angular/cdk/collections';
8 changes: 8 additions & 0 deletions src/cdk/table/table-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,11 @@ export function getTableMissingRowDefsError() {
export function getTableUnknownDataSourceError() {
return Error(`Provided data source did not match an array, Observable, or DataSource`);
}

/**
* Returns an error to be thrown when the text column cannot find a parent table to inject.
* @docs-private
*/
export function getTableTextColumnMissingParentTableError() {
return Error(`Text column could not find a parent table for registration.`);
}
2 changes: 2 additions & 0 deletions src/cdk/table/table-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCell, CdkCellDef,
CdkFooterCellDef, CdkFooterCell
} from './cell';
import {CdkTextColumn} from './text-column';

const EXPORTED_DECLARATIONS = [
CdkTable,
Expand All @@ -37,6 +38,7 @@ const EXPORTED_DECLARATIONS = [
DataRowOutlet,
HeaderRowOutlet,
FooterRowOutlet,
CdkTextColumn,
];

@NgModule({
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2326,7 +2326,7 @@ function getActualTableContent(tableElement: Element): string[][] {
return actualTableContent.map(row => row.map(cell => cell.textContent!.trim()));
}

function expectTableToMatchContent(tableElement: Element, expected: any[]) {
export function expectTableToMatchContent(tableElement: Element, expected: any[]) {
const missedExpectations: string[] = [];
function checkCellContent(actualCell: string, expectedCell: string) {
if (actualCell !== expectedCell) {
Expand Down
184 changes: 184 additions & 0 deletions src/cdk/table/text-column.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';

import {getTableTextColumnMissingParentTableError} from './table-errors';
import {CdkTableModule} from './table-module';
import {expectTableToMatchContent} from './table.spec';
import {TEXT_COLUMN_OPTIONS, TextColumnOptions} from './text-column';


describe('CdkTextColumn', () => {
let fixture: ComponentFixture<BasicTextColumnApp>;
let component: BasicTextColumnApp;
let tableElement: HTMLElement;

beforeEach(async(() => {
TestBed
.configureTestingModule({
imports: [CdkTableModule],
declarations: [
BasicTextColumnApp,
MissingTableApp,
],
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(BasicTextColumnApp);
component = fixture.componentInstance;
fixture.detectChanges();

tableElement = fixture.nativeElement.querySelector('.cdk-table');
});

it('should render the basic columns', () => {
expectTableToMatchContent(tableElement, [
['PropertyA', 'PropertyB', 'PropertyC'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
]);
});

it('should throw an error if the text column is not in the content of a table', () => {
expect(() => TestBed.createComponent(MissingTableApp).detectChanges())
.toThrowError(getTableTextColumnMissingParentTableError().message);
});

it('should allow for alternate header text', () => {
component.headerTextB = 'column-b';
fixture.detectChanges();

expectTableToMatchContent(tableElement, [
['PropertyA', 'column-b', 'PropertyC'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
]);
});

it('should allow for custom data accessor', () => {
component.dataAccessorA = (data: TestData) => data.propertyA + '!';
fixture.detectChanges();

expectTableToMatchContent(tableElement, [
['PropertyA', 'PropertyB', 'PropertyC'],
['a_1!', 'b_1', 'c_1'],
['a_2!', 'b_2', 'c_2'],
]);
});

it('should allow for custom data accessor', () => {
component.dataAccessorA = (data: TestData) => data.propertyA + '!';
fixture.detectChanges();

expectTableToMatchContent(tableElement, [
['PropertyA', 'PropertyB', 'PropertyC'],
['a_1!', 'b_1', 'c_1'],
['a_2!', 'b_2', 'c_2'],
]);
});

it('should update values when data changes', () => {
component.data = [
{propertyA: 'changed-a_1', propertyB: 'b_1', propertyC: 'c_1'},
{propertyA: 'changed-a_2', propertyB: 'b_2', propertyC: 'c_2'},
];
fixture.detectChanges();

expectTableToMatchContent(tableElement, [
['PropertyA', 'PropertyB', 'PropertyC'],
['changed-a_1', 'b_1', 'c_1'],
['changed-a_2', 'b_2', 'c_2'],
]);
});

describe('with options', () => {
function createTestComponent(options: TextColumnOptions<any>) {
// Reset the previously configured testing module to be able set new providers.
// The testing module has been initialized in the root describe group for the ripples.
TestBed.resetTestingModule();
TestBed.configureTestingModule({
imports: [CdkTableModule],
declarations: [BasicTextColumnApp],
providers: [{provide: TEXT_COLUMN_OPTIONS, useValue: options}]
});

fixture = TestBed.createComponent(BasicTextColumnApp);
fixture.detectChanges();

tableElement = fixture.nativeElement.querySelector('.cdk-table');
}

it('should be able to provide a header text transformation', () => {
const defaultHeaderTextTransform = (name: string) => `${name}!`;
createTestComponent({defaultHeaderTextTransform});

expectTableToMatchContent(tableElement, [
['propertyA!', 'propertyB!', 'propertyC!'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
]);
});

it('should be able to provide a general data accessor', () => {
const defaultDataAccessor = (data: TestData, name: string) => {
switch (name) {
case 'propertyA':
return `A: ${data.propertyA}`;
case 'propertyB':
return `B: ${data.propertyB}`;
case 'propertyC':
return `C: ${data.propertyC}`;
default:
return '';
}
};
createTestComponent({defaultDataAccessor});

expectTableToMatchContent(tableElement, [
['PropertyA', 'PropertyB', 'PropertyC'],
['A: a_1', 'B: b_1', 'C: c_1'],
['A: a_2', 'B: b_2', 'C: c_2'],
]);
});
});
});

interface TestData {
propertyA: string;
propertyB: string;
propertyC: string;
}

@Component({
template: `
<cdk-table [dataSource]="data">
<cdk-text-column name="propertyA" [dataAccessor]="dataAccessorA"></cdk-text-column>
<cdk-text-column name="propertyB" [headerText]="headerTextB"></cdk-text-column>
<cdk-text-column name="propertyC" [justify]="justifyC"></cdk-text-column>

<cdk-header-row *cdkHeaderRowDef="displayedColumns"></cdk-header-row>
<cdk-row *cdkRowDef="let row; columns: displayedColumns"></cdk-row>
</cdk-table>
`
})
class BasicTextColumnApp {
displayedColumns = ['propertyA', 'propertyB', 'propertyC'];

data: TestData[] = [
{propertyA: 'a_1', propertyB: 'b_1', propertyC: 'c_1'},
{propertyA: 'a_2', propertyB: 'b_2', propertyC: 'c_2'},
];

headerTextB: string;
dataAccessorA: (data: TestData) => string;
justifyC = 'start';
}

@Component({
template: `
<cdk-text-column name="column-a"></cdk-text-column>
`
})
class MissingTableApp {
}
Loading