File

projects/isy-angular-widgets/src/lib/hauptfenster/hauptfenster.component.ts

Description

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 :
<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>

Implements

OnInit

Metadata

Index

Properties
Methods
Inputs
Outputs

Inputs

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.

Outputs

logoutEvent
Type : EventEmitter

Methods

collapseInformationsbereich
collapseInformationsbereich()
Returns : void
collapseSidebar
collapseSidebar()
Returns : void
expandInformationsbereich
expandInformationsbereich()
Returns : void
expandSidebar
expandSidebar()
Returns : void
ngOnInit
ngOnInit()
Returns : void

Properties

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;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""