0

I have an object like this:

container = {
    row1: [ '1', '2', '3', '4' ],
    row2: [ '5', '6', '7', '8' ],
    row3: [ '9', '10', '11', '12' ]
  }

This data populates a grid drag n drop component in my Angular project.

When an 'item' is moved from one array to the other I would like the last item in that array to move to the next array and push all objects forward by 1 so that I can ensure there is only ever 4 objects in each array.

How can I achieve this? I've been at it for way too many hours now!

I have this 'drop' method that is called when the item is added/dropped into a new array :

drop(event: CdkDragDrop<string[]>) {
    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);
    }
  }

So what I need now is something like :

this.container.data.unshift() //to the next array within the container array.

I also have access to this array in my component so maybe even passing in the row name:

drop(event: CdkDragDrop<string[]>, rowName: string) {
    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);
    }
  }

How can I do this and it would also be nice to be able to reverse it if need be.

Here is a StackBlitz: https://stackblitz.com/edit/angular-cdk-drag-drop-cmyh7k?file=app%2Fcdk-drag-drop-connected-sorting-example.html

10
  • 1
    Are your internal arrays always 4 elements big? What happens if you take an element from the first array and throw it into the second? Does the first array get stuck with 3 elements? Is there a blank? What about throwing an element into the last array? Do we get a new 1 element array at the end? Commented Jan 31, 2020 at 13:52
  • Initially yes but i have intentions on trying to make it dynamic so you can use a variable to set this that could be 2, 4 or 6 and that would set the number of objects on each row Commented Jan 31, 2020 at 13:54
  • 1
    What I suggest is for you to have just one array, not an array of objects, you set a counter to 0, you loop through this array, then make a new row every time your counter is 4 then reset the counter to 0. This way you won't need to filter and you can inject new items anywhere. Commented Jan 31, 2020 at 13:55
  • 1
    I'll create a stackblitz for clarity Commented Jan 31, 2020 at 14:02
  • 1
    stackblitz.com/edit/… Commented Jan 31, 2020 at 14:08

1 Answer 1

2

First you need update the references of the stackblitz to get the new version of Angular 8 and cdk. Remove the references to core.js in pollyfill.ts too.

This make that you can drag among the rows

After change the "drop function" as, e.g.

drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      const dataFrom=event.previousContainer.data[event.previousIndex]
      const dataTo=event.container.data[event.currentIndex]
      event.previousContainer.data.splice(event.previousIndex,1,dataTo)
      event.container.data.splice(event.currentIndex,1,dataFrom)

    }
  }

You needn't use obligatory the function "transferArrayItem". you has in event.previousContainer.data the data from and in event.container.Data the data to, so you can "play" using splice to remove one element from another.

In my case I use the indexex, to put interchange the positions, but you can make in the way you want

Tip: it's interesting make a console.log() of event.previousContainer.data, event.container.data, event.previousIndex and event.currentIndex to see the values

Here is your forked stackbliz

Update Another function

drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      //get the data we are dragging
      const dataFrom=event.previousContainer.data[event.previousIndex]
      //get the last element of the row where dropped
      const dataTo=event.container.data[event.container.data.length-1]

      //remove the element dragged
      event.previousContainer.data.splice(event.previousIndex,1)
      //Add at last the dataTo
      event.previousContainer.data.push(dataTo)

      //Add the data dragged
      event.container.data.splice(event.currentIndex,0,dataFrom)
      //remove the last element
      event.container.data.pop()

    }
  }

Update2 it's looks like some "stanger", So we can think about if using (cdkDropListEntered) (cdkDropListExited) and (cdkDragStarted) we can improve a better effect.

The idea is add one element to each row that will be invisible (display:none) if we are reorden the row or we are not draggind anything. If we are dragging between rows, this element gets the value of the last element of the row where we are dropped.

Puff, first declare three variables

  fade=[" "," "," "]   //to show the elements
  indexDrag:number=-1  //the row from we picked the element
  indexEnter:number=-1  //the row where we want dropped the element

After we are goind to add a div before the drag items

<div cdkDropList cdkDropListOrientation="horizontal" 
  [cdkDropListData]="container.row1" class="example-list"
  (cdkDropListDropped)="drop($event)" 
  (cdkDropListEntered)="enter($event,0)" > //<--for the second row will be enter($event,1)
                                           // and for the third row enter($event,2)
  <!-- our element before the drags elements -->
  <div class="example-box" 
        [style.display]="(indexDrag!=0 ||
                          indexEnter==indexDrag || 
                          indexEnter<0)?'none':null">
    <div class="tile">
      <div class="tile-container">{{fade[0]}}</div>
    </div>
  </div>
  <!--our dragabbles elements -->
  <div class="example-box" *ngFor="let item of container.row1" cdkDrag 
     (cdkDragStarted)="startDrag(0)"> //<--for the second row will be startDrag(1)
                                      //  and startDrag(2) for the third row
    <div class="tile">
      <div class="tile-container">{{item}}</div>
    </div>
  </div>

Then, just add the two functions

  startDrag(index) {
    this.indexDrag=index;
  }
  enter(event: CdkDragEnter<any>,index) {
    this.indexEnter=index;
    const data=event.container.data
    this.fade[this.indexDrag]=data[data.length-1]
  }

And, in drop function we "restart" the variables indexDrag,indexEnter and fade

  drop(event: CdkDragDrop<string[]>) {

    this.indexDrag = -1;
    this.indexEnter = -1;
    this.fade=[" "," "," "]
     ...rest of the code..
  }

the result in new stackblitz

Well, there's a problem becaouse we can drag over the las element of a row and the app fails, sorry. So we need make another change

In drop function check if the currentIndex is equal to data.length

    const currentIndex=(event.currentIndex==event.container.data.length)?
              event.container.data.length-1:event.currentIndex

In the div make another *ngIf is last

    <div class="tile" *ngIf="!last || indexEnter!=1 || indexEnter==indexDrag">
      <div class="tile-container">{{item}}</div>
    </div>
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks Eliseo, this is nearly there. I also thought of switching positions instead of pushing all objects forward but unfortunately it looks strange that when you hover over the container to drop an item, it moves the next item up and then when you drop it, it switches. How can I avoid this?
I updated the answer, I think that it is closer your requeriments
That is very close. I used 'unshift()' instead of 'push()' and that worked! thanks very much for this
I've marked this as correct as it is close enough to what I wanted to achieve but if you have enough time i'd really like to utilize the enter() and exited() methods so that the 'items' push along on enter and remove back on exit to give a better effect - thanks for your help though, its much appreciated
@DBoi, thinking about better effect, I update the answer. the simple idea is add a fixed element before each row, and make it visible when we are dropping between rows. Really I make it (display:none) in the opossite situation
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.