[Solved][TypeScript] Drag & Drop <li> to New Div

Hi, working on some code for a client and I’ve seem to hit a wall. Last minute the guy wants me implement drag and drop functionality and I’ve been reading the docs about it on MDN but don’t really have much of a clue.

Here’s the file I’m working on.


app.ts

interface Task {
    text: string;
    priority: string;
}

let taskList:Task[] = new Array();

// app root
let App = document.getElementById("app-root");

// forms
let low_priority_form = App.querySelectorAll("#form")[0];
let medium_priority_form = App.querySelectorAll("#form")[1];
let high_priority_form = App.querySelectorAll("#form")[2];
let critical_priority_form = App.querySelectorAll("#form")[3];

// lists
let low_priority_list = App.querySelectorAll("#list")[0];
let medium_priority_list = App.querySelectorAll("#list")[1];
let high_priority_list = App.querySelectorAll("#list")[2];
let critical_priority_list = App.querySelectorAll("#list")[3];

///////////////////////////////////////////////////////////////////////////////
// Event Listeners
///////////////////////////////////////////////////////////////////////////////

// Dynamically remove list elements
// https://stackoverflow.com/questions/23835150/javascript-event-listener-for-multiple-buttons-with-same-class-name
App.addEventListener("click", handleClick, false);

// https://stackoverflow.com/questions/11563638/how-do-i-get-the-value-of-text-input-field-using-javascript
low_priority_form.addEventListener("submit", function(e){
    e.preventDefault();
    let new_text:string = (<HTMLInputElement>low_priority_form.querySelector("#form_input")).value;
    let new_priority:string = "low";
    let new_task:Task = { text: new_text, priority: new_priority };
    taskList.push(new_task);
    (<HTMLFormElement>low_priority_form).reset();
    draw();
});

medium_priority_form.addEventListener("submit", function(e){
    e.preventDefault();
    let new_text:string = (<HTMLInputElement>medium_priority_form.querySelector("#form_input")).value;
    let new_priority:string = "medium";
    let new_task:Task = { text: new_text, priority: new_priority };
    taskList.push(new_task);
    (<HTMLFormElement>medium_priority_form).reset();
    draw();
});

high_priority_form.addEventListener("submit", function(e){
    e.preventDefault();
    let new_text:string = (<HTMLInputElement>high_priority_form.querySelector("#form_input")).value;
    let new_priority:string = "high";
    let new_task:Task = { text: new_text, priority: new_priority };
    taskList.push(new_task);
    (<HTMLFormElement>high_priority_form).reset();
    draw();
});

critical_priority_form.addEventListener("submit", function(e){
    e.preventDefault();
    let new_text:string = (<HTMLInputElement>critical_priority_form.querySelector("#form_input")).value;
    let new_priority:string = "critical";
    let new_task:Task = { text: new_text, priority: new_priority };
    taskList.push(new_task);
    (<HTMLFormElement>critical_priority_form).reset();
    draw();
});


///////////////////////////////////////////////////////////////////////////////
// Drag and Drop Functions
///////////////////////////////////////////////////////////////////////////////

// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
function dragstart_handler(event) {
    // Add the target element's id to the data transfer object
    event.dataTransfer.setData("text/html", event.target.dataset.id);
    console.log("" + event.target.dataset.id);
    event.dropEffect = "move";
}
function dragover_handler(event) {
    event.preventDefault();
    // Set the dropEffect to move
    event.dataTransfer.dropEffect = "move";
}
function drop_handler(event) {
    event.preventDefault();
    // Get the id of the target and add the moved element to the target's DOM
    // let data = event.dataTransfer.getData("text");
    let data = event.dataTransfer.getData("text/html");
    event.target.appendChild(document.getElementById(data));
}

///////////////////////////////////////////////////////////////////////////////
// Utility Functions
///////////////////////////////////////////////////////////////////////////////

function handleClick(event) {
    event = event || window.event;
    event.target = event.target || event.srcElement;
    let element = event.target;
    // Climb up the document tree from the target of the event
    while (element) {
        if (element.nodeName === "BUTTON" && /remove/.test(element.className)) {
            // The user clicked on a <button> or clicked on an element inside a <button>
            // with a class name called "remove"
            let id:number = parseInt(element.dataset.id);
            taskList.splice(id, 1);
            // then redraw our lists
            draw();
            break;
        }
        else if (element.nodeName === "BUTTON" && /move/.test(element.className)) {
            let id:number = parseInt(element.dataset.id);
            let direction:string = element.dataset.move;
            if ( direction.localeCompare("up") ){
                changePriority(id, "up");
            } else {
                changePriority(id, "down");
            }            
            // then redraw our lists
            draw();
            break;
        }
        element = element.parentNode;
    }
}

function clearAll(){
    // https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript
    function clear(el){
        while ((<HTMLElement>el.firstChild)){
            el.removeChild(el.firstChild);
        }
    }
    clear(low_priority_list);
    clear(medium_priority_list);
    clear(high_priority_list);
    clear(critical_priority_list);
}

function changePriority(index: number, direction: string){
    let currentPriority = taskList[index].priority;    
    if (direction.localeCompare("up")){
        switch(currentPriority){
            case "low" :
                taskList[index].priority = "medium";
                break;
            case "medium" :
                taskList[index].priority = "high";
                break;
            case "high" :
                taskList[index].priority = "critical";
                break;
            case "critical" :
                alert("Already at highest possible priority!");
                break;
            default: alert("I couldn't move anything :(");
        }
    }
    else {
        switch(currentPriority){
            case "low" :
                alert("Already at lowest possible priority!");
                break;
            case "medium" :
                taskList[index].priority = "low";
                break;
            case "high" :
                taskList[index].priority = "medium";
                break;
            case "critical" :
                taskList[index].priority = "high";
                break;
            default: alert("I couldn't move anything :(");
        }
    }
}

function listItemTemplate(content: string, Id: number){
    return `<li draggable="true" ondragstart="dragstart_handler(event);" data-id="` + Id + `">
                <span>`+ content +`</span>
                <span class="text-right">
                    <button type="button" class="move pure-button" title="lower priority" data-move="down" data-id="` + Id + `">&lt;</button>
                    <button type="button" class="move pure-button" title="raise priority" data-move="up" data-id="` + Id + `">&gt;</button>
                    <button type="button" class="remove pure-button" title="remove this task" aria-label="Close" data-id="` + Id + `">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </span>
            </li>`
}

function draw(){
    let size:number = taskList.length;
    let node = document.getElementById("list");
    
    // clear existing lists
    clearAll();
    
    // then rebuild
    for (let index:number = size - 1; index >= 0; --index){
        switch(taskList[index].priority){
            case "low":
                low_priority_list.innerHTML += listItemTemplate(taskList[index].text, index);
                break;
            case "medium":
                medium_priority_list.innerHTML += listItemTemplate(taskList[index].text, index);
                break;
            case "high":
                high_priority_list.innerHTML += listItemTemplate(taskList[index].text, index);
                break;
            case "critical":
                critical_priority_list.innerHTML += listItemTemplate(taskList[index].text, index);
                break;
        }
    }
}


app.html

<!doctype html>
<html class="no-js" lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title>Threat Levels</title>
        <meta name="description" content="SPA to monitor threat levels">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
        <link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.min.css">
        <link rel="stylesheet" href="https://unpkg.com/[email protected]/build/base-min.css">
        <link rel="stylesheet" href="https://unpkg.com/[email protected]/build/forms-min.css">
        <link rel="stylesheet" href="https://unpkg.com/[email protected]/build/buttons-min.css">
        <link rel="stylesheet" href="app.css">
    </head>
    <body>
        <!--[if lte IE 9]>
            <div id="browserupgrade" style="text-align:center;">
                <p>You are using an <strong>outdated</strong> browser. Please
                <a href="https://browsehappy.com/">upgrade your browser</a>
                to improve your experience and security.</p>
            </div>
        <![endif]-->
        
        <div id="app-root" class="app-grid">

            <!-- Low Threat List  -->
            <div id="low" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">
                <form id="form" class="pure-form">
                    <fieldset>
                        <legend class="">Low</legend>
                        <input id="form_input" type="text" class="" placeholder="..." aria-label="New Item" required>
                        <span>
                            <button type="submit" value="submit" class="button-success pure-button">&#43;</button>
                        </span> 
                    </fieldset>                       
                </form>
                <ol id="list"></ol>
            </div>

            <!-- Medium Threat List  -->
            <div id="medium" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">
                <form id="form" class="pure-form">
                    <fieldset>
                        <legend class="secondary">Medium</legend>
                        <input id="form_input" type="text" class="" placeholder="..." aria-label="New Item" required>
                        <span>
                            <button type="submit" value="submit" class="button-success pure-button">&#43;</button>
                        </span> 
                    </fieldset>                       
                </form>
                <ol id="list"></ol>
            </div>

            <!-- High Threat List  -->
            <div id="high" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">
                <form id="form" class="pure-form">
                    <fieldset>
                        <legend class="warning">High</legend>
                        <input id="form_input" type="text" class="" placeholder="..." aria-label="New Item" required>
                        <span>
                            <button type="submit" value="submit" class="button-success pure-button">&#43;</button>
                        </span> 
                    </fieldset>                        
                </form>
                <ol id="list"></ol>
            </div>

            <!-- Critical Threat List  -->
            <div id="critical" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">
                <form id="form" class="pure-form">
                    <fieldset>
                        <legend class="error">Critical</legend>
                        <input id="form_input" type="text" class="" placeholder="..." aria-label="New Item" required>
                        <span>
                            <button type="submit" value="submit" class="button-success pure-button">&#43;</button>
                        </span> 
                    </fieldset>                       
                </form>
                <ol id="list"></ol>
            </div>

        </div>
        <script src="app.js"></script> 
    </body>
</html>


Basically the way I’ve designed it each list item is an interface with two properties: text and priority.

What I need to do is, when a list item is dragged into a div with other list items, on drop it needs its priority changed adn then I can just call my draw() function as it rebuilds each priority area .

When I click and drag a list item now I get this error:
TypeError: Argument 1 of Node.appendChild is not an object.

Maybe I just need to sit down and think about this some more.

Any thoughts are welcome.

Got it.

Damn it was really hard to find relevant information because all that shows up are JQuery links, when I needed vanilla Javascript.

Read these two links:

What needed to change:

// when a draggable object is clicked
function dragstart_handler(event) {
    event.dataTransfer.setData("text/plain", event.target.dataset.id); //<-- pass data-id
    event.dropEffect = "move";
}

// while the object is being dragged
function dragover_handler(event) {
    event.preventDefault();
    event.dataTransfer.dropEffect = "move";
}

// when the draggable object is dropped onto a droppable object
function drop_handler(event) {
    event.preventDefault();
    let item_data = event.dataTransfer.getData("text");
    let item_index = parseInt(item_data);
    let new_priority = event.target.id;
    taskList[item_index].priority = new_priority;
    draw();
}

This more or less got it working. Still a few bugs to work out but at least now it works.