Skip to content

Commit c4ff88f

Browse files
author
Fernando Gonzalez Goncharov
committed
enhancement/4 - add dynamic table module
Closes #4
1 parent 34a19d0 commit c4ff88f

17 files changed

Lines changed: 981 additions & 2 deletions

projects/ng-rocketparts/src/lib/components/dynamic-table/components/dynamic-table-cell/dynamic-table-cell.component.html

Whitespace-only changes.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
ElementRef,
5+
HostBinding,
6+
Input,
7+
OnChanges,
8+
OnInit,
9+
SimpleChanges,
10+
ViewEncapsulation
11+
} from '@angular/core';
12+
import { get, uniq } from 'lodash';
13+
14+
import { DynamicTableRowData } from '../../models/dynamic-table.model';
15+
import { DynamicTableDecoratorService } from '../../services/dynamic-table-decorator.service';
16+
17+
@Component({
18+
selector: '[rpDynamicTableCell]', // tslint:disable-line:component-selector
19+
templateUrl: './dynamic-table-cell.component.html',
20+
changeDetection: ChangeDetectionStrategy.OnPush,
21+
encapsulation: ViewEncapsulation.None
22+
})
23+
export class DynamicTableCellComponent implements OnInit, OnChanges {
24+
@Input()
25+
decorator: string;
26+
27+
@Input()
28+
decoratorOptions: any;
29+
30+
@Input()
31+
data: DynamicTableRowData;
32+
33+
@Input()
34+
path: string;
35+
36+
@Input()
37+
classPrefix = 'dynamic-table-cell';
38+
39+
@HostBinding('class')
40+
get classNames() {
41+
const externalClasses = this.el.nativeElement.className.split(' ');
42+
const decoratorClass = this.decorator
43+
? `${this.classPrefix}--${this.decorator}`
44+
: '';
45+
const styleHintClasses =
46+
this.decoratorOptions && this.decoratorOptions.styleHints
47+
? this.decoratorOptions.styleHints.map(
48+
styleHint => `${this.classPrefix}--${styleHint}`
49+
)
50+
: [];
51+
const cellStyleHints =
52+
this.decoratorOptions && this.decoratorOptions.styleHintPath && this.data
53+
? get<string[]>(this.data, this.decoratorOptions.styleHintPath)
54+
: [];
55+
const cellStyleHintClasses = cellStyleHints.map(
56+
styleHint => `${this.classPrefix}--${styleHint}`
57+
);
58+
return uniq(
59+
[
60+
...externalClasses,
61+
this.classPrefix,
62+
decoratorClass,
63+
...styleHintClasses
64+
].filter(Boolean)
65+
).join(' ');
66+
}
67+
68+
@HostBinding('innerHtml')
69+
content: string;
70+
71+
constructor(
72+
private decoratorService: DynamicTableDecoratorService,
73+
private el: ElementRef
74+
) {}
75+
76+
ngOnInit() {}
77+
78+
ngOnChanges(changes: SimpleChanges) {
79+
if (
80+
changes['decorator'] ||
81+
changes['decoratorOptions'] ||
82+
changes['data'] ||
83+
changes['path']
84+
) {
85+
this._updateContent();
86+
}
87+
}
88+
89+
private _updateContent() {
90+
this.content = this.decoratorService.applyDecorator(
91+
this.decorator,
92+
this.data,
93+
this.path,
94+
this.decoratorOptions
95+
);
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div #componentContainer></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import {
2+
AfterViewInit,
3+
ChangeDetectionStrategy,
4+
Component,
5+
ComponentFactoryResolver,
6+
ComponentRef,
7+
EventEmitter,
8+
HostBinding,
9+
Injector,
10+
Input,
11+
OnChanges,
12+
OnInit,
13+
Output,
14+
SimpleChange,
15+
SimpleChanges,
16+
ViewChild,
17+
ViewContainerRef,
18+
ViewEncapsulation
19+
} from '@angular/core';
20+
import { Subscription } from 'rxjs';
21+
import { uniq } from 'lodash';
22+
23+
import {
24+
ComponentRegistries,
25+
getInjectionTokenForComponentType
26+
} from '../../helpers/dynamic-component.helper';
27+
import { DynamicTableRowData } from '../../models/dynamic-table.model';
28+
29+
/**
30+
* Component that is used with the `component` decorator options to inject a custom component into the table cell
31+
*/
32+
@Component({
33+
selector: '[rpDynamicTableComponentCell]', // tslint:disable-line:component-selector
34+
templateUrl: './dynamic-table-component-cell.component.html',
35+
changeDetection: ChangeDetectionStrategy.OnPush,
36+
encapsulation: ViewEncapsulation.None
37+
})
38+
export class DynamicTableComponentCellComponent
39+
implements OnInit, OnChanges, AfterViewInit {
40+
@Input()
41+
component: string;
42+
43+
@Input()
44+
componentOptions: any;
45+
46+
@Input()
47+
decoratorOptions: any;
48+
49+
@Input()
50+
data: DynamicTableRowData;
51+
52+
@Input()
53+
path: string;
54+
55+
@Input()
56+
classPrefix = 'dynamic-table-cell';
57+
58+
@Output()
59+
event: EventEmitter<any> = new EventEmitter();
60+
61+
@ViewChild('componentContainer', { read: ViewContainerRef })
62+
componentContainer: ViewContainerRef;
63+
64+
private isViewInitialized = false;
65+
66+
private componentRef: ComponentRef<DynamicTableComponentCell>;
67+
68+
private componentEventSubscription: Subscription;
69+
70+
constructor(
71+
private injector: Injector,
72+
private componentFactoryResolver: ComponentFactoryResolver
73+
) {}
74+
75+
@HostBinding('class')
76+
get classNames() {
77+
const styleHintClasses =
78+
this.decoratorOptions && this.decoratorOptions.styleHints
79+
? this.decoratorOptions.styleHints.map(
80+
styleHint => `${this.classPrefix}--${styleHint}`
81+
)
82+
: [];
83+
return uniq(
84+
[
85+
'dynamic-table-cell',
86+
'dynamic-table-cell--component',
87+
...styleHintClasses
88+
].filter(Boolean)
89+
).join(' ');
90+
}
91+
92+
ngOnInit() {}
93+
94+
ngAfterViewInit() {
95+
this.isViewInitialized = true;
96+
this._updateComponent({});
97+
}
98+
99+
ngOnChanges(changes: SimpleChanges) {
100+
if (changes['component']) {
101+
this._updateComponent(changes, true);
102+
} else {
103+
this._updateComponent(changes);
104+
}
105+
}
106+
107+
/**
108+
* Update the injected component
109+
* @param changes
110+
* @param recreate
111+
*/
112+
private _updateComponent(changes: SimpleChanges, recreate = false) {
113+
let compChanges = {};
114+
if (!this.componentRef || recreate) {
115+
if (this.componentRef && recreate) {
116+
this.componentRef.destroy();
117+
}
118+
const injectionToken = getInjectionTokenForComponentType<
119+
DynamicTableComponentCell
120+
>(ComponentRegistries.TABLE_CELL, this.component);
121+
if (injectionToken) {
122+
const type = this.injector.get(injectionToken);
123+
const factory = this.componentFactoryResolver.resolveComponentFactory(
124+
type
125+
);
126+
this.componentRef = this.componentContainer.createComponent(factory);
127+
compChanges = {
128+
data: new SimpleChange(null, this.data, true),
129+
path: new SimpleChange(null, this.path, true),
130+
options: new SimpleChange(null, this.componentOptions, true)
131+
};
132+
}
133+
} else {
134+
if (changes['data']) {
135+
compChanges['data'] = changes['data'];
136+
}
137+
if (changes['path']) {
138+
compChanges['path'] = changes['path'];
139+
}
140+
if (changes['componentOptions']) {
141+
compChanges['options'] = changes['componentOptions'];
142+
}
143+
}
144+
145+
if (this.componentRef && this.componentRef.instance) {
146+
this.componentRef.instance.data = this.data;
147+
this.componentRef.instance.path = this.path;
148+
this.componentRef.instance.options = this.componentOptions;
149+
150+
if (this.componentEventSubscription) {
151+
this.componentEventSubscription.unsubscribe();
152+
}
153+
if (this.componentRef.instance.event) {
154+
this.componentEventSubscription = this.componentRef.instance.event.subscribe(
155+
this.event
156+
);
157+
}
158+
159+
if (this.componentRef.instance.ngOnChanges) {
160+
this.componentRef.instance.ngOnChanges(compChanges);
161+
}
162+
}
163+
}
164+
}
165+
166+
export interface DynamicTableComponentCell extends OnChanges {
167+
data: DynamicTableRowData;
168+
169+
options: any;
170+
171+
path: string;
172+
173+
event?: EventEmitter<any>;
174+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<a [href]="link">
2+
{{ label }}<i *ngIf="options.icon" class="external-link-icon far fa-{{options.icon}}"></i>
3+
</a>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
Component,
3+
Input,
4+
OnChanges,
5+
OnInit,
6+
SimpleChanges,
7+
ViewEncapsulation
8+
} from '@angular/core';
9+
import { get } from 'lodash';
10+
11+
import { DynamicTableRowData } from '../../models/dynamic-table.model';
12+
import { DynamicTableComponentCell } from '../dynamic-table-component-cell/dynamic-table-component-cell.component';
13+
14+
@Component({
15+
selector: 'rp-dynamic-table-link-cell',
16+
templateUrl: './dynamic-table-link-cell.component.html',
17+
encapsulation: ViewEncapsulation.None
18+
})
19+
export class DynamicTableLinkCellComponent
20+
implements OnInit, OnChanges, DynamicTableComponentCell {
21+
@Input()
22+
data: DynamicTableRowData;
23+
@Input()
24+
options: any;
25+
@Input()
26+
path: string;
27+
28+
label = '';
29+
link = '';
30+
31+
constructor() {}
32+
33+
ngOnInit() {}
34+
35+
ngOnChanges(changes: SimpleChanges): void {
36+
if (changes['data'] || (changes['path'] && this.data && this.path)) {
37+
this._updateComponent();
38+
}
39+
}
40+
41+
private _updateComponent() {
42+
this.label = get(this.data, this.path) || this.options.label;
43+
this.link = get(this.data, this.options.linkPath as string);
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<ng-container *ngFor="let column of config.columns">
2+
<td *ngIf="column.decorator === 'component'; else regularCell" rpDynamicTableComponentCell [component]="column.decoratorOptions?.component"
3+
[decoratorOptions]="column.decoratorOptions" [componentOptions]="column.decoratorOptions?.componentOptions" [data]="data"
4+
[path]="column.path" (event)="onComponentEvent($event)"></td>
5+
<ng-template #regularCell>
6+
<td rpDynamicTableCell [decorator]="column.decorator" [decoratorOptions]="column.decoratorOptions" [data]="data" [path]="column.path"></td>
7+
</ng-template>
8+
</ng-container>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
Component,
3+
EventEmitter,
4+
Input,
5+
OnInit,
6+
Output,
7+
ViewEncapsulation
8+
} from '@angular/core';
9+
10+
import {
11+
DynamicTableConfig,
12+
DynamicTableRowData
13+
} from '../../models/dynamic-table.model';
14+
15+
@Component({
16+
selector: '[rpDynamicTableRow]', // tslint:disable-line:component-selector
17+
templateUrl: './dynamic-table-row.component.html',
18+
encapsulation: ViewEncapsulation.None
19+
})
20+
export class DynamicTableRowComponent implements OnInit {
21+
@Input()
22+
config: DynamicTableConfig;
23+
24+
@Input()
25+
data: DynamicTableRowData;
26+
27+
@Input()
28+
selected: boolean;
29+
30+
@Output()
31+
toggleSelection: EventEmitter<boolean> = new EventEmitter<boolean>();
32+
33+
@Output()
34+
componentEvent: EventEmitter<any> = new EventEmitter();
35+
36+
constructor() {}
37+
38+
ngOnInit() {}
39+
40+
onComponentEvent(event: any) {
41+
this.componentEvent.next(event);
42+
}
43+
}

0 commit comments

Comments
 (0)