Drag-and-Drop with the YUI: Part-2

Exclusive offer: get 50% off this eBook here
Learning the Yahoo! User Interface library

Learning the Yahoo! User Interface library — Save 50%

Develop your next generation web applications with the YUI JavaScript development library

$26.99    $13.50
by Dan Wellman | November 2009 | AJAX Open Source Web Development

Read Part One of Drag-and-Drop with the YUI here.

Scripting DragDrop

There are two ways that we could go about achieving our objective. We could take the easy way and treat each draggable object as an independent module, with its own independent properties and event handlers. This would make the code simpler, but would mean that we would require a good deal more of it.

If there are just one or two objects on your page which can be dragged and dropped then this way is fine. However, when you begin to have more than just a couple of objects that can be moved, the amount of code required to handle these objects efficiently increases dramatically.

The second way may be a little more complex and therefore will require a greater degree of understanding. However, this way allows for sharing properties and event handlers across similar drag-and-drop objects. This reduces the overall footprint of your application and saves us an incredible amount of typing.

Creating Individual Drag Objects

We can look at both methods, so you can see just how much work the second way saves us. We'll start with the easy method. Add the following <script> tag directly before the closing </body> tag:

<script type="text/javascript">
//create the namespace object for this example
YAHOO.namespace("yuibook.dd");
//define the setDDs function
YAHOO.yuibook.dd.setDDs = function() {
//detect the useragent
var ua = YAHOO.env.ua;
if (ua.ie != 0) {
ua.data = "ie";
}
//define variables
var basketTotal = 0;
var basketTot = 0;
var Dom = YAHOO.util.Dom;
//create the 3 DragDropProxy objects
var dd1 = new YAHOO.util.DDProxy("prod1");
dd1.isTarget = false;
dd1.scroll = false;
var dd2 = new YAHOO.util.DDProxy("prod2");
dd2.isTarget = false;
dd2.scroll = false;
var dd3 = new YAHOO.util.DDProxy("prod3");
dd3.isTarget = false;
dd3.scroll = false;
var basket = new YAHOO.util.DDTarget("basket");
//define function to call when dd1 starts being

dragged
dd1.startDrag = function() {
//set cursor position to top-left of proxy
dd1.setDelta(0,0);
}
//define function to call when dd1 stops being

dragded
dd1.endDrag = function() {
}
//define function to call when dd1 enters basket
dd1.onDragEnter = function(e, id) {
//has dd1 been dragged into basket?
if (id == "basket") {
//add 1 to the total number of items
basketTotal += 1;
var tot = YAHOO.util.Dom.get("basketTotal")
tot.innerHTML = basketTotal;
//create new p element to hold product summary
var p = document.createElement("p");
Dom.addClass(p, "prod");
p.id = "prod" + basketTotal;
//add icon for product 1 to basket
var ico = document.createElement("img");
ico.setAttribute("src", "images/prod1_ico.jpg");
ico.id = "imageico" + basketTotal;
Dom.addClass(ico, "icon");
//add product 1 summary to basket
p.appendChild(ico);
Dom.get("basketBody").appendChild(p);
//create new p element for product 1 title
var p2 = document.createElement("p");
var info = Dom.get("prod1info");
//is the browser IE?
if (ua.data == "ie") {
var infos = info.innerHTML.split("<BR>");
} else {
var infos = info.innerHTML.split("<br>");
}
//set product 1 title
var title = document.createTextNode(infos[0]);
Dom.addClass(p2, "prodTitle");
p2.id = "prodTitle" + basketTotal;
//add title to basket
p2.appendChild(title);
Dom.insertAfter(p2, Dom.get(ico));
//create p element for product 1 price
var p3 = document.createElement("p");
var price = document.createTextNode(infos[1]);
Dom.addClass(p3, "prodPrice");
p3.id = "prodPrice" + basketTotal;
//add product 1 price to basket
p3.appendChild(price);
Dom.insertAfter(p3, Dom.get(p2));
//update total basket price
price = Dom.get(p3).innerHTML;
var rawPrice = price.slice(1,6);
var cost = parseFloat(rawPrice);
basketTot += cost;
var newBasketTot = Dom.get("basketCost");
newBasketTot.innerHTML = basketTot;
}
}
//define function to call when dd2 starts being

dragged
dd2.startDrag = function() {
dd2.setDelta(0,0);
}
//define function to call when dd2 stops being

dragged
dd2.endDrag = function() {
}
//define function to call when dd2 enters basket
dd2.onDragEnter = function(e, id) {
if (id == "basket") {
//add 1 to the total number of items
basketTotal += 1;
var tot = YAHOO.util.Dom.get("basketTotal")
tot.innerHTML = basketTotal;
//create new p element to hold product summary
var p = document.createElement("p");
Dom.addClass(p, "prod");
p.id = "prod" + basketTotal;
//add icon for product 2 to basket
var ico = document.createElement("img");
ico.setAttribute("src", "images/prod2_ico.jpg");
ico.id = "imageico" + basketTotal;
Dom.addClass(ico, "icon");
//add product 2 summary to basket
p.appendChild(ico);
Dom.get("basketBody").appendChild(p);
//create new p element for product 2 title
var p2 = document.createElement("p");
var info = Dom.get("prod2info");
//is the browser IE?
if (ua.data == "ie") {
var infos = info.innerHTML.split("<BR>");
} else {
var infos = info.innerHTML.split("<br>");
}
//set product 2 title
var title = document.createTextNode(infos[0]);
Dom.addClass(p2, "prodTitle");
p2.id = "prodTitle" + basketTotal;
//add title to basket
p2.appendChild(title);
Dom.insertAfter(p2, Dom.get(ico));
//create p element for product 2 price
var p3 = document.createElement("p");
var price = document.createTextNode(infos[1]);
Dom.addClass(p3, "prodPrice");
p3.id = "prodPrice" + basketTotal;
//add product 2 price to basket
p3.appendChild(price);
Dom.insertAfter(p3, Dom.get(p2));
//update total basket price
price = Dom.get(p3).innerHTML;
var rawPrice = price.slice(1,6);
var cost = parseFloat(rawPrice);
basketTot += cost;
var newBasketTot = Dom.get("basketCost");
newBasketTot.innerHTML = basketTot;
}
}
//define function to call when dd3 starts being

dragged
dd3.startDrag = function() {
dd3.setDelta(0,0);
}
//define function to call when dd3 stops being

dragged
dd3.endDrag = function() {
}
//define function for when dd3 enters basket
dd3.onDragEnter = function(e, id) {
if (id == "basket") {
//add 1 to the total number of items
basketTotal += 1;
var tot = YAHOO.util.Dom.get("basketTotal")
tot.innerHTML = basketTotal;
//create new p element to hold product summary
var p = document.createElement("p");
Dom.addClass(p, "prod");
p.id = "prod" + basketTotal;
//add icon for product 3 to basket
var ico = document.createElement("img");
ico.setAttribute("src", "images/prod3_ico.jpg");
ico.id = "imageico" + basketTotal;
Dom.addClass(ico, "icon");
//add product 3 summary to basket
p.appendChild(ico);
Dom.get("basketBody").appendChild(p);
//create new p element for product 3 title
var p2 = document.createElement("p");
var info = Dom.get("prod3info");
//is the browser IE?
if (ua.data == "ie") {
var infos = info.innerHTML.split("<BR>");
} else {
var infos = info.innerHTML.split("<br>");
}
//set product 3 title
var title = document.createTextNode(infos[0]);
Dom.addClass(p2, "prodTitle");
p2.id = "prodTitle" + basketTotal;
//add title to basket
p2.appendChild(title);
Dom.insertAfter(p2, Dom.get(ico));
//create p element for product 3 price
var p3 = document.createElement("p");
var price = document.createTextNode(infos[1]);
Dom.addClass(p3, "prodPrice");
p3.id = "prodPrice" + basketTotal;
//add product 3 price to basket
p3.appendChild(price);
Dom.insertAfter(p3, Dom.get(p2));
//update total basket price
price = Dom.get(p3).innerHTML;
var rawPrice = price.slice(1,6);
var cost = parseFloat(rawPrice);
basketTot += cost;
var newBasketTot = Dom.get("basketCost");
newBasketTot.innerHTML = basketTot;
}
}
}
//execute setDDs when DOM is ready
YAHOO.util.Event.onDOMReady

(YAHOO.yuibook.dd.setDDs);
</script>

All of the code here sits within the setDDs() function, which is called using the Event utility's onDOMReady() method.

The first section of code within the setDDs() function uses the .env.ua() method of the YAHOO global object to determine the user-agent string of the browsing environment. We can use this as a quick and easy way of detecting the browser being used to view the page.

The ie property exists within the ua object even if IE is not in use, but if it isn't in use, the property is set to 0. Therefore, if the ie property does not equal to 0 it means that IE is the browser currently being used. In this case, the ie property will hold an integer representing the version of IE in use. When IE is being used, we set the data property of our ua variable to the string ie.

We then create a series of variables for use in the script. The last variable is created purely for convenience. We'll be making heavy use of the DOM utility during this example, so defining that first part of the DOM call as a short variable helps make things easier on us.

Next we create all three of the individual DDProxy objects. Remember, in this implementation, each object has to have its own properties and event handlers defined for it. We also define the basket as a DDTarget so that we can make use of the .onDragEnter() method.

Because we want each product to interact only with our shopping basket and not the other products on the page, we have to set the .isTarget property to false. As well as being able to specifically create drop targets using the YAHOO.util.DDTarget class, any drag object is by default also a drop target. Additionally, we can switch off the .scroll property, which causes the viewport to scroll indefinitely when the dragged object exceeds the window boundary.

Using DragDrop Events

Three event handlers are required for this example: startDrag, endDrag, and onDragEnter. The startDrag event fires as soon as the left mouse button has been held down for the required length of time on a drag object, or the pointer moves the specified number of pixels.

The .setDelta() method used in the startDrag event handler allows us to control where the pointer is relative to the drag object, or in this case, the proxy element. By specifying 0,0 as arguments for this method, we are instructing the pointer to appear at the top-left corner of the proxy element. This is needed so that the element is placed back where it began instead of where the pointer was relative to the drag object when the drag began.

The endDrag event fires when a mouseup event is detected on the object being dragged. The anonymous function here has a very special purpose, even though it is just an empty function.What it does is ensure that the item being dragged is returned to its original position once it has been dropped instead of remaining where it was dropped. This is important because if we didn't keep the listing pictures in their proper locations, the page would soon be littered with abandoned drag objects.

The onDragEnter event fires when the moving object is dragged over a legal target. Since the shopping basket is the only valid target on the page, this will fire whenever a product object is dragged over the basket. This function is where the bulk of our code lies.

Two arguments are specified for this function. The first is the event object, which is automatically passed to our handler. The second argument is the id of the element that triggered the event, which in this example will be the basket.

We first use our YAHOO.util.Dom shortcut to get the <span> element displaying the number of items in the basket, then use the innerHTML property to alter this value in accordance with the basketTotal variable.

We create a new paragraph element and give it a class of prod. This new element will act as a container for a short summary of the item placed in the basket. We can go ahead and create the different items that will make up this short description of the product that has been purchased.

A new <img> element is created and stored in the ico variable. Its src attribute is set to the icon representing the product. We then give it a class name so that we can style it and an id attribute so that we can identify it. Once created and configured, we can append the <img> to the new <p> element, and then append the <p> element to the body of the basket. Now let's work on extracting some of the listing description to display in the basket.

We create another new <p> element and give it a class of prodTitle. We then need to do more detection to determine which product was dropped, getting the full listing text of the product in the title variable once we have.

Each part of the listing text is separated by a <br> element, so we can create an array of each different bit of information contained in the listing text using the .split() method. This is the part of the script where we use the information gathered from the ua property earlier on.

IE for some reason capitalizes all HTML elements in the DOM. This means that IE sees <BR> tags, while other browsers see <br>, and is the reason we need the if statement to determine the browser in use.

The first item in the infos array is the name of the product, so we can use this as the title for the product summary and create a new textNode based on it. This title element is then inserted into the DOM directly after the icon element using the DOM utility's .insertAfter() method, which takes the element to insert and the element it should be inserted after as arguments.

For good measure, let's add the price of the product to the product summary. A final

element is created to hold a new textNode comprised of the second item in the infos array, which happens to be the price of the product. The class for the new <p> element is set and it is then inserted as a sibling of the previous element that was created.

At the bottom of our basket is a price indicator showing the total cost of all items in the basket; we can easily update this using the same method as we updated the basket contents total earlier on. We recycle the price variable, updating it with the HTML element from the basket.

As the price is currently a text string rather than a number, we have to convert it. Before we can do this however, we need to remove the currency symbol from the front of it, which we can do with the .slice() method.

Then we can use the standard JavaScript parseFloat math function to convert the remaining string into a true floating-point number. We can then update the current cost by adding the cost of the dropped product to it. Finally, we set the innerHTML property of the basketCost span element to the new total.

As each drag object is completely independent, the rest of our script is made up of identical event handlers tied to the other drag objects (products 2 and 3). This is what bloats our code to three times the size it actually needs to be, and imagine how much typing would need to be done if there were 30 products on the page

Learning the Yahoo! User Interface library Develop your next generation web applications with the YUI JavaScript development library
Published: March 2008
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

Additional CSS

As our basket creates a series of new elements when products are dragged into it, we can add some additional CSS to target and style these new elements. In basket.css, add the following new selectors and rules:

.prod {
position:relative;
height:40px;
width:250px;
padding:5px;
}
.prodTitle {
margin:11px auto auto 5px;
text-align:left;
float:left;
width:100px;
}
.prodPrice {
margin-top:15px;
width:50px;
float:left;
}

The basket should now function as we want it to within the confines of this example. If you run the page and then drag a product into the basket, the basket should be updated accordingly, as in in the screenshot below:

Learning the Yahoo! User Interface library

Extending the DDProxy Object

Now we'll look at using the YAHOO global object's .extend() method to streamline our code and cut down on unnecessary duplication. Instead of having to define properties and event handlers for each DDProxy object individually, we can customize the DDProxy object itself so that every instance of the object has them already built in.

Here is the complete script:

<script type="text/javascript">
//setup the namespace object for this example
YAHOO.namespace("yuibook.dd");
//define the setDDs function
YAHOO.yuibook.dd.setDDs = function() {
//detect the useragent
var ua = YAHOO.env.ua;
if (ua.ie != 0) {
ua.data = "ie";
}
//define variables
var basketTotal = 0;
var basketTot = 0;
var Dom = YAHOO.util.Dom;
//apply the initProd function to all product objects
product = function() {
product.superclass.constructor.apply(this,

arguments);
this.initProd();
}
//extend the DDProxy class
YAHOO.lang.extend(product, YAHOO.util.DDProxy, {
//define the initProd function
initProd: function() {
this.isTarget = false;
this.scroll = false;
},
//define the startDrag function
startDrag: function() {
this.setDelta(0,0);
},
//define the endDrag function
endDrag: function() {
},
//define the onDragEnter function
onDragEnter: function() {
//add 1 to the total number of items
basketTotal += 1;
var tot = Dom.get("basketTotal");
tot.innerHTML = basketTotal;
//create a new p element to hold product info
var p = document.createElement("p");
Dom.addClass(p, "prod");
var ico = document.createElement("img");
//create a new img element and get matching icon
var ico = document.createElement("img");
ico.setAttribute("src", "images/" + this.id +

"_ico.jpg");
Dom.addClass(ico, "icon");
//add product to the basket
p.appendChild(ico);
Dom.get("basketBody").appendChild(p);
//create p element for product title
var p2 = document.createElement("p");
Dom.addClass(p2, "prodTitle");
//get product info
var info = Dom.get(this.id + "info");
//is the browser IE?
if (ua.data == "ie") {
var infos = info.innerHTML.split("<BR>");
} else {
var infos = info.innerHTML.split("<br>");
}
//add product title to basket
var title = document.createTextNode(infos[0]);
p2.appendChild(title);
Dom.insertAfter(p2, Dom.get(ico));
//create p to hold product price
var p3 = document.createElement("p");
var price = document.createTextNode(infos[1]);
Dom.addClass(p3, "prodPrice");
p3.appendChild(price);
//add price to basket
Dom.insertAfter(p3, Dom.get(p2));
price = Dom.get(p3).innerHTML;
//update total basket price
var rawPrice = price.slice(1,6);
var cost = parseFloat(rawPrice);
basketTot += cost;
var newBasketTot = Dom.get("basketCost");
newBasketTot.innerHTML = basketTot;
}
});
//define the products array
var prods = [
new product("prod1"),
new product("prod2"),
new product("prod3"),
];
var basket = new YAHOO.util.DDTarget("basket");
}
//execute setDDs function when the DOM is ready
YAHOO.util.Event.onDOMReady

(YAHOO.yuibook.dd.setDDs);
</script>

The first part of the script is the same, we still enclose our code in the setDDs() function and execute it as soon as the DOM is in a usable state. We still need to detect the user-agent in the same way, and we'll still need to use the same set of variables.

The first new addition defines a product function, which will act as the constructor used to create new product objects. These objects are our customized version of the DDProxy object, so we'll use this function, instead of the YAHOO.util.DDProxy() constructor to produce our drag objects. All this function does is apply the .initProd() method to the existing methods found in the YAHOO.util.DDProxy class. Every time a new product object is created, the .initProd() method will be called.

Next we need to use the .extend() method of the YAHOO global object. The .extend() method takes three arguments: the first is the object being used to do the extending and the second is the class which we want to extend. The third argument is an object literal that consists of a series of 'name:value' pairs which we can use to define the functions, properties, and event handlers specific to our implementation.

The first pair in our literal object is the .initProd() method which we use to set the isTarget and scroll properties. Although we've only configured the .isTarget and .scroll properties once, any newly created product object will inherit them automatically. This is the power of the superclass.

As you can see, we use our custom constructor to create each new drag object.

All we need to pass in is the id of the corresponding element on the page which is to be made draggable, exactly as if we were using the YAHOO.util.DDProxy constructor directly.

We still need to define the shopping basket as a legal target. As we aren't using our custom constructor but the YAHOO.util.DDTarget constructor instead, none of our custom properties we be applied to it and the basket will be a standard drop target for our drag objects.

The event handlers used in this example are the same as before—we still only require startDrag, endDrag, and onDragEnter. This time they are added to our .extend() method so that they are applied to all new drag objects which negates us having to code them for each object individually. This saves us a huge amount of code.

Our main event, onDragEnter, is very similar as before but there are some subtle differences. The part of the script that gets the correct icon image to display in the basket for example, makes use of the this keyword to address the object. This keeps us from having to work with specific file names. When we come to extract the product info for whichever product has been dragged into the basket, we can again use the this .id property to easily obtain the product title and price.

The final difference is the way the drag objects are created. This time we use a neat and efficient array, each item of which creates a new product object using our new product constructor.

The code used in this second method is much, much more efficient. We only have three products on the page, yet the total amount of JavaScript code required to make the first example work was an excessive 252 lines!

In the second example we have just 107 lines of code, and not only do we create all three existing drag objects from the first example, but we can have an unlimited number of additional drag objects. All we need to do for each one is add a new call to the product constructor in our product array. The screenshot below shows the second example in use:

Learning the Yahoo! User Interface library

Although our demonstration pages work as intended and look passable, extensive testing reveals that they are little more than a practical demonstration of the capabilities and considerations of the Drag-and-Drop utility rather than a full solution to an online store.

To turn this into a fully working application that you could use on a live site, far more work would need to be done. For example, there is no notion of state within our test application and essential basket features such as removing items from the basket, or proceeding to some kind of checkout area simply aren't there.

>> Continue Reading Drag-and-Drop with the YUI: Part-3

[ 1 | 2 | 3 ]

 

If you have read this article you may be interested to view :

Learning the Yahoo! User Interface library Develop your next generation web applications with the YUI JavaScript development library
Published: March 2008
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Books From Packt

Ext JS 3.0 Cookbook
Ext JS 3.0 Cookbook

Drupal 6 JavaScript and jQuery
Drupal 6 JavaScript and jQuery

ICEfaces 1.8: Next Generation Enterprise Web Development
ICEfaces 1.8: Next Generation Enterprise Web Development

alt=
Joomla! E-Commerce with VirtueMart

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development

Zend Framework 1.8 Web Application Development
Zend Framework 1.8 Web Application Development

Oracle SOA Suite Developer's Guide
Oracle SOA Suite Developer's Guide

Spring 2.5 Aspect Oriented Programming
Spring 2.5 Aspect Oriented Programming

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software