Skip to content
Snippets Groups Projects
shared-searchable-abstract-content.presentational.ts 7.25 KiB
Newer Older
import {OverlayRef} from "@angular/cdk/overlay";
William Arsac's avatar
William Arsac committed
  Directive,
  Input,
  OnInit,
  ViewChild,
} from "@angular/core";
import {
  FormBuilder,
  FormGroup,
} from "@angular/forms";
import {environment} from "@environments/environment";
import {SharedAbstractContentPresentational} from "@shared/components/presentationals/shared-abstract-content/shared-abstract-content.presentational";
import {SharedSearchableMultiSelectPresentational} from "@shared/components/presentationals/shared-searchable-multi-select/shared-searchable-multi-select.presentational";
import {SharedSearchableSingleSelectPresentational} from "@shared/components/presentationals/shared-searchable-single-select/shared-searchable-single-select.presentational";
import {Observable} from "rxjs";
import {
  debounceTime,
  distinctUntilChanged,
  tap,
} from "rxjs/operators";
import {
William Arsac's avatar
William Arsac committed
@Directive()
export abstract class SharedSearchableAbstractContentPresentational<TResource extends BaseResourceType> extends SharedAbstractContentPresentational<TResource> implements OnInit, AfterViewInit {
  totalObs: Observable<number>;
  isLoadingObs: Observable<boolean>;
  isLoadingChunkObs: Observable<boolean>;
  navigationResId: string;
  protected _overlayRefs: Set<OverlayRef> = new Set<OverlayRef>();

  @HostBinding("class.is-loading")
  isLoading: boolean;

Quentin Torck's avatar
Quentin Torck committed
  list: TResource[];

  abstract host: SharedSearchableMultiSelectPresentational<ResourceStateModel<TResource>, TResource> | SharedSearchableSingleSelectPresentational<ResourceStateModel<TResource>, TResource>;
William Arsac's avatar
William Arsac committed
  @ViewChild("input")
  @HostListener("scroll", ["$event"])
  onWindowScroll(): void {
    if (ResourceState.totalSnapshot(this._store, this.host.state) === ResourceState.listSnapshot(this._store, this.host.state).length) {
      return;
    }
    const currentPosition = this._elementRef.nativeElement.scrollTop + this._elementRef.nativeElement.offsetHeight;
    const maxPosition = this._elementRef.nativeElement.scrollHeight;

    if (currentPosition >= maxPosition) {
  protected readonly _debounceTime: number = 250;
  protected readonly _pageSize: number = environment.defaultEnumValuePageSizeLazyLoad;

  constructor(protected readonly _store: Store,
              protected readonly _fb: FormBuilder,
              protected readonly _elementRef: ElementRef) {
    super();
  }

  ngOnInit(): void {
    super.ngOnInit();

    this._initSearchForm();
    this._dispatchInitDataLoad();
    this._retrieveSelector();
    this._observeSearchInput();
    this._observeIsLoading();
Quentin Torck's avatar
Quentin Torck committed
    this._observeList();
  }

  private _initSearchForm(): void {
    this.form = this._fb.group({
      [this.host.formDefinition.search]: [""],
    });
  }

  getFocusedResourceAndIndex(): { value: TResource | undefined, index: number } {
Quentin Torck's avatar
Quentin Torck committed
    const list = this.list;
    for (let index = 0; index < list.length; index++) {
      if (this.navigationResId === list[index].resId) {
        return {value: list[index], index};
Quentin Torck's avatar
Quentin Torck committed
    return {value: undefined, index: -1};
Quentin Torck's avatar
Quentin Torck committed
  getFocusedResource(): TResource | undefined {
    return this.getFocusedResourceAndIndex().value;
  }
Quentin Torck's avatar
Quentin Torck committed
  getFocusedIndex(): number {
    return this.getFocusedResourceAndIndex().index;
  }

  selectNextResource(): void {
    const list = this.list;
    const focusedIndex = this.getFocusedIndex();
    if (focusedIndex !== list.length - 1) {
      this.navigationResId = list[focusedIndex + 1].resId;
    }
  }

  selectPreviousResource(): void {
Quentin Torck's avatar
Quentin Torck committed
    const list = this.list;
    const focusedIndex = this.getFocusedIndex();
    if (focusedIndex > 0) {
      this.navigationResId = list[focusedIndex - 1].resId;
Quentin Torck's avatar
Quentin Torck committed
      this.navigationResId = undefined;
    }
  }

  @HostListener("keydown.arrowDown", ["$event"])
  onArrowDown(keyboardEvent: KeyboardEvent): void {
    if (keyboardEvent && !keyboardEvent.defaultPrevented) {
      keyboardEvent.preventDefault();
      this.selectNextResource();
    }
  }

  @HostListener("keydown.arrowUp", ["$event"])
  onArrowUp(keyboardEvent: KeyboardEvent): void {
    if (keyboardEvent && !keyboardEvent.defaultPrevented) {
      keyboardEvent.preventDefault();
      this.selectPreviousResource();
    }
  }

  @HostListener("keydown.enter", ["$event"])
  onSelectValue(keyboardEvent: KeyboardEvent): void {
    if (keyboardEvent && !keyboardEvent.defaultPrevented) {
      keyboardEvent.preventDefault();
      this.select(this.getFocusedResource());
    }
  }

    const queryParameters = new QueryParameters(this._pageSize, this.host.sort);
    if (isNotNullNorUndefined(this.host.extraSearchQueryParam)) {
      queryParameters.search.searchItems = MappingObjectUtil.copy(this.host.extraSearchQueryParam);
    }
    this._store.dispatch(new this.host.resourceNameSpace.GetAll(queryParameters));
    this.listObs = ResourceState.list(this._store, this.host.state);
    this.totalObs = ResourceState.total(this._store, this.host.state);
    this.isLoadingObs = MemoizedUtil.isLoading(this._store, this.host.state);
    this.isLoadingChunkObs = MemoizedUtil.select(this._store, this.host.state, state => state.isLoadingChunk);
  }

  private _observeSearchInput(): void {
    this.subscribe(this.form.get(this.host.formDefinition.search).valueChanges.pipe(
      debounceTime(this._debounceTime),
      distinctUntilChanged(),
      tap(v => {
        const queryParameters = new QueryParameters(this._pageSize, this.host.sort);
        if (isNotNullNorUndefined(this.host.extraSearchQueryParam)) {
          queryParameters.search.searchItems = MappingObjectUtil.copy(this.host.extraSearchQueryParam);
        }
        MappingObjectUtil.set(queryParameters.search.searchItems, (isNotNullNorUndefined(this.host.searchKey) ? this.host.searchKey : this.host.labelKey), v);
        this._store.dispatch(new this.host.resourceNameSpace.GetAll(queryParameters, true));
      }),
    ));
  }

  ngAfterViewInit(): void {
    this.setFocusOnInput();
  }

  private setFocusOnInput(): void {
    setTimeout(() => {
      this.input.nativeElement.focus();
    }, 0);
  }

  private _observeIsLoading(): void {
    this.subscribe(this.isLoadingObs.pipe(
      distinctUntilChanged(),
      tap(isLoading => this.isLoading = isLoading),
    ));
  }
Quentin Torck's avatar
Quentin Torck committed

  private _observeList(): void {
    this.subscribe(this.listObs.pipe(
      distinctUntilChanged(),
      tap(list => this.list = list),
    ));
  }

  computeHighlightText(valueElement: string): string {
    const fc = this.form.get(this.host.formDefinition.search);
    if (isNullOrUndefined(fc)) {
      return valueElement;
    }
    const value = fc.value;
    if (isNullOrUndefined(value) || isEmptyString(value)) {
      return valueElement;
    }
    return valueElement.replace(new RegExp(value, "gi"), match => `<span class="highlight-text">${match}</span>`);

  loadNextChunk(): void {
    this._store.dispatch(new this.host.resourceNameSpace.LoadNextChunkList());
  }