Prevent duplicates created by Mithril while sorting elements with SortableJS

202 Views Asked by At

I am making an dahboard creator tool. Dashboard is set of rows, each row have boxes. So I trying to make list of lists of items. Items can move between lists. Lists can be sorted between each other. Everything is saved in one object, which can be easily exported in JSON format. Cool.

_________          ______________
| A  B  |   move   | A  B  C  C |  
|-------|   C up   |------------|   
| C     |   ===>   |            |  
|_______|          |____________|

If I move box C to first row and call redraw, the C box duplicate self. One C is original box from the second row and second C box is created by mithril from model.

The point is I want just move the the box between rows, not to copy it.

I tried play with onbeforeupdate and m.redraw(), but it is not solution, because I need to working redraw cycle.

class Grid {

    private boxMap = new Map<TDashboardBox, IEditor>();

    constructor(
        public readonly dashboard: DashboardDAO,
        private readonly editorFactory: IBoxFactory
    ) {
        for (const row of dashboard.dashboard.config) {
            for (const box of row) {
                this.boxMap.set(box, editorFactory.create(box.title, box))
            }
        }
    }

    public boxView(box: TDashboardBox): Vnode {
        return m(this.boxMap.get(box) || null);
    }

    public rowView(row: TDashboardRow): Vnode[] {
        return row.map(b => m('.Box', {}, this.boxView(b)));
    }

    public view(): Vnode {
        return m('.Grid', {},
            this.dashboard.dashboard.config.map(r => m('.Row',  {}, this.rowView(r)))
        );
    }

    public oncreate(vnode: VnodeDOM): void {
        Sortable.create(<HTMLElement>vnode.dom, {
            group: "rows",
            animation: 150,
            onEnd: (e: SortableEvent) => {
                this.dashboard.swapRows(e.oldIndex, e.newIndex);
            },
        });

        const rows = vnode.dom.getElementsByClassName('Row');
        for (let i = 0; i < rows.length; i++)
        {
            const row = <HTMLElement>rows[i];
            Sortable.create(row, {
                group: "boxes",
                animation: 150,
                onEnd: (e: SortableEvent) => {
                    const children = e.from.parentElement.childNodes;
                    // Search row indexes of "from row" and "to row"
                    let fromIndex, targetIndex;
                    for (let j = 0; j < children.length; j++) {
                        if (children[j] === e.from) {
                            fromIndex = j;
                        }
                        if (children[j] === e.to) {
                            targetIndex = j;
                        }
                        if (fromIndex !== undefined && targetIndex !== undefined) {
                            break;
                        }
                    }
                    this.dashboard.moveBox(fromIndex, e.oldDraggableIndex, targetIndex, e.newDraggableIndex);

                    m.redraw();
                },
            });
        }
    }
}

EDIT 1

Copied box can be removed in onEnd() callback. It solve the copy problem.

If I swap boxes on one row, they are swapped in model. But on screen after redraw they are swapped back.

EDIT 2

I found solution some solution. It almost there.

// End of onEnd() event
// if boxes moved, then...
m.render(vnode.dom, m(this)); // redraw Grid scratch

I just throw away whole html of Grid and let mithril rebuild it.

But now is in Grid just HTML without events, without redraw cycle.

1

There are 1 best solutions below

0
On

Ok, I figured it out. It is not "best in class" solution, but it works well.

I create callback function passed to Grid component from its parent component. After boxes are moved, that callback fucntion simply destroy Grid and create new one.

parent component

public recreateGrid(): void {
    this.grid = new Grid(
        this.editor.currentDashboard,
        this.factory,
        () => this.recreateGrid()
    )
}
...

public view(): Vnode { // example of view method 
     return m('.Layout.selected-dashboard', {}, [
         ... // some other components
         m(this.grid),
     ]);
}

Grid constructor

constructor(
    public readonly dashboard: DashboardDAO,      // model
    private readonly editorFactory: IBoxFactory,  // boxes factory
    private readonly onBoxMoved: ()=>void,        // callback       <--
    ) { ... }