kamilkp / angular-sortable-view

Fully declarative (multi)sortable for AngularJS
http://kamilkp.github.io/angular-sortable-view
MIT License
423 stars 143 forks source link

Nested sv-root containers #140

Open ornic opened 4 years ago

ornic commented 4 years ago

I have a list of elements. Some elements may have children elements. And so on. Basically, I have a tree. :)

I show this tree as nested lists, and I want user to be able to rearrange items in one dimension (before-after) and move elements freely between any levels of sub-lists.

Physically I render list as recursive component: if element have sub-list, the same component is called. All components have the same sv-root value.

The problem I stumbled upon was that sv always choose element from root list as target. The reason is this part of code: https://github.com/kamilkp/angular-sortable-view/blob/master/src/angular-sortable-view.js#L196-L255

getDistance is used to store distance to elements. But getDistance returns 0 if mouse is inside element. So the first element from several nested elements is selected as best candidate and this first element is always from the root list.

  1. I added field .s with square (width * height) of element/container.
  2. In sort function, if both elements .q are equal, I sort them using their squares.

This way smallest (innermost!) element is placed first among candidates. Exactly what was needed! Hope this helps someone :)

And many thanks to @kamilkp for such great module!

ornic commented 4 years ago

and there is a code:

function getSquare(rect) {
    return rect.width * rect.height;
}
    if (
        !se.container && // not the container element
        (se.element[0].scrollHeight || se.element[0].scrollWidth)
    ) { // element is visible
        var sePart = se.getPart();
        candidates.push({
            element: se.element,
            q: getDistance(mouse, seCoords),
            s: getSquare(rect),                      // <-- Square
            view: sePart,
            targetIndex: se.getIndex(),
            after: shouldBeAfter(center, mouse, ('isGrid' in sePart) ? sePart.isGrid : isGrid)
        });
    }

    if (
        se.container &&
        !se.element[0].querySelector('[sv-element]:not(.sv-placeholder):not(.sv-source)') // empty container
    ) {
        var c = center;
        if (se.centerVariant === 'vertical') {
            c = centerVert;
        } else if (se.centerVariant === 'horizontal') {
            c = centerHoriz;
        }

        candidates.push({
            element: se.element,
            q: (c.x - mouse.x) * (c.x - mouse.x) + (c.y - mouse.y) * (c.y - mouse.y),
            s: getSquare(rect),                      // <-- Square
            view: se.getPart(),
            targetIndex: 0,
            container: true
        });
    }
});
var pRect = $placeholder[0].getBoundingClientRect();
candidates.push({
    q: getDistance(mouse, getCoords(pRect)),
    s: getSquare(pRect),                      // <-- Square (no need it there, really)
    element: $placeholder,
    placeholder: true
});
candidates.sort(function (a, b) {
    if (a.q == b.q) return a.s - b.s;                      // <-- Using the squares
    return a.q - b.q;
});