After ruling out the library, I dove right into the drag and drop code. I used Firebug to put several debugging lines to help understand the flow of how the drag and drop worked. Basically I put a console.log() call at the beginning of every function.

Within a few minutes I had narrowed down the problem to two functions: show() and isAffected().

After a bit more review it was clearly a combination of both. Inside the show() function it was looping through each one of my droppable elements and calling isAffected which returned true or false if the draggable element was touching the droppable.

I did some quick math once I found out that each droppable was being checked and found at that because it's a weekly calendar there is 672 droppable elements (7 days * 24 hours * 4 [15 minute increments]). Clearly there has to be a better way.

The next few things was a bunch of trial and error. I found that the draggable element passes in an X and Y of where the mouse currently is. Using this, I created a calculation to determine which 15 minute increment droppable block was being hovered over.

Inside of dragdrop.js here is the original show function that is slow:

```
show: function(point, element) {
if(!this.drops.length) return;
if(this.last_active) this.deactivate(this.last_active);
this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop)) {
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if(drop.greedy) {
Droppables.activate(drop);
throw $break;
}
}
});
```

My updated dragdrop.js show function is:

```
show: function(point, element) {
if(!this.drops.length) return;
if(this.last_active) this.deactivate(this.last_active);
// Don't loop, call isAffected
// isAffected will return the id of the element being dropped on
// if null, we couldn't find it
var drop = Droppables.isAffected(point, element);
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if(drop.greedy) {
Droppables.activate(drop);
//throw $break;
}
},
```

Below is my updated isAffected function:

```
isAffected: function(point, element) {
// Step 1, determine column
// x position / colWidth
// Step 2, determine row
// y position / rowWidth
var column = Math.floor((point[0] + this.drops[0].gridStartLeft) / this.drops[0].colWidth);
// Get the original top position of the element
var origTop = Droppables.getOffSetTop(element);
origTop -= this.drops[0].gridStart;
var row = Math.ceil((origTop) / 13) + 4;
// (column - 1) * 24 * 4 + row
var pos = (column - 1) * 24 * 4 + row;
return this.drops[pos];
},
```

*As a note, this will not work 100% out of the box for you, it will require some tweaking of variables.*

So, let's break it down a bit. The first thing it does is take the X position of the mouse and adds "gridStartLeft". "gridStartLeft" is a pixel value from the left of the browser to the start of my calendar grid. "colWidth" contains my fixed value of how wide each column is. The following returns a number between 1 and 7.

Next up, we need to do a similar thing with the Y position. The first thing we need to do is determine where our grid starts. Below is the getOffSetTop function:

```
getOffSetTop: function(el) {
var ot=el.offsetTop;
while((el=el.offsetParent) != null) { ot += el.offsetTop; }
return ot;
},
```

Now we have a pixel value of where our column starts. Now we remove the grid start from it. "gridStart" is how many pixels from the top of the browser to where our grid starts.

Now our all important calculation to get the row number. The value "13" in the division is how many pixels tall my 15 minute increments are, this should be changed to match yours.

With the two calculations done our column variable contains a number between 1 and 7 and our row variable contains a number between 1 and 96. The last step is to perform our calculation to determine where in our 672 element JavaScript array does it fit. The second last line performs this calculation and the last one returns the droppable object that is affected by our draggable element being moved over it.

Once you are done modifying the dragdrop.js file you will also need to update where you instantiate your droppable elements to include new parameters for "gridStartLeft" and "colWidth".

After applying these tweaks the 5 second delay before dragging completely disappeared and now my calendar is as fast as Google Calendar!

Thanks for reading and hopefully this will save you one big headache and make a very happy client!

Published on Feb 18, 2009

Tags: Javascript
| Optimization
| scriptaculous
| JavaScript
| jQuery Tutorial