Skip to content
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

Angular support for icon table column #1448

Merged
merged 8 commits into from
Aug 24, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import { NimbleTextAreaModule, NimbleTextFieldModule, NimbleNumberFieldModule, N
import { NimbleLabelProviderCoreModule } from '@ni/nimble-angular/label-provider/core';
import { NimbleLabelProviderTableModule } from '@ni/nimble-angular/label-provider/table';
import { NimbleMappingTextModule } from '@ni/nimble-angular/mapping/text';
import { NimbleMappingIconModule } from '@ni/nimble-angular/mapping/icon';
import { NimbleMappingSpinnerModule } from '@ni/nimble-angular/mapping/spinner';
import { NimbleTableModule } from '@ni/nimble-angular/table';
import { NimbleTableColumnTextModule } from '@ni/nimble-angular/table-column/text';
import { NimbleTableColumnAnchorModule } from '@ni/nimble-angular/table-column/anchor';
import { NimbleTableColumnDateTextModule } from '@ni/nimble-angular/table-column/date-text';
import { NimbleTableColumnEnumTextModule } from '@ni/nimble-angular/table-column/enum-text';
import { NimbleTableColumnIconModule } from '@ni/nimble-angular/table-column/icon';
import { NimbleRichTextViewerModule } from '@ni/nimble-angular/rich-text-viewer';
import { AppComponent } from './app.component';
import { CustomAppComponent } from './customapp/customapp.component';
Expand Down Expand Up @@ -81,6 +84,9 @@ import { HeaderComponent } from './header/header.component';
NimbleMappingTextModule,
NimbleBannerModule,
NimbleRichTextViewerModule,
NimbleTableColumnIconModule,
NimbleMappingIconModule,
NimbleMappingSpinnerModule,
RouterModule.forRoot(
[
{ path: '', redirectTo: '/customapp', pathMatch: 'full' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,22 @@
<nimble-mapping-text key="101" text="Status message 2"></nimble-mapping-text>
Status
</nimble-table-column-enum-text>
<nimble-table-column-icon
field-name="result"
key-type="string"
>
<nimble-mapping-icon
key="success"
text="Success"
icon="nimble-icon-check"
severity="success">
</nimble-mapping-icon>
<nimble-mapping-spinner
key="unknown"
text="Unknown">
</nimble-mapping-spinner>
Result
</nimble-table-column-icon>
<nimble-table-column-text field-name="stringValue2" min-pixel-width="400" group-index="0">String 2</nimble-table-column-text>

<nimble-menu slot="action-menu">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface SimpleTableRecord extends TableRecord {
linkLabel?: string;
date: number;
statusCode: number;
result: string;
}

@Component({
Expand Down Expand Up @@ -109,7 +110,8 @@ export class CustomAppComponent {
href: '/customapp',
linkLabel: 'Link',
date: (tableData.length % 2 === 0) ? new Date(2023, 7, 16, 3, 56, 11).valueOf() : new Date(2022, 2, 7, 20, 28, 41).valueOf(),
statusCode: (tableData.length % 2 === 0) ? 100 : 101
statusCode: (tableData.length % 2 === 0) ? 100 : 101,
result: (tableData.length % 2 === 0) ? 'success' : 'unknown'
});
this.tableDataSubject.next(tableData);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "../../../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public-api.ts"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
import { type MappingIcon, mappingIconTag } from '@ni/nimble-components/dist/esm/mapping/icon';
import type { MappingKey } from '@ni/nimble-components/dist/esm/mapping/base/types';
import type { IconSeverity } from '@ni/nimble-components/dist/esm/icon-base/types';

export type { MappingIcon };
export { mappingIconTag };

/**
* Directive to provide Angular integration for the mapping icon element used by the icon column.
*/
@Directive({
selector: 'nimble-mapping-icon'
})
export class NimbleMappingIconDirective {
public get key(): MappingKey | undefined {
return this.elementRef.nativeElement.key;
}

@Input() public set key(value: MappingKey | undefined) {
this.renderer.setProperty(this.elementRef.nativeElement, 'key', value);
}

public get text(): string | undefined {
return this.elementRef.nativeElement.text;
}

@Input() public set text(value: string | undefined) {
this.renderer.setProperty(this.elementRef.nativeElement, 'text', value);
}

public get icon(): string | undefined {
return this.elementRef.nativeElement.icon;
}

@Input() public set icon(value: string | undefined) {
this.renderer.setProperty(this.elementRef.nativeElement, 'icon', value);
}

public get severity(): IconSeverity {
return this.elementRef.nativeElement.severity;
}

@Input() public set severity(value: IconSeverity) {
this.renderer.setProperty(this.elementRef.nativeElement, 'severity', value);
}

public constructor(protected readonly renderer: Renderer2, protected readonly elementRef: ElementRef<MappingIcon>) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NimbleMappingIconDirective } from './nimble-mapping-icon.directive';

import '@ni/nimble-components/dist/esm/mapping/icon';

@NgModule({
declarations: [NimbleMappingIconDirective],
imports: [CommonModule],
exports: [NimbleMappingIconDirective]
})
export class NimbleMappingIconModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './nimble-mapping-icon.directive';
export * from './nimble-mapping-icon.module';
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IconSeverity } from '@ni/nimble-angular';
import { NimbleTableModule } from '../../../table/nimble-table.module';
import { NimbleTableColumnIconModule } from '../../../table-column/icon/nimble-table-column-icon.module';
import { NimbleMappingIconDirective, type MappingIcon } from '../nimble-mapping-icon.directive';
import { NimbleMappingIconModule } from '../nimble-mapping-icon.module';

describe('NimbleMappingIcon', () => {
describe('module', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NimbleMappingIconModule]
});
});

it('custom element is defined', () => {
expect(customElements.get('nimble-mapping-text')).not.toBeUndefined();
});
});

describe('with template string values', () => {
@Component({
template: `
<nimble-table>
<nimble-table-column-icon key-type="boolean">
<nimble-mapping-icon
#mapping
key="false"
text="nope"
icon="nimble-icon-xmark"
severity="error"
>
</nimble-mapping-icon>
</nimble-table-column-icon>
</nimble-table>
`
})
class TestHostComponent {
@ViewChild('mapping', { read: NimbleMappingIconDirective }) public directive: NimbleMappingIconDirective;
@ViewChild('mapping', { read: ElementRef }) public elementRef: ElementRef<MappingIcon>;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleMappingIconDirective;
let nativeElement: MappingIcon;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleMappingIconModule, NimbleTableColumnIconModule, NimbleTableModule]
});

fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('will use template string values for key', () => {
expect(directive.key).toBe('false');
expect(nativeElement.key).toBe('false');
});

it('will use template string values for text', () => {
expect(directive.text).toBe('nope');
expect(nativeElement.text).toBe('nope');
});

it('will use template string values for icon', () => {
expect(directive.icon).toBe('nimble-icon-xmark');
expect(nativeElement.icon).toBe('nimble-icon-xmark');
});

it('will use template string values for severity', () => {
expect(directive.severity).toBe('error');
expect(nativeElement.severity).toBe('error');
});
});

describe('with property bound values', () => {
@Component({
template: `
<nimble-table>
<nimble-table-column-icon key-type="boolean">
<nimble-mapping-icon
#mapping
[key]="key"
[text]="text"
[icon]="icon"
[severity]="severity"
>
</nimble-mapping-icon>
</nimble-table-column-icon>
</nimble-table>
`
})
class TestHostComponent {
@ViewChild('mapping', { read: NimbleMappingIconDirective }) public directive: NimbleMappingIconDirective;
@ViewChild('mapping', { read: ElementRef }) public elementRef: ElementRef<MappingIcon>;
public key = false;
public text = 'nope';
public icon = 'nimble-icon-xmark';
public severity: IconSeverity = IconSeverity.error;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleMappingIconDirective;
let nativeElement: MappingIcon;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleMappingIconModule, NimbleTableColumnIconModule, NimbleTableModule]
});

fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('can be configured with property binding for key', () => {
expect(directive.key).toBeFalse();
expect(nativeElement.key).toBeFalse();

fixture.componentInstance.key = true;
fixture.detectChanges();

expect(directive.key).toBeTrue();
expect(nativeElement.key).toBeTrue();
});

it('can be configured with property binding for text', () => {
expect(directive.text).toBe('nope');
expect(nativeElement.text).toBe('nope');

fixture.componentInstance.text = 'yep';
fixture.detectChanges();

expect(directive.text).toBe('yep');
expect(nativeElement.text).toBe('yep');
});

it('can be configured with property binding for icon', () => {
expect(directive.icon).toBe('nimble-icon-xmark');
expect(nativeElement.icon).toBe('nimble-icon-xmark');

fixture.componentInstance.icon = 'nimble-icon-check';
fixture.detectChanges();

expect(directive.icon).toBe('nimble-icon-check');
expect(nativeElement.icon).toBe('nimble-icon-check');
});

it('can be configured with property binding for severity', () => {
expect(directive.severity).toBe('error');
expect(nativeElement.severity).toBe('error');

fixture.componentInstance.severity = IconSeverity.success;
fixture.detectChanges();

expect(directive.severity).toBe('success');
expect(nativeElement.severity).toBe('success');
});
});

describe('with attribute bound values', () => {
@Component({
template: `
<nimble-table>
<nimble-table-column-icon key-type="boolean">
<nimble-mapping-icon
#mapping
[attr.key]="key"
[attr.text]="text"
[attr.icon]="icon"
[attr.severity]="severity"
>
</nimble-mapping-icon>
</nimble-table-column-icon>
</nimble-table>
`
})
class TestHostComponent {
@ViewChild('mapping', { read: NimbleMappingIconDirective }) public directive: NimbleMappingIconDirective;
@ViewChild('mapping', { read: ElementRef }) public elementRef: ElementRef<MappingIcon>;
public key = false;
public text = 'nope';
public icon = 'nimble-icon-xmark';
public severity: IconSeverity = IconSeverity.error;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleMappingIconDirective;
let nativeElement: MappingIcon;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleMappingIconModule, NimbleTableColumnIconModule, NimbleTableModule]
});

fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('can be configured with attribute binding for key', () => {
expect(directive.key).toBe('false');
expect(nativeElement.key).toBe('false');

fixture.componentInstance.key = true;
fixture.detectChanges();

expect(directive.key).toBe('true');
expect(nativeElement.key).toBe('true');
});

it('can be configured with attribute binding for text', () => {
expect(directive.text).toBe('nope');
expect(nativeElement.text).toBe('nope');

fixture.componentInstance.text = 'yep';
fixture.detectChanges();

expect(directive.text).toBe('yep');
expect(nativeElement.text).toBe('yep');
});

it('can be configured with property binding for icon', () => {
expect(directive.icon).toBe('nimble-icon-xmark');
expect(nativeElement.icon).toBe('nimble-icon-xmark');

fixture.componentInstance.icon = 'nimble-icon-check';
fixture.detectChanges();

expect(directive.icon).toBe('nimble-icon-check');
expect(nativeElement.icon).toBe('nimble-icon-check');
});

it('can be configured with property binding for severity', () => {
expect(directive.severity).toBe('error');
expect(nativeElement.severity).toBe('error');

fixture.componentInstance.severity = IconSeverity.success;
fixture.detectChanges();

expect(directive.severity).toBe('success');
expect(nativeElement.severity).toBe('success');
});
});
});
Loading