4

below HTML and JS I have written by going through some documentation on drag event. Not able to save anything on LWC playground so posted all code below which renders as shown in the below image.

enter image description here

added drag event on seq no column so that user can re order the data by just drag and drop seq number data.

HTML:

<template>
    <lightning-layout>
        <lightning-layout-item size="12" padding="horizontal-small">
            <lightning-card>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">
                                Seq no.
                            </th>
                            <th scope="col">
                                Name
                            </th>
                            <th scope="col">
                                age
                            </th>
                            <th scope="col">
                                sex
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <template for:each={dataArray} for:item="data">
                            <tr key={data.seqNumber}>
                                <td>
                                    <div draggable="true" ondragstart={handleDragStart} 
                                    ondragend={handleDragEnd} data-item={data.seqNumber}>
                                        {data.seqNumber}
                                    </div>
                                </td>
                                <td>
                                    {data.name}
                                </td>
                                <td>
                                    {data.age}
                                </td>
                                <td>
                                    {data.sex}
                                </td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </lightning-card>
        </lightning-layout-item>
    </lightning-layout>
</template>

JS:

import { LightningElement, track } from 'lwc';

export default class App extends LightningElement {

    dataArray = [{
        seqNumber: 1,
        name: 'John',
        age: 50,
        sex: 'Male'

    },
    {
        seqNumber: 2,
        name: 'Moira',
        age: 51,
        sex: 'Female'
    },
    {
        seqNumber: 3,
        name: 'Sheldon',
        age: 52,
        sex: 'Male'
    },
    {
        seqNumber: 4,
        name: 'Nina',
        age: 42,
        sex: 'Female'
    },
    {
        seqNumber: 5,
        name: 'Morgan',
        age: 34,
        sex: 'Male'

    }]

    handleDragStart(event) {
        event.dataTransfer.dropEffect = 'move';

        let seqNumber = event.target.dataset.item;
        console.log('seqNumber ' + seqNumber);

    }

    handleDragEnd(event) {

        console.log(event);

    }
}

but the above one is not enough to make <td> dragable. I can get the data id of the one which I am dragging but not getting anything on where I dropped handleDragEnd throws error.

can someone please point me to the right direction to make an HTML table content dragable?

2
  • This doesn't seem to throw an error when done in-org, so it might be an issue with the playground?
    – eaeaoo
    Aug 8, 2020 at 12:41
  • yes, It was an issue with playground, I posted my answer also.
    – gs650x
    Aug 8, 2020 at 12:42

3 Answers 3

1

I finally solved it by updating HTML and JS as below:

added ondrop, ondropover with preventdefaults as ondrop alone wont fire in HTML and then move array element in JS to show updated HTML

HTML:

    <template>
        <lightning-layout>
            <lightning-layout-item size="12" padding="horizontal-small">
                <lightning-card>
                    <table>
                        <thead>
                            <tr>
                                <th scope="col">
                                    Seq no.
                                </th>
                                <th scope="col">
                                    Name
                                </th>
                                <th scope="col">
                                    age
                                </th>
                                <th scope="col">
                                    sex
                                </th>
                            </tr>
                        </thead>
                        <tbody>
                            <template for:each={dataArray} for:item="data">
                                <tr key={data.seqNumber}>
                                    <td>
                                        <div draggable="true" ondragstart={handleDragStart} 
                                        ondrop={handleDrop} 
ondragover={handleDragover}
data-item={data.seqNumber}>
                                            {data.seqNumber}
                                        </div>
                                    </td>
                                    <td>
                                        {data.name}
                                    </td>
                                    <td>
                                        {data.age}
                                    </td>
                                    <td>
                                        {data.sex}
                                    </td>
                                </tr>
                            </template>
                        </tbody>
                    </table>
                </lightning-card>
            </lightning-layout-item>
        </lightning-layout>
    </template>

JS:

  import { LightningElement, track } from 'lwc';
    
    var indexFrom;
    var indexTo;
    
    export default class App extends LightningElement {
    
        @track dataArray = [{
            seqNumber: 1,
            name: 'John',
            age: 50,
            sex: 'Male'
    
        },
        {
            seqNumber: 2,
            name: 'Moira',
            age: 51,
            sex: 'Female'
        },
        {
            seqNumber: 3,
            name: 'Sheldon',
            age: 52,
            sex: 'Male'
        },
        {
            seqNumber: 4,
            name: 'Nina',
            age: 42,
            sex: 'Female'
        },
        {
            seqNumber: 5,
            name: 'Morgan',
            age: 34,
            sex: 'Male'
    
        }]
    
        handleDragStart(event) {
            event.dataTransfer.dropEffect = 'move';
    
            indexFrom = this.getIndex(parseInt(event.target.dataset.item, 10));
            
    
        }
    
        getIndex(index) {
            return this.dataArray.map(function(e) { return e.seqNumber; }).indexOf(index);
        }
    
    
        handleDrop(event) {
    
            indexTo = this.getIndex(parseInt(event.target.dataset.key, 10));
            
            let cutOut = this.dataArray.splice(indexFrom, 1) [0]; // cut the element at index 'from'
                this.dataArray.splice(indexTo, 0, cutOut);
          
            console.log(JSON.stringify(this.dataArray));
    
        }
    }

handleDragover(event) {
        event.preventDefault();
    }

If any one has any better solutions, please do post it.

1

I borrowed from this Stack Overflow thread to create a class in a utility component:

export class Drag {
    constructor(array) {
        this.array = array;
    }
    start(e) {
        e.dataTransfer.effectAllowed = "move";
        e.dataTransfer.setData("text/plain", null);
        this._el = e.target;
    }
    over(e) {
        // While dragging, continuously move the element
        if (isBefore(this._el, e.target)) {
            e.target.parentNode.insertBefore(this._el, e.target);
        } else {
            e.target.parentNode.insertBefore(this._el, e.target.nextSibling);
        }
    }
    end(e) {
        // Rebuild the array according to the new order of the repeat elements 
        /// in the DOM
        this.array.splice(
            0,
            this.array.length,
            ...Array.from(e.target.parentNode.children).map(
                item => this.array[item.dataset.index]
            )
        );
    }
}

function isBefore(el1, el2) {
    if (el2.parentNode === el1.parentNode) {
        for (
            let cur = el1.previousSibling;
            cur && cur.nodeType !== 9;
            cur = cur.previousSibling
        ) {
            return cur === el2;
        }
    }
    return false;
}

I call it in my LWC JavaScript file like this:

import { Drag } from "c/utils";

export default class MyFruitList extends LightningElement {
    @track fruits = ["Apples", "Bananas", "Oranges", "Strawberries"];
    drag = new Drag(this.fruits);

    handleDragEnd(e) {
        this.drag.end(e);
    }
}

The template side looks like this:

<template>
    <ul>
        <template for:each={fruits} for:item="fruit" for:index="idx">
            <li
                key={fruit}
                data-index={idx}
                draggable="true"
                ondragstart={drag.start}
                ondragover={drag.over}
                ondragend={handleDragEnd}
            >
                {fruit}
            </li>
        </template>
    </ul>
</template>

I was hoping to be able to use ondragend={drag.end}, but for whatever reason, it wasn't hitting the end() function in the Drag class when I did that directly in the template.

I still think this has some reusability advantages. Additionally, using for:index and the data-index property to keep track of the indices of your elements makes it easier to refer to them generically without having to hard-code a unique object property.

You don't even need to use an array of objects at all. As with my example above, this works equally well with arrays of primitives.

1
  • thanks for this, it's handy! Jul 28, 2023 at 2:03
0

Thank you so much for the sample code that you have posted. It was all good except that wherever we drop a tr it was getting dropped only at a particular index. I have modified the JS to make the table row droppable at any index the user wish to. Please review the below code and upvote if you find it helpful.

Thank you.

App.js

import { LightningElement, track } from 'lwc';

var indexFrom;
var indexTo;

export default class App extends LightningElement {

    @track dataArray = [{
        seqNumber: 32,
        name: 'John',
        age: 50,
        sex: 'Male'

    },
    {
        seqNumber: 56,
        name: 'Moira',
        age: 51,
        sex: 'Female'
    },
    {
        seqNumber: 54,
        name: 'Sheldon',
        age: 52,
        sex: 'Male'
    },
    {
        seqNumber: 64,
        name: 'Nina',
        age: 42,
        sex: 'Female'
    },
    {
        seqNumber: 75,
        name: 'Morgan',
        age: 34,
        sex: 'Male'

    }]

    handleDragStart(event) {
        event.dataTransfer.dropEffect = 'move';

        indexFrom = this.getIndex(parseInt(event.target.dataset.item));
        console.log(indexFrom);
        

    }

    getIndex(index) {
        return this.dataArray.map(function(e) { return e.seqNumber; }).indexOf(index);
    }


    handleDrop(event) {

        indexTo = this.getIndex(parseInt(event.target.dataset.item));
        console.log(indexTo);
        
        let cutOut = this.dataArray.splice(indexFrom,1) [0]; // cut the element at index 'from'
            this.dataArray.splice(indexTo, 0, cutOut);
      
        // console.log(JSON.stringify(this.dataArray));

    }

    handleDragover(event) {
      event.preventDefault();
    }

}

app.html

<template>
<lightning-layout>
    <lightning-layout-item size="12" padding="horizontal-small">
        <lightning-card>
            <table border="1px">
                <thead>
                    <tr>
                        <th scope="col">
                            Seq no.
                        </th>
                        <th scope="col">
                            Name
                        </th>
                        <th scope="col">
                            age
                        </th>
                        <th scope="col">
                            sex
                        </th>
                    </tr>
                </thead>
                <tbody>
                    <template for:each={dataArray} for:item="data">
                        <tr key={data.seqNumber}>
                            <td>
                                <div draggable="true" ondragstart={handleDragStart} ondrop={handleDrop} ondragover={handleDragover}
                                    data-item={data.seqNumber}>
                                    {data.seqNumber}
                                </div>
                            </td>
                            <td>
                                {data.name}
                            </td>
                            <td>
                                {data.age}
                            </td>
                            <td>
                                {data.sex}
                            </td>
                        </tr>
                    </template>
                </tbody>
            </table>
        </lightning-card>
    </lightning-layout-item>
</lightning-layout>

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .