Skillversitas

Drag and Drop API

The Drag and Drop API allows users to move elements by dragging and dropping them.

Drag and Drop Basics

Creating a Draggable Element

<div draggable="true" id="draggable" 
     ondragstart="drag(event)">
    Drag me!
</div>

<div id="dropzone" 
     ondragover="event.preventDefault()" 
     ondrop="drop(event)">
    Jatuhkan di sini
</div>

<script>
    function drag(ev) {
        ev.dataTransfer.setData("text", ev.target.id);
    }

    function drop(ev) {
        ev.preventDefault();
        const data = ev.dataTransfer.getData("text");
        ev.target.appendChild(document.getElementById(data));
    }
</script>

Drag and Drop Events

Event Targets

<div id="source" draggable="true">Drag me</div>
<div id="target">Drop here</div>

<script>
    const source = document.getElementById('source');
    const target = document.getElementById('target');

    // Events on the element being dragged
    source.addEventListener('dragstart', function(e) {
        e.dataTransfer.setData('text/plain', 'Hello');
        e.target.style.opacity = '0.4';
    });

    source.addEventListener('dragend', function(e) {
        e.target.style.opacity = '1';
    });

    // Events on the drop target
    target.addEventListener('dragover', function(e) {
        e.preventDefault();
        e.target.style.background = '#3498db';
    });

    target.addEventListener('dragleave', function(e) {
        e.target.style.background = '';
    });

    target.addEventListener('drop', function(e) {
        e.preventDefault();
        e.target.style.background = '';
        const data = e.dataTransfer.getData('text/plain');
        alert('Dropped: ' + data);
    });
</script>

Case Study: Campus Drag and Drop Dashboard

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard Drag and Drop</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', sans-serif;
            background: #f0f2f5;
            padding: 2rem;
        }

        .container {
            max-width: 1000px;
            margin: 0 auto;
        }

        h1 {
            color: #1e3a5f;
            margin-bottom: 2rem;
            text-align: center;
        }

        .board {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 1.5rem;
        }

        .column {
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            min-height: 400px;
        }

        .column-header {
            padding: 1rem;
            border-bottom: 1px solid #eee;
            font-weight: bold;
            color: #1e3a5f;
        }

        .column-header.todo {
            border-bottom: 3px solid #3498db;
        }

        .column-header.progress {
            border-bottom: 3px solid #f39c12;
        }

        .column-header.done {
            border-bottom: 3px solid #2ecc71;
        }

        .column-content {
            padding: 1rem;
            min-height: 300px;
        }

        .task {
            background: #f8f9fa;
            padding: 1rem;
            border-radius: 6px;
            margin-bottom: 0.8rem;
            cursor: grab;
            border-left: 4px solid #3498db;
            transition: all 0.2s;
        }

        .task:hover {
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
        }

        .task.dragging {
            opacity: 0.5;
            cursor: grabbing;
        }

        .task h3 {
            font-size: 0.95rem;
            color: #333;
            margin-bottom: 0.5rem;
        }

        .task p {
            font-size: 0.85rem;
            color: #666;
            margin-bottom: 0.5rem;
        }

        .task .priority {
            font-size: 0.75rem;
            padding: 0.2rem 0.5rem;
            border-radius: 3px;
            display: inline-block;
        }

        .priority.high {
            background: #fee2e2;
            color: #dc2626;
        }

        .priority.medium {
            background: #fef3c7;
            color: #d97706;
        }

        .priority.low {
            background: #d1fae5;
            color: #059669;
        }

        .column-content.dragover {
            background: #e3f2fd;
        }

        .add-task {
            display: block;
            width: 100%;
            padding: 0.8rem;
            margin-top: 1rem;
            background: #f8f9fa;
            border: 2px dashed #ddd;
            border-radius: 6px;
            cursor: pointer;
            color: #666;
            font-size: 0.9rem;
        }

        .add-task:hover {
            background: #e3f2fd;
            border-color: #3498db;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Campus Task Management</h1>

        <div class="board">
            <!-- Todo Column -->
            <div class="column">
                <div class="column-header todo">To Do</div>
                <div class="column-content" id="todo" 
                     ondrop="drop(event)" 
                     ondragover="event.preventDefault()" 
                     ondragenter="dragEnter(event)"
                     ondragleave="dragLeave(event)">
                    <div class="task" draggable="true" 
                         ondragstart="drag(event)"
                         id="task1">
                        <h3>Create HTML Assignment</h3>
                        <p>Deadline: January 15, 2024</p>
                        <span class="priority high">High/span>
                    </div>

                    <div class="task" draggable="true" 
                         ondragstart="drag(event)"
                         id="task2">
                        <h3>Group Presentation</h3>
                        <p>Deadline: January 20, 2024</p>
                        <span class="priority medium">Sedang</span>
                    </div>

                    <button class="add-task">+ Add Task</button>
                </div>
            </div>

            <!-- In Progress Column -->
            <div class="column">
                <div class="column-header progress">In Progress</div>
                <div class="column-content" id="progress" 
                     ondrop="drop(event)" 
                     ondragover="event.preventDefault()"
                     ondragenter="dragEnter(event)"
                     ondragleave="dragLeave(event)">
                    <div class="task" draggable="true" 
                         ondragstart="drag(event)"
                         id="task3">
                        <h3>Write Thesis Chapter 3</h3>
                        <p>Deadline: February 10, 2024</p>
                        <span class="priority high">High/span>
                    </div>

                    <button class="add-task">+ Add Task</button>
                </div>
            </div>

            <!-- Done Column -->
            <div class="column">
                <div class="column-header done">Done</div>
                <div class="column-content" id="done" 
                     ondrop="drop(event)" 
                     ondragover="event.preventDefault()"
                     ondragenter="dragEnter(event)"
                     ondragleave="dragLeave(event)">
                    <div class="task" draggable="true" 
                         ondragstart="drag(event)"
                         id="task4">
                        <h3>Database Midterm</h3>
                        <p>Done: January 5, 2024</p>
                        <span class="priority low">Low/span>
                    </div>

                    <button class="add-task">+ Add Task</button>
                </div>
            </div>
        </div>
    </div>

    <script>
        function drag(ev) {
            ev.dataTransfer.setData("text", ev.target.id);
            ev.target.classList.add('dragging');
        }

        document.addEventListener('dragend', function(e) {
            e.target.classList.remove('dragging');
        });

        function drop(ev) {
            ev.preventDefault();
            const data = ev.dataTransfer.getData("text");
            const task = document.getElementById(data);

            if (task) {
                // Remove button from parent if exists
                const addBtn = ev.target.querySelector('.add-task');
                ev.target.appendChild(task);
                ev.target.classList.remove('dragover');

                // Re-add button at end
                if (addBtn) {
                    ev.target.appendChild(addBtn);
                }
            }
        }

        function dragEnter(ev) {
            ev.preventDefault();
            ev.target.classList.add('dragover');
        }

        function dragLeave(ev) {
            ev.target.classList.remove('dragover');
        }
    </script>
</body>
</html>

Everyday Case Study

Case: A developer needs to create a sortable list.

<ul id="sortable">
    <li draggable="true">Item 1</li>
    <li draggable="true">Item 2</li>
    <li draggable="true">Item 3</li>
</ul>

<script>
    let draggedItem = null;

    document.querySelectorAll('#sortable li').forEach(item => {
        item.addEventListener('dragstart', function(e) {
            draggedItem = this;
            setTimeout(() => this.style.display = 'none', 0);
        });

        item.addEventListener('dragend', function(e) {
            setTimeout(() => this.style.display = 'block', 0);
            draggedItem = null;
        });

        item.addEventListener('dragover', function(e) {
            e.preventDefault();
            const afterElement = getDragAfterElement(document.getElementById('sortable'), e.clientY);
            const sortable = document.getElementById('sortable');
            if (afterElement == null) {
                sortable.appendChild(draggedItem);
            } else {
                sortable.insertBefore(draggedItem, afterElement);
            }
        });
    });

    function getDragAfterElement(container, y) {
        const draggableElements = [...container.querySelectorAll('li:not(.dragging)')];

        return draggableElements.reduce((closest, child) => {
            const box = child.getBoundingClientRect();
            const offset = y - box.top - box.height / 2;
            if (offset < 0 && offset > closest.offset) {
                return { offset: offset, element: child };
            } else {
                return closest;
            }
        }, { offset: Number.NEGATIVE_INFINITY }).element;
    }
</script>

Best Practices

  1. Visual feedback: Provide indicators while dragging and dropping
  2. DataTransfer: Use it to carry data
  3. Prevent default: Essential for drop targets
  4. Accessibility: Provide non-drag alternatives
  5. Mobile support: Consider touch events

Summary

The Drag and Drop API enables natural drag-and-drop interactions. It's perfect for kanban boards, sortable lists, and other interactive interfaces.