• No results found

Drag-and-Drop API Examples

In document HTML5 Programming Jonathan Reid (Page 93-100)

Listing 3-14 produces the simplest thing imaginable: a set of draggable boxes that can be dropped on a single target. As the boxes are dropped on the target, a counter increases to show the number of times a drop has happened.

Listing 3-14. A Simple Drag-and-Drop Interface

<!DOCTYPE html>

<html>

<head>

<title>The HTML5 Programmer's Reference</title>

<style>

.draggable { margin: 5px;

width: 100px;

height: 100px;

background-color: #ccc;

border: 1px solid #000;

display: inline-block;

}

.target {

border: 10px solid #000;

width: 315px;

height: 100px;

margin-left: 5px;

margin-top: 50px;

}

.target.over {

border: 10px solid green;

}

</style>

</head>

<body>

<div class="draggable" draggable='true'></div>

<div class="draggable" draggable='true'></div>

<div class="draggable" draggable='true'></div>

<div class="target"></div>

<script>

// Get a reference to the drop target.

var dropTarget = document.querySelector('.target');

// Add a dragenter event handler to the drop target.

dropTarget.addEventListener('dragenter', function(event) {

// Add the 'over' CSS class to the drop target. This lets the user know that // they have dragged something over a valid drop target.

this.classList.add('over');

}, false);

// Add a dragleave event handler to the drop target.

dropTarget.addEventListener('dragleave', function(event) { // Remove the 'over' CSS class.

this.classList.remove('over');

}, false);

// Add a dragover event handler to the drop target.

dropTarget.addEventListener('dragover', function(event) { // Prevent the default event action.

event.preventDefault();

}, false);

// A counter that indicates how many times something has been dropped onto the // drop target.

var counter = 1;

// Add a drop event handler to the drop target.

dropTarget.addEventListener('drop', function(event) { // Update the counter and remove the 'over' CSS class.

this.innerHTML = counter;

this.classList.remove('over');

counter++;

}, false);

</script>

</body>

</html>

In the script, all you did was register dragenter, dragleave, dragover, and drop event handlers on the drop target. On dragenter you add a CSS class to the element, and on dragleave you remove the CSS class.

This gives visual feedback that the user has successfully dragged the element over a target that can receive it.

Then you prevent the default action of the dragover event, to prevent drop events. In the drop event handler you update the innerHTML of the target element and increment the counter. You also remove the visual feedback CSS class, since at this point you’ll be terminating the drag-and-drop sequence and a dragleave event will not fire.

This example works great in Internet Explorer and Chrome. It does not work at all in Firefox. This is because Firefox requires that the dataTransfer object be initialized on dragstart by specifying some data—any data.

To update your script, you have to add a dragstart event handler to each of your draggable elements and set some arbitrary data within them so Firefox will initiate drag sequences from them. Listing 3-15 has the necessary changes to the script (the surrounding HTML and CSS remain the same as in Listing 3-14).

Listing 3-15. Drag-and-Drop Script Updated to Work in Firefox // Get a reference to all of the draggable objects.

var draggables = document.querySelectorAll('.draggable');

// On each draggable element intialize the dataTransfer object on dragstart so // Firefox will initiate drag events with them.

for (var i = 0; i < draggables.length; i++) { currEl = draggables[i];

currEl.addEventListener('dragstart', function(event) { event.dataTransfer.setData('text', 'anything');

}, false);

};

// Get a reference to the drop target.

var dropTarget = document.querySelector('.target');

// Add a dragenter event handler to the drop target.

dropTarget.addEventListener('dragenter', function(event) {

// Add the 'over' CSS class to the drop target. This lets the user know that // they have dragged something over a valid drop target.

this.classList.add('over');

}, false);

// Add a dragleave event handler to the drop target.

dropTarget.addEventListener('dragleave', function(event) { // Remove the 'over' CSS class.

this.classList.remove('over');

}, false);

// Add a dragover event handler to the drop target.

dropTarget.addEventListener('dragover', function(event) { // Prevent the default event action.

event.preventDefault();

}, false);

// A counter that indicates how many times something has been dropped onto the // drop target.

var counter = 1;

// Add a drop event handler to the drop target.

dropTarget.addEventListener('drop', function(event) { // Update the counter and remove the 'over' CSS class.

this.innerHTML = counter;

this.classList.remove('over');

counter++;

}, false);

You’ll notice that the code uses querySelectorAll to get a reference to all of your draggable elements.

Then it loops through each of those elements in a for loop and applies the dragstart event listener to each one. (Another way to do this would have been to delegate the dragstart event handler to a containing element.) Now the elements are draggable in Firefox, and the example works in that browser the same as in the others.

In practice, you’ll probably be initializing the data for a drag-and-drop sequence anyway. I mentioned earlier an example of a visual clipboard, which you can see in Listing 3-16.

Listing 3-16. A Visual Clipboard

<!DOCTYPE html>

<html>

<head>

<title>The HTML5 Programmer's Reference</title>

<style>

border: 10px solid black;

}

div#dropTarget.over { border: 10px solid green;

}

<p>Type some text here, then highlight it and drag it to the clipboard below.</p>

<textarea id="dragSource"></textarea>

<p>Clipboard</p>

<div id="dropTarget"></div>

<script>

// Get references to our drag source and drop target.

var dragSource = document.getElementById('dragSource');

var dropTarget = document.getElementById('dropTarget');

// Add a dragstart event handler to the dragsource element.

dragSource.addEventListener('dragstart', function(event) { // Initialize the dataTransfer object with the current text.

event.dataTransfer.setData('text', this.value);

}, false);

// Add a dragenter event handler to the target.

dropTarget.addEventListener('dragenter', function(event) { // Add the 'over' CSS class to the element.

this.classList.add('over');

}, false);

// Add a dragleave event handler to the target.

dropTarget.addEventListener('dragleave', function(event) { // Remove the 'over' CSS class from the element.

this.classList.remove('over');

}, false);

// Add a dragover event handler to the target.

dropTarget.addEventListener('dragover', function(event) { // Prevent the default action of the dragover event.

event.preventDefault();

}, false);

// Finally, add a drop event handler to the target.

dropTarget.addEventListener('drop', function(event) {

// Append the text in the dataTransfer object to the clipboard.

this.innerHTML = event.dataTransfer.getData('text');

// Remove the 'over' CSS class from the element.

this.classList.remove('over');

}, false);

</script>

</body>

</html>

In this example you’ve created a simple textarea where you can enter some text. You can then highlight the text and drag it to the clipboard. Behind the scenes, the code sets the text as data on the dataTransfer object during the dragstart event, and then gets the text from the dataTransfer object on the drop event.

This example works great in almost all browsers, except once again Firefox has a small problem. When you drop text onto the clipboard area, Firefox fires a default action on drop that tries to update the URL of the page to the text that was dropped. To prevent this, you will need to call event.preventDefault() in the drop event handler. By adding that line, the example will work the same in all browsers.

For a final example, consider the need to restrict the movement of the draggable item to a specific area.

You don’t want to allow it to leave a containing element. Or perhaps you want to limit the movement to a single axis. Unfortunately, the HTML5 Drag and Drop API doesn’t provide a ready-made solution for this fairly common use case, but you can build one with the tools it does provide.

The core of the problem is you have no way to limit the mouse pointer with JavaScript. This makes sense; being able to manipulate mouse pointer actions with JavaScript would pose a large security risk. The way the drag-and-drop sequence is set up, wherever the pointer goes, the helper image follows, so if you can’t limit the pointer, you can’t limit the location of the helper image.

This means the first thing you’ll have to do is remove the default helper image using dataTransfer.

setDragImage(), and that means this example won’t work in Internet Explorer because it doesn’t implement that method. But the example does work in other browsers, and it’s a worthwhile example to demonstrate some more complex interactions with the API.

The next step is to build your own helper image that can be manipulated as needed. Once that’s done, it’s just a matter of listening to events.

Listing 3-17 provides the full example.

Listing 3-17. Limiting Drag and Drop to a Specific Region

<!DOCTYPE html>

<html>

<head>

<title>The HTML5 Programmer's Reference</title>

<style>

<div class="container">

<div id="dragTarget" draggable="true">Drag me!</div>

</div>

<div id="helper" style="width: 1px;height: 1px;"></div>

<script>

// Get references to the drag target and container.

var dragTarget = document.getElementById('dragTarget');

var dragContainer = document.querySelector('.container');

// This variable will hold the new helper when we build it.

var dragHelper;

// Add a dragstart event handler to the drag target.

dragTarget.addEventListener('dragstart', function(event) { // Initialize the dataTransfer object for Firefox.

event.dataTransfer.setData('text', 'Fix for Firefox');

// Replace the default drag image with a small, transparent DIV.

var dragImage = document.getElementById('helper');

event.dataTransfer.setDragImage(dragImage, 0, 0);

if (dragHelper == null) {

// Create our own drag helper by cloning the target element. Note that when // we clone the element we need to do some cleanup, like removing the clone's // id attribute (so we do not introduce duplicate ids even temporarily) and // making not draggable.

dragHelper = this.cloneNode(true);

dragHelper.id = '';

dragHelper.classList.add('drag-helper');

dragHelper.draggable = false;

dragContainer.appendChild(dragHelper);

} else {

// We've already created a clone, so let's just use it.

dragHelper.classList.remove('hidden');

} }, false);

// Add a dragover event handler to the drag container.

dragContainer.addEventListener('dragover', function(event) { // Prevent the default action of the event.

event.preventDefault();

// Move the helper to the desired location.

if (event.clientY < 485) {

dragHelper.style.top = event.clientY + 'px';

} }, false);

// Add a dragend event handler to the target.

dragTarget.addEventListener('dragend', function(event) { // Dragging is done, so hide the clone.

dragHelper.classList.add('hidden');

}, false);

// Add a drop event to the drag container.

dragContainer.addEventListener('drop', function(event) {

You’ll do all of your setup in the dragstart event handler. First is the fix for Firefox, otherwise the example wouldn’t work in that browser. Then you hide the default helper image by getting a reference to a small transparent div and using that as the new helper image. Next you clone the target element and set the clone up to be your new helper, and append it to the DOM.

In your dragover event handler, all you have to do is move the helper to the desired position.

Remember that since the dragover event fires continuously throughout the drag process, you should keep its event handler as lightweight as possible. That’s why you’re using a cached reference to the drag helper, so you don’t have to query for it every time the event fires. As you use the example you’ll notice that because you’re only listening for dragover events in the containing element, when you move the pointer outside of that element the helper will stop moving. You could have delegated the dragover event handler to the body element, which would allow the user to move the element from anywhere on the page.

You hide the custom helper in your dragend event handler. This is done so the helper will be hidden regardless of where the user releases the mouse button. This is a common use of the dragend event.

As mentioned, this example won’t work in Internet Explorer, so while this example is interesting it’s not very practical for the majority of situations. Unfortunately there’s no way to polyfill the setDragImage method, either. So if this is the sort of interaction you need and you have to support Internet Explorer, you probably need to look for another solution besides the HTML5 native Drag and Drop API.

In document HTML5 Programming Jonathan Reid (Page 93-100)