haltu / muuri

Infinite responsive, sortable, filterable and draggable layouts
https://muuri.dev
MIT License
10.77k stars 644 forks source link

GetItems() in some cases returns items in incorrect order #507

Closed lofcz closed 2 years ago

lofcz commented 2 years ago

https://codepen.io/niklasramo/pen/xWYdmp image image

I've called grid.getItems() and checked order of returned items. In this case the red outlined item points to the red outlined image and the next item (outlined orange) points to the orange outlined image while I would expect it to point to the brown outlined image.

var x = grid.getItems();
for (i = 0; i < x.length; i++) {
   console.log(x[i]._element);
}

Do I need to use custom layout to solve this or how would I get the correct order of items? This was run against Muuri v0.9.5

niklasramo commented 2 years ago

Hi @lofcz ! I know it might be a bit unintuitive at first, but yes, getItems() method returns the items in "internal order", not in the visual order. That internal order does not always reflect the position of items in the layout (although sometimes it might). When you think about it, it's pretty much impossible to deduct what is the "correct" order as there can be custom layouts in which items are positioned by different rules. In more explicit terms, getItems() returns the items in the same order they are provided to the layout method.

lofcz commented 2 years ago

We've been thinking about how to express "the correct" order formally for a while but so far came to no conclusion. I've seen you've been active around this topic for a while, have you had any luck figuring this out? We are going to force all items to be of the same height otherwise (as we've been unable to come with anything better s/f)

niklasramo commented 2 years ago

Well, you just have to order the items based on your needs. All the data is there. E.g. you can get item's position in the layout via item.getPosition() so you can sort the items easily in any way you wish based on the visual position of the items.

lofcz commented 2 years ago

We've resolved this by modifying this example - https://codepen.io/pauldstar/pen/LzEdmp I'm including the patch here in case it would be of use to anyone with a similar issue.

Original for Muuri 0.4:

layout: function(items, containerWidth, containerHeight) 
{ // custom strict horizontal left-to-right order
    if (!items.length) return layout;
    var layout = {
        slots: {},
        height: 0,
        setHeight: true
    };
    // in this case, container width (1000px) always divisible by item width (200px)
    var colCount = containerWidth / items[0]._width;
    var rowCount = 1 + parseInt(items.length / colCount);
    // save calculated slots in 2D array
    var slotDimensions = array2D(rowCount);
    var newSlot, topSlot, leftSlot, slotRow, slotCol;
    items.forEach(function(item, index)
    {
        newSlot = {
            left: 0, 
            top: 0, 
            height: item._height, 
            width: item._width
        };
        slotCol = index % colCount;
        slotRow = parseInt(index / colCount);
        if (topRowExists(slotRow))
        { // add slot to row below
                topSlot = slotDimensions[slotRow-1][slotCol];
            newSlot.top = topSlot.top + topSlot.height;
        }
        if (leftColExists(slotCol))
        { // add slot to rightward col
                leftSlot = slotDimensions[slotRow][slotCol-1];
            newSlot.left = leftSlot.left + leftSlot.width;
        }
        slotDimensions[slotRow][slotCol] = newSlot;
        layout.slots[item._id] = newSlot;
        layout.height = Math.max(layout.height, newSlot.top + newSlot.height);
    });
    return layout;
}

Patched for 0.9 by @CryoEagle:

layout: function(grid, layoutId, items, width, height, callback) 
{
    var layout = {
        id: layoutId,
        slots: [],
        height: 0,
        setHeight: true,
        items: items,
        styles: {}
    };

    var width = document.getElementById("MUURI_CONTAINER_REPLACE_ME").offsetWidth;
    var colCount = Math.floor(width / items[0]._width);
    var rowCount = 1 + parseInt(items.length / colCount);
    var slotDimensions = array2D(rowCount);
    var newSlot, topSlot, leftSlot, slotRow, slotCol;
    items.forEach(function(item, index)
    {
        newSlot = {
            left: 0, 
            top: 0, 
            height: item._height, 
            width: item._width
        };

        slotCol = index % colCount;
        slotRow = parseInt(index / colCount);

        slotCol = Math.floor(slotCol);
        slotRow = Math.floor(slotRow);

        if (topRowExists(slotRow))
        {
            topSlot = slotDimensions[slotRow-1][slotCol];
            newSlot.top = topSlot.top + topSlot.height;
        }
        if (leftColExists(slotCol))
        {
            leftSlot = slotDimensions[slotRow][slotCol-1];
            newSlot.left = leftSlot.left + leftSlot.width;
        }

        slotDimensions[slotRow][slotCol] = newSlot;
        layout.slots.push(newSlot.left, newSlot.top);

        layout.styles.height = `${Math.max(layout.height, newSlot.top + newSlot.height)}px`;
        layout.styles.width = `${Math.max(layout.width, newSlot.left + newSlot.width)}px`;
    });
    callback(layout);
},