Skip to content
Snippets Groups Projects
drop-list.component.ts 7.04 KiB
Newer Older
import { Component, Input } from '@angular/core';
rhenck's avatar
rhenck committed
import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events';
import {
  CdkDrag, CdkDropList, moveItemInArray, transferArrayItem
} from '@angular/cdk/drag-drop';
import { FormElementComponent } from '../../directives/form-element-component.directive';
import { DragNDropValueObject, DropListElement } from '../../interfaces/elements';
rhenck's avatar
rhenck committed

@Component({
  selector: 'aspect-drop-list',
rhenck's avatar
rhenck committed
  template: `
    <div class="list-container">
      <!-- Border width is a workaround to enable/disable the Material cdk-drop-list-receiving-->
      <!-- class style.-->
      <!-- min-height for the following div is important for iOS 14!
      iOS 14 is not able to determine the height of the flex container-->
      <div class="list"
           [ngClass]="{ 'align-flex' : elementModel.orientation === 'flex' }"
           [style.min-height.px]="elementModel.position.useMinHeight ? elementModel.height - 6 : null"
           [class.dropList-highlight]="elementModel.highlightReceivingDropList"
           [style.outline-color]="elementModel.highlightReceivingDropListColor"
           [style.color]="elementModel.styling.fontColor"
           [style.font-family]="elementModel.styling.font"
           [style.font-size.px]="elementModel.styling.fontSize"
           [style.font-weight]="elementModel.styling.bold ? 'bold' : ''"
           [style.font-style]="elementModel.styling.italic ? 'italic' : ''"
           [style.text-decoration]="elementModel.styling.underline ? 'underline' : ''"
           [style.backgroundColor]="elementModel.styling.backgroundColor"
           [style.display]="elementModel.orientation === 'horizontal' ? 'flex' : ''"
           [style.flex-direction]="elementModel.orientation === 'horizontal' ? 'row' : ''"
           cdkDropList
           [id]="elementModel.id"
           [cdkDropListData]="this"
           [cdkDropListConnectedTo]="elementModel.connectedTo"
           [cdkDropListOrientation]="elementModel.orientation !== 'flex' ? $any(elementModel.orientation) : ''"
           [cdkDropListEnterPredicate]="onlyOneItemPredicate"
           (cdkDropListDropped)="drop($event)">

        <ng-container *ngIf="!parentForm">
          <ng-container *ngFor="let value of $any(elementModel.value)">
            <ng-container [ngTemplateOutlet]="dropObject" [ngTemplateOutletContext]="{ $implicit: value }">
            </ng-container>
          </ng-container>
        </ng-container>

        <ng-container *ngIf="parentForm">
          <ng-container *ngFor="let value of elementFormControl.value">
            <ng-container [ngTemplateOutlet]="dropObject" [ngTemplateOutletContext]="{ $implicit: value }">
            </ng-container>
          </ng-container>
        </ng-container>
        <!--Leave template within the dom to ensure dragNdrop-->
        <ng-template #dropObject let-value>
          <div class="item text-item" *ngIf="!value.imgSrcValue" cdkDrag
               [ngClass]="{ 'vertical-orientation' : elementModel.orientation === 'vertical',
                      'horizontal-orientation' : elementModel.orientation === 'horizontal'}"
rhenck's avatar
rhenck committed
               [style.background-color]="elementModel.styling.itemBackgroundColor"
               (cdkDragStarted)=dragStart() (cdkDragEnded)="dragEnd()">
            <div *cdkDragPreview
                 [style.font-size.px]="elementModel.styling.fontSize"
                 [style.background-color]="elementModel.styling.itemBackgroundColor">
            <div class="drag-placeholder" *cdkDragPlaceholder
                 [style.min-height.px]="elementModel.styling.fontSize">
            </div>
            {{value.stringValue}}
          </div>
          <img *ngIf="value.imgSrcValue"
               [src]="value.imgSrcValue | safeResourceUrl" alt="Image Placeholder"
               [style.display]="elementModel.orientation === 'flex' ? '' : 'block'"
               class="item"
               [ngClass]="{ 'vertical-orientation' : elementModel.orientation === 'vertical',
                      'horizontal-orientation' : elementModel.orientation === 'horizontal'}"
               cdkDrag (cdkDragStarted)=dragStart() (cdkDragEnded)="dragEnd()"
               [style.object-fit]="'scale-down'">
        </ng-template>
rhenck's avatar
rhenck committed
      </div>
      <mat-error *ngIf="elementFormControl.errors && elementFormControl.touched"
                 class="error-message">
        {{elementFormControl.errors | errorTransform: elementModel}}
      </mat-error>
rhenck's avatar
rhenck committed
    </div>
  `,
  styles: [
    '.list-container {display: flex; flex-direction: column; width: 100%; height: 100%;}',
    '.list {border-radius: 10px; width: calc(100% - 6px); height: calc(100% - 6px); margin-top: 3px; margin-left: 3px;}',
    '.text-item {border-radius: 10px; padding: 10px;}',
    '.item {cursor: grab}',
    '.item:active {cursor: grabbing}',
rhenck's avatar
rhenck committed
    '.vertical-orientation.item:not(:last-child) {margin-bottom: 5px;}',
    '.horizontal-orientation.item:not(:last-child) {margin-right: 5px}',
    '.error-message {font-size: 75%; margin-top: 10px;}',
    '.cdk-drag-preview {padding: 8px 20px; border-radius: 10px}',
rhenck's avatar
rhenck committed
    '.drag-placeholder {background-color: lightgrey; border: dotted 3px #999; padding: 10px;}',
    '.drag-placeholder {transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}',
    '.cdk-drag-animating {transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}',

    '.dropList-highlight.cdk-drop-list-receiving {outline: solid;}',
    '.dropList-highlight.cdk-drop-list-dragging {outline: solid;}',
    '.align-flex {flex: 1 1 auto; flex-flow: row wrap; display: flex; place-content: center space-around; gap: 10px}'
rhenck's avatar
rhenck committed
  ]
})
export class DropListComponent extends FormElementComponent {
  @Input() elementModel!: DropListElement;
rhenck's avatar
rhenck committed

  bodyElement: HTMLElement = document.body;

    this.bodyElement.classList.add('inheritCursors');
    this.bodyElement.style.cursor = 'grabbing';
  }

  dragEnd(): void {
    this.bodyElement.classList.remove('inheritCursors');
    this.bodyElement.style.cursor = 'unset';
  }

rhenck's avatar
rhenck committed
  drop(event: CdkDragDrop<DropListComponent>): void {
    if (!this.elementModel.readOnly) {
      if (event.previousContainer === event.container) {
        moveItemInArray(event.container.data.elementFormControl.value as unknown as DragNDropValueObject[],
          event.previousIndex, event.currentIndex);
      } else {
        transferArrayItem(
          event.previousContainer.data.elementFormControl.value as unknown as DragNDropValueObject[],
          event.container.data.elementFormControl.value as unknown as DragNDropValueObject[],
          event.previousIndex,
          event.currentIndex
        );
        event.previousContainer.data.elementFormControl.setValue(
          (event.previousContainer.data.elementFormControl.value as DragNDropValueObject[])
      this.elementFormControl.setValue(
        (event.container.data.elementFormControl.value as DragNDropValueObject[])
rhenck's avatar
rhenck committed
    }
  }
  onlyOneItemPredicate = (drag: CdkDrag, drop: CdkDropList): boolean => (
    !drop.data.elementModel.onlyOneItem || drop.data.elementFormControl.value.length < 1
rhenck's avatar
rhenck committed
}