projects/isy-angular-widgets/src/lib/hauptfenster/hauptfenster.component.ts
The Anwendungsrahmen that contains general, application independent elements as logos or navigation bars. The following template references can be used to insert custom HTML: LogoAnbieterAwl - Access to the top left region. Only works if logoAnbieterAwl is not set. LogoAwl - Access to top right region. Only works if logoAwl is not set. Hauptnavigation-Tools - Access to the Menu bar behind the menu items, e.g. for a language picker. Titelzeile - Access to the Titelzeile between Menu and Content. Only works if title is not set. Seitentoolbar - Access to a space right under the Titelzeile, e.g. for some navigation buttons. Linksnavigation - The left sidebar. Informationsbereich - The right sidebar.
Content without template references is used as main content.
Responsive behaviour is disabled by default and enabled through the responsive attribute.
Because it is based on the width of the component itself, it also works inside a narrower
container. When enabled, the Hauptfenster reacts to its available width:
1024px or less, Linksnavigation and Informationsbereich are hidden automatically.1024px the side areas reappear according to showLinksnavigation and showInformationsbereich.collapsedLinksnavigation and collapsedInformationsbereich remain unchanged.Without the attribute the layout keeps its static behaviour.
Example :<isy-hauptfenster responsive [showLinksnavigation]="true" [showInformationsbereich]="true">
<p-menu Linksnavigation [model]="navigationItems"></p-menu>
<main>Zentraler Inhaltsbereich</main>
<p Informationsbereich>Zusätzliche Informationen</p>
</isy-hauptfenster>The attribute can also be bound dynamically:
```html
<isy-hauptfenster [responsive]="responsiveLayoutEnabled" [showLinksnavigation]="true">
<!-- Inhalte -->
</isy-hauptfenster>
| selector | isy-hauptfenster |
| standalone | true |
| imports |
ButtonModule
MegaMenuModule
SkipLinksComponent
NgClass
MessageModule
|
| styleUrls | ./hauptfenster.component.scss |
| templateUrl | ./hauptfenster.component.html |
| allowSidebarCollapse | |
Type : boolean
|
|
Default value : true
|
|
|
Determines whether the user can collapse the Linksnavigation and Informationsbereich. |
|
| applicationGroupColor | |
Type : string
|
|
Default value : '#A13D6D'
|
|
|
Returns the current UserInfo when the user wants to log out. |
|
| checkBrowserVersion | |
Type : boolean
|
|
Default value : true
|
|
|
Enables the automatic browser version check. The check is enabled by default. |
|
| collapsedInformationsbereich | |
Type : boolean
|
|
Default value : false
|
|
|
Determines whether the Informationsbereich is currently collapsed. |
|
| collapsedLinksnavigation | |
Type : boolean
|
|
Default value : false
|
|
|
Determines whether the Linksnavigation is currently collapsed. |
|
| informationsbereichWidth | |
Type : string
|
|
Default value : '15em'
|
|
|
Determines the width of the Informationsbereich, e.g. "15em". Suggested unit is em. Default is 15em. |
|
| items | |
Type : MegaMenuItem[]
|
|
Default value : []
|
|
|
Items to be shown in the main menu bar. |
|
| links | |
Type : SkipTarget[]
|
|
Default value : []
|
|
| linksNavigationTitle | |
Type : string
|
|
|
The left navigation title to be displayed in the left sidebar. |
|
| linksNavigationWidth | |
Type : string
|
|
Default value : '15em'
|
|
|
Determines the width of the Linksnavigation, e.g. "15em". Suggested unit is em. Default is 15em. |
|
| logoAnbieterAwl | |
Type : string
|
|
Default value : ''
|
|
|
A URI to the logo image file of the Anbieter der Anwendungslandschaft, e.g. Bundesregierung. Will be displayed in the upper left corner. For custom HTML, use the "LogoAnbieterAwl" template reference instead. |
|
| logoAwl | |
Type : string
|
|
Default value : ''
|
|
|
A URI to the logo image file of the Anwendungslandschaft. Will be displayed in the upper right corner. For custom HTML, use the "LogoAwl" template reference instead. |
|
| outlinedLogoutButton | |
Type : boolean
|
|
Default value : true
|
|
|
Boolean to determine if the logout button should be outlined or not. |
|
| responsive | |
Type : boolean
|
|
Default value : false
|
|
|
Enables responsive behavior for the Hauptfenster. When enabled, the Linksnavigation and Informationsbereich are hidden automatically below the defined responsive breakpoint. |
|
| showInformationsbereich | |
Type : boolean
|
|
Default value : false
|
|
|
Determines whether to show or hide the Informationsbereich, a navigation bar on the right screen side. |
|
| showLinksnavigation | |
Type : boolean
|
|
Default value : false
|
|
|
Determines whether to show or hide the Linksnavigation, a navigation bar on the left screen side. |
|
| title | |
Type : string
|
|
|
The page title to be displayed in the Titelzeile under the menu bar. For custom HTML, use the "Titelzeile" template reference instead. |
|
| userInfo | |
Type : UserInfo
|
|
|
Contains information about the user as the name to be displayed. |
|
| logoutEvent | |
Type : EventEmitter
|
|
| collapseInformationsbereich |
collapseInformationsbereich()
|
|
Returns :
void
|
| collapseSidebar |
collapseSidebar()
|
|
Returns :
void
|
| expandInformationsbereich |
expandInformationsbereich()
|
|
Returns :
void
|
| expandSidebar |
expandSidebar()
|
|
Returns :
void
|
| ngOnInit |
ngOnInit()
|
|
Returns :
void
|
| Protected Readonly configService |
Type : unknown
|
Default value : inject(WidgetsConfigService)
|
|
A service used to translate labels within the widgets library. |
| Protected unsupportedBrowserMessage |
Type : string
|
Default value : ''
|
import {NgClass} from '@angular/common';
import {
OnInit,
afterNextRender,
Component,
DestroyRef,
ElementRef,
EventEmitter,
inject,
Injector,
Input,
Output,
ViewChild,
booleanAttribute
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {MegaMenuItem} from 'primeng/api';
import {ButtonModule} from 'primeng/button';
import {MegaMenuModule} from 'primeng/megamenu';
import {MessageModule} from 'primeng/message';
import {UserInfo} from '../api/userinfo';
import {BrowserSupportCheckResult, BrowserVersionService} from '../browser-support/browser-version.service';
import {WidgetsConfigService} from '../i18n/widgets-config.service';
import {SkipLinksComponent} from '../skip-links/skip-links.component';
import {SkipTarget} from './model/model';
/**
* The Anwendungsrahmen that contains general, application independent elements as logos or navigation bars.
* The following template references can be used to insert custom HTML:
* LogoAnbieterAwl - Access to the top left region. Only works if logoAnbieterAwl is not set.
* LogoAwl - Access to top right region. Only works if logoAwl is not set.
* Hauptnavigation-Tools - Access to the Menu bar behind the menu items, e.g. for a language picker.
* Titelzeile - Access to the Titelzeile between Menu and Content. Only works if title is not set.
* Seitentoolbar - Access to a space right under the Titelzeile, e.g. for some navigation buttons.
* Linksnavigation - The left sidebar.
* Informationsbereich - The right sidebar.
*
* Content without template references is used as main content.
*
* ## Responsive behaviour
*
* Responsive behaviour is disabled by default and enabled through the `responsive` attribute.
* Because it is based on the width of the component itself, it also works inside a narrower
* container. When enabled, the Hauptfenster reacts to its available width:
*
* - At a width of `1024px` or less, Linksnavigation and Informationsbereich are hidden automatically.
* - The central content area stays visible and uses the available width.
* - Above `1024px` the side areas reappear according to `showLinksnavigation` and `showInformationsbereich`.
* - The values of `collapsedLinksnavigation` and `collapsedInformationsbereich` remain unchanged.
*
* Without the attribute the layout keeps its static behaviour.
* @example
* ```html
* <isy-hauptfenster responsive [showLinksnavigation]="true" [showInformationsbereich]="true">
* <p-menu Linksnavigation [model]="navigationItems"></p-menu>
* <main>Zentraler Inhaltsbereich</main>
* <p Informationsbereich>Zusätzliche Informationen</p>
* </isy-hauptfenster>
* ```
* @example
* The attribute can also be bound dynamically:
* ```html
* <isy-hauptfenster [responsive]="responsiveLayoutEnabled" [showLinksnavigation]="true">
* <!-- Inhalte -->
* </isy-hauptfenster>
* ```
*/
@Component({
standalone: true,
selector: 'isy-hauptfenster',
templateUrl: './hauptfenster.component.html',
styleUrls: ['./hauptfenster.component.scss'],
imports: [ButtonModule, MegaMenuModule, SkipLinksComponent, NgClass, MessageModule]
})
export class HauptfensterComponent implements OnInit {
/**
* Items to be shown in the main menu bar.
*/
@Input() items: MegaMenuItem[] = [];
/**
* Contains information about the user as the name to be displayed.
*/
@Input() userInfo?: UserInfo;
/**
* A URI to the logo image file of the Anbieter der Anwendungslandschaft, e.g. Bundesregierung.
* Will be displayed in the upper left corner.
* For custom HTML, use the "LogoAnbieterAwl" template reference instead.
*/
@Input() logoAnbieterAwl: string = '';
/**
* A URI to the logo image file of the Anwendungslandschaft.
* Will be displayed in the upper right corner.
* For custom HTML, use the "LogoAwl" template reference instead.
*/
@Input() logoAwl: string = '';
/**
* The page title to be displayed in the Titelzeile under the menu bar.
* For custom HTML, use the "Titelzeile" template reference instead.
*/
@Input() title?: string;
/**
* Boolean to determine if the logout button should be outlined or not.
*/
@Input() outlinedLogoutButton: boolean = true;
/**
* The left navigation title to be displayed in the left sidebar.
*/
@Input() linksNavigationTitle?: string;
/**
* Determines whether to show or hide the Linksnavigation, a navigation bar on the left screen side.
*/
@Input() showLinksnavigation: boolean = false;
/**
* Determines whether to show or hide the Informationsbereich, a navigation bar on the right screen side.
*/
@Input() showInformationsbereich: boolean = false;
/**
* Determines whether the user can collapse the Linksnavigation and Informationsbereich.
*/
@Input() allowSidebarCollapse: boolean = true;
/**
* Determines whether the Linksnavigation is currently collapsed.
*/
@Input() collapsedLinksnavigation: boolean = false;
/**
* Determines whether the Informationsbereich is currently collapsed.
*/
@Input() collapsedInformationsbereich: boolean = false;
/**
* Returns the current {@link UserInfo} when the user wants to log out.
*/
@Input() applicationGroupColor: string = '#A13D6D';
/**
* Determines the width of the Linksnavigation, e.g. "15em". Suggested unit is em.
* Default is 15em.
*/
@Input() linksNavigationWidth = '15em';
/**
* Determines the width of the Informationsbereich, e.g. "15em". Suggested unit is em.
* Default is 15em.
*/
@Input() informationsbereichWidth = '15em';
@Input() links: SkipTarget[] = [];
/**
* Enables the automatic browser version check.
* The check is enabled by default.
*/
@Input() checkBrowserVersion = true;
/**
* Enables responsive behavior for the Hauptfenster.
*
* When enabled, the Linksnavigation and Informationsbereich are hidden
* automatically below the defined responsive breakpoint.
*/
@Input({transform: booleanAttribute}) responsive = false;
@Output() logoutEvent = new EventEmitter<UserInfo>();
/**
* A service used to translate labels within the widgets library.
*/
protected readonly configService = inject(WidgetsConfigService);
@ViewChild('linksNavigationHeader')
private readonly linksNavigationHeader?: ElementRef<HTMLElement>;
@ViewChild('openLinksNavigation')
private readonly openLinksNavigation?: ElementRef<HTMLElement>;
@ViewChild('informationsbereichHeader')
private readonly informationsbereichHeader?: ElementRef<HTMLElement>;
@ViewChild('openInformationsbereich')
private readonly openInformationsbereich?: ElementRef<HTMLElement>;
protected unsupportedBrowserMessage = '';
private browserSupportResult?: BrowserSupportCheckResult;
private readonly browserVersionService = inject(BrowserVersionService);
private readonly destroyRef = inject(DestroyRef);
private readonly injector = inject(Injector);
ngOnInit(): void {
if (!this.checkBrowserVersion) {
return;
}
this.browserSupportResult = this.browserVersionService.checkCurrentBrowser();
if (this.browserSupportResult.supported) {
return;
}
this.configService.translation$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
if (this.browserSupportResult) {
this.unsupportedBrowserMessage = this.createUnsupportedBrowserMessage(this.browserSupportResult);
}
});
}
private createUnsupportedBrowserMessage(browserSupportResult: BrowserSupportCheckResult): string {
const detectedBrowser = browserSupportResult.detectedBrowser;
const browserText = detectedBrowser
? `${detectedBrowser.label} ${detectedBrowser.version}`
: this.configService.getTranslation('hauptfenster.browserWarning.currentBrowserFallback');
const supportedBrowsersText = browserSupportResult.supportedBrowsers
.map((browser) =>
this.configService.getTranslation('hauptfenster.browserWarning.supportedBrowser', {
browser: browser.label,
version: browser.minimumVersion
})
)
.join(', ');
return this.configService.getTranslation('hauptfenster.browserWarning.message', {
browser: browserText,
supportedBrowsers: supportedBrowsersText
});
}
collapseSidebar(): void {
this.collapsedLinksnavigation = true;
this.focusFirstAfterRender(this.linksNavigationHeader);
}
expandSidebar(): void {
this.collapsedLinksnavigation = false;
this.focusFirstAfterRender(this.openLinksNavigation);
}
collapseInformationsbereich(): void {
this.collapsedInformationsbereich = true;
this.focusFirstAfterRender(this.informationsbereichHeader);
}
expandInformationsbereich(): void {
this.collapsedInformationsbereich = false;
this.focusFirstAfterRender(this.openInformationsbereich);
}
private focusFirstAfterRender(container?: ElementRef<HTMLElement>): void {
afterNextRender(
{
write: () => {
const element = container?.nativeElement.querySelector<HTMLElement>(
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
element?.focus();
}
},
{injector: this.injector}
);
}
}
<div class="isy-hauptfenster flex flex-column min-h-screen" [class.isy-hauptfenster-responsive]="responsive">
<isy-skip-links [links]="links"></isy-skip-links>
@if (unsupportedBrowserMessage) {
<p-message
severity="warn"
icon="pi pi-exclamation-triangle"
styleClass="isy-hauptfenster-browser-warning dont-print"
role="alert"
aria-live="assertive"
aria-atomic="true"
>
<span>{{ unsupportedBrowserMessage }}</span>
</p-message>
}
<header class="grid grid-nogutter">
<div class="col isy-hauptfenster-anbieterlogo-bereich dont-print">
@if (logoAnbieterAwl) {
<a href="/">
<img
[alt]="configService.getTranslation('hauptfenster.altLogoAnbieterAwl')"
class="isy-hauptfenster-logo-anbieter-awl"
[src]="logoAnbieterAwl"
/>
</a>
} @else {
<ng-content select="[LogoAnbieterAwl]"></ng-content>
}
</div>
<div class="col isy-hauptfenster-nutzerbereich dont-print">
<div class="flex justify-content-end">
@if (logoAwl) {
<img
[alt]="configService.getTranslation('hauptfenster.altLogoAwl')"
class="isy-hauptfenster-logo-awl"
[src]="logoAwl"
/>
} @else {
<ng-content select="[LogoAwl]"></ng-content>
}
</div>
<div class="flex justify-content-end gap-2">
@if (userInfo) {
<div class="flex align-items-center gap-1">
<em class="pi pi-user"></em>
{{ userInfo.displayName }}
</div>
}
<p-button
id="isy-hauptfenster-logout-button"
(onClick)="logoutEvent.emit(userInfo)"
[outlined]="outlinedLogoutButton"
>
{{ configService.getTranslation('hauptfenster.logout') }}
</p-button>
</div>
</div>
</header>
<nav [attr.aria-label]="configService.getTranslation('hauptfenster.aria.mainNavigation')">
<p-megaMenu [model]="items" class="dont-print">
<ng-template pTemplate="end">
<ng-content select="[Hauptnavigation-Tools]"></ng-content>
</ng-template>
</p-megaMenu>
</nav>
<div class="isy-hauptfenster-titelzeile p-2" [style]="'background-color:' + applicationGroupColor + ';'">
@if (title) {
<div>{{ title }}</div>
} @else {
<ng-content select="[Titelzeile]"></ng-content>
}
</div>
<ng-content select="[Seitentoolbar]"></ng-content>
<div class="isy-hauptfenster-inhaltsbereich flex flex-grow-1 gap-3">
@if (showLinksnavigation) {
<aside
[attr.aria-label]="
collapsedLinksnavigation
? configService.getTranslation('hauptfenster.aria.leftNavigationCollapsed')
: configService.getTranslation('hauptfenster.aria.leftNavigation')
"
class="isy-hauptfenster-linksnavigation dont-print flex flex-column gap-1"
[style.width]="collapsedLinksnavigation ? null : linksNavigationWidth"
>
<div
class="sidebar-nav-header grid grid-nogutter align-items-center"
[id]="collapsedLinksnavigation ? 'collapsed-links-navigation' : null"
>
@if (!collapsedLinksnavigation && allowSidebarCollapse) {
<div class="col-9 pl-3">
<span class="font-bold">{{ linksNavigationTitle }}</span>
</div>
}
@if (allowSidebarCollapse) {
<div class="flex justify-content-end" [ngClass]="collapsedLinksnavigation ? 'col-12' : 'col-3'">
<p-button
styleClass="p-button-text"
[icon]="collapsedLinksnavigation ? 'pi pi-chevron-right' : 'pi pi-chevron-left'"
[ariaLabel]="
collapsedLinksnavigation
? configService.getTranslation('hauptfenster.aria.expandLinksnavigation')
: configService.getTranslation('hauptfenster.aria.collapseLinksnavigation')
"
[attr.aria-controls]="'open-links-navigation'"
[attr.aria-expanded]="!collapsedLinksnavigation"
(onClick)="collapsedLinksnavigation ? expandSidebar() : collapseSidebar()"
/>
</div>
}
</div>
<div [hidden]="collapsedLinksnavigation" id="open-links-navigation">
<ng-content select="[Linksnavigation]"></ng-content>
</div>
</aside>
}
<main class="flex-1 min-w-0 mt-3">
<ng-content></ng-content>
</main>
@if (showInformationsbereich) {
<aside
[attr.aria-label]="
collapsedInformationsbereich
? configService.getTranslation('hauptfenster.aria.informationAreaCollapsed')
: configService.getTranslation('hauptfenster.aria.informationArea')
"
class="isy-hauptfenster-informationsbereich dont-print flex flex-column gap-1"
[style.width]="collapsedInformationsbereich ? null : informationsbereichWidth"
>
@if (allowSidebarCollapse) {
<div
class="sidebar-info-header flex justify-content-start"
[id]="collapsedInformationsbereich ? 'collapsed-informationsbereich' : null"
>
<p-button
styleClass="p-button-text"
[icon]="collapsedInformationsbereich ? 'pi pi-chevron-left' : 'pi pi-chevron-right'"
[ariaLabel]="
collapsedInformationsbereich
? configService.getTranslation('hauptfenster.aria.expandInformationsbereich')
: configService.getTranslation('hauptfenster.aria.collapseInformationsbereich')
"
[attr.aria-controls]="'open-informationsbereich'"
[attr.aria-expanded]="!collapsedInformationsbereich"
(onClick)="collapsedInformationsbereich ? expandInformationsbereich() : collapseInformationsbereich()"
/>
</div>
}
<div [hidden]="collapsedInformationsbereich" id="open-informationsbereich">
<ng-content select="[Informationsbereich]"></ng-content>
</div>
</aside>
}
</div>
</div>
./hauptfenster.component.scss
$responsive-breakpoint: 1024px;
:host {
display: block;
container-type: inline-size;
}
.isy-hauptfenster {
border-top: 10px solid var(--isyfact-hauptfenster-background);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.isy-hauptfenster-titelzeile {
min-height: 15px;
}
.isy-hauptfenster-logo-anbieter-awl {
max-height: 100px;
}
.isy-hauptfenster-logo-awl {
max-height: 60px;
}
.isy-hauptfenster-anbieterlogo-bereich {
margin-left: var(--isyfact-space-sm);
}
.isy-hauptfenster-nutzerbereich {
margin-right: var(--isyfact-space-sm);
margin-bottom: var(--isyfact-space-sm);
}
.isy-hauptfenster-linksnavigation {
background-color: var(--isyfact-surface-ground);
}
.isy-hauptfenster-informationsbereich {
background-color: var(--isyfact-surface-ground);
}
.sidebar-nav-header,
.sidebar-info-header {
padding-top: var(--isyfact-space-xs);
}
:host ::ng-deep .isy-hauptfenster-browser-warning {
border-radius: 0;
justify-content: center;
position: sticky;
top: 0;
width: 100%;
padding: var(--isyfact-space-md) var(--isyfact-space-sm);
z-index: 1000;
}
:host ::ng-deep {
.p-megamenu {
.p-menuitem-text {
color: var(--p-surface-900);
}
}
}
@container (max-width: #{$responsive-breakpoint}) {
.isy-hauptfenster-responsive {
.isy-hauptfenster-linksnavigation,
.isy-hauptfenster-informationsbereich {
display: none !important;
}
.isy-hauptfenster-inhaltsbereich {
width: 100%;
min-width: 0;
gap: 0 !important;
}
.isy-hauptfenster-inhaltsbereich > main {
flex: 1 1 100% !important;
width: 100%;
max-width: 100%;
min-width: 0;
}
}
}
@media print {
.dont-print {
display: none;
}
}
nav {
z-index: 10;
}