@WidgetComponent

nm-stepper

File

src/app/shared/widgets/stepper/stepper-widget.component.ts

Implements

OnDestroy

Metadata

providers { : , : { : false }, }
selector nm-stepper
styleUrls stepper-widget.component.scss
templateUrl ./stepper-widget.component.html

Index

Widget inputs
Widget outputs
Properties
Methods

Methods

Public configureWidget
configureWidget(configuration: WidgetConfig)
Decorators : WidgetConfigure
Parameters :
Name Type Optional
configuration WidgetConfig<StepperConfiguration> no
Returns : void
Public getCssClass
getCssClass(hiddenHeaderSteps: number[])
Parameters :
Name Type Optional
hiddenHeaderSteps number[] no
Returns : string
Private getHiddenHeaderSteps
getHiddenHeaderSteps()
Returns : number[]
ngOnDestroy
ngOnDestroy()
Returns : void
Public onSelectionChange
onSelectionChange(cdkEvent: StepperSelectionEvent)
Parameters :
Name Type Optional
cdkEvent StepperSelectionEvent no
Returns : void

Properties

Private active
active:
Default value : new ReplaySubject<StepSelectedEvent>(1)
Decorators : WidgetOutput

Outputs the currently active step

Private activeIndex
activeIndex: number
Type : number
Default value : 0
Private completeAndNext
completeAndNext:
Default value : new Subject()
Decorators : WidgetInput

Completes the current step and selects the next step

Public configuration
configuration: WidgetConfig<StepperConfiguration>
Type : WidgetConfig<StepperConfiguration>
Decorators : WidgetConfiguration
Public cssClass
cssClass: string
Type : string
Public disabledStates
disabledStates: boolean[]
Type : boolean[]
Default value : []
Private disableStep
disableStep:
Default value : new Subject<{ stepNo: number; state: boolean }>()
Decorators : WidgetInput

Disabled a specified step

Public disableStepsHeader
disableStepsHeader: boolean
Type : boolean
Public header
header: string
Type : string
Public linear
linear: boolean
Type : boolean
Private navigateToThisStep
navigateToThisStep:
Default value : new Subject<number>()
Decorators : WidgetInput

Navigate to a specific step

Private next
next:
Default value : new Subject()
Decorators : WidgetInput

Selects the next step if possible

Private previous
previous:
Default value : new Subject()
Decorators : WidgetInput

Selects the previous step if possible

Private reset
reset:
Default value : new Subject()
Decorators : WidgetInput

Resets the stepper

Public stepper
stepper: MatStepper
Type : MatStepper
Decorators : ViewChild
Private unsubscribe
unsubscribe:
Default value : NgUnsubscribe.create()
Private valid
valid:
Default value : new ReplaySubject<{ index: number; valid: boolean }>(1)
Decorators : WidgetInput

Input to change the valid state of a step, takes an object as parameter, where index is the index of the step and valid is the new value

Private validFrom
validFrom:
Default value : new Subject<{ startIndex: number; valid: boolean }>()
Decorators : WidgetInput

Input to change the valid state from a step until to the last step. Use case:

  • all steps are valid because of user interaction
  • the user changed step x
  • because of the change all steps after x should be changed to invalid
Public validStates
validStates: boolean[]
Type : boolean[]
Default value : []
Public widgetId
widgetId: string
Type : string
Decorators : WidgetId
Public withBorder
withBorder: boolean
Type : boolean
Public withHeader
withHeader: boolean
Type : boolean
import { Component, OnDestroy, ViewChild } from "@angular/core";

import {
  WidgetComponent,
  WidgetConfiguration,
  WidgetConfigure,
  WidgetId,
  WidgetInput,
  WidgetOutput,
} from "../widget.metadata";
import {
  getOrDefault,
  throwErrorIfUndefined,
  WidgetConfig,
} from "../widget.configuration";
import { NgUnsubscribe } from "../../ng-unsubscribe";
import { ReplaySubject, Subject } from "rxjs";
import {
  STEPPER_GLOBAL_OPTIONS,
  StepperSelectionEvent,
} from "@angular/cdk/stepper";
import { takeUntil, debounceTime } from "rxjs/operators";
import { MatStepper } from "@angular/material/stepper";
import { BaseConfiguration } from "../widgetframe/widgetframe.component";

export interface StepperConfiguration extends BaseConfiguration {
  /**
   * Is it needed that previous steps are completed before starting the next one. @default(true)
   */
  linear?: boolean;

  /**
   * The steps this component has
   */
  steps: Step[];

  /**
   * It is needed to disable clicking steps header. @default(false)
   */
  disableStepsHeader?: boolean;
}

export interface Step {
  /**
   * The label of this step - should be a translation key
   */
  label: string;

  /**
   * The component that should be used to render this step
   */
  component: string;

  /**
   * If this step is optional, @default(false)
   */
  optional?: boolean;

  /**
   * Hides/Shows step header, @default(false)
   */
  hiddenHeader?: boolean;

  /**
   * Step is editable on return to previously completed steps @default(true)
   */
  editable?: boolean;

  /**
   * If this step is disabled, @default(false)
   */
  disabled?: boolean;
}

export interface StepSelectedEvent {
  /**
   * Selected step
   */
  step: Step;

  /**
   * The step selection event containing: selectedIndex, selectedStep, previouslySelectedIndex, previouslySelectedStep
   */
  event: StepperSelectionEvent;
}

@WidgetComponent("nm-stepper")
@Component({
  selector: "nm-stepper",
  templateUrl: "./stepper-widget.component.html",
  styleUrls: ["./stepper-widget.component.scss"],
  providers: [
    {
      provide: STEPPER_GLOBAL_OPTIONS,
      useValue: { displayDefaultIndicatorType: false },
    },
  ],
})
export class StepperWidgetComponent implements OnDestroy {
  public header: string;
  public withHeader: boolean;
  public withBorder: boolean;
  public linear: boolean;
  public disableStepsHeader: boolean;
  public cssClass: string;

  private unsubscribe = NgUnsubscribe.create();

  public validStates: boolean[] = [];
  public disabledStates: boolean[] = [];

  @ViewChild("stepper")
  public stepper: MatStepper;

  /**
   * Input to change the valid state of a step, takes an object as parameter, where index is the index of the step and valid is the new value
   */
  @WidgetInput("valid")
  private valid = new ReplaySubject<{ index: number; valid: boolean }>(1);

  /**
   * Input to change the valid state from a step until to the last step.
   * Use case:
   * - all steps are valid because of user interaction
   * - the user changed step x
   * - because of the change all steps after x should be changed to invalid
   */
  @WidgetInput("validFrom")
  private validFrom = new Subject<{ startIndex: number; valid: boolean }>();

  /**
   * Outputs the currently active step
   */
  @WidgetOutput("active")
  private active = new ReplaySubject<StepSelectedEvent>(1);

  /**
   * Selects the next step if possible
   */
  @WidgetInput("next")
  private next = new Subject();

  /**
   * Completes the current step and selects the next step
   */
  @WidgetInput("completeAndNext")
  private completeAndNext = new Subject();

  /**
   * Navigate to a specific step
   */
  @WidgetInput()
  private navigateToThisStep = new Subject<number>();

  /**
   * Selects the previous step if possible
   */
  @WidgetInput("previous")
  private previous = new Subject();

  /**
   * Resets the stepper
   */
  @WidgetInput("reset")
  private reset = new Subject();

  /**
   * Disabled a specified step
   */
  @WidgetInput("disableStep")
  private disableStep = new Subject<{ stepNo: number; state: boolean }>();

  @WidgetId()
  public widgetId: string;

  @WidgetConfiguration()
  public configuration: WidgetConfig<StepperConfiguration>;

  private activeIndex = 0;

  @WidgetConfigure()
  public configureWidget(configuration: WidgetConfig<StepperConfiguration>) {
    throwErrorIfUndefined("steps", configuration.configuration);
    this.header = getOrDefault(configuration.configuration.header, "primary");
    this.withHeader = getOrDefault(
      configuration.configuration.withHeader,
      true
    );

    this.withBorder = getOrDefault(
      configuration.configuration.withBorder,
      true
    );

    configuration.configuration.steps.forEach((step, index) => {
      this.validStates[index] = false;
      this.disabledStates[index] = step.disabled;
    });

    this.linear = getOrDefault(this.configuration.configuration.linear, true);
    this.disableStepsHeader = getOrDefault(
      this.configuration.configuration.disableStepsHeader,
      false
    );

    const hiddenHeaderSteps: number[] = this.getHiddenHeaderSteps();
    this.cssClass = this.getCssClass(hiddenHeaderSteps);

    this.next.pipe(takeUntil(this.unsubscribe)).subscribe(() => {
      if (this.disabledStates[this.activeIndex + 1]) {
        this.stepper.next();
        this.next.next();
        return;
      }
      this.stepper.next();
    });

    this.completeAndNext.pipe(takeUntil(this.unsubscribe)).subscribe(() => {
      this.stepper.selected.completed = true;
      this.validStates[this.activeIndex] = true;
      this.next.next();
    });

    this.navigateToThisStep
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((targetIndex) => {
        if (targetIndex > this.validStates.length || targetIndex < 0) {
          return;
        }
        if (this.disabledStates[targetIndex]) {
          this.navigateToThisStep.next(targetIndex + 1);
          return;
        }

        this.stepper.selected.completed = true;
        this.validStates[targetIndex] = true;
        this.activeIndex = targetIndex;
        const currentIndex = this.stepper.selectedIndex;

        if (currentIndex < targetIndex) {
          for (let i = 0; i < targetIndex - currentIndex; i++) {
            this.stepper.next();
          }
        } else {
          for (let i = 0; i < currentIndex - targetIndex; i++) {
            this.stepper.previous();
          }
        }
      });

    this.previous.pipe(takeUntil(this.unsubscribe)).subscribe(() => {
      if (this.activeIndex > 1 && this.disabledStates[this.activeIndex - 1]) {
        this.stepper.previous();
        this.previous.next();
        return;
      }
      this.stepper.previous();
    });

    this.reset
      .pipe(
        takeUntil(this.unsubscribe),
        //is needed because of an infinite recursion (from 'active' to 'reset')
        //case: change the step over the head and when active step is 0, reset the stepper (occurred in vwproductdatazadb)
        debounceTime(100)
      )
      .subscribe(() => {
        this.stepper.reset();
        configuration.configuration.steps.forEach((step, index) => {
          this.validStates[index] = false;
          this.stepper.steps.get(index).state = step.disabled
            ? "disabled"
            : undefined;
          this.disabledStates[index] = step.disabled;
        });
      });

    this.valid.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
      this.validStates[data.index] = data.valid;
    });

    this.validFrom.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
      for (var i = data.startIndex; i < this.validStates.length; i++) {
        this.validStates[i] = data.valid;
      }
    });

    this.disableStep.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
      this.stepper.steps.get(data.stepNo).state = data.state
        ? "disabled"
        : undefined;
      this.disabledStates[data.stepNo] = data.state;
      this.stepper._stateChanged();
    });
  }

  public onSelectionChange(cdkEvent: StepperSelectionEvent) {
    this.activeIndex = cdkEvent.selectedIndex;
    const event: StepSelectedEvent = {
      event: cdkEvent,
      step: this.configuration.configuration.steps[this.activeIndex],
    };
    this.active.next(event);
  }

  public getCssClass(hiddenHeaderSteps: number[]): string {
    var cssClass = "";

    if (this.disableStepsHeader) {
      cssClass += "nm-stepper__header--disabled ";
    }

    if (hiddenHeaderSteps.length > 0) {
      hiddenHeaderSteps.forEach((hiddenIndex) => {
        const stepIndex: number = hiddenIndex + 1;
        cssClass += "nm-stepper__step" + stepIndex + "-hidden ";
      });
    }

    return cssClass;
  }

  private getHiddenHeaderSteps(): number[] {
    const steps = this.configuration.configuration.steps;
    const hiddenHeaderSteps = [];

    for (var i = 0; i < steps.length; i++) {
      if (steps[i].hiddenHeader) {
        hiddenHeaderSteps.push(i);
      }
    }

    return hiddenHeaderSteps;
  }

  ngOnDestroy(): void {
    this.unsubscribe.destroy();
  }
}
<nm-widgetframe
  [header]="header"
  [configuration]="configuration"
  [infoTitle]="configuration.configuration.infoTitle"
  [widgetId]="widgetId"
  [infoText]="configuration.configuration.infoText"
  [infoPlacement]="'bottom'"
  [wikiLink]="configuration.configuration.wikiLink"
  [toolbarInvisible]="!withHeader"
  [withBorder]="withBorder"
>
  <div slot="title" class="nm-widgetframe__title">
    <span class="title-font"
      >{{ configuration.configuration.title | translate }}
    </span>
  </div>

  <div slot="content" class="nm-widgetframe__content">
    <mat-horizontal-stepper
      [ngClass]="cssClass"
      [linear]="linear"
      #stepper
      (selectionChange)="onSelectionChange($event)"
    >
      <ng-container
        *ngFor="let step of configuration.configuration.steps; let i = index"
      >
        <mat-step
          [label]="step.label | translate"
          [completed]="validStates[i]"
          [optional]="step.optional"
          [editable]="step.editable !== false"
          [state]="step.disabled ? 'disabled' : undefined"
          [aria-labelledby]="step.disabled ? 'disabled' : undefined"
        >
          <nm-container
            *ngIf="step.component"
            [configuration]="step.component | widgetFor: configuration"
            [parent]="widgetId"
            [id]="step.component"
          >
          </nm-container>
        </mat-step>

        <ng-template matStepperIcon="disabled">
          <mat-icon>remove_circle_outline</mat-icon>
        </ng-template>
      </ng-container>
    </mat-horizontal-stepper>
  </div>
</nm-widgetframe>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""