Skip to content

feat(table): add optional footer row #10330

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 1 commit into from
Apr 23, 2018

Conversation

andrewseguin
Copy link
Contributor

Adds an optional footer row to the table. Since header row has similar logic, a refactoring of the table code was necessary to avoid duplicating work.

Note that the table.spec.ts was reworked as well to remove a lot of boilerplate (most tests begin by setting up a fixture, component, table element, etc.). Added a utility function at the top of the file to handle this.

Closes #7548

@andrewseguin andrewseguin added pr: needs review target: minor This PR is targeted for the next minor release labels Mar 8, 2018
@andrewseguin andrewseguin requested a review from jelbourn March 8, 2018 22:09
@andrewseguin andrewseguin requested a review from amcdnl as a code owner March 8, 2018 22:09
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Mar 8, 2018
@@ -8,12 +8,17 @@

import {ContentChild, Directive, ElementRef, Input, TemplateRef} from '@angular/core';

/** Base class for a cell definition. Captures a column's cell template definition. */
export interface BaseCellDef {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically you don't use "Base" for interfaces. Just CellDef or CellDefinition?

})
export class CdkFooterCell {
constructor(columnDef: CdkColumnDef, elementRef: ElementRef) {
elementRef.nativeElement.classList.add(`cdk-column-${columnDef.cssClassFriendlyName}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a base class with the constructor so that all three directives don't duplicate it? Similar for the mat versions

the column a name. Each column definition then further defines both a header-cell template
(`cdkHeaderCellDef`) and a data-cell template (`cdkCellDef`).
the column a name. Each column definition then further defines a header-cell template
(`cdkHeaderCellDef`), data-cell template (`cdkCellDef`), and footer-cell
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"...and an optional footer-cell template"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below

the column a name. Each column definition then further defines a header-cell template
(`cdkHeaderCellDef`), data-cell template (`cdkCellDef`), and footer-cell
template (`cdkFooterCellDef`). Note that each of these cell definitions need to be present if they
are used by their respective row definition.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that each of these cell definitions need to be present if they are used by their respective row definition.

It's not clear to me what this means

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the sentence and clarified the previous one a bit.

Each column definition can contain a header-cell template
(cdkHeaderCellDef), data-cell template (cdkCellDef), and footer-cell
template (cdkFooterCellDef).

Technically they are all optional, but it depends on which row def uses it. E.g. if a header row def uses the column, it assumes a header cell def is present in the column

* @docs-private
*/
@Directive({selector: '[footerRowPlaceholder]'})
export class FooterRowPlaceholder {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change "Placeholder" to "Outlet" in this PR since we're adding a new one?

@@ -214,6 +238,14 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
*/
@ContentChild(CdkHeaderRowDef) _headerRowDef: CdkHeaderRowDef;

/**
* Template definition used as the header container. By default it stores the footer row
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"header" -> "footer"

// Make sure that the user has at least added a header row or row def.
if (!this._headerRowDef && !this._rowDefs.length) {
// Make sure that the user has at least added header, footer, or data row def.
if (!this._headerRowDef && !this._footerRowDef && !this._rowDefs.length) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the presence of a footer should negate the need for a header or rows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the only requirement is that the table must have defined either amat-header-row, mat-row. If you do not have either, then an error is thrown. If you do not have one of them, they just are not rendered.

This continues the check - if you do not have any rows defined, an error is thrown. Otherwise, anything excluded just is not rendered.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you would say it's valid to have a footer row template defined with no header and no data row templates?

Copy link
Contributor Author

@andrewseguin andrewseguin Mar 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes mostly for symmetry. I've seen requests for tables that have only a header row and some requests to hide the header row. Doing the same treatment for footer

Some context: #7604

* of where to place the new row template in the placeholder.
*/
private _renderRow(
placeholder: any, rowDef: BaseRowDef, context: any = {}, index = 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the context not CdkCellOutletRowContext?

mat-table {
height: 500px;
overflow: auto;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a demo- class around these?

@jelbourn
Copy link
Member

jelbourn commented Mar 9, 2018

You should sync your client as well

@andrewseguin
Copy link
Contributor Author

Synced

@andrewseguin
Copy link
Contributor Author

Ready for review

* of where to place the new row template in the outlet.
*/
private _renderRow(
outlet: any, rowDef: BaseRowDef, context: CdkCellOutletRowContext<T> = {}, index = 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outlet should be able to be more specific than any

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - added a base class for the row outlets

@andrewseguin andrewseguin force-pushed the table-footer branch 2 times, most recently from 3fda997 to a749798 Compare March 26, 2018 17:27
@jelbourn
Copy link
Member

Will need to be rebased after the native <table> change goes in; let me know when it's ready

@andrewseguin
Copy link
Contributor Author

Rebased

@@ -0,0 +1,16 @@
<h3>Table with Footer</h3>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to change this one to a native table?

@@ -43,21 +50,34 @@ import {
getTableUnknownDataSourceError
} from './table-errors';

export interface RowOutlet {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add description

@@ -66,8 +86,9 @@ export class HeaderRowPlaceholder {
* material library.
*/
export const CDK_TABLE_TEMPLATE = `
<ng-container headerRowPlaceholder></ng-container>
<ng-container rowPlaceholder></ng-container>`;
<ng-container headerRowOutlet></ng-container>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed this, but this constant should be @docs-private

// header row def change so that the content check will render the header row.
if (this._footerRowDef) {
this._footerRowDefChanged = true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this check (and the header one above) be replaced with

this._footerRowDefChanged = !!this._footerRowDef;

Or should it not be always set to false when _footerRowDef is undefined?


this._getCellTemplatesForRow(row).forEach(cell => {
this._getCellTemplates(rowDef).forEach(cellTemplate => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was like this before, but this would probably be better as a for of just to avoid the extra function invocations in the critical path.

tbody.appendChild(this._rowPlaceholder.elementRef.nativeElement);
thead.appendChild(this._headerRowOutlet.elementRef.nativeElement);
tbody.appendChild(this._rowOutlet.elementRef.nativeElement);
tfoot.appendChild(this._footerRowOutlet.elementRef.nativeElement);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid some repetition:

    const sections = [
      {tag: 'thead', outlet: this._headerRowOutlet},
      {tag: 'tbody', outlet: this._rowOutlet},
      {tag: 'tfoot', outlet: this._footerRowOutlet},
    ];
    
    for (const section of sections) {
      const element = document.createElement(section.tag);
      element.appendChild(section.outlet.elementRef.nativeElement);
      this._elementRef.nativeElement.appendChild(element);
    }

@andrewseguin
Copy link
Contributor Author

Cool thanks for the review -- good suggestions

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker and removed pr: needs review labels Mar 30, 2018
@mmalerba
Copy link
Contributor

@andrewseguin needs rebase, re-apply merge ready when done

@mmalerba mmalerba removed the action: merge The PR is ready for merge by the caretaker label Apr 18, 2018
@andrewseguin andrewseguin added the action: merge The PR is ready for merge by the caretaker label Apr 19, 2018
@andrewseguin andrewseguin merged commit 6df3709 into angular:master Apr 23, 2018
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Table] Add a footer row (e.g. totals)
4 participants