Sortable icon indicating copy to clipboard operation
Sortable copied to clipboard

Is there a way to "pin" an element at the start or end of a Sortable region?

Open rouilj opened this issue 3 years ago • 8 comments

Custom I am making a simple kanban. I have a sortable declared on a UL. The first LI inside the UL is formatted as a header. I have multiple LI's after it in the UL. The header LI is excluded from drag/drop by using a filter. However I can sort/drop li items above the header item.

Is there a way to prevent moving the header down and making a drop "above" the header put the dropped item into the second LI position.

https://output.jsbin.com/jufinuz/

rouilj avatar Dec 03 '21 07:12 rouilj

use filter in options for example

new Sortable(example6, {
    filter: '.filtered', // 'filtered' class is not draggable
});

shedh avatar Dec 03 '21 18:12 shedh

Thanks for your followup but I am filtering it makes no difference. From the original post:

The header LI is excluded from drag/drop by using a filter. However I can sort/drop li items above the header item.

The code from the jsbin:

Sortable.create(bar, {
  group: {
    name: 'bar',
    pull: true,
    put: ['foo', 'qux', 'qux2']
  },
  animation: 100,
   filter: ".header"

});

and the html:

  <ul id="bar">
    <li class="header"><h2>In Progress</h2></li>
    <li>bar 1</li>
    <li>bar 2</li>
    <li>bar 3</li>
  </ul>

Is filter supposed to prevent the item from having things dropped above/below it?

rouilj avatar Dec 03 '21 21:12 rouilj

You can check for evt.to.classList.contains('header') and do return false maybe?

pvh-archifact avatar Apr 07 '22 08:04 pvh-archifact

Hi @pvh-archifact, you said:

You can check for evt.to.classList.contains('header') and do return false maybe?

Interesting idea. It doesn't work as evt.to is the ul drop zone/Sortable and not the li.header element in the zone. I.E. evt.to.children shows all the li elements including the header.

Also I wasn't sure where to make that check. The only place false seems to work is the onMove event and even there evt.to is the ul element.

In the onMove, evt.related does seem to be set to li.header, but cancelling the move seems to be confusing as the header shifts out of the way making it look like it could work.

Setting pointer-events: none on the li.header does prevent the header from shifting. But it still looks like it will accept a drop, but it won't. Setting cursor: not-allowed on the li.header element has no effect in chrome at least.

This kind of works:

  • in the onEnd handler
  • see if the newly dropped element is at newIndex 0
  • if so, it is above the header and the header is at position 1
  • get the order for the sortable using toArray()
  • swap the left two elements moving the header back to the top
  • call sortable.sort() suppresing animation.

code:

        onEnd: function(evt) {
            if (evt.newIndex == 0) {
                /* get name of Sortable used for this event */
                let s = Object.keys(evt.to)[0]
                /* marshal sort order */
                let order = evt.to[s].toArray()
                /* getting:
                     Uncaught ReferenceError: Cannot access 'order'
                        before initialization
                   if I do not include the following line to avoid issue.
                   Race issue? Some sort of lazy eval/def??
                   Also console.log(order) works to suppress
                   issue as well. */
                j = Array(...order)
                /* swap the top dragged element with the header */
                [ order[0], order[1] ] = [ order[1], order[0] ]
                /* resort with swapped elements */
                evt.to[s].sort(order, false)
            }
        },

It seems icky as I am calling into Sortable while handling an event. Seems like the sort could also trigger an End event and I end up in an infinite loop.

Not sure why I need to reference the order variable before I swap the first two elements.

But this ends up with the header staying at the top of the ul after the drop. A drop anywhere in the ul works. But the header does move out of the way during the drag. Ideally, the user would have to drop below the header always but I guess this makes for a larger target area.

rouilj avatar Apr 11 '22 08:04 rouilj

did you try this In the onmove event: evt.related.classList.contains('header'){ return false;}

I made an example.... https://jsbin.com/nomicuy/edit?html,css,js,output

pvh-archifact avatar Apr 11 '22 08:04 pvh-archifact

Hi pvh-archifact:

That works except I have to change to conditional to:

    if(evt.related.classList.contains("header") && ! evt.willInsertAfter ){

otherwise I can't drop a tile into an empty column (one with only a header). It still has the issue that it looks like I can drop the tile on the header, but it does prevent dropping a tile above the header. So thanks for the example.

But having someway to tell Sortable that a field is a header field that can be used as a drop target but can not be moved down would be an improvement.

Also for some reason my example using onEnd stopped working. The sort seems to not be applied. onEnd was kind of icky anyway but....

rouilj avatar Apr 11 '22 17:04 rouilj

itemEl.fixed = true

init like this:

new Sortable... {
  , sort : true
  , onMove(evt) {
	if(evt.dragged.fixed||evt.related.fixed) 
		return false;
  }
}

KnIfER avatar Nov 04 '22 15:11 KnIfER