src/app/core/admin/admin-menu/menu-item-form/menu-item-form.component.ts

Metadata

Relationships

Depends on

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor()

Inputs

hideLabel
Type : boolean
Default value : false
hideLink
Type : boolean
Default value : false
isNew
Type : boolean
Default value : false
item
Type : MenuItem
Required :  true
linkOptions
Type : { value: string; label: string }[]
Default value : []

Available routes that are offered to the user for selection.

showLinkError
Type : boolean
Default value : false

Outputs

item
Type : MenuItem

Methods

isNoLinkModeEnabled
isNoLinkModeEnabled()
Returns : boolean
Async toggleNoLinkMode
toggleNoLinkMode(event: MatSlideToggleChange)
Parameters :
Name Type Optional
event MatSlideToggleChange No
Returns : any

Properties

customLinkMode
Type : unknown
Default value : linkedSignal(() => { const linkOptions = this.linkOptions(); const item = this.item(); // If no options are available, always start in custom link mode if (!linkOptions || linkOptions.length === 0) { return true; } // If there's a link value but it's not in the available options, switch to custom mode if ( item?.link && !linkOptions.some((option) => option.value === item.link) ) { return true; } return false; })

If true: show free-text input. If false: show dropdown with linkOptions.

itemIcon
Type : unknown
Default value : linkedSignal({ source: this.item, computation: (item) => item?.icon ?? "", })
itemLabel
Type : unknown
Default value : linkedSignal({ source: this.item, computation: (item) => item?.label ?? "", })
itemLink
Type : unknown
Default value : linkedSignal({ source: this.item, computation: (item) => item?.link ?? "", })
linkErrorStateMatcher
Type : ErrorStateMatcher
Default value : { isErrorState: (_control: FormControl | null): boolean => { return this.showLinkError() && !this.item()?.link?.trim(); }, }
noLinkMode
Type : unknown
Default value : linkedSignal(() => !this.isNew() && !this.item()?.link?.trim())

Whether this item is intentionally configured as a parent section without a link.

import {
  Component,
  inject,
  linkedSignal,
  ChangeDetectionStrategy,
  input,
  model,
  effect,
} from "@angular/core";
import { MenuItem } from "../../../ui/navigation/menu-item";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { FormControl, FormsModule } from "@angular/forms";
import { ErrorStateMatcher } from "@angular/material/core";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { MatTooltipModule } from "@angular/material/tooltip";
import { MatSelectModule } from "@angular/material/select";
import { MatIconButton } from "@angular/material/button";
import {
  MatSlideToggleChange,
  MatSlideToggleModule,
} from "@angular/material/slide-toggle";
import { IconComponent } from "#src/app/core/common-components/icon-input/icon-input.component";
import { ConfirmationDialogService } from "#src/app/core/common-components/confirmation-dialog/confirmation-dialog.service";

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-menu-item-form",
  standalone: true,
  imports: [
    MatFormFieldModule,
    MatInputModule,
    FormsModule,
    IconComponent,
    FaIconComponent,
    MatTooltipModule,
    MatSelectModule,
    MatIconButton,
    MatSlideToggleModule,
  ],
  templateUrl: "./menu-item-form.component.html",
  styleUrls: ["./menu-item-form.component.scss"],
})
export class MenuItemFormComponent {
  private readonly confirmationDialog = inject(ConfirmationDialogService);

  item = model.required<MenuItem>();
  hideLabel = input<boolean>(false);
  hideLink = input<boolean>(false);
  isNew = input<boolean>(false);
  showLinkError = input<boolean>(false);

  /**
   * Available routes that are offered to the user for selection.
   */
  linkOptions = input<{ value: string; label: string }[]>([]);

  /**
   * If true: show free-text input. If false: show dropdown with linkOptions.
   */
  customLinkMode = linkedSignal(() => {
    const linkOptions = this.linkOptions();
    const item = this.item();

    // If no options are available, always start in custom link mode
    if (!linkOptions || linkOptions.length === 0) {
      return true;
    }

    // If there's a link value but it's not in the available options, switch to custom mode
    if (
      item?.link &&
      !linkOptions.some((option) => option.value === item.link)
    ) {
      return true;
    }

    return false;
  });

  /**
   * Whether this item is intentionally configured as a parent section without a link.
   */
  noLinkMode = linkedSignal(() => !this.isNew() && !this.item()?.link?.trim());

  linkErrorStateMatcher: ErrorStateMatcher = {
    isErrorState: (_control: FormControl | null): boolean => {
      return this.showLinkError() && !this.item()?.link?.trim();
    },
  };

  async toggleNoLinkMode(event: MatSlideToggleChange) {
    if (event.checked && this.item()?.link?.trim()) {
      // Warn before removing an existing link.
      const confirmed = await this.confirmationDialog.getConfirmation(
        $localize`:Confirmation title for removing link from menu item:Remove link?`,
        $localize`:Confirmation message for removing link from menu item:This item currently has a link. Turning this on will remove it. Do you still want to proceed?`,
      );

      if (!confirmed) {
        event.source.checked = false;
        return;
      }

      const newItem = { ...this.item() };
      delete newItem.link;
      this.item.set(newItem);
    }

    this.noLinkMode.set(event.checked);
  }

  isNoLinkModeEnabled(): boolean {
    return this.noLinkMode();
  }

  itemLabel = linkedSignal({
    source: this.item,
    computation: (item) => item?.label ?? "",
  });

  itemLink = linkedSignal({
    source: this.item,
    computation: (item) => item?.link ?? "",
  });

  itemIcon = linkedSignal({
    source: this.item,
    computation: (item) => item?.icon ?? "",
  });

  constructor() {
    effect(() => {
      const label = this.itemLabel();
      const link = this.itemLink();
      const icon = this.itemIcon();
      const current = this.item();
      if (
        label !== (current?.label ?? "") ||
        link !== (current?.link ?? "") ||
        icon !== (current?.icon ?? "")
      ) {
        this.item.set({ ...current, label, link, icon });
      }
    });
  }
}
<div class="menu-item-form">
  @if (!hideLabel()) {
    <mat-form-field appearance="fill" class="full-width">
      <mat-label i18n>Label</mat-label>
      <input
        matInput
        [ngModel]="itemLabel()"
        (ngModelChange)="itemLabel.set($event)"
      />
    </mat-form-field>
  }

  @if (!hideLink()) {
    <mat-slide-toggle
      [checked]="noLinkMode()"
      (change)="toggleNoLinkMode($event)"
      i18n
      >Add as parent section without link</mat-slide-toggle
    >
  }

  @if (!hideLink() && !noLinkMode()) {
    <div class="flex-row align-center gap-small">
      <mat-form-field appearance="fill" class="full-width">
        <mat-label i18n>Link</mat-label>

        @if (linkOptions()?.length && !customLinkMode()) {
          <mat-select
            [ngModel]="itemLink()"
            (ngModelChange)="itemLink.set($event)"
            [errorStateMatcher]="linkErrorStateMatcher"
          >
            @for (option of linkOptions(); track option.value) {
              <mat-option [value]="option.value">
                {{ option.label }}
              </mat-option>
            }
          </mat-select>
        } @else {
          <input
            matInput
            [ngModel]="itemLink()"
            (ngModelChange)="itemLink.set($event)"
            [errorStateMatcher]="linkErrorStateMatcher"
          />
        }

        <mat-error i18n>This field is required</mat-error>

        @if (customLinkMode()) {
          <mat-hint align="start" i18n>
            You can define a link manually. To create a new link, open Aam
            Digital in a new tab, go to the desired record type, copy everything
            after ‘https://&lt;your-system&gt;.aam-digital.app/’, and paste it
            here as the link target.
          </mat-hint>
        }
      </mat-form-field>

      @if (customLinkMode()) {
        <button
          mat-icon-button
          (click)="customLinkMode.update((v) => !v)"
          class="margin-left-small"
          matTooltip="Switch to select mode"
          i18n-matTooltip
        >
          <fa-icon icon="list"></fa-icon>
        </button>
      } @else {
        <button
          mat-icon-button
          (click)="customLinkMode.update((v) => !v)"
          class="margin-left-small"
          matTooltip="Switch to custom link mode"
          i18n-matTooltip
        >
          <fa-icon icon="edit"></fa-icon>
        </button>
      }
    </div>
  }

  <app-icon-input
    [icon]="itemIcon()"
    (iconChange)="itemIcon.set($event)"
  ></app-icon-input>
</div>

./menu-item-form.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""