Rildo Gomez
Rildo Gomez

Reputation: 305

Drag and drop cdk Angular material

I created 1 container with images (using ngb-carousel) and 3 drop zones where to place those images however i'm having some issues when i tried to transfer items between them. First i'm getting always the same indexes whenever i try to move an item from my images container to any drop zone.

here is my template

<mat-card class="card">
    <div
        cdkDropList
        #todoList="cdkDropList"
        [cdkDropListData]="images"
        [cdkDropListConnectedTo]="[doneList, intermedio, menos]"
        cdkDropListOrientation="horizontal"
        class="contenedor">
        <ngb-carousel *ngIf="images">
            <ng-template *ngFor="let img of images" ngbSlide>
                <div class="picsum-img-wrapper" [cdkDragData]="img" cdkDrag>
                    <img [src]="path+img.imagen" width="100%" alt="Random first slide">
                </div>
            </ng-template>
        </ngb-carousel>
    </div>

    <div class="results">
        <div class="talents">
            <div class="example-container">
                <h4>(+) Talento más desarrollado</h4>
                <div cdkDropList #doneList="cdkDropList" [cdkDropListData]="list2"
                     [cdkDropListConnectedTo]="[intermedio, menos]"
                     class="example-list" (cdkDropListDropped)="drop($event)">
                    <ol class="example-box">
                        <li *ngFor="let l2 of list2" [cdkDragData]="l2" cdkDrag>
                            {{l2.nombre}}
                        </li>
                    </ol>
                </div>
            </div>
        </div>
        <div class="talents">
            <div class="example-container">
                <h4>Talento Intermedio</h4>
                <div cdkDropList #intermedio="cdkDropList" [cdkDropListData]="list3"
                     [cdkDropListConnectedTo]="[doneList, menos]"
                     class="example-list" (cdkDropListDropped)="drop($event)">
                    <ol class="example-box">
                        <li *ngFor="let l3 of list3" [cdkDragData]="l3" cdkDrag>
                            {{l3.nombre}}
                        </li>
                    </ol>
                </div>
            </div>
        </div>
        <div class="talents">
            <div class="example-container">
                <h4>(-) Talento menos desarrollado</h4>
                <div cdkDropList #menos="cdkDropList" [cdkDropListData]="list4"
                     [cdkDropListConnectedTo]="[intermedio, doneList]"
                     class="example-list" (cdkDropListDropped)="drop($event)">
                    <ol class="example-box">
                        <li *ngFor="let l4 of list4" [cdkDragData]="l4" cdkDrag>
                            {{l4.nombre}}
                        </li>
                    </ol>
                </div>
            </div>
        </div>
    </div>
</mat-card> 

here is my drop function

drop(event: CdkDragDrop<any>) {
        if (event.previousContainer === event.container) {
            moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
        } else {
            /*transferArrayItem(event.previousContainer.data,
                event.container.data,
                event.previousIndex,
                event.currentIndex);*/

            const newArray = event.previousContainer.data.filter(e => e.id !== event.item.data.id);
            event.previousContainer.data = newArray;
            event.container.data.push(event.item.data)
        }
    }

i tried using transferArrayItem but it didnt work since i was getting the wrong index so i tried changing event.previousContainer.data and event.container.data, i works with event.container.data but it doesnt change event.previousContainer.data.

any ideas why it's not working? thanks in advance

Upvotes: 0

Views: 5017

Answers (1)

matvs
matvs

Reputation: 1873

Almost everything is fine with your code.

The problem is that since all <div> containers with cdkDropList directive have no children their height is equal zero, so there is no area, where elements can be dropped.

One possible solution would be to move cdkDropList higher to for example <div class="example-container"> or if you don't want headers (like (+) Talento más desarrollado</h4>) to trigger drag events, then you need to provide some height underneath, so that items can be dragged there.

 <div class="results">
    <div class="talents">
        <div class="example-container" cdkDropList
             #doneList="cdkDropList" 
             [cdkDropListData]="list2"
             [cdkDropListConnectedTo]="[intermedio, menos]"
             class="example-list" 
             (cdkDropListDropped)="drop($event)"
                >
            <h4>(+) Talento más desarrollado</h4>
    <!-- Probably no need for this div then !-->
            <div> 
                <ol class="example-box">
                    <li *ngFor="let item of list2" [cdkDragData]="item" cdkDrag>
                        {{item.name}}
                    </li>
                </ol>
            </div>
        </div>
    </div>

Moving directive up in DOM tree stackblitz example.

With this approach previousIndex of CdkDragDrop event is always equal to the length of the "images" array. I thought that maybe CdkDropListGroup needs to be used, but it looks like it is an issue with combining <ngb-carousel> with DragDropModule. If used together, then DragDropModule cannot properly recognize an index of a dragged item.

Like I expected if images are simply listed, then index is correct:
Without ngb-carousel - stackblitz example.

Another problem in your example is that draggable items should be direct children of HTML element with angular cdkDropList directive. Otherwise one gets following error on dragging second element:

Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.

Having said all that let us now try to use this knowledge and refactor this code into a working example.

First our template:

 <div class="talents">
    <div class="example-container" >
       <h4>(+) Talento más desarrollado</h4>
          <ol class="example-box"  cdkDropList 
              #doneList="cdkDropList" 
              [cdkDropListData]="list2"
              [cdkDropListConnectedTo]="[intermedio, menos]"
              (cdkDropListDropped)="drop($event)">
                <li *ngFor="let item of list2" [cdkDragData]="item" cdkDrag>
                   {{item.name}}
                </li>
             </ol>
       </div>
   </div>

Now some CSS:

.example-box:empty {
  height:50px;
  border: 5px dashed #cecece;
}
.example-box:empty:hover:after {
  content: 'Drag & Drop items here';
  color: #aeaeae;
  font-weight: bold;
  font-size: 18px;
  width:50%;
  position: relative;
  left:25%;
  top:5px;
}

By setting height on elements without children we create a drop zone. Adding border and text is a good UX design, that lets our end users know, that items can be dragged into given area.

Last, but not least TypeScript:

drop(event: CdkDragDrop<any>) {
// indexes are wrong due to ngb-carousel 
   if (event.previousContainer === event.container) {
// this is okay, no moving around in ngb-carousel, since it always displays one element;
        moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
   } else {
       let previousIndex = event.previousIndex;
       const element = event.previousContainer.element.nativeElement;
       const id = element.getAttribute('id');
       // Only indexes of items  dragged from carousel needs to be fixed
       if (id === this.carouselContainerId) {
           previousIndex = event.previousContainer.data.indexOf(event.item.data);
       } 
       transferArrayItem(event.previousContainer.data,
          event.container.data,
          previousIndex,
          event.currentIndex);  
   }

}

Working example - stackblitz

Upvotes: 3

Related Questions