diff --git a/files/en-us/_redirects.txt b/files/en-us/_redirects.txt index f68f47b4415dd54..537c61db4da0d7e 100644 --- a/files/en-us/_redirects.txt +++ b/files/en-us/_redirects.txt @@ -3374,7 +3374,7 @@ /en-US/docs/DragDrop/DataTransfer /en-US/docs/Web/API/DataTransfer /en-US/docs/DragDrop/Drag_Operations /en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations /en-US/docs/DragDrop/Drag_and_Drop /en-US/docs/Web/API/HTML_Drag_and_Drop_API -/en-US/docs/DragDrop/Recommended_Drag_Types /en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types +/en-US/docs/DragDrop/Recommended_Drag_Types /en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store /en-US/docs/Drawing_Graphics_with_Canvas /en-US/docs/Web/API/Canvas_API/Tutorial /en-US/docs/Drawing_text_using_a_canvas /en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text /en-US/docs/ECMA-262 /en-US/docs/Web/JavaScript/Reference/JavaScript_technologies_overview @@ -9119,6 +9119,7 @@ /en-US/docs/Web/API/HTMLVideoElement/onleavepictureinpicture /en-US/docs/Web/API/HTMLVideoElement/leavepictureinpicture_event /en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture() /en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture /en-US/docs/Web/API/HTML_DOM /en-US/docs/Web/API/HTML_DOM_API +/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types /en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store /en-US/docs/Web/API/HashChangeEvent/HashChangeEvent.oldURL /en-US/docs/Web/API/HashChangeEvent/oldURL /en-US/docs/Web/API/Headers/getAll /en-US/docs/Web/API/Headers/get /en-US/docs/Web/API/History.length /en-US/docs/Web/API/History/length @@ -12991,7 +12992,7 @@ /en-US/docs/Web/Guide/HTML/Inline_event_handler /en-US/docs/Learn_web_development/Core/Scripting/Events#Inline_event_handlers_—_don't_use_these /en-US/docs/Web/Guide/HTML/Introduction /en-US/docs/Learn_web_development/Core/Structuring_content /en-US/docs/Web/Guide/HTML/Obsolete_things_to_avoid /en-US/docs/Learn_web_development/Core/Structuring_content -/en-US/docs/Web/Guide/HTML/Recommended_Drag_Types /en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types +/en-US/docs/Web/Guide/HTML/Recommended_Drag_Types /en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store /en-US/docs/Web/Guide/HTML/The_Importance_of_Correct_HTML_Commenting /en-US/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax#HTML_comments /en-US/docs/Web/Guide/HTML/Tips_for_authoring_fast-loading_HTML_pages /en-US/docs/Web/HTML/How_to/Author_fast-loading_HTML_pages /en-US/docs/Web/Guide/HTML/Using_HTML5_audio_and_video /en-US/docs/Learn_web_development/Core/Structuring_content/HTML_video_and_audio diff --git a/files/en-us/_wikihistory.json b/files/en-us/_wikihistory.json index c7b7601ce2d22cb..125076941932de4 100644 --- a/files/en-us/_wikihistory.json +++ b/files/en-us/_wikihistory.json @@ -41103,6 +41103,24 @@ "Enn" ] }, + "Web/API/HTML_Drag_and_Drop_API/Drag_data_store": { + "modified": "2020-06-02T06:32:13.194Z", + "contributors": [ + "Tigt", + "SphinxKnight", + "chrisdavidmills", + "w3l", + "AFBarstow", + "idmean", + "Neil", + "teoli", + "Sheppy", + "Mgalli", + "DaveG", + "Piro", + "Enn" + ] + }, "Web/API/HTML_Drag_and_Drop_API/Drag_operations": { "modified": "2020-01-25T13:26:01.457Z", "contributors": [ @@ -41154,24 +41172,6 @@ "AFBarstow" ] }, - "Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types": { - "modified": "2020-06-02T06:32:13.194Z", - "contributors": [ - "Tigt", - "SphinxKnight", - "chrisdavidmills", - "w3l", - "AFBarstow", - "idmean", - "Neil", - "teoli", - "Sheppy", - "Mgalli", - "DaveG", - "Piro", - "Enn" - ] - }, "Web/API/HTML_Sanitizer_API": { "modified": "2020-12-13T12:18:13.088Z", "contributors": ["Rumyra"] diff --git a/files/en-us/web/api/datatransfer/addelement/index.md b/files/en-us/web/api/datatransfer/addelement/index.md index 48a82f48542a0b2..9e6b2a37b22e04d 100644 --- a/files/en-us/web/api/datatransfer/addelement/index.md +++ b/files/en-us/web/api/datatransfer/addelement/index.md @@ -57,4 +57,4 @@ This method is not defined in any Web standard. - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/cleardata/index.md b/files/en-us/web/api/datatransfer/cleardata/index.md index c3f7d1b7beab534..10099bce3c1c022 100644 --- a/files/en-us/web/api/datatransfer/cleardata/index.md +++ b/files/en-us/web/api/datatransfer/cleardata/index.md @@ -179,4 +179,4 @@ function dropHandler(event) { - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/dropeffect/index.md b/files/en-us/web/api/datatransfer/dropeffect/index.md index f6fa8f3aa1e54b1..3dcae69d3f3f3bd 100644 --- a/files/en-us/web/api/datatransfer/dropeffect/index.md +++ b/files/en-us/web/api/datatransfer/dropeffect/index.md @@ -137,4 +137,4 @@ target.addEventListener("dragover", (ev) => { - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/effectallowed/index.md b/files/en-us/web/api/datatransfer/effectallowed/index.md index 55300f901976e6f..9d8ac74bfb4f4b5 100644 --- a/files/en-us/web/api/datatransfer/effectallowed/index.md +++ b/files/en-us/web/api/datatransfer/effectallowed/index.md @@ -154,4 +154,4 @@ reset.addEventListener("click", () => document.location.reload()); - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/files/index.md b/files/en-us/web/api/datatransfer/files/index.md index 9626e84f540f508..707926170e88264 100644 --- a/files/en-us/web/api/datatransfer/files/index.md +++ b/files/en-us/web/api/datatransfer/files/index.md @@ -13,7 +13,7 @@ The **`files`** read-only property of [`DataTransfer`](/en-US/docs/Web/API/DataT This feature can be used to drag files from a user's desktop to the browser. > [!NOTE] -> The `files` property of [`DataTransfer`](/en-US/docs/Web/API/DataTransfer) objects can only be accessed from within the {{domxref("HTMLElement/drop_event", "drop")}} and {{domxref("Element/paste_event", "paste")}} events. For all other events, the `files` property will be empty — because its underlying data store will be in a [protected mode](https://html.spec.whatwg.org/multipage/dnd.html#the-drag-data-store). +> The `files` property of [`DataTransfer`](/en-US/docs/Web/API/DataTransfer) objects can only be accessed from within the {{domxref("HTMLElement/drop_event", "drop")}} and {{domxref("Element/paste_event", "paste")}} events. For all other events, the `files` property will be empty — because its underlying data store will be in a [protected mode](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store#protected_mode). ## Value diff --git a/files/en-us/web/api/datatransfer/getdata/index.md b/files/en-us/web/api/datatransfer/getdata/index.md index d5bdaac8b24e091..68c7aaf69d489a3 100644 --- a/files/en-us/web/api/datatransfer/getdata/index.md +++ b/files/en-us/web/api/datatransfer/getdata/index.md @@ -106,4 +106,4 @@ function drop(dropEvent) { - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/index.md b/files/en-us/web/api/datatransfer/index.md index a45ba777fbb9572..d8e04e3289ade1f 100644 --- a/files/en-us/web/api/datatransfer/index.md +++ b/files/en-us/web/api/datatransfer/index.md @@ -179,4 +179,4 @@ form.addEventListener("reset", () => { - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/items/index.md b/files/en-us/web/api/datatransfer/items/index.md index 1cbd33148d0affc..39de8cbb37c5468 100644 --- a/files/en-us/web/api/datatransfer/items/index.md +++ b/files/en-us/web/api/datatransfer/items/index.md @@ -107,4 +107,4 @@ reset.addEventListener("click", () => document.location.reload()); - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/setdata/index.md b/files/en-us/web/api/datatransfer/setdata/index.md index f31117c31a4382a..2acfd89ae3767a3 100644 --- a/files/en-us/web/api/datatransfer/setdata/index.md +++ b/files/en-us/web/api/datatransfer/setdata/index.md @@ -132,4 +132,4 @@ reset.addEventListener("click", () => document.location.reload()); - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/setdragimage/index.md b/files/en-us/web/api/datatransfer/setdragimage/index.md index d07349de73c7d54..d72785accf6073f 100644 --- a/files/en-us/web/api/datatransfer/setdragimage/index.md +++ b/files/en-us/web/api/datatransfer/setdragimage/index.md @@ -119,4 +119,4 @@ target.addEventListener("drop", (ev) => { - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransfer/types/index.md b/files/en-us/web/api/datatransfer/types/index.md index 700d18bfa767673..f0a088039baeece 100644 --- a/files/en-us/web/api/datatransfer/types/index.md +++ b/files/en-us/web/api/datatransfer/types/index.md @@ -103,4 +103,4 @@ target.addEventListener("dragover", (ev) => { - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/datatransferitem/kind/index.md b/files/en-us/web/api/datatransferitem/kind/index.md index 8447d862f93d513..c77dee662844e2c 100644 --- a/files/en-us/web/api/datatransferitem/kind/index.md +++ b/files/en-us/web/api/datatransferitem/kind/index.md @@ -58,4 +58,4 @@ function dropHandler(ev) { - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/dragevent/index.md b/files/en-us/web/api/dragevent/index.md index a7aa3bb01ed526d..fbb3ff9ac636c40 100644 --- a/files/en-us/web/api/dragevent/index.md +++ b/files/en-us/web/api/dragevent/index.md @@ -58,4 +58,4 @@ An Example of each property, constructor, event type and global event handlers i - [Drag and drop](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/html_drag_and_drop_api/drag_data_store/index.md b/files/en-us/web/api/html_drag_and_drop_api/drag_data_store/index.md new file mode 100644 index 000000000000000..6eab795ba5df4a8 --- /dev/null +++ b/files/en-us/web/api/html_drag_and_drop_api/drag_data_store/index.md @@ -0,0 +1,299 @@ +--- +title: Working with the drag data store +slug: Web/API/HTML_Drag_and_Drop_API/Drag_data_store +page-type: guide +--- + +{{DefaultAPISidebar("HTML Drag and Drop API")}} + +The {{domxref("DragEvent")}} interface has a {{domxref("DragEvent.dataTransfer","dataTransfer")}} property, which is a {{domxref("DataTransfer")}} object. {{domxref("DataTransfer")}} objects represent the main context of the drag operation, and it stays consistent across the firing of different events. It includes the [drag data](/en-US/docs/Web/API/HTML_Drag_and_Drop_API#drag_data_store), [drag image](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#setting_the_drag_feedback_image), [drop effect](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#drop_effects), etc. This article focuses on the _data store_ part of the `dataTransfer`. + +## Structure of the drag data store + +Fundamentally, the drag data store is a list of items, represented as a {{domxref("DataTransferItemList")}} of {{domxref("DataTransferItem")}} objects. Each item can be one of two [kinds](/en-US/docs/Web/API/DataTransferItem/kind): + +- `string`: its payload is a string, retrievable with {{domxref("DataTransferItem.getAsString", "getAsString()")}}. +- `file`: its payload is a file object, retrievable with {{domxref("DataTransferItem.getAsFile", "getAsFile()")}} (or {{domxref("DataTransferItem.getAsFileSystemHandle", "getAsFileSystemHandle()")}} or {{domxref("DataTransferItem.webkitGetAsEntry", "webkitGetAsEntry()")}}, if more complex file system operations are needed). + +Furthermore the item is also identified by a [type](/en-US/docs/Web/API/DataTransferItem/type), which by convention is in the form of a [MIME type](/en-US/docs/Web/HTTP/Guides/MIME_types). This type can instruct the consumer about how the payload should be parsed or decoded. For all text items, the list can only have one item of each type, so the list, in effect, contains two disjoint collections: a list of files with potentially duplicate types, and a {{jsxref("Map")}} of text items keyed by their type. Generally, the files list represents multiple files being dragged. The text map _does not_ represent multiple resources being transferred, but the same resource encoded in different ways, so that the receiving end can choose the most appropriate supported interpretation. The text items are intended to be sorted in descending order of preference. + +This list is accessible via the {{domxref("DataTransfer.items")}} property. + +The HTML Drag and Drop API went through multiple iterations, resulting in two coexisting ways to manage the data store. Before the `DataTransferItemList` and `DataTransferItem` interfaces, the "old way" used the following properties on `DataTransfer`: + +- {{domxref("DataTransfer.types", "types")}}: contains the `type` properties of the _text items_ in the list, plus the value `"files"` if there are any _file items_. +- {{domxref("DataTransfer.setData", "setData()")}}, {{domxref("DataTransfer.getData", "getData()")}}, {{domxref("DataTransfer.clearData", "clearData()")}}: provide access to the _text items_ in the list using the "type-to-payload mapping" model. +- {{domxref("DataTransfer.files", "files")}}: provides access to the _file items_ in the list as a {{domxref("FileList")}}. + +You may see that the types of the _file items_ are not directly exposed. They are still accessible, but only via the {{domxref("Blob.type", "type")}} property of each {{domxref("File")}} object in the `files` list, so if you can't read the files, then you can't know their types either (see [reading the drag data store](#reading_the_drag_data_store) for when the store is readable). + +To get the files and their types, we recommend using the `items` property because it provides a more flexible and consistent interface. For text items, you should also prefer using the `items` property for consistency, although the `getData()` method is more convenient for accessing or removing a specific type. + +Another key difference between the {{domxref("DataTransfer")}} and {{domxref("DataTransferItem")}} interfaces is that the former uses the synchronous {{domxref("DataTransfer.getData","getData()")}} method to access the text payload, but the latter instead uses the asynchronous {{domxref("DataTransferItem.getAsString","getAsString()")}} method. + +## Modifying the drag data store + +For the default-draggable items such as images, links, and selections, the drag data is already defined by the browser; for custom draggable elements defined using the `draggable` attribute, you must define the drag data yourself. The only time to make any modifications to the data store is within the {{domxref("HTMLElement/dragstart_event", "dragstart")}} handler—for the `dataTransfer` of any other drag event, the data store is unmodifiable. + +To add text data to the drag data store, the "new way" uses the {{domxref("DataTransferItemList.add()")}} method, while the "old way" uses the {{domxref("DataTransfer.setData()")}} method. + +```js +function dragstartHandler(ev) { + // New way: add(data, type) + ev.dataTransfer.items.add(ev.target.innerText, "text/plain"); + // Old way: setData(type, data) + ev.dataTransfer.setData("text/html", ev.target.outerHTML); +} + +const p1 = document.getElementById("p1"); +p1.addEventListener("dragstart", dragstartHandler); +``` + +For both methods, if they are called when the data store is unmodifiable, nothing happens. If a text item with the same type already exists, `add()` throws an error while `setData()` overwrites the existing item. + +To add file data to the drag data store, the "new way" still uses the {{domxref("DataTransferItemList.add()")}} method. Because the "old way" stores file items in the {{domxref("DataTransfer.files")}} property, which is a read-only {{domxref("FileList")}}, there's no direct equivalent. + +```js +function dragstartHandler(ev) { + // New way: add(data) + ev.dataTransfer.items.add(new File([blob], "image.png")); +} + +const p1 = document.getElementById("p1"); +p1.addEventListener("dragstart", dragstartHandler); +``` + +Note that when adding file data, `add()` ignores the `type` parameter and uses the {{domxref("Blob.type", "type")}} property of the `File` object. + +> [!NOTE] +> Read/write protection is done on a [per-job](/en-US/docs/Web/JavaScript/Reference/Execution_model#job_queue_and_event_loop) basis, which means only the _synchronous code_ within the `dragstart` handler can modify the data store. If you try to access the data store after an asynchronous operation, you will no longer have write permissions. For example, this does not work: +> +> ```js example-bad +> function dragstartHandler(ev) { +> canvas.toBlob((blob) => { +> ev.dataTransfer.items.add(new File([blob], "image.png")); +> }); +> } +> ``` + +Removing data is similar, using the {{domxref("DataTransferItemList.remove()")}}, {{domxref("DataTransferItemList.clear()")}}, or {{domxref("DataTransfer.clearData()")}} methods. + +## Reading the drag data store + +The only time you can _read_ from the data store, apart from the `dragstart` event when you have full access to the data store, is during the {{domxref("HTMLElement/drop_event", "drop")}} event, allowing the drop target to retrieve the data. + +To read text data from the drag data store, the "new way" uses the {{domxref("DataTransferItemList")}} object, while the "old way" uses the {{domxref("DataTransfer.getData()")}} method. The new way is more convenient for looping through all items, while the old way is more convenient for accessing a specific type. + +```js +function dropHandler(ev) { + // New way: loop through items + for (const item of ev.dataTransfer.items) { + if (item.kind === "string") { + item.getAsString((data) => { + // Do something with data + }); + } + } + // Old way: getData(type) + const data = ev.dataTransfer.getData("text/plain"); +} + +const p1 = document.getElementById("p1"); +p1.addEventListener("drop", dropHandler); +``` + +To read file data from the drag data store, the "new way" still uses the {{domxref("DataTransferItemList")}} object, while the "old way" uses the {{domxref("DataTransfer.files")}} property. + +```js +function dropHandler(ev) { + // New way: loop through items + for (const item of ev.dataTransfer.items) { + if (item.kind === "file") { + const file = item.getAsFile(); // A File object + } + } + // Old way: loop through files + for (const file of ev.dataTransfer.files) { + // Do something with file + } +} + +const p1 = document.getElementById("p1"); +p1.addEventListener("drop", dropHandler); +``` + +### Protected mode + +Outside of `dragstart` and `drop` events, the data store is in _protected mode_, disallowing code from accessing any payload. Namely: + +- All [modification](#modifying_the_drag_data_store) attempts silently do nothing or throw an `DOMException` (for `items.add()` and `items.remove()` only). +- `DataTransfer.getData()` always returns the empty string. +- `DataTransfer.files` always returns an empty list. +- `DataTransferItem.getAsString()` returns without ever calling the callback. +- `DataTransferItem.getAsFile()` always returns `null`. + +Again, read/write protection is done on a [per-job](/en-US/docs/Web/JavaScript/Reference/Execution_model#job_queue_and_event_loop) basis, which means only the _synchronous code_ within the `drop` handler can read the data store. If you try to access the data store after an asynchronous operation, you will no longer have write permissions. For example, this does not work: + +```js example-bad +function getDataPromise(item) { + return new Promise((resolve) => { + item.getAsString((data) => { + resolve(data); + }); + }); +} + +async function dropHandler(ev) { + for (const item of ev.dataTransfer.items) { + if (item.kind === "string") { + // Bad: by the second time this runs, we are no longer in the same job + const data = await getDataPromise(item); + } + } +} + +const p1 = document.getElementById("p1"); +p1.addEventListener("drop", dropHandler); +``` + +Instead, you must call all the access methods synchronously upfront, and wait for their results later: + +```js example-good +async function dropHandler(ev) { + const promises = []; + for (const item of ev.dataTransfer.items) { + if (item.kind === "string") { + // Bad: by the second time this runs, we are no longer in the same job + promises.push(getDataPromise(item)); + } + } + const results = await Promise.all(promises); +} +``` + +## Common drag data types + +The spec only defines the behavior for a few data types, but browsers sometimes have native support for more types. In general, types are intended as a _protocol_ just like MIME types, and you can use any type as long as the receiving end (another webpage, another part of the same webpage, or even somewhere outside the browser) understands it. This section describes some common conventions and browsers' default behaviors. + +Note that the scenarios below refer to the _intention_ and not the _behavior_. For example, when we say "dragging a link", the user may not be dragging an actual `` element; they may be dragging a container that contains one or more links, but the intention is to transfer the link(s) as data, so the data store you prepare can be the same as if the user were dragging an actual link. + +### Dragging text + +For dragging text, use the `text/plain` type, with the dragged string as the value. For example: + +```js +event.dataTransfer.items.add("This is text to drag", "text/plain"); +``` + +You should always add data of the `text/plain` type as a fallback for applications or drop targets that do not support other types, unless there is no logical text alternative. Always add this `text/plain` type last, as it is the least specific and shouldn't be preferred. + +In `getData()`, `setData()`, and `clearData()`, the `Text` type (case-insensitive) is treated as `text/plain`. + +By default, when a selection is dragged, the following data items are created: + +- `text/plain`: containing the selected text. Firefox and Safari sorts this item after `text/html`, although the spec requires it to be first. +- `text/html`: containing the full HTML source of the selected elements (with all styles inlined). + +The spec also requires another item of type `application/microdata+json`, containing the [microdata](/en-US/docs/Web/HTML/Guides/Microdata) extracted from the element(s) in the dragged selection. No browser implements this item. + +When dropping onto an editable text field, such as a {{HTMLElement("textarea")}} or [``](/en-US/docs/Web/HTML/Reference/Elements/input/text), the `text/plain` item gets copied into the field by default (without any event handling). + +### Dragging links + +Dragged hyperlinks should include data of two types: `text/uri-list`, and `text/plain`. _Both_ types should use the link's URL for their data. Note: the URL type is `uri-list` with an _I_, not an _L_. + +As usual, set the `text/plain` type last, as a fallback for the `text/uri-list` type. For example: + +```js +event.dataTransfer.items.add("https://www.mozilla.org", "text/uri-list"); +event.dataTransfer.items.add("https://www.mozilla.org", "text/plain"); +``` + +To drag multiple links, separate each link inside the `text/uri-list` data with a CRLF linebreak. Lines that begin with a number sign (`#`) are comments, and should not be considered URLs. You can use comments to indicate the purpose of a URL, the title associated with a URL, or other data. + +> [!WARNING] +> The `text/plain` fallback for multiple links should include all URLs, but no comments. + +For example, this sample `text/uri-list` data contains two links and a comment: + +```plain +https://www.mozilla.org +#A second link +http://www.example.com +``` + +When retrieving a dropped link, ensure you handle when multiple links are dragged, including any comments. + +In `getData()`, `setData()`, and `clearData()`, the `URL` type (case-insensitive) is treated as `text/uri-list`. For `getData()`, the result only contains the first URL in the list. + +By default, when an {{HTMLElement("a")}} element is dragged, the following data items are created: + +- `text/x-moz-url` (Firefox-only): containing both the `href` attribute and the link text, separated by a line break. +- `text/x-moz-url-data` (Firefox-only): containing just the `href`. +- `text/x-moz-url-desc` (Firefox-only): containing just the link text. +- `text/uri-list`: containing the `href` attribute. +- `text/html` (Chrome and Firefox only): containing the full HTML source of the `` element (with all styles inlined). +- `text/plain`: also containing the `href` attribute. Chrome sorts this item before `text/uri-list`. + +### Dragging images + +Direct image dragging (that is, the data is the pixel content) is not common, and may be unsupported on certain platforms. Instead, images are usually dragged only by their URLs. To do this, use the `text/uri-list` type as with other URLs. The data should be the URL of the image, or a [`data:` URL](/en-US/docs/Web/URI/Reference/Schemes/data) if the image is not stored on a website or disk. + +As with links, the data for the `text/plain` type should also contain the URL. However, a `data:` URL is not usually useful in a text context, so you may wish to exclude the `text/plain` data in this situation. + +```js +event.dataTransfer.items.add(imageURL, "text/uri-list"); +event.dataTransfer.items.add(imageURL, "text/plain"); +``` + +By default, when an {{HTMLElement("img")}} element is dragged, the following data items are created: + +- `text/x-moz-url` (Firefox-only): containing both the `src` attribute and the alt text (or the `src` again if the alt is empty), separated by a line break. +- `text/x-moz-url-data` (Firefox-only): containing just the `src` attribute. +- `text/x-moz-url-desc` (Firefox-only): containing just the alt text (or the `src` if the alt is empty). +- `text/uri-list`: containing the `src` attribute. +- `text/html`: containing the full HTML source of the `` element (with all styles inlined). +- `text/plain` (Firefox-only): containing the `src` attribute. + +Safari also creates a file item containing the image data, with the appropriate MIME type such as `image/png`. + +### Dragging elements + +When the dragged item is an arbitrary element with `draggable="true"`, what data to set depends on what you intend to transfer. + +A common way to transfer the element is to use the `text/html` type containing serialized HTML source code, which the receiving end can then parse and insert. For example, it would be suitable to set its data to the value of the {{domxref("Element/outerHTML","outerHTML")}} property of an element. `text/xml` can be used too, but ensure that the data is well-formed XML. + +You may also include a plain text representation of the HTML or XML data using the `text/plain` type. The data should be just the text without any of the source tags or attributes. For instance: + +```js +event.dataTransfer.items.add("text/html", element.outerHTML); +event.dataTransfer.items.add("text/plain", element.innerText); +``` + +You can also use other types that you invent for custom purposes. Strive to always include a `text/plain` alternative, unless the dragged object is specific to a particular site or application. In this case, the custom type ensures that the data cannot be dropped elsewhere. + +### Dragging files from an operating system file explorer + +When the dragged item is a file, an item of kind `file` is added to the drag data. The `type` is set to the MIME type of the file (as provided by the operating system), or `application/octet-stream` if the type is unknown. Currently, dragged files can only originate outside of the browser, such as from a file explorer. + +Firefox also adds a non-standard text item of type `application/x-moz-file` containing the full path of the file on the user's file system. Unless within privileged code (such as an extension), its value is the empty string. + +### Dragging files to an operating system file explorer + +What can be transferred out of the browser mostly depends on the browser and where it is dragged to. [Dragging images](#dragging_images) to the local file system is commonly supported and results in the image being downloaded. + +Chrome supports the non-standard `DownloadURL` type. The payload should be text in the form `::`. For example: + +```js +event.dataTransfer.items.add( + "DownloadURL", + "image/png:example.png:data:image/png;base64,iVBORw0K...", +); +``` + +This allows an arbitrary file to be downloaded when dragged to the file explorer, or, when dropping into another browser window, as if a [file is being dropped](#dragging_files_from_an_operating_system_file_explorer) (although CORS restrictions may apply). See [Drag out files like Gmail](https://ryanseddon.com/html5/gmail-dragout/) for a practical use case. + +## See also + +- [HTML Drag and Drop API (Overview)](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) +- [Drag Operations](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations) diff --git a/files/en-us/web/api/html_drag_and_drop_api/drag_operations/index.md b/files/en-us/web/api/html_drag_and_drop_api/drag_operations/index.md index fd4f544ead2adf6..260eb2f8e9a3655 100644 --- a/files/en-us/web/api/html_drag_and_drop_api/drag_operations/index.md +++ b/files/en-us/web/api/html_drag_and_drop_api/drag_operations/index.md @@ -6,44 +6,21 @@ page-type: guide {{DefaultAPISidebar("HTML Drag and Drop API")}} -The following describes the steps that occur during a drag and drop operation. +Central to the Drag and Drop API are the various [drag events](/en-US/docs/Web/API/HTML_Drag_and_Drop_API#drag_events) that fire in a specific order and are expected to be handled in a specific way. This document describes the steps that occur during a drag and drop operation, and what the application is supposed to do within each handler. -The drag operations described in this document use the {{domxref("DataTransfer")}} interface. This document does _not_ use the {{domxref("DataTransferItem")}} interface nor the {{domxref("DataTransferItemList")}} interface. +At a high level, here are the possible steps in a drag and drop operation: -## The draggable attribute +- The user [starts the drag](#starting_a_drag) on a source node; the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event is fired on the source node. Within this event, the source node prepares the context for the drag operation, including the drag data, feedback image, and allowed drop effects. +- The user [drags the item around](#dragging_over_elements_and_specifying_drop_targets): every time a new element is entered, the {{domxref("HTMLElement/dragenter_event", "dragenter")}} event is fired on that element, and the {{domxref("HTMLElement/dragleave_event", "dragleave")}} event is fired on the previous element. Every few hundred milliseconds, a {{domxref("HTMLElement/dragover_event", "dragover")}} event is fired on the element the drag is currently inside, and the {{domxref("HTMLElement/drag_event", "drag")}} event is fired on the source node. +- The drag enters a valid drop target: the drop target cancels its `dragover` event to indicate that it is a valid drop target. Some form of [drop feedback](#drop_feedback) indicates the expected drop effect to the user. +- The user [performs the drop](#performing_a_drop): the {{domxref("HTMLElement/drop_event", "drop")}} event is fired on the drop target. Within this event, the target node reads the drag data. +- The [drag operation ends](#finishing_the_drag): the {{domxref("HTMLElement/dragend_event", "dragend")}} event is fired on the source node. This event is fired regardless of whether the drop was successful or not. -In a web page, there are certain cases where a default drag behavior is used. These include text selections, images, and links. When an image or link is dragged, the URL of the image or link is set as the drag data, and a drag begins. For other elements, they must be part of a selection for a default drag to occur. To see this in effect, select an area of a webpage, and then click and hold the mouse and drag the selection. An OS-specific rendering of the selection will appear and follow the mouse pointer as the drag occurs. However, this behavior is only the default drag behavior, if no listeners adjust the data to be dragged. +## Starting a drag -In HTML, apart from the default behavior for images, links, and selections, no other elements are draggable by default. +The drag starts on a [draggable item](/en-US/docs/Web/API/HTML_Drag_and_Drop_API#draggable_items), which can be a selection, a draggable element (including links, images, and any element with `draggable="true"`), a file from the operating system's file explorer, etc. First, the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event is fired on the _source node_, which is the draggable element or, for selections, the text node that the drag started on. If this event is cancelled, then the drag operation is aborted. Otherwise, the {{domxref("Element/pointercancel_event", "pointercancel")}} event is also fired on the source node. -To make other HTML elements draggable, three things must be done: - -1. Set the [`draggable`](/en-US/docs/Web/HTML/Reference/Global_attributes/draggable) attribute to `"true"` on the element that you wish to make draggable. -2. Add a listener for the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event. -3. [Set the drag data](/en-US/docs/Web/API/DataTransfer/setData) in the above listener. - -Here is an example which allows a section of content to be dragged. - -```html -

This text may be dragged.

-``` - -```js -const draggableElement = document.querySelector('p[draggable="true"]'); - -draggableElement.addEventListener("dragstart", (event) => - event.dataTransfer.setData("text/plain", "This text may be dragged"), -); -``` - -The [`draggable`](/en-US/docs/Web/HTML/Reference/Global_attributes/draggable) attribute is set to `"true"`, so this element becomes draggable. If this attribute were omitted or set to `"false"`, the element would not be dragged, and instead the text would be selected. - -The [`draggable`](/en-US/docs/Web/HTML/Reference/Global_attributes/draggable) attribute may be used on any element, including images and links. However, for these last two, the default value is `true`, so you would only use the [`draggable`](/en-US/docs/Web/HTML/Reference/Global_attributes/draggable) attribute with a value of `false` to disable dragging of these elements. - -> [!NOTE] -> When an element is made draggable, text or other elements within it can no longer be selected in the normal way by clicking and dragging with the mouse. Instead, the user must hold down the Alt key to select text with the mouse, or use the keyboard. - -## Starting a drag operation +The `dragstart` event is the only time you can modify the {{domxref("DragEvent.dataTransfer", "dataTransfer")}}. For a custom draggable element, you almost always want to modify the drag data, which is covered in detail in [Modifying the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store#modifying_the_drag_data_store). There are two other things you can change: the [feedback image](#setting_the_drag_feedback_image) and the [allowed drop effects](#drop_effects). In this example, we add a listener for the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event by using the `addEventListener()` method. @@ -53,76 +30,37 @@ In this example, we add a listener for the {{domxref("HTMLElement/dragstart_even ```js const draggableElement = document.querySelector('p[draggable="true"]'); -draggableElement.addEventListener("dragstart", (event) => - event.dataTransfer.setData("text/plain", "This text may be dragged"), -); -``` - -When a user begins to drag, the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event is fired. - -In this example the {{domxref("HTMLElement/dragstart_event", "dragstart")}} listener is added to the draggable element itself. However, you could listen to a higher ancestor as drag events bubble up as most other events do. - -Within the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event, you can specify the **drag data**, the **feedback image**, and the **drag effects**, all of which are described below. However, only the **drag data** is required. (The default image and drag effects are suitable in most situations.) - -## Drag data - -All {{domxref("DragEvent")}} objects have a property called {{domxref("DragEvent.dataTransfer","dataTransfer")}} which holds the drag data (`dataTransfer` is a {{domxref("DataTransfer")}} object). - -When a drag occurs, data must be associated with the drag which identifies _what_ is being dragged. For example, when dragging the selected text within a textbox, the data associated with the _drag data item_ is the text itself. Similarly, when dragging a link on a web page, the drag data item is the link's URL. - -The {{domxref("DataTransfer")}} contains two pieces of information, the **type** (or format) of the data, and the data's **value**. The format is a type string (such as [`text/plain`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_text) for text data), and the value is a string of text. When the drag begins, you add data by providing a type and the data. During the drag, in an event listener for the {{domxref("HTMLElement/dragenter_event", "dragenter")}} and {{domxref("HTMLElement/dragover_event", "dragover")}} events, you use the data types of the data being dragged to check whether a drop is allowed. For instance, a drop target that accepts links would check for the type [`text/uri-list`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_links). During a drop event, a listener would retrieve the data being dragged and insert it at the drop location. - -The {{domxref("DataTransfer")}}'s {{domxref("DataTransfer.types","types")}} property returns a list of MIME-type like strings, such as [`text/plain`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_text) or [`image/jpeg`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_images). You can also create your own types. The most commonly used types are listed in the article [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types). - -A drag may include data items of several different types. This allows data to be provided in more specific types, often custom types, yet still provide fallback data for drop targets that do not support more specific types. It is usually the case that the least specific type will be normal text data using the type [`text/plain`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_text). - -To set a drag data item within the {{domxref("DragEvent.dataTransfer","dataTransfer")}}, use the {{domxref("DataTransfer.setData","setData()")}} method. It takes two arguments: the type of data and the data value. For example: - -```js -event.dataTransfer.setData("text/plain", "Text to drag"); -``` - -In this case, the data value is "Text to drag" and is of the format [`text/plain`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_text). - -You can provide data in multiple formats. To do this, call the {{domxref("DataTransfer.setData","setData()")}} method multiple times with different formats. You should call it with formats in order from most specific to least specific. - -```js -const dt = event.dataTransfer; -dt.setData("application/x.bookmark", bookmarkString); -dt.setData("text/uri-list", "https://www.mozilla.org"); -dt.setData("text/plain", "https://www.mozilla.org"); +draggableElement.addEventListener("dragstart", (event) => { + event.dataTransfer.setData("text/plain", "This text may be dragged"); +}); ``` -Here, data is added in three different types. The first type, `application/x.bookmark`, is a custom type. Other applications won't support this type, but you can use a custom type for drags between areas of the same site or application. - -By providing data in other types as well, we can also support drags to other applications in less specific forms. The `application/x.bookmark` type can provide data with more details for use within the application whereas the other types can include just a single URL or text version. - -Note that both the [`text/uri-list`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_links) and [`text/plain`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_text) contain the same data in this example. This will often be true, but doesn't need to be the case. - -If you attempt to add data twice with the same format, the new data will replace the old data, but in the same position within the list of types as the old data. - -You can clear the data using the {{domxref("DataTransfer.clearData","clearData()")}} method, which takes one argument: the type of the data to remove. +You could also listen to a higher ancestor as drag events bubble up as most other events do. For this reason, it is common to also check the event's target, so that dragging a selection contained within this element does not trigger the `setData` (although selecting text within the element is hard, it is not impossible): ```js -event.dataTransfer.clearData("text/uri-list"); +draggableElement.addEventListener("dragstart", (event) => { + if (event.target === draggableElement) { + event.dataTransfer.setData("text/plain", "This text may be dragged"); + } +}); ``` -The `type` argument to the {{domxref("DataTransfer.clearData","clearData()")}} method is optional. If the `type` is not specified, the data associated with all types is removed. If the drag contains no drag data items, or all of the items have been subsequently cleared, then no drag will occur. +### Setting the drag feedback image -## Setting the drag feedback image - -When a drag occurs, a translucent image is generated from the drag target (the element the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event is fired at), and follows the user's pointer during the drag. This image is created automatically, so you do not need to create it yourself. However, you can use {{domxref("DataTransfer.setDragImage","setDragImage()")}} to specify a custom drag feedback image. +When a drag occurs, a translucent image is generated from the source node, and follows the user's pointer during the drag. This image is created automatically, so you do not need to create it yourself. However, you can use {{domxref("DataTransfer.setDragImage","setDragImage()")}} to specify a custom drag feedback image. ```js -event.dataTransfer.setDragImage(image, xOffset, yOffset); +draggableElement.addEventListener("dragstart", (event) => { + event.dataTransfer.setDragImage(image, xOffset, yOffset); +}); ``` Three arguments are necessary. The first is a reference to an image. This reference will typically be to an `` element, but it can also be to a `` or any other element. The feedback image will be generated from whatever the image looks like on screen, although for images, they will be drawn at their original size. The second and third arguments to the {{domxref("DataTransfer.setDragImage","setDragImage()")}} method are offsets where the image should appear relative to the mouse pointer. -It is also possible to use images and canvases that are not in a document. This technique is useful when drawing custom drag images using the canvas element, as in the following example: +You can also use images and canvases that are not in a document. This technique is useful when drawing custom drag images using the canvas element, as in the following example: ```js -function dragWithCustomImage(event) { +draggableElement.addEventListener("dragstart", (event) => { const canvas = document.createElement("canvas"); canvas.width = canvas.height = 50; @@ -134,196 +72,296 @@ function dragWithCustomImage(event) { ctx.lineTo(50, 0); ctx.stroke(); - const dt = event.dataTransfer; - dt.setData("text/plain", "Data to Drag"); - dt.setDragImage(canvas, 25, 25); -} + event.dataTransfer.setDragImage(canvas, 25, 25); +}); ``` In this example, we make one canvas the drag image. As the canvas is 50×50 pixels, we use offsets of half of this (`25`) so that the image appears centered on the mouse pointer. -## Drag effects +## Dragging over elements and specifying drop targets -When dragging, there are several operations that may be performed. The `copy` operation is used to indicate that the data being dragged will be copied from its present location to the drop location. The `move` operation is used to indicate that the data being dragged will be moved, and the `link` operation is used to indicate that some form of relationship or connection will be created between the source and drop locations. +For the entire course of the drag operation, all device input events (such as mouse or keyboard) are suppressed. The dragged data can be moved over various elements in the document, or even elements in other documents. Whenever a new element is entered, a {{domxref("HTMLElement/dragenter_event", "dragenter")}} event is fired on that element, and a {{domxref("HTMLElement/dragleave_event", "dragleave")}} event is fired on the previous element. -You can specify which of the three operations are allowed for a drag source by setting the {{domxref("DataTransfer.effectAllowed","effectAllowed")}} property within a {{domxref("HTMLElement/dragstart_event", "dragstart")}} event listener. +> [!NOTE] +> `dragleave` always fires _after_ `dragenter`, so conceptually, in between these two events, the target has entered a new element but has not exited the previous one yet. -```js -event.dataTransfer.effectAllowed = "copy"; +Every few hundred milliseconds, two events fire: a {{domxref("HTMLElement/drag_event", "drag")}} event at the source node, and a {{domxref("HTMLElement/dragover_event", "dragover")}} event at the element the drag is currently inside. Most areas of a web page or application are not valid places to drop data, so elements by default ignore any drop that happened on it. The element can elect itself to be a valid drop target by cancelling the `dragover` event. If the element is an editable text field, such as a {{HTMLElement("textarea")}} or [``](/en-US/docs/Web/HTML/Reference/Elements/input/text), and the data store contains one `text/plain` item, then the element is a valid drop target by default without cancelling `dragover`. + +```html +
You can drag and then drop a draggable item here
``` -In this example, only a **copy** is allowed. +```js +const dropElement = document.getElementById("drop-target"); -You can combine the values in various ways: +dropElement.addEventListener("dragover", (event) => { + event.preventDefault(); +}); +``` -- `none` - - : no operation is permitted -- `copy` - - : `copy` only -- `move` - - : `move` only -- `link` - - : `link` only -- `copyMove` - - : `copy` or `move` only -- `copyLink` - - : `copy` or `link` only -- `linkMove` - - : `link` or `move` only -- `all` - - : `copy`, `move`, or `link` -- `uninitialized` - - : the default value when the effect has not been set, equivalent to `all` +> [!NOTE] +> The spec requires the `dragenter` event to be cancelled too for a drop target, otherwise the `dragover` or `dragleave` events won't even start firing on this element; in practice no browser implements this, and the "current element" changes every time a new element is entered. + +> [!NOTE] +> The spec requires that cancelling the `drag` event [aborts](#a_failed_drop) the drag; in practice no browser implements this. See example below: +> +> {{EmbedLiveSample("cancel_drag", "", 100)}} -Note that these values must be used exactly as listed above. For example, setting the {{domxref("DataTransfer.effectAllowed","effectAllowed")}} property to `copyMove` allows a copy or move operation but prevents the user from performing a link operation. If you don't change the {{domxref("DataTransfer.effectAllowed","effectAllowed")}} property, then any operation is allowed, just like with the `all` value. So you don't need to adjust this property unless you want to exclude specific types. +```html hidden live-sample___cancel_drag +

Drag me for 1 second!

+

+``` -During a drag operation, a listener for the {{domxref("HTMLElement/dragenter_event", "dragenter")}} or {{domxref("HTMLElement/dragover_event", "dragover")}} events can check the {{domxref("DataTransfer.effectAllowed","effectAllowed")}} property to see which operations are permitted. A related property, {{domxref("DataTransfer.dropEffect","dropEffect")}}, should be set within one of these events to specify which single operation should be performed. Valid values for {{domxref("DataTransfer.dropEffect","dropEffect")}} are `none`, `copy`, `move`, or `link`. The combination values are not used for this property. +```js hidden live-sample___cancel_drag +const draggableElement = document.getElementById("draggable"); +const output = document.getElementById("output"); +let time = null; +draggableElement.addEventListener("dragstart", (event) => { + time = Date.now(); + output.textContent = ""; +}); +draggableElement.addEventListener("drag", (event) => { + if (time !== null && Date.now() - time > 1000) { + event.preventDefault(); + output.textContent = + "Drag operation cancelled; if you are still dragging the node, then your browser does not support cancelling the drag programmatically."; + time = null; + } +}); +``` -With the {{domxref("HTMLElement/dragenter_event", "dragenter")}} and {{domxref("HTMLElement/dragover_event", "dragover")}} event, the {{domxref("DataTransfer.dropEffect","dropEffect")}} property is initialized to the effect that the user is requesting. The user can modify the desired effect by pressing modifier keys. Although the exact keys used vary by platform, typically the Shift and Control keys would be used to switch between copying, moving, and linking. The mouse pointer will change to indicate which operation is desired. For instance, for a `copy`, the cursor might appear with a plus sign next to it. +### Conditional drop targets -You can modify the {{domxref("DataTransfer.dropEffect","dropEffect")}} property during the {{domxref("HTMLElement/dragenter_event", "dragenter")}} or {{domxref("HTMLElement/dragover_event", "dragover")}} events, if for example, a particular drop target only supports certain operations. You can modify the {{domxref("DataTransfer.dropEffect","dropEffect")}} property to override the user effect, and enforce a specific drop operation to occur. Note that this effect must be one listed within the {{domxref("DataTransfer.effectAllowed","effectAllowed")}} property. Otherwise, it will be set to an alternate value that is allowed. +You usually only want the drop target to accept drops in certain situations (for example, only if a link is being dragged). To do this, check a condition and only cancel the event when the condition is met. For example, you can check if the dragged data contains links: ```js -event.dataTransfer.dropEffect = "copy"; +dropElement.addEventListener("dragover", (event) => { + const isLink = event.dataTransfer.types.includes("text/uri-list"); + if (isLink) { + event.preventDefault(); + } +}); ``` -In this example, copy is the effect that is performed. +In this example, we use the `includes` method to check if the type [`text/uri-list`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store#dragging_links) is present in the list of types. If it is, we will cancel the event so that a drop may be allowed. If the drag data does not contain a link, the event will not be cancelled, and a drop cannot occur at that location. -You can use the value `none` to indicate that no drop is allowed at this location, although it is preferred not to cancel the event in this case. +## Drop feedback -Within the {{domxref("HTMLElement/drop_event", "drop")}} and {{domxref("HTMLElement/dragend_event", "dragend")}} events, you can check the {{domxref("DataTransfer.dropEffect","dropEffect")}} property to determine which effect was ultimately chosen. If the chosen effect were `move`, then the original data should be removed from the source of the drag within the {{domxref("HTMLElement/dragend_event", "dragend")}} event. +Now the user is dragging into a valid drop target. There are several ways in which you can indicate to the user that a drop is allowed at this location, and what might happen if the drop happens. Usually, the mouse pointer will update as necessary depending on the value of the {{domxref("DataTransfer.dropEffect", "dropEffect")}} property. Although the exact appearance depends on the user's platform, typically a plus sign icon will appear for a `copy` for example, and a "cannot drop here" icon will appear when a drop is not allowed. This mouse pointer feedback is sufficient in many cases. -## Specifying drop targets +### Drop effects -A listener for the {{domxref("HTMLElement/dragenter_event", "dragenter")}} and {{domxref("HTMLElement/dragover_event", "dragover")}} events are used to indicate valid drop targets, that is, places where dragged items may be dropped. Most areas of a web page or application are not valid places to drop data. Thus, the default handling of these events is not to allow a drop. +When dropping, there are several operations that may be performed: -If you want to allow a drop, you must prevent the default behavior by cancelling both the `dragenter` and `dragover` events. You can do this by calling their {{domxref("Event.preventDefault","preventDefault()")}} methods: +- `copy` + - : The data will be simultaneously present at the source and target locations after dropping. +- `move` + - : The data will only be present at the target location, and will be removed from the source location. +- `link` + - : Some form of linking will be created between the source and drop locations; there is only one instance of the data at the source location. +- `none` + - : Nothing happens; the drop failed. -```html -
You can drag and then drop a draggable item here
-``` +With the {{domxref("HTMLElement/dragenter_event", "dragenter")}} and {{domxref("HTMLElement/dragover_event", "dragover")}} events, the {{domxref("DataTransfer.dropEffect","dropEffect")}} property is initialized to the effect that the user is requesting. The user can modify the desired effect by pressing modifier keys. Although the exact keys used vary by platform, typically the Shift and Control keys would be used to switch between copying, moving, and linking. The mouse pointer will change to indicate which operation is desired. For instance, for a `copy`, the cursor might appear with a plus sign next to it. -```js -const dropElement = document.getElementById("drop-target"); +You can modify the {{domxref("DataTransfer.dropEffect","dropEffect")}} property during the {{domxref("HTMLElement/dragenter_event", "dragenter")}} or {{domxref("HTMLElement/dragover_event", "dragover")}} events, if for example, a particular drop target only supports certain operations. You can modify the {{domxref("DataTransfer.dropEffect","dropEffect")}} property to override the user effect, and enforce a specific drop operation to occur. -dropElement.addEventListener("dragenter", (event) => { - event.preventDefault(); +```js +target.addEventListener("dragover", (event) => { + event.dataTransfer.dropEffect = "move"; }); +``` -dropElement.addEventListener("dragover", (event) => { - event.preventDefault(); +In this example, move is the effect that is performed. + +You can use the value `none` to indicate that no drop is allowed at this location. You should usually do this if the element is only temporarily not accepting drops; if it's not intended to be a drop target, you should just not cancel the event. + +Note that setting `dropEffect` only indicates the desired effect _at this particular instant_; a later `dragover` dispatch may change it. To persist the choice, you must set it in every `dragover` event. Also, this effect is only _informational_, and what effects ends up being implemented depends on both the source and the target nodes (for example, if the source node cannot be modified, then even if a "move" effect is requested, it may not be possible). + +For both user gestures and programmatically setting `dropEffect`, by default, all three drop effects are available. The draggable element can restrict itself to only allow certain effects by setting the {{domxref("DataTransfer.effectAllowed","effectAllowed")}} property within a {{domxref("HTMLElement/dragstart_event", "dragstart")}} event listener. + +```js +draggableElement.addEventListener("dragstart", (event) => { + event.dataTransfer.effectAllowed = "copyLink"; }); ``` -Calling the {{domxref("Event.preventDefault","preventDefault()")}} method during both the {{domxref("HTMLElement/dragenter_event", "dragenter")}} and {{domxref("HTMLElement/dragover_event", "dragover")}} event will indicate that a drop is allowed at that location. However, you will commonly wish to call the {{domxref("Event.preventDefault","preventDefault()")}} method only in certain situations (for example, only if a link is being dragged). +In this example, only a copy or link operation is allowed, but a move operation is not possible to be selected either via script or via user gestures. + +The values of `effectAllowed` are combinations of `dropEffect`: + +| Value | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `none` | No operation is permitted | +| `copy` | `copy` only | +| `move` | `move` only | +| `link` | `link` only | +| `copyMove` | `copy` or `move` only | +| `copyLink` | `copy` or `link` only | +| `linkMove` | `link` or `move` only | +| `all` | `copy`, `move`, or `link` | +| `uninitialized` | The default value when the effect has not been set; generally equivalent to `all`, except the default `dropEffect` may not always be `copy`. | + +By default, the `dropEffect` is initialized based on `effectAllowed`, in the order of `copy`, `link`, `move`, selecting the first one that is allowed. The unselected but allowed effects may also be selected as default if appropriate; for example, on Windows, holding the Alt key causes `link` to be used in priority. If `effectAllowed` is `uninitialized` and the dragged element is an `
` link, the default `dropEffect` is `link`; if `effectAllowed` is `uninitialized` and the dragged element is a selection from an editable text field, the default `dropEffect` is `move`. + +```html hidden live-sample___drop_effects +
+ These are the sources with different allowedEffect +
+
+
+ These are the targets with different dropEffect +
+
+``` -To do this, call a function which checks a condition and only cancels the event when the condition is met. If the condition is not met, don't cancel the event, and a drop will not occur there if the user releases the mouse button. +```css hidden live-sample___drop_effects +.sources-container, +.targets-container { + width: calc(100% - 2rem); + border: 2px dashed gray; + padding: 0.5rem; + margin: 1rem 0; +} -It is most common to accept or reject a drop based on the type of drag data in the data transfer — for instance, allowing images, or links, or both. To do this, you can check the {{domxref("DataTransfer.types","types")}} property of the event's {{domxref("DragEvent.dataTransfer","dataTransfer")}} (property). The {{domxref("DataTransfer.types","types")}} property returns an array of the string types that were added when the drag began, in the order from most significant to least significant. +#sources, +#targets { + display: grid; + gap: 0.5rem; + width: 100%; +} -```js -function doDragOver(event) { - const isLink = event.dataTransfer.types.includes("text/uri-list"); - if (isLink) { - event.preventDefault(); - } +#sources { + grid-template-columns: 1fr 1fr 1fr; } -``` -In this example, we use the `includes` method to check if the type [`text/uri-list`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_links) is present in the list of types. If it is, we will cancel the event so that a drop may be allowed. If the drag data does not contain a link, the event will not be cancelled, and a drop cannot occur at that location. +#targets { + grid-template-columns: 1fr 1fr; +} -You may also wish to set either the {{domxref("DataTransfer.effectAllowed","effectAllowed")}}, {{domxref("DataTransfer.dropEffect","dropEffect")}} property, or both at the same time, if you wish to be more specific about the type of operation that will performed. Naturally, changing either property will have no effect if you do not cancel the event as well. +#sources div, +#targets div { + border: 2px solid black; + flex: 1 0 auto; + display: flex; + align-items: center; + justify-content: center; +} -## Drop feedback +#sources div { + height: 50px; +} -There are several ways in which you can indicate to the user that a drop is allowed at a certain location. The mouse pointer will update as necessary depending on the value of the {{domxref("DataTransfer.dropEffect","dropEffect")}} property. +#targets div { + height: 75px; +} +``` -Although the exact appearance depends on the user's platform, typically a plus sign icon will appear for a `copy` for example, and a 'cannot drop here' icon will appear when a drop is not allowed. This mouse pointer feedback is sufficient in many cases. +```js hidden live-sample___drop_effects +for (const allowedEffect of [ + "none", + "copy", + "move", + "link", + "copyMove", + "copyLink", + "linkMove", + "all", + "uninitialized", +]) { + const div = document.createElement("div"); + div.textContent = allowedEffect; + div.draggable = true; + div.addEventListener("dragstart", (event) => { + event.dataTransfer.effectAllowed = allowedEffect; + }); + document.getElementById("sources").appendChild(div); +} + +for (const dropEffect of ["none", "copy", "move", "link"]) { + const div = document.createElement("div"); + div.textContent = dropEffect; + div.addEventListener("dragover", (event) => { + event.preventDefault(); + event.dataTransfer.dropEffect = dropEffect; + }); + document.getElementById("targets").appendChild(div); +} +``` + +{{EmbedLiveSample("drop_effects", "", 500)}} -For more complex visual effects, you can perform other operations during the {{domxref("HTMLElement/dragenter_event", "dragenter")}} event. For example, by inserting an element at the location where the drop will occur. This might be an insertion marker, or an element that represents the dragged element in its new location. To do this, you could create an [``](/en-US/docs/Web/HTML/Reference/Elements/img) element and insert it into the document during the {{domxref("HTMLElement/dragenter_event", "dragenter")}} event. +### Custom drop feedback -The {{domxref("HTMLElement/dragover_event", "dragover")}} event will fire at the element the mouse is pointing at. Naturally, you may need to move the insertion marker around a {{domxref("HTMLElement/dragover_event", "dragover")}} event as well. You can use the event's {{domxref("MouseEvent.clientX","clientX")}} and {{domxref("MouseEvent.clientY","clientY")}} properties as with other mouse events to determine the location of the mouse pointer. +For more complex visual effects, you can perform other operations during the {{domxref("HTMLElement/dragenter_event", "dragenter")}} event, for example, by inserting an element at the location where the drop will occur. This might be an insertion marker or an element that represents the dragged element in its new location. To do this, you could create an [``](/en-US/docs/Web/HTML/Reference/Elements/img) element and insert it into the document during the {{domxref("HTMLElement/dragenter_event", "dragenter")}} event. + +The {{domxref("HTMLElement/dragover_event", "dragover")}} event will fire at the element the mouse is pointing at. Naturally, you may need to move the insertion marker around inside the {{domxref("HTMLElement/dragover_event", "dragover")}} event handler as well. You can use the event's {{domxref("MouseEvent.clientX","clientX")}} and {{domxref("MouseEvent.clientY","clientY")}} properties as with other mouse events to determine the location of the mouse pointer. Finally, the {{domxref("HTMLElement/dragleave_event", "dragleave")}} event will fire at an element when the drag leaves the element. This is the time when you should remove any insertion markers or highlighting. You do not need to cancel this event. The {{domxref("HTMLElement/dragleave_event", "dragleave")}} event will always fire, even if the drag is cancelled, so you can always ensure that any insertion point cleanup can be done during this event. +For a practical example of using these events, see our [Kanban board example](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Kanban_board#inserting_at_a_particular_location). + ## Performing a drop When the user releases the mouse, the drag and drop operation ends. -If the mouse is released over an element that is a valid drop target, that is, one that cancelled the last {{domxref("HTMLElement/dragenter_event", "dragenter")}} or {{domxref("HTMLElement/dragover_event", "dragover")}} event, then the drop will be successful, and a {{domxref("HTMLElement/drop_event", "drop")}} event will fire at the target. Otherwise, the drag operation is cancelled, and no {{domxref("HTMLElement/drop_event", "drop")}} event is fired. +In order for the drop to be _potentially successful_, the drop must happen over a valid [drop target](#dragging_over_elements_and_specifying_drop_targets), and the `dropEffect` must not be `none` at the time of mouse release. Otherwise, the drop operation is considered [failed](#a_failed_drop). -During the {{domxref("HTMLElement/drop_event", "drop")}} event, you should retrieve that data that was dropped from the event and insert it at the drop location. You can use the {{domxref("DataTransfer.dropEffect","dropEffect")}} property to determine which drag operation was desired. +If the drop is potentially successful, a {{domxref("HTMLElement/drop_event", "drop")}} event is fired on the drop target. You need to cancel this event using `preventDefault()` in order for the drop to be considered actually successful. Otherwise, the drop is also considered successful if the drop was dropping text (the data contains a `text/plain` item) into an editable text field. In this case, the text is inserted into the field (either at the cursor position or at the end, depending on platform conventions) and, if the `dropEffect` is `move` while the source is a selection within an editable region, the source is removed. Otherwise, for all other drag data and drop targets, the drop is considered failed. -As with all drag-related events, the event's {{domxref("DataTransfer","dataTransfer")}} property will hold the data that is being dragged. The {{domxref("DataTransfer.getData","getData()")}} method may be used to retrieve the data again. +During the {{domxref("HTMLElement/drop_event", "drop")}} event, you should retrieve the desired data from the drag data store using {{domxref("DataTransfer.getData()")}}, and insert it at the drop location. You can use the {{domxref("DataTransfer.dropEffect","dropEffect")}} property to determine which drag operation was desired. The `drop` event is the only time when you can read the drag data store, other than `dragstart`. ```js -function onDrop(event) { - const data = event.dataTransfer.getData("text/plain"); - event.target.textContent = data; +target.addEventListener("drop", (event) => { event.preventDefault(); -} + const data = event.dataTransfer.getData("text/plain"); + target.textContent = data; +}); ``` -The {{domxref("DataTransfer.getData","getData()")}} method takes one argument, the type of data to retrieve. It will return the string value that was set when {{domxref("DataTransfer.setData","setData()")}} was called at the beginning of the drag operation. An empty string will be returned if data of that type does not exist. (Naturally, though, you would likely know that the right type of data was available, as it was previously checked during a {{domxref("HTMLElement/dragover_event", "dragover")}} event.) - In the example here, once the data has been retrieved, we insert the string as the textual content of the target. This has the effect of inserting the dragged text where it was dropped, assuming that the drop target is an area of text such as a `p` or `div` element. -In a web page, you should call the {{domxref("Event.preventDefault","preventDefault()")}} method of the event if you have accepted the drop, so that the browser's default handling is not triggered by the dropped data as well. For example, when a link is dragged to a web page, Firefox will open the link. By cancelling the event, this behavior will be prevented. +The `getData()` method returns an empty string if the data store does not contain data of the specified type. If you implemented [conditional drop targets](#conditional_drop_targets), this situation should not occur, because the drop target should only accept drops when the desired data is present. -You can retrieve other types of data as well. If the data is a link, it should have the type [`text/uri-list`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_links). You could then insert a link into the content. +You can retrieve other types of data as well. If the data is a link, it should have the type [`text/uri-list`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store#dragging_links). You could then insert a link into the content. ```js -function doDrop(event) { - const lines = event.dataTransfer.getData("text/uri-list").split("\n"); +target.addEventListener("drop", (event) => { + event.preventDefault(); + const lines = event.dataTransfer.getData("text/uri-list").split("\r\n"); lines .filter((line) => !line.startsWith("#")) .forEach((line) => { const link = document.createElement("a"); link.href = line; link.textContent = line; - event.target.appendChild(link); + target.appendChild(link); }); - event.preventDefault(); -} +}); ``` -This example inserts a link from the dragged data. As the name implies, the [`text/uri-list`](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#dragging_links) type actually may contain a list of URLs, each on a separate line. The above code uses [`split`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) to break the string into lines, then iterates over the list of lines, and inserts each as a link into the document. (Note also that links starting with a number sign (`#`) are skipped, as these are comments.) +For more information about how to read drag data, see [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store#reading_the_drag_data_store). -For simple cases, you can use the special type `URL` just to retrieve the first valid URL in the list. For example: +It is also the source and the target elements' responsibility to collaborate to implement the `dropEffect`—the source listens for the `dragend` event and the target listens for the `drop` event. For example, if the `dropEffect` is `move`, then one of these elements must remove the dragged item from its old location (usually the source element itself, because the target element doesn't necessarily know or have control over the source). -```js -const link = event.dataTransfer.getData("URL"); -``` + -This eliminates the need to check for comments or iterate through lines yourself. However, it is limited to only the first URL in the list. +## A failed drop -The `URL` type is a special type. It is used only as a shorthand, and it does not appear within the list of types specified in the {{domxref("DataTransfer.types","types")}} property. +The drag-and-drop operation is considered failed if one of the following is true: -Sometimes you may support some different formats, and you want to retrieve the data that is most specific that is supported. In the following example, three formats are supported by a drop target. +1. The user pressed the Escape key +2. The drop happened outside of a valid [drop target](#dragging_over_elements_and_specifying_drop_targets) +3. The drop effect was `none` at the time of mouse release +4. The `drop` event was not cancelled and the drop was not dropping text (containing a `text/plain` data) into an editable text field (see [performing a drop](#performing_a_drop)) -The following example returns the data associated with the best-supported format: +For cases 1 and 3, if the abortion happens while hovering over a valid drop target, the drop target receives a {{domxref("HTMLElement/dragleave_event", "dragleave")}} event, as if the drop no longer happens over it, so that it can clean up any [drop feedback](#custom_drop_feedback). In all cases, the `dropEffect` is set to `none` for subsequent events. -```js -function doDrop(event) { - const supportedTypes = [ - "application/x-moz-file", - "text/uri-list", - "text/plain", - ]; - const types = event.dataTransfer.types.filter((type) => - supportedTypes.includes(type), - ); - if (types.length) { - const data = event.dataTransfer.getData(types[0]); - // Use this type of data… - } - event.preventDefault(); -} -``` +Afterwards, a {{domxref("HTMLElement/dragend_event", "dragend")}} event is fired at the source node. The browser may display an animation of the dragged selection going back to the source of the drag-and-drop operation. -## Finishing a drag +## Finishing the drag -Once the drag is complete, a {{domxref("HTMLElement/dragend_event", "dragend")}} event is fired at the source of the drag (the same element that received the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event). This event will fire if the drag was successful or if it was cancelled. However, you can use the {{domxref("DataTransfer.dropEffect","dropEffect")}} property to determine which drop operation occurred. +Once the drag is complete, a {{domxref("HTMLElement/dragend_event", "dragend")}} event is fired at the source of the drag (the same element that received the {{domxref("HTMLElement/dragstart_event", "dragstart")}} event). This event will fire regardless of whether the drag succeeded. If the {{domxref("DataTransfer.dropEffect","dropEffect")}} property has the value `none` during a {{domxref("HTMLElement/dragend_event", "dragend")}}, then the drag was cancelled. Otherwise, the effect specifies which operation was performed. The source can use this information after a `move` operation to remove the dragged item from the old location. @@ -334,5 +372,4 @@ After the {{domxref("HTMLElement/dragend_event", "dragend")}} event has finished ## See also - [HTML Drag and Drop API (Overview)](/en-US/docs/Web/API/HTML_Drag_and_Drop_API) -- [Recommended Drag Types](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types) -- [HTML Living Standard: Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) +- [Working with the drag data store](/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store) diff --git a/files/en-us/web/api/html_drag_and_drop_api/file_drag_and_drop/index.md b/files/en-us/web/api/html_drag_and_drop_api/file_drag_and_drop/index.md index 9130911da2fc9a9..cabb9189517b069 100644 --- a/files/en-us/web/api/html_drag_and_drop_api/file_drag_and_drop/index.md +++ b/files/en-us/web/api/html_drag_and_drop_api/file_drag_and_drop/index.md @@ -6,91 +6,188 @@ page-type: guide {{DefaultAPISidebar("HTML Drag and Drop API")}} -HTML Drag and Drop interfaces enable web applications to drag and drop files on a web page. This document describes how an application can accept one or more files that are dragged from the underlying platform's _file manager_ and dropped on a web page. +As mentioned on [the landing page](/en-US/docs/Web/API/HTML_Drag_and_Drop_API#concepts_and_usage), the Drag and Drop API simultaneously models three use cases: dragging elements within a page, dragging data out of a page, and dragging data into a page. This tutorial demonstrates the third use case: dragging data into a page. We will be implementing a basic drop zone that allows the user to drop image files from the user's operation system file explorer and displays them on the page. For users who can't or don't want to use drag and drop, we also provide the alternative functionality of file selection via an `` element. -The main steps to drag and drop are to define a _drop zone_ (i.e., a target element for the file drop) and to define event handlers for the {{domxref("HTMLElement/drop_event", "drop")}} and {{domxref("HTMLElement/dragover_event", "dragover")}} events. These steps are described below, including example code snippets. +## Basic page layout -## Define the drop zone - -The HTML defines the drop zone as a {{htmlelement("div")}}, and an output region ({{htmlelement("pre")}}) to be populated later. +Because we want to allow normal `` file selection as well, it makes sense for the drop zone to be backed by an `` element so that we can simultaneously drag into it and click on it. We take advantage of a common trick, which is to make the `` invisible, and use its associated {{HTMLElement("label")}} to interact with the user instead, because `