File

projects/storefrontlib/shared/components/popover/popover.component.ts

Implements

OnInit OnDestroy AfterViewChecked

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector cx-popover
templateUrl ./popover.component.html

Index

Properties
Methods
HostBindings
HostListeners

Constructor

constructor(positioningService: PositioningService, winRef: WindowRef, changeDetectionRef: ChangeDetectorRef, renderer: Renderer2, router: Router)
Parameters :
Name Type Optional
positioningService PositioningService No
winRef WindowRef No
changeDetectionRef ChangeDetectorRef No
renderer Renderer2 No
router Router No

HostBindings

className
Type : string

Binding class name property.

HostListeners

click
click()

Listens for click inside popover component wrapper.

document:click
Arguments : '$event'
document:click(event: MouseEvent)

Listens for every document click and ignores clicks inside component.

keydown.escape
keydown.escape()

Listens for escape keydown event.

Methods

close
close(event: MouseEvent | KeyboardEvent)

Emits close event trigger.

Parameters :
Name Type Optional
event MouseEvent | KeyboardEvent No
Returns : void
escapeKeydown
escapeKeydown()
Decorators :
@HostListener('keydown.escape')

Listens for escape keydown event.

Returns : void
insideClick
insideClick()
Decorators :
@HostListener('click')

Listens for click inside popover component wrapper.

Returns : void
Protected isClickedOnDirective
isClickedOnDirective(event)
Parameters :
Name Optional
event No
Returns : any
Protected isClickedOnPopover
isClickedOnPopover(event)
Parameters :
Name Optional
event No
Returns : any
ngAfterViewChecked
ngAfterViewChecked()
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
outsideClick
outsideClick(event: MouseEvent)
Decorators :
@HostListener('document:click', ['$event'])

Listens for every document click and ignores clicks inside component.

Parameters :
Name Type Optional
event MouseEvent No
Returns : void
positionPopover
positionPopover()

Method uses positioning service calculation and based on that updates class name for popover component instance.

Returns : void
triggerScrollEvent
triggerScrollEvent()

Method uses Renderer2 service to listen window scroll event.

Registered only if property positionOnScroll is set to true.

Returns : void

Properties

Optional appendToBody
Type : boolean

Flag which informs positioning service if popover component should be appended to body. Otherwise popover is displayed right after trigger element in DOM.

Optional autoPositioning
Type : boolean

Flag used to define if popover should look for the best placement in case if there is not enough space in viewport for preferred position.

By default this property is set to true.

Value of this flag is omitted if preferred position is set to auto.

baseClass
Type : string
Decorators :
@HostBinding('className')

Binding class name property.

content
Type : string | TemplateRef<any>

String or template to be rendered inside popover wrapper component.

Optional customClass
Type : string

Custom class name passed to popover component.

If this property is not set the default popover class is cx-popover.

Optional displayCloseButton
Type : boolean

Flag used to show/hide close button in popover component.

eventSubject
Type : Subject<PopoverEvent>

Subject which emits specific type of PopoverEvent.

focusConfig
Type : FocusConfig

Configuration for a11y improvements.

iconTypes
Default value : ICON_TYPE

Icon types for close button icon.

isTemplate
Type : boolean

Flag which indicates if passed content is a TemplateRef or string.

popoverClass
Type : PopoverPosition

Class name generated by positioning service indicating position of popover.

popoverInstance
Type : ComponentRef<PopoverComponent>

Current initiated popover instance.

Optional position
Type : PopoverPosition

The preferred placement of the popover. Default popover position is 'top'.

Allowed popover positions: 'auto', 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right', 'left-top', 'left-bottom', 'right-top', 'right-bottom'.

Optional positionOnScroll
Type : boolean

Flag indicates if popover should be re-positioned on scroll event.

resizeSub
Type : Subscription

After popover component is initialized position needs to be changing dynamically in case if any viewport changes happened.

routeChangeSub
Type : Subscription

After popover component is initialized popover should be closed in case if current route has been changed.

scrollEventUnlistener
Type : function

Scroll event unlistener.

triggerElement
Type : ElementRef

Element which triggers displaying popover component. This property is needed to calculate valid position for popover.

import {
  AfterViewChecked,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ComponentRef,
  ElementRef,
  HostBinding,
  HostListener,
  OnDestroy,
  OnInit,
  Renderer2,
  TemplateRef,
} from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Subject, Subscription } from 'rxjs';
import { WindowRef } from '@spartacus/core';
import { PopoverEvent, PopoverPosition } from './popover.model';
import { PositioningService } from '../../services/positioning/positioning.service';
import { FocusConfig } from '../../../layout/a11y/keyboard-focus/keyboard-focus.model';
import { ICON_TYPE } from '../../../cms-components/misc/icon/icon.model';

@Component({
  selector: 'cx-popover',
  templateUrl: './popover.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PopoverComponent implements OnInit, OnDestroy, AfterViewChecked {
  /**
   * String or template to be rendered inside popover wrapper component.
   */
  content: string | TemplateRef<any>;

  /**
   * Element which triggers displaying popover component.
   * This property is needed to calculate valid position for popover.
   */
  triggerElement: ElementRef;

  /**
   * Current initiated popover instance.
   */
  popoverInstance: ComponentRef<PopoverComponent>;

  /**
   * Flag which informs positioning service if popover component
   * should be appended to body. Otherwise popover is displayed right after
   * trigger element in DOM.
   */
  appendToBody?: boolean;

  /**
   * The preferred placement of the popover. Default popover position is 'top'.
   *
   * Allowed popover positions: 'auto', 'top', 'bottom', 'left', 'right',
   * 'top-left', 'top-right', 'bottom-left', 'bottom-right',
   * 'left-top', 'left-bottom', 'right-top', 'right-bottom'.
   */
  position?: PopoverPosition;

  /**
   * Flag used to define if popover should look for the best placement
   * in case if there is not enough space in viewport for preferred position.
   *
   * By default this property is set to `true`.
   *
   * Value of this flag is omitted if preferred position is set to `auto`.
   */
  autoPositioning?: boolean;

  /**
   * Custom class name passed to popover component.
   *
   * If this property is not set the default popover class is `cx-popover`.
   */
  customClass?: string;

  /**
   * Flag used to show/hide close button in popover component.
   */
  displayCloseButton?: boolean;

  /**
   * Flag which indicates if passed content is a TemplateRef or string.
   */
  isTemplate: boolean;

  /**
   * After popover component is initialized position needs to be changing dynamically
   * in case if any viewport changes happened.
   */
  resizeSub: Subscription;

  /**
   * After popover component is initialized popover should be closed in case
   * if current route has been changed.
   */
  routeChangeSub: Subscription;

  /**
   * Class name generated by positioning service indicating position of popover.
   */
  popoverClass: PopoverPosition;

  /**
   * Configuration for a11y improvements.
   */
  focusConfig: FocusConfig;

  /**
   * Flag indicates if popover should be re-positioned on scroll event.
   */
  positionOnScroll?: boolean;

  /**
   * Icon types for close button icon.
   */
  iconTypes = ICON_TYPE;

  /**
   * Subject which emits specific type of `PopoverEvent`.
   */
  eventSubject: Subject<PopoverEvent>;

  /**
   * Scroll event unlistener.
   */
  scrollEventUnlistener: () => void;

  /**
   * Binding class name property.
   */
  @HostBinding('className') baseClass: string;

  /**
   * Listens for click inside popover component wrapper.
   */
  @HostListener('click')
  insideClick() {
    this.eventSubject.next(PopoverEvent.INSIDE_CLICK);
  }

  /**
   * Listens for every document click and ignores clicks
   * inside component.
   */
  @HostListener('document:click', ['$event'])
  outsideClick(event: MouseEvent) {
    if (!this.isClickedOnPopover(event) && !this.isClickedOnDirective(event)) {
      this.eventSubject.next(PopoverEvent.OUTSIDE_CLICK);
    }
  }

  /**
   * Listens for `escape` keydown event.
   */
  @HostListener('keydown.escape')
  escapeKeydown() {
    this.eventSubject.next(PopoverEvent.ESCAPE_KEYDOWN);
  }

  protected isClickedOnPopover(event) {
    return this.popoverInstance.location.nativeElement.contains(event.target);
  }

  protected isClickedOnDirective(event) {
    return this.triggerElement.nativeElement.contains(event.target);
  }

  /**
   * Emits close event trigger.
   */
  close(event: MouseEvent | KeyboardEvent) {
    event.preventDefault();
    if (event instanceof MouseEvent) {
      this.eventSubject.next(PopoverEvent.CLOSE_BUTTON_CLICK);
    } else {
      this.eventSubject.next(PopoverEvent.CLOSE_BUTTON_KEYDOWN);
    }
  }

  /**
   * Method uses `Renderer2` service to listen window scroll event.
   *
   * Registered only if property `positionOnScroll` is set to `true`.
   */
  triggerScrollEvent() {
    this.scrollEventUnlistener = this.renderer.listen(
      this.winRef.nativeWindow,
      'scroll',
      () => this.positionPopover()
    );
  }

  /**
   * Method uses positioning service calculation and based on that
   * updates class name for popover component instance.
   */
  positionPopover() {
    this.popoverClass = this.positioningService.positionElements(
      this.triggerElement.nativeElement,
      this.popoverInstance.location.nativeElement,
      this.positioningService.getPositioningClass(
        this.position,
        this.autoPositioning
      ),
      this.appendToBody
    );

    this.changeDetectionRef.markForCheck();
    this.baseClass = `${this.customClass} ${this.popoverClass} opened`;
  }

  ngOnInit(): void {
    this.isTemplate = this.content instanceof TemplateRef;

    if (!this.customClass) this.customClass = 'cx-popover';
    if (!this.position) this.position = 'top';
    if (this.autoPositioning === undefined) this.autoPositioning = true;

    this.baseClass = `${this.customClass}`;

    this.resizeSub = this.winRef.resize$.subscribe(() => {
      this.positionPopover();
    });

    this.routeChangeSub = this.router.events
      .pipe(filter((event) => event instanceof NavigationStart))
      .subscribe(() => {
        this.eventSubject.next(PopoverEvent.ROUTE_CHANGE);
      });

    if (this.positionOnScroll) {
      this.triggerScrollEvent();
    }
  }

  ngAfterViewChecked(): void {
    this.positionPopover();
  }

  ngOnDestroy(): void {
    if (this.resizeSub) {
      this.resizeSub.unsubscribe();
    }

    if (this.routeChangeSub) {
      this.routeChangeSub.unsubscribe();
    }

    if (this.scrollEventUnlistener) {
      this.scrollEventUnlistener();
    }
  }

  constructor(
    protected positioningService: PositioningService,
    protected winRef: WindowRef,
    protected changeDetectionRef: ChangeDetectorRef,
    protected renderer: Renderer2,
    protected router: Router
  ) {}
}
<div class="arrow"></div>
<div class="popover-body" [cxFocus]="focusConfig">
  <div class="cx-close-row">
    <button
      *ngIf="displayCloseButton"
      type="button"
      class="close"
      (keydown.enter)="close($event)"
      (keydown.space)="close($event)"
      (click)="close($event)"
    >
      <cx-icon [type]="iconTypes.CLOSE"></cx-icon>
    </button>
  </div>
  <ng-container *ngIf="isTemplate">
    <ng-container *ngTemplateOutlet="content"></ng-container>
  </ng-container>
  <span *ngIf="!isTemplate">{{ content }}</span>
</div>
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""