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].jswith 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-listand IDdraggable-list. Thisulelement 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-btnand IDcheck. This button will be used to trigger the order checking functionality.style.cssandscript.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:
draggableListandcheckBtn: We usedocument.getElementByIdto select theulelement (draggable-list) and the button element (check) from the HTML. These variables will allow us to manipulate these elements in our JavaScript code.richestPeopleArray: 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.listItemsArray: This array is initialized as empty. It will store the dynamically created list items as they are generated and potentially reordered.dragStartIndexVariable: This variable will be used to keep track of the index of the list item that is being dragged. It is initialized withletbecause its value will change during the drag and drop process.createList()Function Call: This line calls thecreateListfunction, 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 therichestPeoplearray, preventing modification of the original ordered array.- .
map(a => ({ value: a, sort: Math.random() })): Themapmethod 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): Thesortmethod shuffles the array of objects based on thesortproperty (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): Anothermapmethod 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) => { ... }): TheforEachmethod 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-indexattribute is added to eachlielement. This attribute stores the original index of the item in therichestPeoplearray (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):** TheinnerHTMLproperty is used to set the HTML content within eachli` element.innerHTMLis 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 eachliincludes:<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
listItemsArray:listItems.push(listItem);: The newly createdlistItemelement is added to thelistItemsarray. This array now holds references to all thelielements in the order they are currently displayed (scrambled initially).draggableList.appendChild(listItem);: ThelistItemelement is appended as a child to thedraggableList(ulelement) 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, thecreateListfunction calls theaddEventListenersfunction (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(thedivelements within each list item).querySelectorAllreturns 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 alllielements that are descendants of the element with the classdraggable-list(ourulelement).- Event Listeners for Draggable Elements (
draggables):draggables.forEach(draggable => { ... });: Iterates over eachdraggableelement.draggable.addEventListener('dragstart', dragStart);: Attaches adragstartevent listener to eachdraggableelement. When a drag operation begins on adraggableelement, thedragStartfunction will be executed.addEventListener()is a DOM method that attaches an event handler to the specified element. Thedragstartevent 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 (lielement).item.addEventListener('dragover', dragOver);: Attaches adragoverevent listener. This event is fired continuously as the mouse pointer is over an element during a drag operation. We will usee.preventDefault()in thedragOverfunction to allow dropping on the list items.The
dragoverevent 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 adropevent listener. This event is fired when a dragged element or text selection is dropped on a valid drop target.The
dropevent is fired when the user releases the mouse button or touch screen at a drop target.item.addEventListener('dragenter', dragEnter);: Attaches adragenterevent 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
dragenterevent is fired when a dragged element or text selection enters a valid drop target.item.addEventListener('dragleave', dragLeave);: Attaches adragleaveevent 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
dragleaveevent 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,thisrefers to thedraggableelement that triggered thedragstartevent..closest('li'): Theclosest('li')method traverses up the DOM tree from thedraggableelement until it finds the closest ancestorlielement (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-indexattribute from the foundlielement.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 thedragStartIndexvariable.
-
dragOver(e):e.preventDefault();: This line is crucial. By default, data/elements cannot be dropped onto other elements. Callinge.preventDefault()in thedragoverhandler prevents the default behavior and allows thedropevent 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-indexof the list item where the element was dropped.swapItems(dragStartIndex, dragEndIndex);: Calls theswapItemsfunction (defined below) to reorder the list items in the DOM based on thedragStartIndexanddragEndIndex.this.classList.remove('over');: Removes theoverclass 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 theoverclass 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 theoverclass, 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 thedraggableelement 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 thedraggableelement 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 movesitemTwoto 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, movingitemOneto 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 aclickevent listener to thecheckBtn(the “Check Order” button). When the button is clicked, thecheckOrderfunction is executed.listItems.forEach((listItem, index) => { ... });: Iterates over thelistItemsarray (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.draggableelement (which contains the person’s name) usinginnerTextand removes any leading/trailing whitespace usingtrim(). >innerTextis 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 therichestPeoplearray at the correspondingindex.if (personName !== correctName) { ... } else { ... }: Compares thepersonNamefrom the current list item with thecorrectNamefrom therichestPeoplearray.if (personName !== correctName): If the names do not match (incorrect position):listItem.classList.add('wrong');: Adds thewrongclass to the list item (to style it red, indicating an incorrect position).listItem.classList.remove('right');: Removes therightclass if it was previously added.
else { ... }: If the names match (correct position):listItem.classList.remove('wrong');: Removes thewrongclass if it was previously added.listItem.classList.add('right');: Adds therightclass 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:rootpseudo-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-listStyles: Styles theulcontainer with a border and removes default list styling.draggable-list liStyles: Styles the list items for layout and borders..numberStyles: Styles the number span to appear as a square box on the left..draggableStyles: Styles the draggable area, sets the cursor to pointer, and usesspace-betweenfor content distribution..draggable iStyles: Styles the drag handle icon..draggable.overStyles: Provides visual feedback by changing the background color when a draggable element is dragged over a list item..draggable-list li.right .person-nameand.draggable-list li.wrong .person-nameStyles: Styles the person’s name to green for correct positions and red for incorrect positions, based on therightandwrongclasses added by JavaScript..check-btnStyles: 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.