nm-interaction-layout
src/app/shared/widgets/layoutcontainer/layoutcontainer.component.ts
layout implementation that allows the display and switching between multiple interactions.
The widget input "interaction" can be used to assign individual interactions the layout. While only the latest interaction will actually be visible, all interactions set via the input will be loaded but hidden.
Each interaction (identified by "identifier" or its JSON representation) will only be loaded_once_.
This allows the implementation of externally displayed tab components, without having to re-instantiate the interaction every time the active interaction is switched.
To further reduce resources, interactions can be detached from the app-service, by setting the widget's "keep-attached" configuration property to false. When a detached interaction is re-activated (i.e., when the interaction is submitted using the "interaction" input), all connections are re-subscribed withint the app-service.
selector | nm-interaction-layout |
template |
|
Widget inputs |
Widget outputs |
Properties |
|
Methods |
|
constructor(cdr: ChangeDetectorRef)
|
||||||
Parameters :
|
Public configureWidget | ||||||
configureWidget(configuration: WidgetConfig
|
||||||
Decorators : WidgetConfigure
|
||||||
Parameters :
Returns :
void
|
Public trackByFn | ||||||
trackByFn(index: , item: )
|
||||||
Parameters :
Returns :
any
|
Public active |
active:
|
Type : string
|
Public id |
id:
|
Decorators : WidgetId
|
Public interaction |
interaction:
|
Type : Subject<Content>
|
Default value : new ReplaySubject(1)
|
Decorators : WidgetInput
|
Public interactions |
interactions:
|
Type : Map<string | Content>
|
Default value : new Map<string, Content>()
|
Public keepAttached |
keepAttached:
|
Type : boolean
|
Public param |
param:
|
Type : Subject<any>
|
Default value : new ReplaySubject(1)
|
Decorators : WidgetInput
|
import { ChangeDetectorRef, Component, OnDestroy } from "@angular/core";
import { getOrDefault, WidgetConfig } from "../widget.configuration";
import {
WidgetComponent,
WidgetConfiguration,
WidgetConfigure,
WidgetId,
WidgetInput,
} from "../widget.metadata";
import { ReplaySubject, Subject } from "rxjs";
import { distinctUntilChanged, filter, takeUntil } from "rxjs/operators";
import { NgUnsubscribe } from "../../ng-unsubscribe";
import {
DeletionMode,
Scope,
} from "../../components/local-storage/local-storage-constants";
import { LocalStorageEntryConfig } from "../../components/local-storage/local-storage.service";
import { Content } from "../../components/app-context/api";
import { BaseConfiguration } from "../widgetframe/widgetframe.component";
export const densityStorageConfig: LocalStorageEntryConfig = {
scope: Scope.UNSCOPED,
key: "nm-toolbox-table-density",
deletionMode: DeletionMode.NEVER,
};
export const fontSizeStorageConfig: LocalStorageEntryConfig = {
scope: Scope.UNSCOPED,
key: "nm-toolbox-table-fontsize",
deletionMode: DeletionMode.NEVER,
};
export interface EmptyWidgetConfiguration {
/**
* Sets the title shown in the empty widget header
*/
title: string;
/**
* Sets CSS class name for the embedded header component
*/
header: string;
/**
* Shows / Hides the border around the empty widget
*/
withBorder: boolean;
/**
* Sets the Info text
*/
infoText: string;
/**
* Sets the title shown for the info icon
*/
infoTitle: string;
/**
* Sets the link to external wiki system for more information
*/
wikiLink: string;
/**
* Shows / Hides the empty widget header @default(true)
*/
withHeader?: boolean;
/**
* Shows / Hides the empty widget @default(false)
*/
hideWidget?: boolean;
}
@WidgetComponent("nm-empty-widget")
@Component({
selector: "nm-empty-widget",
template: `
<nm-widgetframe
[header]="_widgetConfiguration.configuration['header']"
[toolbarInvisible]="!withHeader"
[visible]="!hideWidget"
[withBorder]="_widgetConfiguration.configuration.withBorder"
[infoText]="_widgetConfiguration.configuration['infoText']"
[infoTitle]="_widgetConfiguration.configuration['infoTitle']"
[infoWidth]="_widgetConfiguration.configuration['infoWidth']"
[infoHeight]="_widgetConfiguration.configuration['infoHeight']"
[title]="_widgetConfiguration.configuration.title | translate"
[configuration]="_widgetConfiguration"
>
<ng-container slot="title">
{{ _widgetConfiguration.configuration["title"] | translate }}
{{ widgetTitle | translate }}
</ng-container>
<nm-container
style="position: absolute; display: inline; right: 53px;"
class="header"
*ngIf="_widgetConfiguration && _widgetConfiguration.header"
[configuration]="
_widgetConfiguration.header | widgetFor: _widgetConfiguration
"
[parent]="id"
id="header"
>
</nm-container>
<nm-help-icon
*ngIf="_widgetConfiguration.configuration['infoText']"
style="position: absolute; display: inline; right: 47px; z-index: 666;"
[info-text]="_widgetConfiguration.configuration['infoText'] | translate"
[info-title]="_widgetConfiguration.configuration['title'] | translate"
[info-width]="_widgetConfiguration.configuration['infoWidth']"
[info-height]="_widgetConfiguration.configuration['infoHeight']"
[info-placement]="'left'"
[wiki-link]="_widgetConfiguration.configuration['wikiLink']"
></nm-help-icon>
</nm-widgetframe>
`,
})
export class EmptyWidgetComponent {
@WidgetId()
public id;
public withHeader: boolean = true;
@WidgetConfiguration()
public _widgetConfiguration: WidgetConfig<EmptyWidgetConfiguration>;
/**
* Shows / Hides the empty widget
*/
@WidgetInput("hide")
public hide: Subject<any> = new ReplaySubject<any>();
/**
* Appends text to the title shown in the empty widget header
*/
@WidgetInput("title")
public title: Subject<any> = new ReplaySubject<any>();
public hideWidget: boolean = false;
public widgetTitle: string;
private unsubscribe = NgUnsubscribe.create();
@WidgetConfigure()
protected configureWidget(
configuration: WidgetConfig<EmptyWidgetConfiguration>
) {
if (this._widgetConfiguration.configuration.withHeader != undefined) {
this.withHeader = this._widgetConfiguration.configuration.withHeader;
}
if (this._widgetConfiguration.configuration.hideWidget != undefined) {
this.hideWidget = this._widgetConfiguration.configuration.hideWidget;
}
this.hide
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((hide) => {
this.hideWidget = hide;
});
this.title
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((title) => {
this.widgetTitle = title;
});
}
}
@WidgetComponent("nm-switch-layout")
@Component({
selector: "nm-switch-layout",
template: `
<nm-container
*ngIf="!!component"
[configuration]="component | widgetFor: _widgetConfiguration"
[parent]="id"
[id]="component"
>
</nm-container>
`,
})
export class SwitchLayoutWidgetComponent implements OnDestroy {
@WidgetId()
public id;
@WidgetConfiguration()
public _widgetConfiguration: WidgetConfig;
/**
* The component id to display inside the placeholder widget
*/
@WidgetInput("active-component")
public activeComponent: Subject<string> = new ReplaySubject(1);
public component: string;
private unsubscribe = NgUnsubscribe.create();
constructor(private cdr: ChangeDetectorRef) {}
@WidgetConfigure()
public configureWidget(configuration: WidgetConfig) {
this.activeComponent
.pipe(
filter((component) => !!component),
distinctUntilChanged(),
takeUntil(this.unsubscribe)
)
.subscribe((component) => {
this.component = component;
this.cdr.markForCheck();
});
}
ngOnDestroy(): void {
this.unsubscribe.destroy();
}
}
export interface InteractionLayoutConfiguration extends BaseConfiguration {
/**
* If set to false, inactive interactions will not receive any events over the connected channels.
* Once the interaction is re-activated, all channels will be re-subscribed and emit their latest values.
*
* @default(true)
*/
"keep-attached": boolean;
}
/**
* layout implementation that allows the display and switching between multiple interactions.
*
* The widget input "interaction" can be used to assign individual interactions the layout.
* While only the latest interaction will actually be visible, _all_ interactions set via
* the input will be loaded but hidden.
*
* Each interaction (identified by "identifier" or its JSON representation) will only
* be loaded_once_.
*
* This allows the implementation of externally displayed tab components, without having to
* re-instantiate the interaction every time the active interaction is switched.
*
* To further reduce resources, interactions can be detached from the app-service, by setting the
* widget's "keep-attached" configuration property to false.
* When a detached interaction is re-activated (i.e., when the interaction is submitted using
* the "interaction" input), all connections are re-subscribed withint the app-service.
*/
@WidgetComponent("nm-interaction-layout")
@Component({
selector: "nm-interaction-layout",
template: `
<ng-container
*ngFor="let entry of interactions | keyvalue; trackBy: trackByFn"
>
<nm-interaction
[configuration]="entry.value"
[param]="param | async"
[is-attached]="keepAttached || entry.key == active"
[hidden]="entry.key != active"
>
</nm-interaction>
</ng-container>
`,
})
export class InteractionLayoutWidgetComponent {
public interactions: Map<string, Content> = new Map<string, Content>();
public active: string;
@WidgetId()
public id;
@WidgetInput("interaction")
public interaction: Subject<Content> = new ReplaySubject(1);
@WidgetInput("param")
public param: Subject<any> = new ReplaySubject(1);
public keepAttached: boolean;
constructor(private cdr: ChangeDetectorRef) {}
@WidgetConfigure()
public configureWidget(
configuration: WidgetConfig<InteractionLayoutConfiguration>
) {
this.keepAttached = getOrDefault(
configuration.configuration["keep-attached"],
true
);
this.interaction.subscribe((content) => {
let key = content.identifier || JSON.stringify(content);
this.active = key;
this.cdr.detectChanges();
if (!this.interactions.has(key)) {
this.interactions.set(key, content);
}
this.cdr.markForCheck();
});
}
public trackByFn(index, item) {
return item.key;
}
}