Cross application drag 'n drop using Transmat
Tags: javascript, webdev, tutorial, frontend
Published: 2021-04-08 09:43:38

**With [Transmat](https://google.github.io/transmat) you can add drag-drop and copy-paste interactions to your webpage. That is nothing new, but with its ability to interact with external applications by simply dragging (or copying!) elements, a new world of possibilities open up!**

The Transmat library providing a wrapper around the browser's DataTransfer API, and makes it easy to respond to drag-drop interactions. This interaction is [supported by all desktop browsers](https://caniuse.com/mdn-api_datatransfer_setdata) that have been released after Internet Explorer 11 (10 years ago!)

In this post, you'll learn how to transfer data to external applications, receive incoming external data, and how to highlight drop areas.

{% codepen https://codepen.io/jtangelder/pen/eYgWVoo %}

# 1. Transferable elements in your HTML
Add a draggable and focusable element to your webpage.

```html
<div id="source" draggable="true" tabindex="0">
  Drag or copy me
</div>

<div id="target" tabindex="0">
  Drop here!
</div>
```
The elements need to be marked as such in order to enable the browser's native Drag and Drop interactions. The tabindex will make the element selectable, which enables the user to trigger a copy.

# 2. Setting up drag and copy interactions
The examples below have the transmat library imported. You can get it from npm, `npm install transmat`.

```js
import {Transmat, addListeners} from 'transmat';

const source = document.getElementById('source');

// Add listeners for 'drag' and 'copy'.
addListeners(source, 'transmit', event => {
  // Create a Transmat instance based on the incoming event.
  const transmat = new Transmat(event);
  transmat.setData({
    // Text data. This will show up in Text fields, 
    // but also as the fallback for text/html when pasting 
    // to WYSIWYG editors.
    'text/plain': 'Hi there!',

    // HTML data. This will be accepted by many WYSIWYG 
    // editors like Google Docs, Gmail, Microsoft Word, etc.
    'text/html': `
         <h1>This is formatted!</h1>
         <p>
           Pretty cool, and you can also add
           <a href="https://example.com/">links</a>!
         </p>
         <img src="https://example.com/test.jpg" />`,

    // URL data. This URL will open when dropping on the
    // URL bar of your browser, will create a shortcut file 
    // when dropping on the desktop.
    'text/uri-list': 'https://example.com/foobar',

    // Share a structured JS object. Transmat with serialize 
    // this object using JSON.stringify()
    'application/json': {name: 'Rory'},
  });
});
```

Transmat works as following;
- The `addListeners` method assigns the event `transmit` listeners for `dragstart` and `copy` events. The event callback gets invoked when one of these interactions get triggered.
- A `new Transmat(event)` instance is created using the `event` object. This instance makes it easier for you to interact with the underlying DataTransfer API and ensures the same behavior across browsers.
- Provide data with the `setData(data)` method. By providing MIME types, you will describe the type of data. The MIME types listed in the code example below are primitives that have wide support across the operating system. See the code comments to see the behavior for each of these.

Now if you'll run this example, see what happens when you drag-drop the example object to your favourite text editor ([Codepen](https://codepen.io/pen), Sublime, VS Code), WYSIWYG editor ([Google Docs](https://doc.new), Apple Pages, Microsoft Word) and browser windows. It will show and open the content you provided in this element. *Like magic!*

# 3. Listening for incoming data
The same way as listening for the `transmit` event, you can listen to incoming data using the `receive` event.

```js
const target = document.getElementById('target');

// Add listeners for 'drop' and 'paste'.
addListeners(target, 'receive', event => {
  // Create a Transmat instance based on the incoming event.
  const transmat = new Transmat(event);
  if (
    // Only want to accept JSON data.
    transmat.hasType('application/json') && 
    // Call this in order to accept incoming data.
    transmat.accept()
  ) {
    // Get the JSON string data, parse it, and log.
    const jsonString = transmat.getData('application/json');
    const data = JSON.parse(jsonString);
    alert(`Hi ${data.name}!`);
  });
});
```

- Like as transmitting data, you will need to set up a `receive` event listener to listen for incoming data transfers.
- A `new Transmat(event)` is created to interact with the underlying incoming data.
- In this example, you only want to accept `application/json` data. The `hasType(type)` method will return whether this payload is being tranferred.
- In order to accept the tranfer, you need to call the `accept()` method. This will prevent the document from doing its default behavior, like navigating to an URL when the `text/uri-list` payload is present.
- When accepted, you will use the `getData(type)` to read the string payload. In this example, you expect JSON data which needs to be parsed first using JSON.parse. 

# 4. Highlight drop areas
It can be hard for the user to discover valid drop areas. You can use the `TransmatObserver` class to observe incoming transfer events on targets, and respond to them. The example below will add a className when a JSON payload is being transferred.

```html
<style>
.drag-active { background: rgba(0, 255, 0, .1); }
.drag-over { background: rgba(0, 255, 0, .5); }
</style>
```

```js
// Observe Transmat (drag) events happening on your page.
const obs = new TransmatObserver(entries => {
  for (const entry of entries) {
    const transmat = new Transmat(entry.event);
    // Only want to highlight elements containing JSON data.
    if(transmat.hasType('application/json')) {
      // Add a className when an element is dragged over 
      // your page.
      entry.target.classList
          .toggle('drag-active', entry.isActive);

      // Add a className when an element is dragged over 
      // the observed target.
      entry.target.classList
          .toggle('drag-over', entry.isTarget);
    }
  }
});

// Observe the target element.
obs.observe(target);
```

# There's more!
You should now know the basics of the [Transmat](https://google.github.io/transmat) library. But there's more! The library offers utilities for interacting with JSON-LD for a connected web, and a minimalistic drag-image to integrate with your existing drag-drop implementation. 

Curious to hear what your ideas are with this *barrier breaking* technique!