File

src/modules/message-pane/components/message-pane.component.ts

Description

Component to display messages in a single pane grouped by level: info, errors and warnings.

Extends

AbstractStarkUiComponent

Implements

OnInit OnChanges

Metadata

Index

Properties
Methods
Inputs
Accessors

Constructor

Public constructor(logger: StarkLoggingService, messagePaneService: StarkMessagePaneService, renderer: Renderer2, elementRef: ElementRef, cdRef: ChangeDetectorRef)

Class constructor

Parameters :
Name Type Optional Description
logger StarkLoggingService No
  • The StarkLoggingService instance of the application.
messagePaneService StarkMessagePaneService No
  • The service to display/hide messages in the pane.
renderer Renderer2 No
  • Angular Renderer2 wrapper for DOM manipulations.
elementRef ElementRef No
  • Reference to the DOM element where this component is attached to.
cdRef ChangeDetectorRef No
  • Reference to the change detector attached to this component.

Inputs

align
Type : AlignTypes

Alignment to be used: "left", "center" or "right".

Default: "right".

clearOnNavigation
Type : boolean

Whether the messages should be cleared on every navigation to another view/page.

Default: false.

color
Type : string
Inherited from AbstractStarkUiComponent

Color theme

Methods

Public clearAllMessages
clearAllMessages()

Clear all messages

Returns : void
Public collapseMessages
collapseMessages()

collapse the message pane

Returns : void
Private countMessages
countMessages(messageCollection: StarkMessageCollection)

Count the number of messages by type in the collection

Parameters :
Name Type Optional Description
messageCollection StarkMessageCollection No
  • The collection to count
Returns : number
Public expandMessages
expandMessages()

expand the message pane

Returns : void
Private getMaxLevel
getMaxLevel(messageCollection: StarkMessageCollection)

Retrieve the level of the message pane

Parameters :
Name Type Optional Description
messageCollection StarkMessageCollection No
  • The collection to analyse
Public hidePane
hidePane()

Hide the message pane

Returns : void
Public ngOnChanges
ngOnChanges(changesObj: SimpleChanges)

Component lifecycle hook

Parameters :
Name Type Optional Description
changesObj SimpleChanges No
  • Contains the changed properties
Returns : void
Public ngOnInit
ngOnInit()
Inherited from AbstractStarkUiComponent

Component lifecycle hook

Returns : void
Public removeMessage
removeMessage(message: StarkMessage)

Remove one of the message

Parameters :
Name Type Optional Description
message StarkMessage No
  • The message to remove
Returns : void
Public showPane
showPane()

Display the message pane

Returns : void
Public toggleActive
toggleActive(navItem: StarkMessagePaneNavItem)

Toggle from one item to another

Parameters :
Name Type Optional Description
navItem StarkMessagePaneNavItem No
  • The item to navigate to
Returns : void

Properties

Public currentNavItem
Type : StarkMessagePaneNavItem
Default value : ""

Messages tab currently selected

Public hideAnimationDelay
Type : number
Default value : 500

Delay of the animation when the messages are hidden

Public isVisible
Default value : false

Whether the message pane is currently visible

Public logger
Type : StarkLoggingService
Decorators :
@Inject(STARK_LOGGING_SERVICE)
- The `StarkLoggingService` instance of the application.
Public maxLevel
Type : StarkMessagePaneNavItem
Default value : "infos"

Maximum level of the messages currently shown (error, warning or info).

Public messageCollection
Type : StarkMessageCollection
Default value : { infoMessages: [], warningMessages: [], errorMessages: [] }

Collection of messages currently shown.

Public messagePaneService
Type : StarkMessagePaneService
Decorators :
@Inject(STARK_MESSAGE_PANE_SERVICE)
- The service to display/hide messages in the pane.
Public showAnimationDelay
Type : number
Default value : 20

Delay of the animation when the messages are shown

Public totalMessages
Type : number
Default value : 0

Total number of messages currently shown

Accessors

align
getalign()
setalign(value: AlignTypes)

Alignment to be used: "left", "center" or "right".

Default: "right".

Parameters :
Name Type Optional
value AlignTypes No
Returns : void
import {
	ChangeDetectionStrategy,
	ChangeDetectorRef,
	Component,
	ElementRef,
	Inject,
	Input,
	OnChanges,
	OnInit,
	Renderer2,
	SimpleChanges
} from "@angular/core";
import { Observable, of, Subject } from "rxjs";
import { delay, distinctUntilChanged, map, switchMap, take, tap } from "rxjs/operators";
import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core";
import {
	starkMessagePaneAlignClassPrefix,
	starkMessagePaneCollapsedClass,
	starkMessagePaneDisplayAnimatedClass,
	starkMessagePaneDisplayedClass
} from "./message-pane.constants";
import { STARK_MESSAGE_PANE_SERVICE, StarkMessagePaneService, starkMessagePaneServiceName } from "../services/message-pane.service.intf";
import { StarkMessage, StarkMessageCollection } from "@nationalbankbelgium/stark-ui/src/common";
import { AbstractStarkUiComponent } from "@nationalbankbelgium/stark-ui/src/internal-common";

/**
 * Type of messages that can be displayed in the message pane
 */
export type StarkMessagePaneNavItem = "" | "errors" | "warnings" | "infos";

/**
 * Type of alignment that can be set
 */
export type AlignTypes = "left" | "center" | "right";

/**
 * The default align
 */
const DEFAULT_ALIGN: AlignTypes = "right";

/**
 * @ignore
 */
const componentName = "stark-message-pane";

// FIXME: refactor the template of this component function to reduce its cyclomatic complexity
/* eslint-disable @angular-eslint/template/cyclomatic-complexity */
/**
 * Component to display messages in a single pane grouped by level: info, errors and warnings.
 */
@Component({
	selector: "stark-message-pane",
	templateUrl: "./message-pane.component.html",
	changeDetection: ChangeDetectionStrategy.OnPush,
	// We need to use host instead of @HostBinding: https://github.com/NationalBankBelgium/stark/issues/664
	host: {
		class: componentName
	}
})
export class StarkMessagePaneComponent extends AbstractStarkUiComponent implements OnInit, OnChanges {
	/**
	 * Whether the messages should be cleared on every navigation to another view/page.
	 *
	 * Default: `false`.
	 */
	@Input()
	public clearOnNavigation?: boolean;

	/**
	 * Alignment to be used: `"left"`, `"center"` or `"right"`.
	 *
	 * Default: `"right"`.
	 */
	@Input()
	public set align(value: AlignTypes) {
		this._align = value || DEFAULT_ALIGN;
	}
	public get align(): AlignTypes {
		return this._align;
	}

	/**
	 * @ignore
	 */
	private _align: "left" | "center" | "right" = DEFAULT_ALIGN;

	/**
	 * Collection of messages currently shown.
	 */
	public messageCollection: StarkMessageCollection = {
		infoMessages: [],
		warningMessages: [],
		errorMessages: []
	};

	/**
	 * Messages tab currently selected
	 */
	public currentNavItem: StarkMessagePaneNavItem = "";

	/**
	 * Total number of messages currently shown
	 */
	public totalMessages = 0;

	/**
	 * Maximum level of the messages currently shown (error, warning or info).
	 */
	public maxLevel: StarkMessagePaneNavItem = "infos";

	/**
	 * Whether the message pane is currently visible
	 */
	public isVisible = false;

	/**
	 * Delay of the animation when the messages are shown
	 */
	public showAnimationDelay = 20;

	/**
	 * Delay of the animation when the messages are hidden
	 */
	public hideAnimationDelay = 500; // the CSS animation takes 0.4 secs

	/**
	 * @ignore
	 */
	public errorMessages$!: Observable<StarkMessage[]>;

	/**
	 * @ignore
	 */
	public infoMessages$!: Observable<StarkMessage[]>;

	/**
	 * @ignore
	 */
	public warningMessages$!: Observable<StarkMessage[]>;

	/**
	 * @ignore
	 * @internal
	 */
	public hide$?: Subject<string>;

	/**
	 * Class constructor
	 * @param logger - The `StarkLoggingService` instance of the application.
	 * @param messagePaneService - The service to display/hide messages in the pane.
	 * @param renderer - Angular `Renderer2` wrapper for DOM manipulations.
	 * @param elementRef - Reference to the DOM element where this component is attached to.
	 * @param cdRef - Reference to the change detector attached to this component.
	 */
	public constructor(
		@Inject(STARK_LOGGING_SERVICE) public logger: StarkLoggingService,
		@Inject(STARK_MESSAGE_PANE_SERVICE) public messagePaneService: StarkMessagePaneService,
		renderer: Renderer2,
		elementRef: ElementRef,
		protected cdRef: ChangeDetectorRef
	) {
		super(renderer, elementRef);
	}

	/**
	 * Component lifecycle hook
	 */
	public override ngOnInit(): void {
		super.ngOnInit();
		this.renderer.addClass(this.elementRef.nativeElement, starkMessagePaneAlignClassPrefix + this.align);

		const appMsgCollection$: Observable<StarkMessageCollection> = this.messagePaneService.getAll();

		appMsgCollection$.subscribe((msgCollection: StarkMessageCollection) => {
			this.messageCollection = msgCollection;
			this.totalMessages = this.countMessages(msgCollection);
			this.maxLevel = this.getMaxLevel(msgCollection);
			if (this.totalMessages === 0) {
				this.currentNavItem = "";
				if (this.isVisible) {
					this.hidePane();
					this.isVisible = false;
				}
			} else {
				if (!this.isVisible) {
					this.showPane();
					this.isVisible = true;
				}
			}
			this.cdRef.detectChanges(); // needed due to ChangeDetectionStrategy.OnPush in order to refresh the CSS classes
		});

		this.errorMessages$ = appMsgCollection$.pipe(
			map((msgCollection: StarkMessageCollection) => msgCollection.errorMessages),
			distinctUntilChanged()
		);

		this.infoMessages$ = appMsgCollection$.pipe(
			map((msgCollection: StarkMessageCollection) => msgCollection.infoMessages),
			distinctUntilChanged()
		);

		this.warningMessages$ = appMsgCollection$.pipe(
			map((msgCollection: StarkMessageCollection) => msgCollection.warningMessages),
			distinctUntilChanged()
		);

		this.logger.debug(componentName + ": controller initialized");
	}

	/**
	 * Component lifecycle hook
	 * @param changesObj - Contains the changed properties
	 */
	public ngOnChanges(changesObj: SimpleChanges): void {
		if (changesObj["clearOnNavigation"] && this.clearOnNavigation !== changesObj["clearOnNavigation"].previousValue) {
			this.messagePaneService.clearOnNavigation = !!this.clearOnNavigation;
		}
	}

	/**
	 * Count the number of messages by type in the collection
	 * @param messageCollection - The collection to count
	 */
	private countMessages(messageCollection: StarkMessageCollection): number {
		let msgCount = 0;

		if (messageCollection) {
			if (messageCollection.errorMessages.length > 0) {
				msgCount += messageCollection.errorMessages.length;
			}
			if (messageCollection.warningMessages.length > 0) {
				msgCount += messageCollection.warningMessages.length;
			}
			if (messageCollection.infoMessages.length > 0) {
				msgCount += messageCollection.infoMessages.length;
			}
		}

		return msgCount;
	}

	/**
	 * Retrieve the level of the message pane
	 * @param messageCollection - The collection to analyse
	 */
	private getMaxLevel(messageCollection: StarkMessageCollection): StarkMessagePaneNavItem {
		let level: StarkMessagePaneNavItem = "infos";

		if (messageCollection.warningMessages.length > 0) {
			level = "warnings";
		}
		if (messageCollection.errorMessages.length > 0) {
			level = "errors";
		}

		return level;
	}

	/**
	 * Remove one of the message
	 * @param message - The message to remove
	 */
	public removeMessage(message: StarkMessage): void {
		this.messagePaneService.remove([message]);
	}

	/**
	 * Clear all messages
	 */
	public clearAllMessages(): void {
		this.messagePaneService.clearAll();
	}

	/**
	 * Display the message pane
	 */
	public showPane(): void {
		const show$: Observable<string> = of(starkMessagePaneServiceName + ": showing pane...").pipe(
			tap(() => this.renderer.addClass(this.elementRef.nativeElement, starkMessagePaneDisplayedClass)),
			delay(this.showAnimationDelay),
			map(() => {
				this.renderer.addClass(this.elementRef.nativeElement, starkMessagePaneDisplayAnimatedClass);
				return starkMessagePaneServiceName + ": pane shown";
			})
		);

		let composedShow$: Observable<string> = show$;

		// check if the 'show' needs to wait for the 'hide' to finish, otherwise it is executed immediately
		if (this.hide$) {
			composedShow$ = this.hide$.pipe(
				switchMap(
					() => show$ // call the 'show' logic after the 'hide' finishes
				),
				take(1)
			); // unsubscribe from the 'hide' in this chain (every call to the showPane() method must listen only to one emission)
		}

		composedShow$.subscribe();
	}

	/**
	 * Hide the message pane
	 */
	public hidePane(): void {
		if (!this.hide$) {
			// should wait for the panel to be hidden
			this.hide$ = new Subject<string>();
		}

		const hide$: Observable<string> = of(starkMessagePaneServiceName + ": hiding pane...").pipe(
			tap(() => this.renderer.removeClass(this.elementRef.nativeElement, starkMessagePaneDisplayAnimatedClass)),
			delay(this.hideAnimationDelay),
			map(() => {
				this.renderer.removeClass(this.elementRef.nativeElement, starkMessagePaneDisplayedClass);
				// emit result in the hide$ subject so that 'show' can continue (in case it was called)
				(<Subject<string>>this.hide$).next("pane hidden");
				// complete and remove the subject so that next calls to 'show' don't need to wait (unless 'hide' is called first)
				(<Subject<string>>this.hide$).complete();
				this.hide$ = undefined;
				return starkMessagePaneServiceName + ": pane hidden";
			})
		);

		hide$.subscribe();
	}

	/**
	 * Toggle from one item to another
	 * @param navItem - The item to navigate to
	 */
	public toggleActive(navItem: StarkMessagePaneNavItem): void {
		if (this.currentNavItem === navItem) {
			this.currentNavItem = "";
		} else {
			this.currentNavItem = navItem;
		}
	}

	/**
	 * collapse the message pane
	 */
	public collapseMessages(): void {
		this.renderer.addClass(this.elementRef.nativeElement, starkMessagePaneCollapsedClass);
	}

	/**
	 * expand the message pane
	 */
	public expandMessages(): void {
		this.renderer.removeClass(this.elementRef.nativeElement, starkMessagePaneCollapsedClass);
	}

	/**
	 * @ignore
	 */
	public trackItemFn(index: number, _item: StarkMessage): number {
		return index;
	}
}
<div class="inner">
	<div class="summary">
		<button
			class="errors tab may-collapse"
			[ngClass]="{ active: currentNavItem === 'errors' }"
			*ngIf="messageCollection.errorMessages.length > 0"
			(click)="toggleActive('errors')"
			[matTooltip]="'STARK.MESSAGE_PANE.ERROR_MESSAGES' | translate"
			[attr.aria-label]="'STARK.MESSAGE_PANE.ERROR_MESSAGES' | translate"
			type="button"
		>
			<mat-icon svgIcon="alert-circle"></mat-icon>
			<b>{{ messageCollection.errorMessages.length }}</b>
		</button>

		<button
			class="warnings tab may-collapse"
			[ngClass]="{ active: currentNavItem === 'warnings' }"
			*ngIf="messageCollection.warningMessages.length > 0"
			(click)="toggleActive('warnings')"
			[matTooltip]="'STARK.MESSAGE_PANE.WARNING_MESSAGES' | translate"
			[attr.aria-label]="'STARK.MESSAGE_PANE.WARNING_MESSAGES' | translate"
			type="button"
		>
			<mat-icon svgIcon="alert"></mat-icon>
			<b>{{ messageCollection.warningMessages.length }}</b>
		</button>

		<button
			class="infos tab may-collapse"
			[ngClass]="{ active: currentNavItem === 'infos' }"
			*ngIf="messageCollection.infoMessages.length > 0"
			(click)="toggleActive('infos')"
			[matTooltip]="'STARK.MESSAGE_PANE.INFO_MESSAGES' | translate"
			[attr.aria-label]="'STARK.MESSAGE_PANE.INFO_MESSAGES' | translate"
			type="button"
		>
			<mat-icon svgIcon="information"></mat-icon>
			<b>{{ messageCollection.infoMessages.length }}</b>
		</button>

		<button
			class="stark-message-pane-total"
			[ngClass]="maxLevel"
			(click)="expandMessages()"
			[matTooltip]="'STARK.MESSAGE_PANE.EXPAND' | translate"
			[attr.aria-label]="'STARK.MESSAGE_PANE.EXPAND' | translate"
			type="button"
		>
			<mat-icon svgIcon="information" *ngIf="maxLevel === 'infos'"></mat-icon>
			<mat-icon svgIcon="alert" *ngIf="maxLevel === 'warnings'"></mat-icon>
			<mat-icon svgIcon="alert-circle" *ngIf="maxLevel === 'errors'"></mat-icon>
			<b>{{ totalMessages }}</b>
		</button>

		<button
			mat-icon-button
			class="may-collapse clear-all-messages"
			(click)="clearAllMessages()"
			[matTooltip]="'STARK.MESSAGE_PANE.DISMISS_ALL' | translate"
			[attr.aria-label]="'STARK.MESSAGE_PANE.DISMISS_ALL' | translate"
			type="button"
		>
			<mat-icon svgIcon="close"></mat-icon>
		</button>

		<button
			mat-icon-button
			class="collapse-pane"
			(click)="collapseMessages()"
			[matTooltip]="'STARK.MESSAGE_PANE.COLLAPSE' | translate"
			[attr.aria-label]="'STARK.MESSAGE_PANE.COLLAPSE' | translate"
			type="button"
		>
			<mat-icon svgIcon="adjust"></mat-icon>
		</button>
	</div>

	<div class="stark-message-pane-content">
		<ng-container *ngIf="currentNavItem === 'errors'">
			<div
				class="stark-message-pane-item stark-message-pane-item-error errors"
				*ngFor="let message of errorMessages$ | async; trackBy: trackItemFn"
			>
				<mat-icon svgIcon="alert-circle"></mat-icon>
				<div translate="{{ message.key }}" translate-values="message.interpolateValues"></div>
				<button
					mat-icon-button
					class="mat-no-ink mat-icon-button"
					[matTooltip]="'STARK.MESSAGE_PANE.CLOSE_MESSAGE' | translate"
					(click)="removeMessage(message)"
					[attr.aria-label]="'STARK.MESSAGE_PANE.CLOSE_MESSAGE' | translate"
				>
					<mat-icon svgIcon="close"></mat-icon>
				</button>
			</div>
		</ng-container>
		<ng-container *ngIf="currentNavItem === 'warnings'">
			<div
				class="stark-message-pane-item stark-message-pane-item-warning warnings"
				*ngFor="let message of warningMessages$ | async; trackBy: trackItemFn"
			>
				<mat-icon svgIcon="alert"></mat-icon>
				<div translate="{{ message.key }}" translate-values="message.interpolateValues"></div>
				<button
					mat-icon-button
					class="md-no-ink mat-icon-button"
					[matTooltip]="'STARK.MESSAGE_PANE.CLOSE_MESSAGE' | translate"
					(click)="removeMessage(message)"
					[attr.aria-label]="'STARK.MESSAGE_PANE.CLOSE_MESSAGE' | translate"
				>
					<mat-icon svgIcon="close"></mat-icon>
				</button>
			</div>
		</ng-container>
		<ng-container *ngIf="currentNavItem === 'infos'">
			<div
				class="stark-message-pane-item stark-message-pane-item-info infos"
				*ngFor="let message of infoMessages$ | async; trackBy: trackItemFn"
			>
				<mat-icon svgIcon="information"></mat-icon>
				<div translate="{{ message.key }}" translate-values="message.interpolateValues"></div>
				<button
					mat-icon-button
					class="md-no-ink mat-icon-button"
					[matTooltip]="'STARK.MESSAGE_PANE.CLOSE_MESSAGE' | translate"
					(click)="removeMessage(message)"
					[attr.aria-label]="'STARK.MESSAGE_PANE.CLOSE_MESSAGE' | translate"
				>
					<mat-icon svgIcon="close"></mat-icon>
				</button>
			</div>
		</ng-container>
	</div>
</div>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""