Mastering the Drag and Drop API

Enhance Your Web Applications using Web File System API

Amresh Kumar
11 min readJun 9, 2024

In this article, we’ll explore the Drag and Drop API. You’ll learn how to drag files and folders and understand the advantages and disadvantages of using this APIs. We’ll also provide coding examples to illustrate how to implement these features in your web applications.

What is the Drag and Drop API?

The Drag and Drop API allows web developers to implement drag-and-drop functionality in their applications. This API provides an intuitive way to handle user interactions for dragging elements, such as files or folders, and dropping them into designated areas.

Key Features of the Drag and Drop API

  • User-friendly interactions: Simplifies the process of moving items within a web application.
  • Customizable events: Allows developers to create custom behaviors for drag-and-drop actions.
  • File and folder handling: Supports dragging and dropping files and folders directly into web applications.

How to Drag a File

To enable file dragging, you’ll need to handle the drag events (dragenter, dragover, dragleave, and drop) on a target element. Here's a step-by-step guide:

HTML

<div id="drop-zone">
Drop files here
</div>
<div id="file-info"></div>

CSS

#drop-zone {
width: 300px;
height: 200px;
border: 2px dashed #ccc;
text-align: center;
line-height: 200px;
color: #ccc;
}
#drop-zone.dragover {
border-color: #000;
color: #000;
}

JavaScript

document.addEventListener('DOMContentLoaded', () => {
const dropZone = document.getElementById('drop-zone');
const fileInfo = document.getElementById('file-info');

dropZone.addEventListener('dragenter', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add('dragover');
});

dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add('dragover');
});

dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('dragover');
});

dropZone.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('dragover');
const files = e.dataTransfer.files;
handleFiles(files);
});

const handleFiles = (files) => {
for (let i = 0; i < files.length; i++) {
const file = files[i];
const listItem = document.createElement('div');
listItem.textContent = `File: ${file.name}, Size: ${file.size} bytes`;
fileInfo.appendChild(listItem);
}
};
});

Explanation of the Code

This JavaScript code snippet sets up drag-and-drop functionality for a web page. When users drag files over a designated area (the “drop zone”) and drop them, the files’ names and sizes are displayed. Here’s a detailed explanation of how it works:

Step-by-Step Breakdown

document.addEventListener('DOMContentLoaded', () => {

})

This event listener waits until the HTML document has been completely loaded and parsed before executing the enclosed function. This ensures that the DOM elements we want to manipulate are available.

Get Drop Zone and File Info Elements:

const dropZone = document.getElementById('drop-zone');
const fileInfo = document.getElementById('file-info');

These lines retrieve the HTML elements with the IDs drop-zone and file-info and store them in variables. dropZone is the area where users will drop files, and fileInfo is the area where information about the dropped files will be displayed.

Drag Enter Event:

dropZone.addEventListener('dragenter', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add('dragover');
});

This event fires when a dragged item enters the drop zone. The default behavior and event propagation are prevented to ensure the drag-and-drop functionality works correctly. The dragover class is added to the drop zone to provide visual feedback to the user that the drop zone is active.

Drag Over Event:

dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add('dragover');
});

This event fires continuously while the dragged item is over the drop zone. It keeps the dragover class active, ensuring visual feedback is maintained.

Drag Leave Event:

dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('dragover');
});

This event fires when the dragged item leaves the drop zone. The dragover class is removed to revert the drop zone to its normal state, indicating the dragged item is no longer over it.

Drop Event:

dropZone.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('dragover');
const files = e.dataTransfer.files;
handleFiles(files);
});

This event fires when the dragged item is dropped into the drop zone. The default behavior and event propagation are again prevented. The dragover class is removed, and the dropped files are retrieved from the dataTransfer object. The handleFiles function is then called with the dropped files as its argument.

Handle Files Function:

const handleFiles = (files) => {
for (let i = 0; i < files.length; i++) {
const file = files[i];
const listItem = document.createElement('div');
listItem.textContent = `File: ${file.name}, Size: ${file.size} bytes`;
fileInfo.appendChild(listItem);
}
};

This function iterates over the dropped files. For each file, it creates a new div element, sets its text content to display the file's name and size, and appends this div to the fileInfo element.

Understanding e.dataTransfer

The e.dataTransfer object is a property of the DragEvent interface. It holds data about the item being dragged and allows data to be transferred between the drag source and drop target.

Key Properties of dataTransfer:

  • dataTransfer.files: A FileList object containing the files being dragged. This is used in the drop event to access the dropped files.
  • dataTransfer.items: A DataTransferItemList object containing the drag items. Each item can be a file or other type of data.
  • dataTransfer.types: An array of data formats that were set during the drag operation.
  • dataTransfer.effectAllowed: Specifies the allowed type of drag-and-drop operations (e.g., copy, move, link).
  • dataTransfer.dropEffect: Specifies the desired action (e.g., copy, move, link) when the drop occurs. It can be modified within drag events to control the operation's behavior.

Events and dataTransfer Usage:

  • dragenter: Triggers when a dragged item enters a valid drop target. Used to provide visual feedback.
  • dragover: Triggers continuously as the item is dragged over a valid drop target. Used to indicate that the target can accept the item.
  • dragleave: Triggers when a dragged item leaves the target. Used to revert visual feedback.
  • drop: Triggers when the item is dropped onto the target. This is where the dataTransfer object is most crucial as it provides access to the dropped data.

By leveraging the dataTransfer object and its associated events, you can create rich drag-and-drop interactions in your web applications, enhancing the user experience significantly.

How to Drag a Folder

Handling folder dragging involves more complex operations since the browser needs to differentiate between files and directories. This can be done using the webkitdirectory attribute on the file input element, but direct support in drag-and-drop operations varies.

HTML

<div id="folder-drop-zone">
Drop folders here
</div>
<div id="folder-info"></div>

JavaScript

document.addEventListener('DOMContentLoaded', () => {
const folderDropZone = document.getElementById('folder-drop-zone');
const folderInfo = document.getElementById('folder-info');

folderDropZone.addEventListener('dragenter', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.add('dragover');
});

folderDropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.add('dragover');
});

folderDropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.remove('dragover');
});

folderDropZone.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.remove('dragover');

const items = e.dataTransfer.items;
handleItems(items);
});

const handleItems = (items) => {
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry();
if (item.isDirectory) {
processDirectory(item);
} else {
processFile(item);
}
}
};

const processDirectory = (directory) => {
const dirReader = directory.createReader();
dirReader.readEntries((entries) => {
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.isDirectory) {
processDirectory(entry);
} else {
processFile(entry);
}
}
});
};

const processFile = (fileEntry) => {
fileEntry.file((file) => {
const listItem = document.createElement('div');
listItem.textContent = `File: ${file.name}, Size: ${file.size} bytes`;
folderInfo.appendChild(listItem);
});
};
});

Explanation of the Code

This JavaScript code enables drag-and-drop functionality specifically for folders. When users drag a folder over a designated area (the “folder drop zone”) and drop it, the contents of the folder, including subfolders and files, are recursively processed and displayed. Here’s a detailed breakdown of the code:

DOMContentLoaded Event:

document.addEventListener('DOMContentLoaded', () => {

})

This event listener waits for the entire HTML document to be loaded and parsed before executing the enclosed function. This ensures that the DOM elements we want to manipulate are available.

Get Folder Drop Zone and Folder Info Elements:

const folderDropZone = document.getElementById('folder-drop-zone'); 
const folderInfo = document.getElementById('folder-info');

These lines retrieve the HTML elements with the IDs folder-drop-zone and folder-info and store them in variables. folderDropZone is the area where users will drop folders, and folderInfo is the area where information about the dropped folder contents will be displayed.

Drag Enter Event:

folderDropZone.addEventListener('dragenter', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.add('dragover');
});

This event fires when a dragged item enters the drop zone. The default behavior and event propagation are prevented to ensure the drag-and-drop functionality works correctly. The dragover class is added to the drop zone to provide visual feedback that the drop zone is active.

Drag Over Event:

folderDropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.add('dragover');
});

This event fires continuously while the dragged item is over the drop zone. It keeps the dragover class active, ensuring visual feedback is maintained.

Drag Leave Event:

folderDropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.remove('dragover');
});

This event fires when the dragged item leaves the drop zone. The dragover class is removed to revert the drop zone to its normal state, indicating that the dragged item is no longer over it.

Drop Event:

folderDropZone.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
folderDropZone.classList.remove('dragover');
const items = e.dataTransfer.items;
handleItems(items);
});

This event fires when the item is dropped into the drop zone. The default behavior and event propagation are again prevented. The dragover class is removed, and the dropped items are retrieved from the dataTransfer object. The handleItems function is then called with the dropped items as its argument.

Handle Items Function:

const handleItems = (items) => {
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry();
if (item.isDirectory) {
processDirectory(item);
} else {
processFile(item);
}
}
};

This function iterates over the dropped items. For each item, it calls webkitGetAsEntry() to get a FileSystemEntry object. If the item is a directory, it calls processDirectory(). If the item is a file, it calls processFile().

Process Directory Function:

const processDirectory = (directory) => {
const dirReader = directory.createReader();
dirReader.readEntries((entries) => {
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.isDirectory) {
processDirectory(entry);
} else {
processFile(entry);
}
}
});
};

This function reads the contents of a directory. It creates a directory reader and reads all entries in the directory. For each entry, it recursively calls processDirectory() if the entry is a directory, or processFile() if the entry is a file.

Process File Function:

const processFile = (fileEntry) => {
fileEntry.file((file) => {
const listItem = document.createElement('div');
listItem.textContent = `File: ${file.name}, Size: ${file.size} bytes`;
folderInfo.appendChild(listItem);
});
};

This function processes a file entry. It gets the file object and creates a div element to display the file's name and size, then appends this div to the folderInfo element.

Understanding webkitGetAsEntry

The webkitGetAsEntry() method is part of the DataTransferItem interface. It returns a FileSystemEntry object representing the dropped item. This method is non-standard and primarily supported in WebKit browsers (like Chrome).

Key points about webkitGetAsEntry

  • Returns FileSystemEntry: This object represents either a file (FileEntry) or a directory (DirectoryEntry).
  • Non-Standard: This method is not part of any official web standard and may not be supported in all browsers.
  • Used for Directory Access: It provides the ability to access the contents of directories, enabling recursive processing of folder structures.

Alternatives

As of now, there are no direct standard alternatives to webkitGetAsEntry() for accessing directory structures via drag-and-drop in all browsers. However, the File System Access API provides standardized methods to interact with the file system, though it is still in the experimental stage and may not be fully supported across all browsers.

Understanding directory.createReader()

The createReader method is part of the File and Directory Entries API, specifically associated with the DirectoryEntry interface. This method is used to create a DirectoryReader object, which provides a way to asynchronously read the contents of a directory.

When you want to access the contents of a directory using the File and Directory Entries API, you first obtain a DirectoryEntry object representing that directory. Once you have the DirectoryEntry, you can call the createReader method on it to create a DirectoryReader object.

The DirectoryReader object allows you to read the entries (files and subdirectories) within the directory asynchronously. You can use the readEntries method of the DirectoryReader object to retrieve a batch of entries at a time. This method returns a promise that resolves with an array of FileSystemEntry objects representing the contents of the directory.

In summary, createReader is a method used to create a DirectoryReader object, which provides an interface for asynchronously reading the contents of a directory using the File and Directory Entries API.

Understanding FileSystemEntry

The FileSystemEntry interface is part of the File and Directory Entries API. It represents an entry in a file system, which can be either a file or a directory. This API allows web applications to interact with the file system in a more comprehensive manner, enabling operations like reading directory contents and handling files.

Key Properties and Methods

  • isFile: A boolean property that returns true if the entry is a file.
  • isDirectory: A boolean property that returns true if the entry is a directory.
  • name: A string representing the name of the file or directory.
  • fullPath: A string representing the full path of the file or directory within the file system.
  • filesystem: The file system in which the entry resides.

Methods

  • getMetadata(successCallback, errorCallback): Retrieves metadata about the entry, such as modification date and size.
  • moveTo(parent, newName, successCallback, errorCallback): Moves the entry to a different location.
  • copyTo(parent, newName, successCallback, errorCallback): Copies the entry to a different location.
  • remove(successCallback, errorCallback): Deletes the entry.
  • getParent(successCallback, errorCallback): Gets the parent directory of the entry.

FileEntry and DirectoryEntry

FileSystemEntry serves as the base interface for two other interfaces:

FileEntry: Represents a file in the file system.

  • file(successCallback, errorCallback): Returns a File object that can be used to read the file's contents.

DirectoryEntry: Represents a directory in the file system.

  • createReader(): Returns a DirectoryReader object to read the contents of the directory.
  • getFile(name, options, successCallback, errorCallback): Retrieves a file from the directory.
  • getDirectory(name, options, successCallback, errorCallback): Retrieves a subdirectory from the directory.

Example Usage

Here’s an example to illustrate how FileSystemEntry and its derived interfaces can be used:

document.getElementById('fileInput').addEventListener('change', function(event) {
const items = event.target.files;
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry();
if (item.isDirectory) {
processDirectory(item);
} else {
processFile(item);
}
}
});

function processDirectory(directory) {
const dirReader = directory.createReader();
dirReader.readEntries((entries) => {
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.isDirectory) {
processDirectory(entry);
} else {
processFile(entry);
}
}
});
}

function processFile(fileEntry) {
fileEntry.file((file) => {
console.log(`File: ${file.name}, Size: ${file.size} bytes`);
});
}

Advantages

Advantages

  • Improved User Experience: Intuitive drag-and-drop interactions enhance user experience.
  • Efficient File Handling: Allows for direct interaction with files and folders, making file management more efficient.
  • Flexible and Customizable: Provides extensive customization options for handling drag-and-drop operations.

Disadvantages

  • Browser Compatibility: Not all browsers support the full functionality of these APIs.
  • Security Concerns: Requires careful handling of file system access to avoid security vulnerabilities.
  • Complexity: Implementing full drag-and-drop support with directory handling can be complex and requires thorough testing.

Conclusion

The Drag and Drop API offers powerful tools for enhancing user interactions with web applications. By understanding and implementing these APIs, you can provide a more intuitive and efficient user experience. However, it’s essential to be aware of their limitations and ensure robust security practices.

Refrences

Congratulations on completing this article, which is part of my series, Top Web APIs Every Frontend Developer Should Know. If you’re eager to discover more about other essential APIs, make sure to check out the rest of the series. Each article dives deep into a different set of APIs, providing valuable insights and practical examples to help you level up your frontend development skills.

If you found this article helpful, don’t forget to clap for it! 👏 It helps me know what content you enjoy and want to see more of.

Also, feel free to connect with me on LinkedIn to stay updated with my latest articles and insights into software development. Let’s grow and learn together!

--

--

Amresh Kumar

Software Engineer with 3 years of experience in building full-stack application.