Search code examples
angulardrag-and-drop

restricting drag and drop between two non-dragable items in angular


I am making an angular app where I have two lists. From list 'A', I can copy items to list 'B' again and again. The problem is, I want to make the dragged items be added only in between the start and end items in list 'B'. see image

This is what I have tried so far:

HTML:

        <mat-drawer-container class="example-container">
            <mat-drawer mode="side" opened>
                <h3>Create your Workflow</h3>
                <h5>Drag and drop the elements to visualize the process.</h5>
                <div cdkDropList
                #optionsList="cdkDropList"
                [cdkDropListData]="options"
                [cdkDropListConnectedTo]="[workflowList]"
                class="example-list"
                cdkDropListSortingDisabled
                (cdkDropListDropped)="drop($event)"
                [cdkDropListEnterPredicate]="noReturnPredicate">
                <div class="example-box" cdkDrag *ngFor="let item of options"> {{item}} </div>
            </div>
            </mat-drawer>
            <mat-drawer-content>
                <div cdkDropList
                    #workflowList="cdkDropList"
                    [cdkDropListData]="workflow"
                    [cdkDropListConnectedTo]="[optionsList]"
                    class="example-list"
                    (cdkDropListDropped)="drop($event)"
                    [cdkDropListSortPredicate]="sortPredicate"
                    >
                    <div class="example-box"  [cdkDragDisabled]="item.disabled" *ngFor="let item of workflow" cdkDragLockAxis="y" cdkDrag>{{item.value}}</div>
                </div>
            </mat-drawer-content>

.ts -> export class:

options = [
    'Step',
    'Branch' 
  ];

  workflow=[
      {value: 'Start', disabled: true},
      {value: 'Finish', disabled: true}
    ];


  drop(event: any) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      copyArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
    }  
  }

  noReturnPredicate(){
    return false;
  }

  sortPredicate(){
    return false;
  }

Right now, I can only enter the options after the 'End' in workflow. I want it to be between start and end. Can anyone help, please?

Also, the options name is not showing in the added list, if anyone could guide me on that part too would be really helpful. Thanks


Solution

  • There are two problems:

    1. Both drop lists should have same value type. You've got:

      options: string[],
      workflow: {value: string, disabled: boolean}[],
      

      So, you should unify types (or write you own copyArrayItem method which will convert between types):

      interface DragItem {
        value: string;
        disabled: boolean;
      }
      ...
      
        options: DragItem[] = [
          { value: 'Step', disabled: ... },
          { value: 'Branch', disabled: ... }
        ];
      
        workflow: DragItem[] = [
          { value: 'Start', disabled: true },
          { value: 'Finish', disabled: true }
        ];
      
    2. If you want keep index in defined range, check it:

      sortPredicate(index: number, drag: CdkDrag, drop: CdkDropList): boolean {
        // preliminary check, not reliable
        return index > 0 && index < drop.getSortedItems().length;
      }
      

      sortPredicate is actually not reliable (I am able to place items in forbidden positions), you should double check index behaviour in drop event and fix it:

      drop(event: CdkDragDrop<DragItem[], any>) {
        if (event.previousContainer === event.container) {
          moveItemInArray(
            event.container.data,
            event.previousIndex,
            event.currentIndex
          );
        } else {
          let currentIndex = event.currentIndex;
      
          // double check index range
          if (0 === currentIndex) {
            currentIndex++;
          }
          if (event.container.getSortedItems().length === currentIndex) {
            currentIndex--;
          }
      
          copyArrayItem(
            event.previousContainer.data,
            event.container.data,
            event.previousIndex,
            currentIndex
          );
        }
      }
      

    Working example: https://stackblitz.com/edit/angular-drag-drop-range