Drag and Drop


Drag-and-drop functionality is becoming more and more popular and in the right situations it can be extremely useful. With all the new personal data storage applications that are on the Web, this functionality becomes very useful when a user wants to sort elements that contain data. For example, if you are using an application to create a list of things you have to do, you might create the list only to find out you would like to do certain things before others. This could be a real pain in the butt, or it could easily be solvable if there was a way to drag and drop the items in the order you would like, making them into a sortable list. In this section, we will be creating a sortable list of data. Let's get started by creating the object that makes it all happen.

The DragDrop Object

The DragDrop object is a Singleton object that allows HTML elements to have drag-and-drop functionality. This functionality allows lists to become sortable with other elements that include the same class name and parent. This means that if we have a group of elements and we would like them all to be drag and drop and sortable, we would simply call the initialize method of the object and pass a shared class name that all the elements would need to have in common. Here is an example of the HTML we will use for this sample:

<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Drag and Drop</title> <link href="css/dragdrop.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="/books/1/87/1/html/2/javascript/Utilities.js"></script> <script type="text/javascript" src="/books/1/87/1/html/2/javascript/utils/DragDrop.js"></script> </head> <body onload="DragDrop.initialize('dItem');"> <div >     <div  >1</div>     <div  >2</div>     <div  >3</div>     <div  >4</div>     <div  >5</div>     <div  >6</div>     <div  >7</div>     <div  >8</div>     <div  >9</div>     <div  >10</div> </div> </body> </html>


In order to separate our div elements from each other visually, and add a dotted outline to the drag state of elements and a move cursor to the draggable elements, we will write the following CSS and save it to a file called dragdrop.css:

#container {     float: left; } #drag_dummy {     border: #333 1px dotted; } .dItem {     width: 400px;     height: 20px;     background-color: #ccc;     border: #333 1px solid;     margin: 2px;     cursor: move; }


In order to create the DragDrop object, we will start by instantiating and initializing it:

DragDrop = {}; DragDrop.initialize = function(className) {     DragDrop.className = className;     DragDrop.currentItem = '';     document.onmousedown = DragDrop.onmousedown;     document.onmouseup = DragDrop.onmouseup; }


This method accepts the shared class name for the drag-and-drop elements and sets it to an object variable for later reference. Then it creates a new property called currentItem and sets it to an empty string so that we can later set it to the element that is currently being dragged in order to reference it during a drag-and-drop occurrence. Last, we set two events to drag-and-drop events. These include the onmousedown and onmouseup events, which are set to the onmousedown and onmouseup methods in the DragDrop object. These two methods will fire every time there is a mousedown or mouseup event, but we will check to make sure the event is resonating from a drag element in order to keep the calls under control. The way in which we will verify that a drag item is being clicked is by taking the received event objectwhich is passed to our methods by default since they are fired by eventsand checking whether an event object exists. After we verify that one exists, we check the target, which is the source of the event, to see whether its class name is equal to the class name that we set in the initialize method. If it is, we know we have a drag item that is being triggered and we move forward with the startDrag or stopDrag actions. Here is the code that handles firing the events, checking the elements, and calling the start and stop drag methods:

 DragDrop.onmousedown = function(evt) {     var evt = DragDrop.getEvent(evt);     if(evt != null)     {         var t = DragDrop.getTarget(evt);         if(t.className == DragDrop.className)         {             if (!t.isDragging)             {                 DragDrop.startDrag(t, evt.clientX, evt.clientY);             }         }     } } DragDrop.onmouseup = function(evt) {     var evt = DragDrop.getEvent(evt);     if(evt != null)     {         var t = DragDrop.getTarget(evt);         if(t.className == DragDrop.className +' dragging')         {             DragDrop.stopDrag(t);         }     } } DragDrop.getEvent = function(evt) {     if(!evt)     {         var evt = window.event;     }     return evt; } DragDrop.getTarget = function(evt) {     return t = (evt.target) ? evt.target : evt.srcElement; }


You are probably wondering how we fire the mousedown event. This will occur at the end of the startDrag method where we will add a listener to the document's mousemove event rather than setting it by default. Setting it by default would mean the event would be fired each time the mouse moved and while it was moving, which would be a lot of method calling.

The startDrag method accepts three parameters. The parameters are the HTML element itself, and the x and y positions of the mouse, which are gathered by the mousedown event object. The first thing we do in this method is set a few variables for later use. These properties are called orgParent, currentItem, and isDragging. orgParent is set to the parentNode of the drag element when the startDrag event is fired because we will need it in the dragTo method, and the drag item will not have the same parent at that time. The other two properties are currentItem and isDragging, which are fairly self-explanatory.

The next section of code gathers the coordinates and size of the current element and sets two properties called dragOffsetX and dragOffsetY, which will be used in the dragTo method to keep the element's coordinates relevant to the mouse coordinates while it is moving. The next group is a bit more interesting and creates a nice effect for the drag functionality. We will create a new element on the fly and give it an id value of drag_dummy. This element will take on the coordinates of the current drag element and eventually replace it. Before we replace it, though, we need to convert the current element to an absolute position, set its coordinates to absolute values and, as an extra effect, also set the alpha to 25% so that when the element is being dragged, it will allow us to view its position over the other elements. Adding this effect makes it seem as though there is depth to the page and makes the drag-and-drop more realistic. Now comes the replacement code, which places drag_dummy in place of the current drag item. The last thing we will do is add the listener for the mousemove event and set it to a method called dragTo, as was mentioned earlier. The following is the code for the startDrag method. It may seem like a lot at first glance, but most of it is code for gathering coordinates for the drag_dummy element.

DragDrop.startDrag = function(_this, mouseX, mouseY) {      this.orgParent = _this.parentNode;     DragDrop.currentItem = _this;      this.isDragging = true;     // Get coordinates     var pos = Utilities.getXY(_this);     var x = pos.x;     var y = pos.y;     var w = _this.offsetWidth;     var h = _this.offsetHeight;      this.dragOffsetX = mouseX - x;      this.dragOffsetY = mouseY - y;     // Create dummy     var dummy = Utilities.createElement("div", {id:'drag_dummy'});     dummy.style.height = (h) + 'px';     dummy.style.width = (w) +'px';     Utilities.appendChild(document.body, dummy);     // Convert to drag class      this.className = DragDrop.className+' dragging';      this.style.position = 'absolute';      this.style.left = x + 'px';      this.style.top = y + 'px';      this.style.width = w + 'px';      this.style.height = h + 'px';     Utilities.changeOpac(25, _this.id);     // Replace with dummy      this.parentNode.replaceChild(dummy, _this);     document.body.appendChild(_this);     Utilities.addListener(document, "mousemove", DragDrop.dragTo); }


The dragTo method is triggered by the mousemove event, so it accepts an event object as its parameter by default. The first group of code in this method gathers the current drag element and gets the mouse x and y positions. Once we gather this information, we can use it moving forward. The first piece of code that will use these properties will be when we set the left and top style properties for the current element to the current mouse positions. Then we will subtract the dragOffset values we set in the startDrag method so that the element moves to the current position. Next, we will create a dummy variable that is equivalent to the drag_dummy element so that we can use it to place the current drag element later in the method. In order to place the element in a new position, we need to first figure out where the element is located and where it should be placed when it is dropped. We will do this by calling the getNewPositionElement method, which will iterate through the orgParent we set in the startDrag method and figure out which sibling the current element is over, based on the mouse x and y properties and their relation to the elements. After we find the correct placement, we will receive the element in that place and insert our drag element before it. If something happens to go wrong, we will simply append it back to the parent and not worry about the exact placement. The following is the dragTo method in its entirety, plus the getNewPositionElement method for calculating the placement:

DragDrop.dragTo = function(evt) {      this = DragDrop.currentItem;     var evt = DragDrop.getEvent(evt);     mouseX = evt.clientX;     mouseY = evt.clientY;      this.style.left = (mouseX - _this.dragOffsetX) + 'px';      this.style.top = (mouseY - _this.dragOffsetY) + 'px';     var dummy = Utilities.getElement('drag_dummy');     var el = DragDrop.getNewPositionElement(_this, mouseX, mouseY);     if(el != null)     {         el.parentNode.insertBefore(dummy, el);     }     else     {          this.orgParent.appendChild(dummy);     } } DragDrop.getNewPositionElement = function(_this, mx, my) {     var target = null;     var y = null;     var ly = null;     var p = _this.orgParent;     for(var i in p.childNodes)     {         if(p.childNodes[i] != undefined)         {             if(p.childNodes[i].id != 'drag_dummy' && p.childNodes[i].id != undefined)             {                 var pos = Utilities.getXY(p.childNodes[i]);                 y = pos.y;                 h = p.childNodes[i].offsetHeight;                 if(my<(y+h) && (target == null || y < ly))                 {                     target = p.childNodes[i];                     ly = y;                 }             }         }     }     return target; }


Now that we are able to drag elements, we need a way to stop the drag. The stopDrag method will be used and is what we set in the beginning when we added the mouseup event in the intialize method. This method simply sets the current drag element's class name back to its original class name in order to return the element back to the way it started. Next we get the drag_dummy element and replace it with the actual element that was being dragged. This returns everything back to normalcy, with the dragged element in the new position. After the element is in place, we reset all the rest of its properties. This includes all its styles and the isDragging Boolean. Last, we remove the listener we implemented for the mousemove event, change the opacity back to 100%, and reset the currentItem to nothing.

DragDrop.stopDrag = function(_this) {      this.className = DragDrop.className;     var dummy = Utilities.getElement('drag_dummy');     dummy.parentNode.replaceChild(_this, dummy);      this.isDragging = false;      this.style.position = '';      this.style.left = '';      this.style.top = '';      this.style.width = '';      this.style.height = '';     Utilities.removeListener(document, "mousemove", DragDrop.dragTo);     Utilities.changeOpac(100, _this.id);     DragDrop.currentItem = ''; }


This object may be a bit complex, but now that it has been created it can be used without ever touching the code again. But, of course, we could always add more code to create more functionality, such as adding a sort method to the object that would reset the order of the items. Although we have not integrated this object with Ajax, it can easily be done to save the positions of elements in a database for later retrieval. This is a common theme in a lot of new web applications because it lends itself well to personalizing user data as we have done in this section.



Ajax for Web Application Developers
Ajax for Web Application Developers
ISBN: 0672329123
EAN: 2147483647
Year: 2007
Pages: 129
Authors: Kris Hadlock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net