Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
OSUblake

How to create a sortable list with Draggable

Recommended Posts

I keep getting a lot of questions asking about creating sortable lists with Draggable, so I'm just going to make a post about it.

 

My

See the Pen RNLdpz by osublake (@osublake) on CodePen

example is outdated, and I no longer use that technique, so I'm not going to update it. It relies on hit testing, which I would no longer recommend doing for grids/lists. A better way is to create a model of your list, and map the location of your draggable to that model.

 

This is real easy to do using arrays. You can use the index of an array to represent a cell location in the grid. Draggable already creates an array for you if you use the .create() method, so you could use that if you really wanted to.

 

But first you need to figure out the size of the cells in your list. If everything is uniform, you could probably just divide the size by the number of items in that dimension. If my list is 400px high, and there are 4 elements, the cell size for a row is 100. Notice how the cell size may not be the same size as your element. The cells are in red.

 

9kORfXv.png

 

 

When you drag, you can figure out the index of a cell like this. 

var index = Math.round(this.y / rowSize);

This can result in errors if you drag outside of your list, so you should clamp the values like this.

var index = clamp(Math.round(this.y / rowSize), 0, totalRows - 1);

function clamp(value, a,  {
  return value < a ? a : (value > b ? b : value);
}

Now you can easily determine where everything is. 

 

You also need to keep track of the last index, and compare it to the index you calculate while dragging. If they are different, this means your draggable is inside a different cell, so you need to update your layout.

 

Before you can change the layout, your first need to change the items in your array. Here's a function you can use to move stuff around in an array.

arrayMove(myArray, oldIndex, newIndex);

function arrayMove(array, from, to) {
  array.splice(to, 0, array.splice(from, 1)[0]);
}

Now that your array is in the correct order, you can update the layout. If you were using an array of draggables, you could animate the entire layout like this.

myArray.forEach((draggable, index) => {
  if (!draggable.isDragging) {
    TweenLite.to(draggable.target, 0.4, { y: index * rowSize });
  }
});

That's pretty much it!

 

Doing it this way is not only easier, but it performs a lot better, making it really smooth. I made a demo based off of this Framer.js example. It's about 100 lines of code, and is pretty easy to understand. For comparison, The Framer.js example is about 180 lines of code.

See the Pen jrqjdy by osublake (@osublake) on CodePen

  • Like 10
Link to comment
Share on other sites

That is great Blake! 

 

I just wanted to take a moment and thank you for hanging out here on the forum. I can honestly say I've learned just as much (if not more) by reading your posts and deconstructing your pens than I have by reading books and viewing online tutorials. You are truly an amazingly talented coding rock star and we're lucky to have you as an inspirational teacher around here. 

 

Thank you Blake.

 

:)

  • Like 7
Link to comment
Share on other sites

Yeah, great stuff Blake. Thanks for sharing this. 

Link to comment
Share on other sites

Thanks PointC! That's how I learn any new concept. I find something interesting, and deconstruct the code line-by-line. This is pretty much the only book you'll ever need.

 

R9qBSxo.png

 

  • Like 4
Link to comment
Share on other sites

I should add that converting a list to a grid is real easy. 

 

First, map the cell locations to an array.

var cells = [];

for (var row = 0; row < totalRows; row++) {
  for (var col = 0; col < totalCols; col++) {
    cells.push({
      x: col * colSize,
      y: row * rowSize
    });
  }
}

Now modify the drag method to find the index of a cell.

var col = clamp(Math.round(this.x / colSize), 0, totalCols - 1);
var row = clamp(Math.round(this.y / rowSize), 0, totalRows - 1);
       
var index = totalCols * row + col;

Now you can easily layout the grid using the saved cell locations. Everything else is pretty much the same!

See the Pen NrRJwm by osublake (@osublake) on CodePen

  • Like 6
Link to comment
Share on other sites

Blake...

 

I WANT YOUR CHILDREN!

 

Think of the possibilities of such talented DNA. :D

  • Like 3
Link to comment
Share on other sites

That's actually not a bad idea. Do you know any filmmakers? I'd like to document the process.

  • Haha 1
Link to comment
Share on other sites

I know this lot:

 

junior.jpg?raw=1

  • Like 6
Link to comment
Share on other sites

Pedro,

 

I've never been so extremely horrified yet equally humored at the same time.

 

  :-o  :-o  :mrgreen:  :-P

  • Like 3
Link to comment
Share on other sites

He's the one who wants to film it. I'm not so keen in this new ideas the youngster have but hey, Blake's calling the shots on this one.

 

8-)

  • Like 1
Link to comment
Share on other sites

This thread has taken an unusual turn. What is happening here? I'm amused and confused.  :lol:  :blink:  

 

I'll be in my lab inventing a time travel machine so I can somehow warn myself not to look at that picture. :shock:

Link to comment
Share on other sites

I'm framing that!!!  :shock:  :-o  :-o  :lol:

 

On a side note, somebody suggested swapping positions vertically. Another easy modification. Instead of shifting the array around, you can swap positions in the array like this...

var temp = sortables[to];
sortables[to] = item;
sortables[item.index] = temp;

I changed it so that it will use the swap method if the new position is adjacent to the last position.

See the Pen NrRJwm by osublake (@osublake) on CodePen

  • Like 2
Link to comment
Share on other sites

  • 2 months later...

That's awesome! And thanks for sharing this. We get a lot of request asking how to make drag and drop containers, but I haven't had to time make another version, so this is perfect.

  • Like 1
Link to comment
Share on other sites

JoelCox, Very nice! Thanks for sharing.

Link to comment
Share on other sites

  • 2 weeks later...

I am trying to set a bounds to the example you have done @OSUBlake, however it seems that I can't apply a bounds? If I do that, when I start dragging, the element will jump to a odd spot instantly? The reason I am looking at applying bounds is so that I can do things like:

throwProps: true,
edgeResistance: 0.9,
overshootTolerance: 0.1,
maxDuration: 0.2,

I don't want the draggable element to be able to drag to where ever but contained within an area with momentum applied.

 

Updates*

 

Ah, I tried again and it seems to work (:

Link to comment
Share on other sites

It should be just as simple as defining the bounds on the Draggable.

bounds: ".container"

See the Pen 732069136a5b69e3e985c667345aad4a by osublake (@osublake) on CodePen

 

Can you make of demo of what you're trying? There are other ways of handling the bounds if needed.

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...

Hello guys!

 

Exist any example with Angular 1.x using directives? I want to do that 

See the Pen jrqjdy by osublake (@osublake) on CodePen

but using Ionic Framework.

 

Using Ionic list component

<ion-list>
  <ion-item draggable ng-repeat="item in items">
    Hello, {{item}}!
  </ion-item>
</ion-list>

And using Angular directives =>

app.directive('draggable', ['$ionicGesture', function ($ionicGesture) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
          //Ionic list
          var container = element.parent()[0];

          var animation = TweenLite.to(element, 0.3, {
            boxShadow: "rgba(0,0,0,0.2) 0px 16px 32px 0px",
            force3D: true,
            scale: 1.1,
            paused: true
          });

          var dragger = new Draggable(element, {
            onPress: onPress,
            onDragStart: downAction,
            onRelease: upAction,
            onDrag: dragAction,
            cursor: "inherit",    
            type: "y"
          });


          //Any example please?
        }
    }
}]);

With the following code doesn't work well => https://gist.github.com/jdnichollsc/4c1ae672bfbad6bc364aa42f4dacd38e

 

And other PoC in Codepen

See the Pen kkvpwb?editors=1010 by jdnichollsc (@jdnichollsc) on CodePen

 

Thanks in advance, Nicholls  :mrgreen:

Link to comment
Share on other sites

Hi jdnichollsc,

 

It looks you're off to a good start. I'm going to come back and look at this in more detail later today, but for now try adding autoScroll to your Draggable instance.

dragger = new Draggable(element, {
  autoScroll: 1,
  ...
});
BTW, I really like all the work you've done with Ionic, Angular, and Phaser. It's really impressive!
  • Like 2
Link to comment
Share on other sites

Hi jdnichollsc,

 

It looks you're off to a good start. I'm going to come back and look at this in more detail later today, but for now try adding autoScroll to your Draggable instance.

dragger = new Draggable(element, {
  autoScroll: 1,
  ...
});
BTW, I really like all the work you've done with Ionic, Angular, and Phaser. It's really impressive!

 

 

 

Wowwww is beautiful!!

Thanks for your examples! Check the repo with the last fix https://jdnichollsc.github.io/Ionic-Drag-and-Drop/

 

What do you think?  :-P

 

 

Regards, Nicholls

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×