Building a Sortable List with Drag and Drop using Vanilla JavaScript: An Educational Guide
This chapter will guide you through the process of creating a dynamic, sortable list using vanilla JavaScript, enhanced with drag and drop functionality. This project, originally from a web development course, serves as an excellent practical exercise for understanding core JavaScript concepts, DOM manipulation, and the browser’s Drag and Drop API. We will break down each step, from setting up the HTML structure to implementing the JavaScript logic and styling with CSS.
1. Introduction to Sortable Lists and Drag and Drop
Interactive web applications often require users to manipulate data visually. Sortable lists are a common UI pattern that allows users to reorder items within a list intuitively. Combined with drag and drop functionality, this becomes a powerful tool for creating engaging and user-friendly interfaces.
This chapter focuses on building a sortable list of the “Top 10 Richest People” as a practical example. However, the principles and code structure can be easily adapted to any list of items you wish to make sortable. The primary focus will be on leveraging the browser’s built-in Drag and Drop API to enable this interactivity.
The Drag and Drop API is a set of interfaces that allows web applications to enable drag-and-drop features using native browser events. It provides a standardized way to handle drag-and-drop interactions within web pages.
We will also explore various JavaScript methods and concepts including:
- DOM Manipulation: Dynamically creating and modifying HTML elements using JavaScript.
- Array Methods:
forEach
,map
,sort
, and the spread operator for efficient data handling. - Event Handling: Listening for and responding to user interactions like drag and drop events.
- CSS Styling: Applying styles to enhance the visual presentation of the list.
Let’s begin by setting up the basic HTML structure.
2. Setting Up the HTML Structure
For this project, we will start with a minimal HTML file. The list items themselves will be generated dynamically using JavaScript. This approach highlights the power of JavaScript in manipulating the Document Object Model (DOM).
The Document Object Model (DOM) is a programming interface for web documents. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as nodes and objects; that way, programming languages can interact with the page.
Here’s the basic HTML structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Top 10 Richest People</title>
<link rel="stylesheet" href="style.css">
<script src="https://kit.fontawesome.com/[YOUR_FONT_AWESOME_KIT].js" crossorigin="anonymous"></script>
</head>
<body>
<h1>10 Richest People</h1>
<p>Drag and drop the items into their corresponding spots</p>
<ul class="draggable-list" id="draggable-list">
<!-- List items will be inserted here by JavaScript -->
</ul>
<button class="check-btn" id="check">
Check Order <i class="fas fa-paper-plane"></i>
</button>
<script src="script.js"></script>
</body>
</html>
Key elements in the HTML:
- Font Awesome: We are including Font Awesome for icons, specifically the “grip lines” icon for drag handles and the “paper plane” icon for the “Check Order” button. You will need to replace
[YOUR_FONT_AWESOME_KIT].js
with your own Font Awesome kit script. <h1>
: The main title of the page.<p>
: A descriptive paragraph instructing the user.<ul>
: An unordered list with the classdraggable-list
and IDdraggable-list
. Thisul
element will serve as the container for our sortable list items. Crucially, it is initially empty; list items will be dynamically added via JavaScript.<button>
: A button with the classcheck-btn
and IDcheck
. This button will be used to trigger the order checking functionality.style.css
andscript.js
: Links to external CSS and JavaScript files, respectively.
3. JavaScript: Setting up DOM Elements and Data
Now, let’s move to the script.js
file and start implementing the JavaScript logic. First, we need to select the relevant DOM elements and define our data.
const draggableList = document.getElementById('draggable-list');
const checkBtn = document.getElementById('check');
const richestPeople = [
'Jeff Bezos',
'Bill Gates',
'Warren Buffett',
'Bernard Arnault',
'Carlos Slim Helu',
'Amancio Ortega',
'Larry Ellison',
'Mark Zuckerberg',
'Michael Bloomberg',
'Larry Page'
];
const listItems = [];
let dragStartIndex;
createList();
// ... (rest of JavaScript code will be added in subsequent sections)
Explanation:
draggableList
andcheckBtn
: We usedocument.getElementById
to select theul
element (draggable-list
) and the button element (check
) from the HTML. These variables will allow us to manipulate these elements in our JavaScript code.richestPeople
Array: This array holds the names of the top 10 richest people in the correct order. This array serves as our source data and the basis for comparison when checking the order.listItems
Array: This array is initialized as empty. It will store the dynamically created list items as they are generated and potentially reordered.dragStartIndex
Variable: This variable will be used to keep track of the index of the list item that is being dragged. It is initialized withlet
because its value will change during the drag and drop process.createList()
Function Call: This line calls thecreateList
function, which we will define next, to generate and insert the list items into the DOM.
4. JavaScript: Creating and Inserting List Items into the DOM
The createList
function is responsible for dynamically generating the list items based on the richestPeople
array and inserting them into the draggableList
(the ul
element in our HTML).
function createList() {
[...richestPeople] // Using spread operator to create a copy
.map(a => ({ value: a, sort: Math.random() })) // Map to objects with value and random sort value
.sort((a, b) => a.sort - b.sort) // Sort based on random value
.map(a => a.value) // Map back to array of names
.forEach((person, index) => {
const listItem = document.createElement('li');
listItem.setAttribute('data-index', index);
listItem.innerHTML = `
<span class="number">${index + 1}</span>
<div class="draggable" draggable="true">
<p class="person-name">${person}</p>
<i class="fas fa-grip-lines"></i>
</div>
`;
listItems.push(listItem);
draggableList.appendChild(listItem);
});
addEventListeners(); // Call function to attach event listeners
}
Detailed Explanation of createList
Function:
-
Scrambling the List Order:
[...richestPeople]
(Spread Operator):The spread operator (
...
) allows an iterable such as an array or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. Here, it creates a shallow copy of therichestPeople
array, preventing modification of the original ordered array.- .
map(a => ({ value: a, sort: Math.random() }))
: Themap
method iterates over the copied array and transforms each element (person’s name) into an object. Each object has two properties:value
: The person’s name.sort
: A random decimal number generated usingMath.random()
.
The
map()
method is a higher-order array method that creates a new array by calling a provided function on every element in the calling array. - .
sort((a, b) => a.sort - b.sort)
: Thesort
method shuffles the array of objects based on thesort
property (the random numbers). This effectively randomizes the order of the names.The
sort()
method is a higher-order array method that sorts the elements of an array in place and returns the sorted array. It can take a compare function to customize the sort order. - .
map(a => a.value)
: Anothermap
method is used to transform the array of objects back into an array of just the person names, but now in a scrambled order.
-
Generating List Item Elements:
- .
forEach((person, index) => { ... })
: TheforEach
method iterates over the scrambled array of person names.The
forEach()
method is a higher-order array method that executes a provided function once for each array element. const listItem = document.createElement('li');
: For each person, a newli
(list item) element is created usingdocument.createElement('li')
. This is a core DOM manipulation technique.document.createElement()
is a DOM method that creates an HTML element specified bytagName
.listItem.setAttribute('data-index', index);
: A customdata-index
attribute is added to eachli
element. This attribute stores the original index of the item in therichestPeople
array (before scrambling). This is crucial for tracking the correct order and for drag and drop functionality. Data attributes are used to store custom data private to the page or application.- **
listItem.innerHTML = \
…`;(Template Literals):** The
innerHTMLproperty is used to set the HTML content within each
li` element.innerHTML
is a property that gets or sets the HTML markup contained within an element. Template literals (backticks`
) are used to create multi-line strings and embed expressions (\${expression}
) within strings, making string construction more readable and powerful. The HTML structure within eachli
includes:<span class="number">
: Displays the item number (index + 1).<div class="draggable" draggable="true">
: The draggable area. Thedraggable="true"
attribute is essential for enabling drag and drop functionality on this element using the Drag and Drop API.<p class="person-name">
: Displays the person’s name.<i class="fas fa-grip-lines">
: The Font Awesome icon to visually indicate draggable handle.
- .
-
Adding List Items to the DOM and
listItems
Array:listItems.push(listItem);
: The newly createdlistItem
element is added to thelistItems
array. This array now holds references to all theli
elements in the order they are currently displayed (scrambled initially).draggableList.appendChild(listItem);
: ThelistItem
element is appended as a child to thedraggableList
(ul
element) in the DOM. This makes the list item visible on the webpage.appendChild()
is a DOM method that adds a node to the end of the list of children of a specified parent node.
-
addEventListeners();
: Finally, thecreateList
function calls theaddEventListeners
function (which we will define in the next section) to attach event listeners for drag and drop interactions to the generated list items.
5. JavaScript: Implementing Drag and Drop Event Listeners
To enable drag and drop functionality, we need to attach event listeners to the draggable elements and list items. The addEventListeners
function handles this.
function addEventListeners() {
const draggables = document.querySelectorAll('.draggable');
const dragListItems = document.querySelectorAll('.draggable-list li');
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', dragStart);
});
dragListItems.forEach(item => {
item.addEventListener('dragover', dragOver);
item.addEventListener('drop', dragDrop);
item.addEventListener('dragenter', dragEnter);
item.addEventListener('dragleave', dragLeave);
});
}
Explanation of addEventListeners
Function:
const draggables = document.querySelectorAll('.draggable');
: Selects all elements with the classdraggable
(thediv
elements within each list item).querySelectorAll
returns a NodeList (similar to an array) of all matching elements.document.querySelectorAll()
is a DOM method that returns a static (not live) NodeList representing a list of the document’s elements that match the specified group of selectors.const dragListItems = document.querySelectorAll('.draggable-list li');
: Selects allli
elements that are descendants of the element with the classdraggable-list
(ourul
element).- Event Listeners for Draggable Elements (
draggables
):draggables.forEach(draggable => { ... });
: Iterates over eachdraggable
element.draggable.addEventListener('dragstart', dragStart);
: Attaches adragstart
event listener to eachdraggable
element. When a drag operation begins on adraggable
element, thedragStart
function will be executed.addEventListener()
is a DOM method that attaches an event handler to the specified element. Thedragstart
event is fired when the user starts to drag an element.
- Event Listeners for List Item Elements (
dragListItems
):dragListItems.forEach(item => { ... });
: Iterates over each list item (li
element).item.addEventListener('dragover', dragOver);
: Attaches adragover
event listener. This event is fired continuously as the mouse pointer is over an element during a drag operation. We will usee.preventDefault()
in thedragOver
function to allow dropping on the list items.The
dragover
event is fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds).item.addEventListener('drop', dragDrop);
: Attaches adrop
event listener. This event is fired when a dragged element or text selection is dropped on a valid drop target.The
drop
event is fired when the user releases the mouse button or touch screen at a drop target.item.addEventListener('dragenter', dragEnter);
: Attaches adragenter
event listener. This event is fired when the mouse pointer enters an element during a drag operation. We will use this to visually highlight the target list item.The
dragenter
event is fired when a dragged element or text selection enters a valid drop target.item.addEventListener('dragleave', dragLeave);
: Attaches adragleave
event listener. This event is fired when the mouse pointer leaves an element during a drag operation. We will use this to remove the visual highlight from the target list item.The
dragleave
event is fired when a dragged element or text selection leaves a valid drop target.
6. JavaScript: Implementing Drag Event Handlers
Now, we need to define the functions that will be executed when the drag and drop events are triggered. These functions will handle the logic for visual feedback and reordering the list items.
let dragStartIndex;
function dragStart(e) {
dragStartIndex = +this.closest('li').getAttribute('data-index');
}
function dragOver(e) {
e.preventDefault(); // Necessary to allow dropping
}
function dragDrop(e) {
const dragEndIndex = +this.getAttribute('data-index');
swapItems(dragStartIndex, dragEndIndex);
this.classList.remove('over'); // Remove highlight after drop
}
function dragEnter(e) {
this.classList.add('over'); // Add highlight when entering
}
function dragLeave(e) {
this.classList.remove('over'); // Remove highlight when leaving
}
function swapItems(fromIndex, toIndex) {
const itemOne = listItems[fromIndex].querySelector('.draggable');
const itemTwo = listItems[toIndex].querySelector('.draggable');
listItems[fromIndex].appendChild(itemTwo);
listItems[toIndex].appendChild(itemOne);
}
Explanation of Drag Event Handler Functions:
-
dragStart(e)
:dragStartIndex = +this.closest('li').getAttribute('data-index');
: When a drag operation starts, this function determines the index of the dragged list item.this
: Inside the event handler,this
refers to thedraggable
element that triggered thedragstart
event..closest('li')
: Theclosest('li')
method traverses up the DOM tree from thedraggable
element until it finds the closest ancestorli
element (the list item).closest()
is a DOM method that traverses the Element and its parents (heading toward the document root) until it finds a node that matches the specified CSS selector..getAttribute('data-index')
: Retrieves the value of thedata-index
attribute from the foundli
element.getAttribute()
is a DOM method that returns the string value of a named attribute on the current element.+
(Unary Plus Operator): Converts the attribute value (which is a string) to a number. This numerical index is then stored in thedragStartIndex
variable.
-
dragOver(e)
:e.preventDefault();
: This line is crucial. By default, data/elements cannot be dropped onto other elements. Callinge.preventDefault()
in thedragover
handler prevents the default behavior and allows thedrop
event to be fired when an element is dropped onto a valid drop target.e.preventDefault()
is a method on the event object (e
). It prevents the default action of the event from happening. In the context of drag and drop, it’s necessary to prevent the browser’s default drag-and-drop behavior and allow JavaScript to handle the drop.
-
dragDrop(e)
:const dragEndIndex = +this.getAttribute('data-index');
: When an element is dropped onto a list item, this function gets thedata-index
of the list item where the element was dropped.swapItems(dragStartIndex, dragEndIndex);
: Calls theswapItems
function (defined below) to reorder the list items in the DOM based on thedragStartIndex
anddragEndIndex
.this.classList.remove('over');
: Removes theover
class from the list item where the element was dropped, removing the visual highlight.
-
dragEnter(e)
:this.classList.add('over');
: When a dragged element enters a list item, this function adds theover
class to the list item, triggering a visual highlight (defined in CSS) to indicate that this is a potential drop target.classList.add()
is a DOM method that adds one or more classes to the element’s class list.
-
dragLeave(e)
:this.classList.remove('over');
: When a dragged element leaves a list item, this function removes theover
class, removing the visual highlight.classList.remove()
is a DOM method that removes one or more classes from the element’s class list.
-
swapItems(fromIndex, toIndex)
:const itemOne = listItems[fromIndex].querySelector('.draggable');
: Selects thedraggable
element within the list item at thefromIndex
(the item being dragged).querySelector()
is a DOM method that returns the first element within the element that matches a specified CSS selector.const itemTwo = listItems[toIndex].querySelector('.draggable');
: Selects thedraggable
element within the list item at thetoIndex
(the drop target).listItems[fromIndex].appendChild(itemTwo);
: AppendsitemTwo
(the draggable element of the target list item) tolistItems[fromIndex]
(the original list item’s container). This effectively movesitemTwo
to the position ofitemOne
.listItems[toIndex].appendChild(itemOne);
: AppendsitemOne
(the draggable element of the dragged list item) tolistItems[toIndex]
(the target list item’s container). This completes the swap, movingitemOne
to the position ofitemTwo
.
7. JavaScript: Checking the Order
Finally, we implement the checkOrder
function, which is triggered when the “Check Order” button is clicked. This function compares the current order of the list items with the original correct order (richestPeople
array) and visually indicates correct and incorrect positions.
checkBtn.addEventListener('click', checkOrder);
function checkOrder() {
listItems.forEach((listItem, index) => {
const personName = listItem.querySelector('.draggable').innerText.trim();
const correctName = richestPeople[index];
if (personName !== correctName) {
listItem.classList.add('wrong');
listItem.classList.remove('right');
} else {
listItem.classList.remove('wrong');
listItem.classList.add('right');
}
});
}
Explanation of checkOrder
Function:
checkBtn.addEventListener('click', checkOrder);
: Attaches aclick
event listener to thecheckBtn
(the “Check Order” button). When the button is clicked, thecheckOrder
function is executed.listItems.forEach((listItem, index) => { ... });
: Iterates over thelistItems
array (which reflects the current order of list items in the DOM).const personName = listItem.querySelector('.draggable').innerText.trim();
: For each list item, it gets the text content of the.draggable
element (which contains the person’s name) usinginnerText
and removes any leading/trailing whitespace usingtrim()
. >innerText
is a property that gets or sets the text content of an element and its descendants. >trim()
is a string method that removes whitespace from both ends of a string.const correctName = richestPeople[index];
: Gets the correct person’s name from therichestPeople
array at the correspondingindex
.if (personName !== correctName) { ... } else { ... }
: Compares thepersonName
from the current list item with thecorrectName
from therichestPeople
array.if (personName !== correctName)
: If the names do not match (incorrect position):listItem.classList.add('wrong');
: Adds thewrong
class to the list item (to style it red, indicating an incorrect position).listItem.classList.remove('right');
: Removes theright
class if it was previously added.
else { ... }
: If the names match (correct position):listItem.classList.remove('wrong');
: Removes thewrong
class if it was previously added.listItem.classList.add('right');
: Adds theright
class to the list item (to style it green, indicating a correct position).
8. CSS Styling (style.css
)
To visually enhance the sortable list and provide feedback to the user, we use CSS. Here’s a basic CSS stylesheet (style.css
) to complement the JavaScript functionality:
@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap');
:root {
--border-color: #e3e5e4;
--background-color: #c3c7ca;
--text-color: #34444f;
}
* {
box-sizing: border-box;
}
body {
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
font-family: 'Lato', sans-serif;
margin: 0;
height: 100vh;
}
.draggable-list {
border: 1px solid var(--border-color);
padding: 0;
list-style-type: none;
}
.draggable-list li {
background-color: #fff;
display: flex;
flex: 1;
}
.draggable-list li:not(:last-of-type) {
border-bottom: 1px solid var(--border-color);
}
.draggable-list .number {
background-color: var(--background-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
height: 60px;
width: 60px;
}
.draggable {
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
flex: 1;
}
.draggable p {
margin: 0;
}
.draggable i {
margin-left: 10px;
cursor: grab;
}
.draggable.over {
background-color: #eaeaea;
}
.draggable-list li.right .person-name {
color: #3ae374; /* Green */
}
.draggable-list li.wrong .person-name {
color: #ff3838; /* Red */
}
.check-btn {
background-color: var(--background-color);
border: none;
color: var(--text-color);
font-size: 16px;
padding: 10px 20px;
cursor: pointer;
}
.check-btn:active {
transform: scale(0.98);
}
.check-btn:focus {
outline: none;
}
CSS Highlights:
- CSS Variables (
:root
): Custom CSS properties (variables) are defined in the:root
pseudo-class for reusable colors.CSS Variables (Custom Properties) are entities defined by CSS authors that contain specific values to be reused throughout a stylesheet. They are defined using
--
followed by the variable name. - Basic Styling: Sets up basic body styling, centers content, and applies the Lato font from Google Fonts.
draggable-list
Styles: Styles theul
container with a border and removes default list styling.draggable-list li
Styles: Styles the list items for layout and borders..number
Styles: Styles the number span to appear as a square box on the left..draggable
Styles: Styles the draggable area, sets the cursor to pointer, and usesspace-between
for content distribution..draggable i
Styles: Styles the drag handle icon..draggable.over
Styles: Provides visual feedback by changing the background color when a draggable element is dragged over a list item..draggable-list li.right .person-name
and.draggable-list li.wrong .person-name
Styles: Styles the person’s name to green for correct positions and red for incorrect positions, based on theright
andwrong
classes added by JavaScript..check-btn
Styles: Styles the “Check Order” button.
9. Conclusion
This chapter has provided a comprehensive guide to building a sortable list with drag and drop functionality using vanilla JavaScript. By following these steps, you can create interactive lists that allow users to reorder items intuitively. This project demonstrates the power of JavaScript for DOM manipulation, event handling, and leveraging browser APIs like the Drag and Drop API.
Key Takeaways:
- Vanilla JavaScript Implementation: You learned how to implement drag and drop without relying on external libraries, using only core JavaScript features.
- Drag and Drop API: You gained practical experience using the Drag and Drop API and its key events (
dragstart
,dragover
,drop
,dragenter
,dragleave
). - DOM Manipulation: You practiced dynamic DOM manipulation techniques, including creating elements, setting attributes, and modifying element content.
- Array Methods: You utilized various JavaScript array methods (
map
,sort
,forEach
, spread operator) for efficient data processing and manipulation. - Event Handling: You learned how to attach and handle events to make web pages interactive.
- CSS Styling for Interactivity: You applied CSS to enhance the visual appearance and provide user feedback during drag and drop operations.
This project serves as a solid foundation for building more complex interactive web applications. You can adapt and extend this code to create sortable lists for various purposes, enhancing user experience and application functionality. Remember to experiment with different data, styling, and functionalities to deepen your understanding and explore the full potential of vanilla JavaScript and the Drag and Drop API.