Sencha Inc. | HTML5 Apps

Blog

5 Steps to Understanding Drag and Drop with Ext JS

September 13, 2009 | Jay Garcia

One of the most powerful interaction design patterns available to developers is "Drag and Drop." We utilize Drag and Drop without really giving it much thought - especially when its done right. Here are 5 easy steps to ensure an elegant implementation.

Defining drag and drop

A drag operation, essentially, is a click gesture on some UI element while the mouse button is held down and the mouse is moved. A drop operation occurs when the mouse button is released after a drag operation. From a high level, drag and drop decisions can be summed up by the following flow chart. Drag and drop decision flow chart. To speed up our development, Ext JS provides us with the Ext.dd classes to manage the basic decisions for us. In this post, we will cover coding for the appearance and removal of the drop invitation, invalid drop repair and what happens when a successful drop occurs.

Organzing the drag and drop classes

A first glance of the classes in the Ext.dd documentation might seem a bit intimidating.  But, if we take a quick moment to look at the classes, we see that they all stem from the DragDrop class and most can be categorized into Drag or Drop groups.  With a bit more time and digging, we can see that the classes can be further categorized into single node and multiple node drag or drop interactions. Ext JS Drag and drop class diagram. In order to learn about the basics of drag and drop we'll focus on applying single drag and drop interactions to DOM nodes.  To do this, we'll utilize the DD and DDTarget classes, which provide the base implementations for their respective drag and drop behaviors. However, we need to discuss what our objectives are before we can start implementing drag and drop.

The task at hand

Lets say we've been asked to develop an application that will provide a rental car company the ability to place their cars and trucks in one of three states:  available, rented or in repair status.  The cars and trucks are only allowed to be placed in their respective "available" container. Drag and drop story. To get started, we must make the cars and trucks "dragable". For this, we'll use DD. We'll need to make the rented, repair and vehicle containers "drop targets".  For this we'll use DDTarget.  Lastly, we'll use different drag drop groups to help enforce the requirement that cars and trucks can only be dropped into their respective "available" containers. The HTML and CSS for this example is already constructed and can be downloaded here.  With that downloaded, we can begin coding by adding drag operations to the cars and trucks.

Step 1: Starting with drag

To configure the vehicle DIVs elements as dragable, we'll need to obtain a list and loop through it to instantiate new instances of DD.  Here's how we do it.
 
// Create an object that we'll use to implement and override drag behaviors a little later
var overrides = {};
// Configure the cars to be draggable
var carElements = Ext.get('cars').select('div');
Ext.each(carElements.elements, function(el) {
    var dd = new Ext.dd.DD(el, 'carsDDGroup', {
        isTarget  : false
    });
    //Apply the overrides object to the newly created instance of DD
    Ext.apply(dd, overrides);
});
 
var truckElements = Ext.get('trucks').select('div');
Ext.each(truckElements.elements, function(el) {
    var dd = new Ext.dd.DD(el, 'trucksDDGroup', {
        isTarget  : false
    });
    Ext.apply(dd, overrides);
});
 
All drag and drop classes are designed to be implemented by means of overriding its methods. That's why in the above code segment, we have create an empty object called overrides, which will be filled in later with overrides specific to the action we need. We get of list of car and truck elements by leveraging the DomQuery select method to query the cars container for all the child div elements. To make the cars and truck elements dragable, we create a new instance of DD, passing in the car or truck element to be dragged and the drag drop group that it is to participate in. Notice that the vehicle types have their own respective drag drop group. This will be important to remember later when we setup the rented and repair containers as drop targets. Also notice that we're applying the overrides object to the newly created instances of DD using Ext.apply., which is a handy way to add properties or methods to an existing object. Before we can continue with our implementation, we need to take a quick moment to analyze what happens when you drag an element on screen. With this understanding, the rest of the implementation will fall into place.

Peeking at how drag nodes are affected

The first thing you'll notice when dragging the car or truck elements around is that they will stick wherever they are dropped. This is OK for now because we've just begun our implementation. What is important is to understand how the drag nodes are being affected. This will aid us in coding for the return to their original positions when they are dropped on anything that is a valid drop target, which is known as an "invalid drop". The below illustration uses FireBug's HTML inspection panel and highlights the changes being made by when a drag operation is applied to the Camaro element. Looking at how the drag operation changes the drag element's style Click the above image to test the drag operation. While inspecting the drag element during a drag operation, we can see a style attribute added to the element with three CSS values populated: position, top and left. Further inspection reveals that the position attribute set to relative and top and left attributes updating while the node is being dragged around. After a the drag gesture completes, the style attribute remains along with the styles contained therein. This is what we have to clean up when we code for the repair of an invalid drop. Until we setup proper drop targets, all drop operations are considered invalid.

Step 2: Repairing an invalid drop

The path of least resistance is to repair an invalid drop by reseting the style attribute that is applied during the drag operation. This means that the drag element would disappear from under the mouse and reappear where it originated and would be quite boring. To make it smoother, we'll use Ext.Fx to animate this action. Remember that the drag and drop classes were designed to have methods overridden. To implement repair, we'll need to override the b4StartDrag, onInvalidDrop and endDrag methods. Lets add the following methods to our overrides object above and we'll discuss what they are and do.
 
// Called the instance the element is dragged.
b4StartDrag : function() {
    // Cache the drag element
    if (!this.el) {
        this.el = Ext.get(this.getEl());
    }
 
    //Cache the original XY Coordinates of the element, we'll use this later.
    this.originalXY = this.el.getXY();
},
// Called when element is dropped not anything other than a dropzone with the same ddgroup
onInvalidDrop : function() {
    // Set a flag to invoke the animated repair
    this.invalidDrop = true;
},
// Called when the drag operation completes
endDrag : function() {
    // Invoke the animation if the invalidDrop flag is set to true
    if (this.invalidDrop === true) {
        // Remove the drop invitation
        this.el.removeClass('dropOK');
 
        // Create the animation configuration object
        var animCfgObj = {
            easing   : 'elasticOut',
            duration : 1,
            scope    : this,
            callback : function() {
                // Remove the position attribute
                this.el.dom.style.position = '';
            }
        };
 
        // Apply the repair animation
        this.el.moveTo(this.originalXY[0], this.originalXY[1], animCfgObj);
        delete this.invalidDrop;
    }
 
},
 
In the above code, we begin by overriding the b4StartDrag method, which is called the instant the drag element starts being dragged around screen and makes it an ideal place to cache the drag element and original XY coordinates - which we will use later on in this process. Next, we override onInvalidDrop, which is is called when a drag node is dropped on anything other than a drop target that is participating in the same drag drop group. This override simply sets a local invalidDrop property to true, which will be used in the next method. The last method we override is endDrag, which is called when the drag element is no longer being dragged around screen and the drag element is no longer being controlled by the mouse movements. This override will move the drag element back to its original X and Y position using animation. We configured the animation to use the elasticOut easing to provide a cool and fun bouncy effect at end of the animation. Looking at how the drag repair operation works Click the above image to view the animated repair operation in action. OK, now we have the repair operation complete. In order for it to work on the drop invitation and valid drop operations, we need to setup the drop targets.

Step 3: Configuring the drop targets

Our requirements dictate that we will allow cars and trucks to be in be dropped in the rented and repair containers as well as their respective original containers. To do this, we'll need to instantiate instances of the DDTarget class. Here's how its done.
 
//Instantiate instances of Ext.dd.DDTarget for the cars and trucks container
var carsDDTarget    = new Ext.dd.DDTarget('cars','carsDDGroup');
var trucksDDTarget = new Ext.dd.DDTarget('trucks', 'trucksDDGroup');
 
//Instantiate instnaces of DDTarget for the rented and repair drop target elements
var rentedDDTarget = new Ext.dd.DDTarget('rented', 'carsDDGroup');
var repairDDTarget = new Ext.dd.DDTarget('repair', 'carsDDGroup');
 
//Ensure that the rented and repair DDTargets will participate in the trucksDDGroup 
rentedDDTarget.addToGroup('trucksDDGroup');
repairDDTarget.addToGroup('trucksDDGroup');
 
In the above code snippet, we have setup drop targets for the cars, trucks, rented and repair elements. Notice that the cars container element only participates in the "carsDDGroup" and the trucks container element participates in the "trucksDDGroup". This helps enforce the requirement that cars and trucks can only be dropped in their originating container. Next, we instantiate instances DDTarget for the rented and repair elements. Initially, they are configured to only participate in the "carsDDGroup". In order to allow them to participate in the "trucksDDGroup", we have to add it by means of addToGroup. OK, now we've configured our drop targets. Lets see what happens when we drop the cars or trucks on a valid drop element. Click to see the partially completed drop operation in action. Click the above image see the progress thus far. In exercising the drop targets, we see that the drag element stays exactly its dropped. That is, images can be dropped anywhere on a drop target and stay there. This means that our drop implementation is not complete. To complete it, we need to actually code for the "complete drop" operation, by means of another override for the instances of DD that we created some time ago.

Step 4: Completing the drop

To complete the drop, we will need to actually drag the element from its parent element to the drop target element using DOM tools. This is accomplished by overriding the DD onDragDrop method. Add the following method to the overrides object.
 
// Called upon successful drop of an element on a DDTarget with the same
onDragDrop : function(evtObj, targetElId) {
    // Wrap the drop target element with Ext.Element
    var dropEl = Ext.get(targetElId);
 
    // Perform the node move only if the drag element's 
    // parent is not the same as the drop target
    if (this.el.dom.parentNode.id != targetElId) {
 
        // Move the element
        dropEl.appendChild(this.el);
 
        // Remove the drag invitation
        this.onDragOut(evtObj, targetElId);
 
        // Clear the styles
        this.el.dom.style.position ='';
        this.el.dom.style.top = '';
        this.el.dom.style.left = '';
    }
    else {
        // This was an invalid drop, initiate a repair
        this.onInvalidDrop();
    }
 
In the above override, the drag element is moved to the drop target element, but only if it is not the same as the drag element's parent node. After the drag element is moved, the styles are cleared from it. If the drop element is the same as the drag element's parent, we ensure a repair operation occurs by calling this.onInvalidDrop. Click to see the completed drop operation in action. Click the above image to see the complete drop operation in action. Upon a successful drop, the drag elements will now will be moved from their parent element to the drop target. How does the user know if they are hovering above a valid drop target? We'll give the user some visual feedback by configuring the drop invitation.

Step 5: Adding drop invitation

In order to make drag and drop a bit more useful, we need to provide feedback to the user on whether or not a drop operation can successfully occur. This means that we'll have to override the onDragEnter and onDragOut methods Add these last two methods to the overrides object.
 
// Only called when the drag element is dragged over the a drop target with the same ddgroup
onDragEnter : function(evtObj, targetElId) {
    // Colorize the drag target if the drag node's parent is not the same as the drop target
    if (targetElId != this.el.dom.parentNode.id) {
        this.el.addClass('dropOK');
    }
    else {
        // Remove the invitation
        this.onDragOut();
    }
},
// Only called when element is dragged out of a dropzone with the same ddgroup
onDragOut : function(evtObj, targetElId) {
    this.el.removeClass('dropOK');
}
 
In the above code, we override the onDragEnter and onDragOut methods, both of which are only utilized when the drag element is interacting with a drop target participating in the same drag drop group. The onDragEnter method is only called when the mouse cursor first intersects the boundaries of a drop target while a drag item is in drag mode. Likewise, onDragOut is called when the mouse cursor is first dragged outside the boundaries of the drop target while in drag mode. Click to see the drop invitation. Click the above image to see the drop invitation. By adding overrides to the onDragEnter and onDragOut methods we can see that the background of the drag element will turn green when the mouse cursor first intersects a valid drop target and will lose its green background when it leaves the drop target or is dropped. This completes our implementation of drag and drop with DOM elements.

It doesn't stop here

Drag and drop can be a can be applied to mostly everything in the Ext JS framework. Here are a few examples that you can use to learn how to implement drag and drop with various widgets:

Summary

Today, we learned how to implement end to end drag and drop of DOM nodes using the first-level drag and drop implementation classes. From a high-level, we defined and discussed what drag and drop is and how to think about it in terms of the framework. We also learned that the drag and drop classes can be grouped by drag or drop behaviors and whether or not they support single or multiple drag or drop operations. While implementing this behavior, we illustrated that the dd classes help make some of the behavioral decisions, and that we are responsible for coding the end-behaviors. We hope you've enjoyed this thorough look at some fundamental drag and drop operations with DOM nodes. We look forward to bringing you more articles about this topic in the future.

There are 61 responses. Add yours.

Salih Gedik

5 years ago

+1 wink Thank you so much for this article smile)

5 Steps to Understanding Drag and Drop with Ext JS

5 years ago

[...] from ExtJS publishe an excellent article about understanding principles of Drag & Drop.  One of the most powerful interaction design [...]

Scott

5 years ago

Thanks for taking the time to explain this with live examples. Being able to clearly see code changes between examples makes this really easy to follow.

David Davis

5 years ago

Great article!

mjoksa

5 years ago

Excellent article. Just keep writing articles like this for everyone to enjoy.
10x

Burn Your To Do List

5 years ago

Thanks to your explanation, i now understand how it works.

TDG innovations LLC » Blog Archive » L

5 years ago

Harley Jones

5 years ago

Excellent article!

Luckyman

5 years ago

Very useful manual.

thx.

Nick

5 years ago

Great Article! Very helpful.

Tweets that mention Ext JS - Blog -- Topsy.com

5 years ago

[...] This post was mentioned on Twitter by Cutter, Jay Garcia, pinoystartup, Radoslav Stankovand others. Radoslav Stankov said: Great DragAndDrop article from ExtJS http://bit.ly/3MnR0d , almost as good as @snookca 's - "Anatomy of a Drag and Drop" http://bit.ly/vCdNq [...]

Ed Spencer

5 years ago

This was much needed - I remember how much difficulty I had finding DnD information when creating Ext Solitaire! Thanks.

??

5 years ago

??????????ext????

Ralph

5 years ago

Very nice write up.  Thank you for sharing your knowledge.  It has helped me tremendously.

Andrea

5 years ago

Hi, I haven’t read the article yet (only bookmarked it for later), but I have checked the demo and, at least with Opera 10 on Windows XP, there is a problem with the position of the proxy you are dragging along: it can be, at times, quite far from the pointer, which is of course confusing. On Firefox 3.5.3 everything is fine.

Jay Garcia

5 years ago

I honestly did not test on Opera 10, as it’s only just above 2% of the market share for 2009 and shows no signs of growing.  I did test this in IE and Firefox.

http://marketshare.hitslink.com/report.aspx?qprid=0


Thank you all for the nice feedback.

nico

5 years ago

Brilliant article - really helpfull. I always missed the big picture of DnD. Thank you!

alldevnet.com

5 years ago

Ext JS - Blog…

One of the most powerful interaction design patterns available to developers is “Drag and Drop.” We utilize Drag and Drop without really giving it much thought – especially when its done right. Here are 5 easy steps to ensure an elegant implementation….

Steven Roussey

5 years ago

Nice to have a DD article! Thanks! It’s one of those things I haven’t used much, but I suppose that will be changing. smile

How is Ext DD going to interact with native HTML5 drag and drop as implemented in Firefox 3.5, for example?

http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dnd
https://developer.mozilla.org/En/DragDrop/Drag_and_Drop

brendan carroll

5 years ago

Nice one Jay.

small dragon

5 years ago

good job. My QQ:285622226

JJ

5 years ago

To understand this manual I red it about 5 times, but thanks for posting Jay.

andreasn

5 years ago

This article will definitally save lifes… wink

jay

5 years ago

nice example

Jay Garcia

5 years ago

@JJ, what did you have difficulty with?

Kevin Guo

5 years ago

Thanks Jay for the post. Would you recommend using multiple drag/drop zones or keep the drag/drop zone number smaller?

nickevin

5 years ago

good job. thx guy.

Bluewing

5 years ago

Join In Ext.My QQ: 43431 .Welcome.

Dhazer

5 years ago

I’m using Firefox 3.0.14, and I noticed that 1 out of 5 drags didn’t stick the thumbnail to my cursor; however when I still ‘dropped’ the the cursor at a target the picture was appended to that container.

Did anyone else happen to notice this?  If so, do you also know why? Thanks

Jay Garcia

5 years ago

@Dhazer,

It’s probably the fact that the animation has not completed.  wait > 1 second or disable animation all together.

Jay Garcia

5 years ago

@Kevin I can’t answer that question, depends on what you’re trying to do.

Sefa Keles

5 years ago

Ext JS community needs this kind of articles. Besides documentation, developers need an insight. Thank you Jay. We appreciate!

auckland escort

5 years ago

This article will definitally save lifes

Alex

5 years ago

Great Article! But how can I reorder or sort the images in the several boxes?

kral oyun

5 years ago

Danke..Being able to clearly see code changes between examples makes this really easy to follow.

laptop

5 years ago

very nice informations. thanks for sharing !

saç ekimi

5 years ago

very useful article. thank you.

j

5 years ago

??????????????

Flavio Cysne

5 years ago

Thank you very much. Great article. The explanation is awesome.

David

5 years ago

Thank you very much for a such a wonderful article. Your illustration of codes are great.

krzysztof

5 years ago

Great support. Keep up good work.

Cheers,

Krzysztof

werbeagentur

5 years ago

Great job! Thanks for your work - this article really threw light upon drag’n drop with ExtJS.

tot2ivn

5 years ago

Thanks for the tut bro.

Ramesh Tatipigari

5 years ago

Thanks for given.

gal

5 years ago

Great tutorial.

is it supported on extjs 2.2?

TDG innovations LLC » Blog Archive » J

5 years ago

[...] and provides daily support on the Ext JS forums. He has authored two articles for JSMagazine, the Ext JS blog and his own blog at http://tdg-i.com He&#8.217;s developed enterprise-level applications using Ext [...]

Three Pillars, Four Geeks and Ext JS | VinylFox

5 years ago

[...] forums as well as free extensions and widgets to the library. He is an author for JSMagazine, the Ext JS blog and his own blog at http://tdg-i.com He&#8.217;s developed enterprise-level applications using Ext [...]

Tyy Ward

5 years ago

Great information!  Thank you very much for sharing.

Yannix

5 years ago

This tutorial rocks. Thank you so much for sharing.

Vida

5 years ago

your illustration codes are ridiculously amazing. Thx for that : )

Granite Slab Countertop bellevue

Pascke

5 years ago

Excellent job

Laverna Reiser

5 years ago

[..] A bit unrelated, but I rather liked this website post [..]

Zay?flama

5 years ago

// Called the instance the element is dragged.
b4StartDrag : function() {
  // Cache the drag element
  if (!this.el) {
      this.el = Ext.get(this.getEl());
  }

  //Cache the original XY Coordinates of the element, we’ll use this later.
  this.originalXY = this.el.getXY();
},
// Called when element is dropped not anything other than a dropzone with the same ddgroup
onInvalidDrop : function() {
  // Set a flag to invoke the animated repair
  this.invalidDrop = true;
},
// Called when the drag operation completes
endDrag : function() {
  // Invoke the animation if the invalidDrop flag is set to true
  if (this.invalidDrop === true) {
      // Remove the drop invitation
      this.el.removeClass(‘dropOK’);

      // Create the animation configuration object
      var animCfgObj = {
        easing   : ‘elasticOut’,
        duration : 1,
        scope   : this,
        callback : function() {
          // Remove the position attribute
          this.el.dom.style.position = ‘’;
        }
      };

      // Apply the repair animation
      this.el.moveTo(this.originalXY[0], this.originalXY[1], animCfgObj);
      delete this.invalidDrop;
  }

},

Diyet

5 years ago

Thanks for this. Really good stuff.

Dantel Modelleri

5 years ago

your illustration codes are ridiculously amazing. Thx for that : )

Diyet

5 years ago

// Called the instance the element is dragged.
b4StartDrag : function() {
  // Cache the drag element
  if (!this.el) {
      this.el = Ext.get(this.getEl());
  }

  //Cache the original XY Coordinates of the element, we’ll use this later.
  this.originalXY = this.el.getXY();
},
// Called when element is dropped not anything other than a dropzone with the same ddgroup
onInvalidDrop : function() {
  // Set a flag to invoke the animated repair
  this.invalidDrop = true;
},
// Called when the drag operation completes
endDrag : function() {
  // Invoke the animation if the invalidDrop flag is set to true
  if (this.invalidDrop === true) {
      // Remove the drop invitation
      this.el.removeClass(‘dropOK’);

      // Create the animation configuration object
      var animCfgObj = {
        easing   : ‘elasticOut’,
        duration : 1,
        scope   : this,
        callback : function() {
          // Remove the position attribute
          this.el.dom.style.position = ‘’;
        }
      };

      // Apply the repair animation
      this.el.moveTo(this.originalXY[0], this.originalXY[1], animCfgObj);
      delete this.invalidDrop;
  }

},

??

5 years ago

what ?i can’t see~

??

5 years ago

. Thank you so much for sharing.

patrick

4 years ago

Sorry, but i don’t understand this code :( its to complicated

dizustu

3 years ago

thank you good newer looks

Comments are Gravatar enabled. Your email address will not be shown.

Commenting is not available in this channel entry.