﻿// Initialize mouse event handlers
document.onmousemove = mouseMove;
document.onmouseup   = mouseUp;
window.onscroll = mouseMove;

function dragDrop__addLoadEvent(func)
{
    var oldonload = window.onload;
    if (typeof window.onload != 'function')
    {
        window.onload = func;
    } else {
        window.onload = function()
        {
            if (oldonload)
                oldonload();
            func();
        }
    }
}

dragDrop__addLoadEvent(function()
{
    for(i in dropZones)
    {
        for(var j = 0; j < dropZones[i].length; j++)
            dropZones[i][j].initDragItems();
    }
}
);

// Some global constants

var DROPZONE_TARGET_CLASS = "dropZoneTarget";
var DROPZONE_OVER_CLASS = "dropZoneOver";
var DROPZONE_PARENT_OVER_CLASS = "dropZoneParentOver";
var DRAGITEM_VERTICAL_POSITION = "top";
var DRAGITEM_DRAW_IN_PLACE = "true";
var DRAGITEM_POSTBACK_TO = "[DRAGITEMNAME]";
var DRAGITEM_POSTBACK_ARGS = "move,[DROPZONENAME],[INDEX]";

// Global bools to track the current state of the mouse button
var iMouseDown = false;
var lMouseState = false;

// A global variable to maintain a reference to the div element which a
// drag item will be cloned to upon a drag
var dragHelper = null;

var mouseOffset = null;

// Global variables to hold references to the DragItem object currently being dragged
var curTarget = null;
var lastTarget = null;

// Global variables to track where a drag item is moved to, used for ASP.NET postback
var newZone = null;
var newIndex = null;

// Global array to store collections of drop zones
var dropZones = [];

/*=== DragItem object ===*\

 This object wraps a DOM
 object and makes it
 capable of being dragged

\*===      START      ===*/

function recursiveFindHandle(oParent)
{
    var output = null;
    for(var i=0; i<oParent.childNodes.length; i++)
    {
        if(oParent.childNodes[i].nodeName == "#text") continue;
    
        if(oParent.childNodes[i].getAttribute('DragHandle'))
            output = oParent.childNodes[i];
        else if(!oParent.childNodes[i].getAttribute('DragItem'))
            output = recursiveFindHandle(oParent.childNodes[i]);
            
        if(null != output)
            break;
    }
    return output;
}

function DragItem(oItem,oZone) {

    // Initialize variables to give drag item awareness
    // of its location in its parent dropzone

    this.dropZone = oZone;
    this.index = oZone.dragItems.length;
    
    this.inZone = true;
    
    // Initialize reference to physical DOM object representing the drag item
    
    this.obj = oItem;
    this.obj.setAttribute('DropZone',oZone.index);
    
    // Initialise class members specified in markup
    this.doPostback = this.obj.getAttribute('doPostback');
    this.drawInPlace = (this.obj.getAttribute('drawInPlace') != 'false');
    if(this.obj.getAttribute('drawInPlace') == null)
        this.drawInPlace = DRAGITEM_DRAW_IN_PLACE;
    this.verticalPosition = this.obj.getAttribute('dragItemVerticalPosition');
    if(!this.verticalPosition)
        this.verticalPosition = DRAGITEM_VERTICAL_POSITION;
        
    this.postbackTo = this.obj.getAttribute('postbackTo');
    if(!this.postbackTo)
        this.postbackTo = DRAGITEM_POSTBACK_TO;
        
    this.postbackArgs = this.obj.getAttribute('postbackArgs');
    if(!this.postbackArgs)
        this.postbackArgs = DRAGITEM_POSTBACK_ARGS;
            
    // Identify the handle of the drag item where one exists. Handles are given
    // a custom attribute called "DragHandle" set to true in the markup.

    /*for(var i=0; i<this.obj.childNodes.length; i++)
    {
        if(this.obj.childNodes[i].nodeName == "#text") continue;
    
        if(this.obj.childNodes[i].getAttribute('DragHandle'))
        {
            this.handle = this.obj.childNodes[i];
            break;
        }
    }*/
    
    this.handle = recursiveFindHandle(this.obj);
    
    // If we don't have a handle, make the drag item itself the handle.
    if (!this.handle)
        this.handle = this.obj;
        
    /*==== DragItem Methods ====*/
     
    // The StartMove method is called when the handle is clicked on        
    this.StartMove = function(ev){
    
        ev = ev || window.event;                 // establish a cross-browser reference to the calling event
        var target = ev.target || ev.srcElement; // and the object that was clicked on

        // There is a possibility that the handle may contain child elements
        // We only want to move if the handle itself was clicked on
        if(target != this.handle && (target.tagName == 'A' || target.tagName == 'INPUT' || target.onclick != null || target.onmousedown != null))
            return false;
    
        // Itialize relevant globals
        
        iMouseDown = true; // we know the mouse has been clicked
        curTarget = this; // We are dragging this DragItem
        mouseOffset = getMouseOffset(this.obj, ev); // Establish where we clicked in relation to the object
        
        // Remove the DragItem from its current location in its dropZone
        this.dropZone.dragItems.splice(this.index,1);
        for(var i = this.index; i < this.dropZone.dragItems.length; i++)
            this.dropZone.dragItems[i].index = i;
            
            
        // Establish root values so the object can be returned to its current DropZone
        // with the returnToRoot method if it is dropped in an invalid new location
        this.rootSibling = this.obj.nextSibling;
        this.rootParent = this.obj.parentNode;
        this.rootZone = this.dropZone;
        this.rootIndex = this.index;
        this.rootClass = this.obj.className;
        
        // Update our position snapshot
        this.setPositionAttributes();
        
        // Loop through all DropZones in the same group as the zone we are
        // dragging from and make them valid targets
        for(var i = 0; i <dropZones[this.rootZone.group].length; i++)
        {
            dropZones[this.rootZone.group][i].makeTarget();
        }
        
        // Place a clone of the current DragItem object in the dragHelper div object        
        initDragHelper(this);
        
        // Hide the object being dragged and prevent it from being drawn
        this.obj.style.visibility = 'hidden';
        this.obj.style.display = 'none';

        return false;
    }
    
    this.handle.onmousedown = funcPointerWithEvent(this,this.StartMove);
    
    this.StopMove = function()
    {
        // if our object is not being drawn, we have not been
        // dropped in a valid location so we return to our root
        if(!this.inZone)
        {
            this.returnToRoot();
        }else
        {
            // we are in a valid drop location so move to our new zone
            this.moveToZone(newZone,newIndex);
            
            // do a call to ASP.NET's __doPostBack if specified in markup
            // will do a postback from the DragItem object with the argument
            // "move,<DropZoneObjName>,<DropZoneIndex>" to be handled server-side
            if(this.doPostback)
            {
                var args = this.postbackArgs.replace("[DRAGITEMNAME]",curTarget.obj.getAttribute("name"));             
                args = args.replace("[DROPZONENAME]",newZone.obj.getAttribute("name"));
                args = args.replace("[INDEX]",newIndex);
                
                var to = this.postbackTo.replace("[DRAGITEMNAME]",curTarget.obj.getAttribute("name"));             
                to = to.replace("[DROPZONENAME]",newZone.obj.getAttribute("name"));
                to = to.replace("[INDEX]",newIndex);
                __doPostBack(to,args);
            }
        }

        // We are no longer being dragged so make our
        // object visible and stop drawing the drag helper
        this.obj.style.visibility = '';
        this.inZone = true;
        dragHelper.style.display='none';
        
        // Loop through all DropZones in the same group as the zone we are
        // dropped in and let them know they are no longer valid targets
        for(var i = 0; i <dropZones[this.dropZone.group].length; i++)
        {
            dropZones[this.dropZone.group][i].cancelTarget();
        }
    }
    
    // The returnToRoot method is called when an object is dropped
    // and a new location cannot be established
    this.returnToRoot = function()
    {
        // Check we had a parent to return to
        if(this.rootParent)
        {
            // We have a parent. If we had a sibling element after us then insert
            // ourselves before it again
            if(this.rootSibling){
                this.rootParent.insertBefore(this.obj,this.rootSibling);
            // Otherwise we were the last element
            // so we add ourselves to the end of the collection
            }else{
                this.rootParent.appendChild(this.obj);
            }
            
            // Add ourselves to the appropriate DropZone object again            
            this.moveToZone(this.rootZone,this.rootIndex);
            
            // Make sure we are visible
            this.obj.style.display = '';
            this.inZone = true;
        }
    }
    
    // This is a helper method which stores the original location of the object
    // in DOM attributes to save repeated calls to getPosition and is called whenever
    // a new drag event is started
    this.setPositionAttributes = function(){
    
        // Get the current position of the object
        var pos = getPosition(this.obj);
        
        // update the object with custom attributes to represent its location
        with(this.obj){
            setAttribute('startPosX',parseInt(pos.x));
            setAttribute('startPosY',parseInt(pos.y));
            setAttribute('startHeight',parseInt(offsetHeight));
            setAttribute('startWidth',parseInt(offsetWidth));
        }
    }
    
    // This is a helper method to access the position of the object
    // when it has been stored as custom attributes
    this.getPositionAttributes = function()
    {
        var xVal = parseInt(this.obj.getAttribute('startPosX'));
        var yVal = parseInt(this.obj.getAttribute('startPosY'));
        var heightVal = parseInt(this.obj.getAttribute('startHeight'));
        var widthVal = parseInt(this.obj.getAttribute('startWidth'));

        return { x:xVal, y:yVal, h:heightVal, w:widthVal };
    }
    
    // A method to move the DragItem instance to a specified zone and index    
    this.moveToZone = function(iZone,index)
    {
        // if the specified zone has DragItems in its collection after where
        // we want to be inserted, splice ourselves into the collection and 
        // update our own record of our index to match
        if(iZone.dragItems[index])
        {
            iZone.dragItems.splice(index,0,this)
            this.index = index;
        // else add ourselves to the end of the collection and update index
        }else{
            this.index = iZone.dragItems.length;
            iZone.dragItems.push(this);
        }
        
        // update the index records of DragItems we may have displaced accordingly
        for(var i = this.index + 1; i < iZone.dragItems.length; i++)
            iZone.dragItems[i].index = i;
        
        // update our own reference of our parent DropZone
        this.dropZone = iZone;
    }
}

/*===       END       ===*\
\*=== DragItem object ===*/


/*=== DropZone object ===*\

 This object wraps a DOM
 object and makes it
 capable of having
 draggable objects dropped
 on it. It also maintains
 a collection of DragItems
 currently in the zone.

\*===      START      ===*/

function DropZone(oZone, iGroup)
{
    // In weird JS OOP terms, this is the constructor
    // Yay for lax JS array handling. If we don't have a global dropZone
    // collection for the specified group, create one
    if (!dropZones[iGroup])
        dropZones[iGroup] = [];
        
    // Add ourselves to the end of the spcified dropZone
    // collection and update our index
    this.index = dropZones[iGroup].length;
    dropZones[iGroup][this.index] = this;
    
    // initialize our internal collection of DragItems    
    this.dragItems = [];
    
    // maintain an internal reference to the group of DropZones 
    // we belong to. The global dropZones[iGroup] will contain a
    // collection of all DropZones in iGroup
    this.group = iGroup;
    
    // Initialize our internal reference to the physical DOM object
    // representing the DropZone    
    this.obj = oZone;
    this.obj.setAttribute('DropGroup',iGroup);
    
    // local bool to track whether the Drop Zone is active or not
    this.isActive = false;
    
    // Initialise class members specified in markup or else set from constants
    this.changeClassOnTarget = this.obj.getAttribute('changeClassOnTarget');
    
    this.dropTargetClass = this.obj.getAttribute('dropTargetClass');
    if(!this.dropTargetClass)
        this.dropTargetClass = DROPZONE_TARGET_CLASS;
        
    this.changeClassOnOver = this.obj.getAttribute('changeClassOnOver');
    
    this.dropOverClass = this.obj.getAttribute('dropOverClass');
    if(!this.dropOverClass)
        this.dropOverClass = DROPZONE_OVER_CLASS;
    
    this.dropParent = this.obj.getAttribute('dropParent');
    if(this.dropParent)
        this.dropParent = document.getElementById(this.dropParent);
        
    this.changeParentClassOnOver = this.obj.getAttribute('changeParentClassOnOver');
    
    this.dropParentOverClass = this.obj.getAttribute('dropParentOverClass');
    if(!this.dropParentOverClass)
        this.dropParentOverClass = DROPZONE_PARENT_OVER_CLASS;
    
    this.constrainHelperXPos = this.obj.getAttribute('constrainHelperXPos');
    
    this.zIndex = parseInt(this.obj.getAttribute('dropZoneZIndex'));
    
    // Create DragItem instances for all child nodes of the DOM object
    // with the custom attribute "DragItem" set to true and add them to
    // our internal collection
    this.initDragItems = function()
    {
        for(var i=0; i<this.obj.childNodes.length; i++){
            if(this.obj.childNodes[i].nodeName == "#text") continue;
            
            if(this.obj.childNodes[i].getAttribute('DragItem'))
                this.dragItems.push(new DragItem(this.obj.childNodes[i],this));
        }
    }
    
    /*==== DropZone Methods ====*/
    
    // The appendClass method stores the original css class of the object
    // in a local member and adds a specified value to the end of the class
    this.appendClass = function(val)
    {
        // Store the initial class of the object in the rootClass member
        // if it is not already initialised.
        if(!this.rootClass)
            this.rootClass = this.obj.className;
        if(!this.rootClass)
            this.rootClass = " ";
            
        // Append the specified value to the end of the DOM object class string
        this.obj.className += " " + val;
    }
    
    // The resetClass method works in conjuction with the appendClass method
    // and returns the DOM object class string to its original value from the
    // rootClass member if it is initialised. It then resets the rootClass
    // member so the process can be repeated
    this.resetClass = function()
    {
        if(this.rootClass)
        {
            this.obj.className = this.rootClass;
            this.rootClass = null;
        }
    }
    
    // makeTarget is called when we start to drag a DragItem and a DropZone
    // is a valid target. It is used to update the class of the DropZone
    // if necessary
    this.makeTarget = function()
    {
        // If we should change class when we becokme a target, do so
        if(this.changeClassOnTarget)
            this.appendClass(this.dropTargetClass);

        // Refresh the snapshot of the position of the
        // DOM object and child DragItems
        this.setPositionAttributes();
    }
    
    // cancelTarget is called when an associated DragItem is dropped and
    // we are no longer a valid target
    this.cancelTarget = function()
    {
        this.cancelActive();
        this.resetClass();
    }
    
    this.makeActive = function()
    {
        if (!this.isActive)
        {
            if(this.changeClassOnOver)
                this.appendClass(this.dropOverClass);
                
            if(this.changeParentClassOnOver && this.dropParent){
                var className = this.dropParent.className || ' ';
                this.dropParent.setAttribute("rootClass",className);
                this.dropParent.className += " " + this.dropParentOverClass;
            }
            
            /*for(var i = 0; i <dropZones[this.group].length; i++)
            {
                //alert("hello");
                dropZones[this.group][i].setPositionAttributes();
            }*/
        }
        this.isActive = true;
    }
    
    this.cancelActive = function()
    {
        if(this.isActive)
        {
            this.resetClass();

            if(this.changeClassOnTarget)
                this.appendClass(this.dropTargetClass);
            
            if(this.dropParent)
            {
                if(this.dropParent.getAttribute("rootClass"))
                {
                    this.dropParent.className = this.dropParent.getAttribute("rootClass");
                    this.dropParent.removeAttribute("rootClass");
                }
            }
        }
        this.isActive = false;
    }
    
    // setPositionAttributes is a helper method which stores the original location of
    // the object in DOM attributes to save repeated calls to getPosition and is
    // called whenever a new drag event is started
    this.setPositionAttributes = function(){
    
        // Get our current position
        var pos = getPosition(this.obj);
        
        // Update our own attributes
        with(this.obj){
            setAttribute('startPosX',parseInt(pos.x));
            setAttribute('startPosY',parseInt(pos.y));
            setAttribute('startHeight',parseInt(offsetHeight));
            setAttribute('startWidth',parseInt(offsetWidth));
            setAttribute('startClass',className);	
        }
        
        // Call the setPositionAttributes of all the DragItems in our internal collection
        for(var i = 0; i < this.dragItems.length; i++)
        {
            this.dragItems[i].setPositionAttributes();
        }
    }
    
    // getPositionAttributes simply returns the position of the object from its custom
    // attributes from when they were last set with setPositionAttributes
    this.getPositionAttributes = function()
    {
        var xVal = parseInt(this.obj.getAttribute('startPosX'));
        var yVal = parseInt(this.obj.getAttribute('startPosY'));
        var heightVal = parseInt(this.obj.getAttribute('startHeight'));
        var widthVal = parseInt(this.obj.getAttribute('startWidth'));

        return { x:xVal, y:yVal, h:heightVal, w:widthVal };
    }
}

/*===       END       ===*\
\*=== DragItem object ===*/

// funcPointer is a helper function which takes an object and function as arguments
// and returns a function which will execute the given method as if it were executed
// from the context of the given object.

// It is utilized by DragItem so that the click event of the drag handle can call the
// StartMove method of DragItem as if the DragItem instance itself was calling it.

// JavaScript is... interesting. :-P
function funcPointerWithEvent(context,method){
    return function(ev){method.apply(context,arguments)};
}

// mouseCoords is a helper function to retrieve the coordinates of the mouse pointer
// from the event object for all (modern) browsers (that I can be bothered to support)
function mouseCoords(ev){
    if(ev.pageX || ev.pageY){
        return {x:ev.pageX, y:ev.pageY};
    }
    
    if (document.documentElement && document.documentElement.scrollTop)
	    scrollTop = document.documentElement.scrollTop;
    else if (document.body)
	    scrollTop = document.body.scrollTop
    
    if (document.documentElement && document.documentElement.scrollLeft)
	    scrollLeft = document.documentElement.scrollLeft;
    else if (document.body)
	    scrollLeft = document.body.scrollLeft
    
    if (document.documentElement && document.documentElement.clientLeft)
	    clientLeft = document.documentElement.clientLeft;
    else if (document.body)
	    clientLeft = document.body.clientLeft
    
    if (document.documentElement && document.documentElement.clientTop)
	    clientTop = document.documentElement.clientTop;
    else if (document.body)
	    clientTop = document.body.clientTop
    
    return {
        x:ev.clientX + scrollLeft - clientLeft,
        y:ev.clientY + scrollTop  - clientTop
    };
}

// getMouseOffset returns the position of the mouse pointer relative to a given object
function getMouseOffset(target, ev){
    ev = ev || window.event;

    // utilise the mouseCoords and getPosition functions
    // and perform arithmetic to return an offset
    var docPos    = getPosition(target);
    var mousePos  = mouseCoords(ev);
    return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
}

// getPosition returns the absolute position of a target DOM object
// by recursively adding the offset position of the object and any
// offset parents
function getPosition(target){

    // initialise local variables to track our total offset
    var left = 0;
    var top  = 0;

    while (target.offsetParent){
        // while the target object has an offset parent
        // add its offset to our total offset values
        left += target.offsetLeft;
        top  += target.offsetTop;
        
        // target the next offset parent in the DOM chain and repeat
        target = target.offsetParent;
    }

    // there are no more offset parents but the target may
    // still be offset so we add the final offset values
    left += target.offsetLeft;
    top  += target.offsetTop;

    // and return the total offset as coordinates in relation to the page origin
    return {x:left, y:top};
}

// initDragHelper creates a helper div which will contain a clone of the object
// currently being dragged which will appear under the cursor in the event of a drag.
var debug;
function initDragHelper(dragItem)
{
    // If the dragHelper is not initialised then
    // create it and add it to the DOM tree
    if (!dragHelper)
    {
        dragHelper = document.createElement('DIV');
        dragHelper.className = 'dragHelperDiv';
        dragHelper.style.cssText = 'position:absolute;display:none;';
        debug = document.createElement('DIV');
        document.body.appendChild(dragHelper);
    }
    
    // Remove any child elements the dragHelper may have so we can start afresh
    for(var i=0; i<dragHelper.childNodes.length; i++)
        dragHelper.removeChild(dragHelper.childNodes[i]);
    
    // Add a clone of the DOM object for the
    // DragItem we are dragging into the helper div
    dragHelper.appendChild(dragItem.obj.cloneNode(true));
    dragHelper.appendChild(debug);
    
    // make sure the helper div is displayed and initialize its width
    dragHelper.style.display = 'block';
    dragHelper.style.width = dragItem.obj.offsetWidth + 'px';
}

// mouseMove is The Big One and handles the onmousemove event
function mouseMove(ev){

    // establish a browser independent reference to the calling event
    ev = ev || window.event;
    
    // find out where the mouse moved to
    var mousePos = mouseCoords(ev);
    
    // establish a browser independent reference to the object the cursor is over
    var target = ev.target || ev.srcElement;
    
    // Not specific to Drag-N-Drop, we check if the mouse has moved over a new element
    // if it has, we check whether the new target element has a custom "overClass"
    // attribute set in the markup. If it does, we switch the class string of the target
    // element appropriately and store its old value in a new "rootClass" attribute
    
    // This "rootClass" attribute is used to check whether the previous target needs to
    // have its class reset, and restores it if necessary
    
    // Seeing as we are handling the onmousemove event anyway, we may as well add this
    if (target != lastTarget && lastTarget != null)
    {
        if(lastTarget.getAttribute("rootClass"))
        {
            lastTarget.className = lastTarget.getAttribute("rootClass");
            lastTarget.removeAttribute("rootClass");
        }
        if(target != null && target.getAttribute("overClass"))
        {
            var className = target.className || ' ';
            target.setAttribute("rootClass",className);
            target.className = target.getAttribute("overClass");
        } 
    }
    
    // if a drag handle has been clicked and StartMove of a DragItem has been called
    // the curTarget will contain a reference to the DragItem being dragged
    if(curTarget)
    {
    
        //debug.innerText = document.documentElement.scrollLeft;
        // Compare the current mouse state to the previous mouse state to establish
        // whether this is the initial click of the mouse
        if(iMouseDown && !lMouseState)
        {
            // May be necessary to call stuff here            
        }
        
        // Position the dragHelper under the mouse cursor, utilising the mouseOffset
        // global variable which contains the position of the mouse pointer relative
        // to the current DragItem at the start of the current drag
        dragHelper.style.top = parseInt(mousePos.y - mouseOffset.y) + "px";
        dragHelper.style.left = parseInt(mousePos.x - mouseOffset.x) + "px";

        // Establish where the current drag item is so we can compare it to the snapshot
        // of the locations of target drop zones and their contained items to see if it
        // is at a valid drop location
        var posX = mousePos.x - mouseOffset.x + (curTarget.getPositionAttributes().w / 2);
        var posY = mousePos.y - mouseOffset.y; 
        
        if (curTarget.verticalPosition == 'center')
            posY += (curTarget.getPositionAttributes().h / 2);
        
        // a local variable to contain any drop zone the current DragItem may be over
        var activeDropZone = null;
        
        // Loop through all the DropZones which are valid drop targets
        for(var i=0; i < dropZones[curTarget.rootZone.group].length; i++)
        {
            // retrieve the position of the DropZone we are iterating over
            var contPos = dropZones[curTarget.rootZone.group][i].getPositionAttributes();
            var contZIndex = dropZones[curTarget.rootZone.group][i].zIndex;

            // if the current DragItem is over the DropZone we are iterating over
            if (posX < contPos.x + contPos.w &&
                posX > contPos.x &&
                posY < contPos.y + contPos.h &&
                posY > contPos.y
                && (!activeDropZone || contZIndex > activeDropZone.zIndex))
            {
                // set activeDropZone to reference the DropZone the DragItem is over
                activeDropZone = dropZones[curTarget.rootZone.group][i];
                
                // Call the makeActive method of the active DropZone
                activeDropZone.makeActive();

                // Set the width of the helper div to match the target drop zone
                if(curTarget.drawInPlace)
                    dragHelper.style.width = activeDropZone.getPositionAttributes().w + "px";
                                
                // If we should be constraining the X position of the drag
                // helper to match the active drop zone then do so
                if(activeDropZone.constrainHelperXPos)
                    dragHelper.style.left = activeDropZone.getPositionAttributes().x + "px";
             }
             else
             {
                // we are not over the DropZone so make sure it isn't active
                dropZones[curTarget.rootZone.group][i].cancelActive();
             }
        }
        
        // If we are over a DropZone then insert the
        // DragItem DOM object into the DropZone DOM object
        if(activeDropZone)
        {
            // a local variable to store the DragItem to insert before
            var nextSibling = null;
            
            // Loop through all the DragItems in the active drop zone
            for(var i=0; i < activeDropZone.dragItems.length; i++)
            {
                // find out if the DragItem we are iterating over is positioned after
                // the current position of our drag helper
                var contPos = activeDropZone.dragItems[i].getPositionAttributes();
                if (posX < contPos.x + contPos.w &&
                    posY < contPos.y + (contPos.h/2))
                {
                    // We have found the first DragItem after the current cursor position
                    // so update the value of nextSibling and break the loop
                    nextSibling = activeDropZone.dragItems[i];
                    break;
                }
            }
            
            // Insert the current DragItem object into the DOM
            if(nextSibling)
            {
                // If we are here we have a sibling to insert before so we can 
                // insert the DragItem object accordingly if it is not already
                // in place
                if(curTarget.obj.nextSibling != nextSibling.obj)
                    activeDropZone.obj.insertBefore(curTarget.obj,nextSibling.obj);
                
                // Set the newZone and newIndex global variables to represent the
                // new location of our DragItem
                newZone = activeDropZone;
                newIndex = nextSibling.index;
            } else {
                // We have no sibling to insert before, but we are in a DropZone
                // so insert the DragItem at the end of the collection if it is
                // not already in place
                if(curTarget.obj.nextSibling || (curTarget.rootZone != activeDropZone))
                    activeDropZone.obj.appendChild(curTarget.obj);
                    
                // Set the newZone and newIndex global variables to represent the
                // new location of our DragItem
                newZone = activeDropZone
                newIndex = activeDropZone.dragItems.length;
            }
            
            // The object is in a drop zone so make sure it is drawn so that the space
            // is filled (although the object is still invisible)
            curTarget.inZone = true;
        }
        else
        {
            // We are not over a DropZone so don't draw the DragItem object
            curTarget.inZone = false;
        }
        
        curTarget.obj.style.display = (curTarget.inZone && curTarget.drawInPlace) ? '':'none';

    }
    
    // Update global variables so we can our previous state in the next event call
    lastTarget = target;
    lMouseState = iMouseDown;
    
    if(curTarget)
        return false;
}

// mouseUp is called on the onmouseup event.
// We have stopped dragging so we clear up after ourselves
function mouseUp(){

    if(curTarget)
    {
        // if we were dragging a DragItem target then call its StopMove method
        curTarget.StopMove();
    }
    
    // reset some global variables
    curTarget = null;
    newZone = null;
    newTarget = null;
    iMouseDown = false;
}
