Search code examples
javascriptvuejs2sortables

Table sorting with index assignment


I have an array of objects:

ruta: [
    { 'order': 1, 'id': 121 },
    { 'order': 2, 'id': 123 }
]

I use it as a model for a buefy table and at the same time, I'm using the extension sortable.js to manually order the table rows:

const createSortable = (el, options, vnode) => {
    return Sortable.create(el, {
        ...options,
        onEnd: function (evt) {
            const data = vnode.context.ruta
            const item = data[evt.oldIndex]
            if (evt.newIndex > evt.oldIndex) {
                for (let i = evt.oldIndex; i < evt.newIndex; i++) {
                    data[i] = data[i + 1]
                }
            } else {
                for (let i = evt.oldIndex; i > evt.newIndex; i--) {
                    data[i] = data[i - 1]
                }
            }
            data[evt.newIndex] = item
            //here
            for (let i = 0; i < data.length; i++) {
                data[i].order = i + 1;
            }
        }
    })
} 

The table is rendered correctly, but I need to update the order parameter on each manually sorting to reflect the real order o the table. For example, I need to move the fifth row to the beginning of the table, so its order parameter should be 1 and the rest of the rows need to reflect 2, 3, 4 and 5.

As you can see in the code, I've tried:

for (let i = 0; i < data.length; i++) {
    data[i].order = i + 1;
}

Because I want to start from 1 the value of the order. I also tried to put the change into the if / else blocks:

if
    data[i].order = i + 1;
else
    data[i].order = i - 1;

But it didn't work either. The order of the rows is changed in a wrong way.


Solution

  • You already asked this on the Spanish SO site and I gave you a solution there. I know that you already solved the issue, but I'm going to post another solution to your question because it might be useful to other users in the future.

    First of all, I already explained to you why this issue occurs: if you change the order of the model changing the values of its indexes, Vue will not detect the change, you need to modify the array in another way, for example, making an splice. In your code, Vue detects a change only when you change the order parameter and at that moment the list is manually sorted, and the values of each index of the array have changed, so, the view will be updated wrongly:

    ┌───────────────┬───────────────┬──────────────────┬───────────────┐
    │ Initial state │ -> Model      │ Manually sorting │ -> Model      │
    ├───────────────┼───────────────┼──────────────────┼───────────────┤
    │ 1 - item 1    │ Array index 0 │ 1 - item 4       │ Array index 3 │
    │ 2 - item 2    │ Array index 1 │ 2 - item 1       │ Array index 0 │
    │ 3 - item 3    │ Array index 2 │ 3 - item 2       │ Array index 1 │
    │ 4 - item 4    │ Array index 3 │ 4 - item 3       │ Array index 2 │
    └───────────────┴───────────────┴──────────────────┴───────────────┘
    

    The solution that I gave you before:

    const createSortable = (el, options, vnode) => {
    
        // Copy the order property
        vnode.context.data.forEach( (obj) => {obj.norder = obj.order} );
    
        // Create an array of orders
        const orders = vnode.context.data.map((obj) => obj.order);
    
        return Sortable.create(el, {
            ...options,
            onEnd: function (evt) {
    
                const data = vnode.context.data;      
      
                // Update the position of the objects
                orders.splice(evt.newIndex, 0, ...orders.splice(evt.oldIndex, 1));
      
                // Change the order parameter
                data.forEach((obj) => {        
                    obj.order = orders.findIndex((n) => n === obj.norder) + 1;
                });
      
            }
        });
    };
    

    Working example: https://codepen.io/elchininet/pen/JLQqEV

    Another solution:

    Another solution is to reset the manually sorting after each movement and change the array order using a splice. Take a look:

    const createSortable = (el, options, vnode) => {
    
        let order = [];
    
        return Sortable.create(el, {
            ...options,
            
            onStart: function (evt) {
                // when the sort starts, store the initial order of the array
                order = this.toArray();
            },
    
            onEnd: function (evt) {
                
                // when the sort ends, set the order to the initial state
                this.sort(order);
    
                // change the order using splice
                const data = vnode.context.data;
      
                data.splice(evt.newIndex, 0, ...data.splice(evt.oldIndex, 1));
    
                // now it is safe, you can update the order parameter
                data.forEach((o, i) => {
                    o.order = i + 1;
                });
    
            }
    
        });
    
    };
    

    Here you have a working example: https://codepen.io/elchininet/pen/MVNaON