Current File : /home/jvzmxxx/wiki/extensions/InteractiveTimeline/chap-links-library/js/src/treegrid/dnd.js
/**
 * Drag and Drop library
 *
 * This module allows to create draggable and droppable elements in a webpage,
 * and easy transfer of data of any type between drag and drop areas.
 *
 * The interface of the library is equal to the 'real' drag and drop API.
 * However, this library works on all browsers without issues (in contrast to
 * the official drag and drop API).
 * https://developer.mozilla.org/En/DragDrop/Drag_and_Drop
 *
 * The library is tested on: Chrome, Firefox, Opera, Safari,
 * Internet Explorer 5.5+
 *
 * DOCUMENTATION
 *
 * To create a draggable area, use the method makeDraggable:
 *   dnd.makeDraggable(element, options);
 *
 * with parameters:
 *   {HTMLElement} element   The element to become draggable.
 *   {Object}      options   An object with options.
 *
 * available options:
 *   {String} effectAllowed  The allowed drag effect. Available values:
 *                           'copy', 'move', 'link', 'copyLink',
 *                           'copyMove', 'linkMove', 'all', 'none'.
 *                           Default value is 'all'.
 *   {String or HTMLElement} dragImage
 *                           Image to be used as drag image. If no
 *                           drag image is provided, an opague clone
 *                           of the drag area is used.
 *   {Number} dragImageOffsetX
 *                           Horizontal offset for the drag image
 *   {Number} dragImageOffsetY
 *                           Vertical offset for the drag image
 *   {function} dragStart    Method called once on start of a drag.
 *                           The method is called with an event object
 *                           as parameter. The event object contains a
 *                           parameter 'data' to pass data to a drop
 *                           event. This data can be any type.
 *   {function} drag         Method called repeatedly while dragging.
 *                           The method is called with an event object
 *                           as parameter.
 *   {function} dragEnd      Method called after the drag event is
 *                           finished. The method is called with an
 *                           event object as parameter. This event
 *                           object contains a parameter 'dropEffect'
 *                           with the applied drop effect, which is
 *                           undefined when no drop occurred.
 *
 * To make a droppable area, use the method makeDroppable:
 *   dnd.makeDroppable(element, options);
 *
 * with parameters:
 *   {HTMLElement} element   The element to become droppable.
 *   {Object}      options   An object with options.
 *
 * available options:
 *   {String} dropEffect     The drop effect. Available
 *                           values: 'copy', 'move', 'link', 'none'.
 *                           Default value is 'link'
 *   {function} dragEnter    Method called once when the dragged image
 *                           enters the drop area. Can be used to
 *                           apply visual effects to the drop area.
 *   {function} dragLeave    Method called once when the dragged image
 *                           leaves the drop area. Can be used to
 *                           remove visual effects from the drop area.
 *   {function} dragOver     Method called repeatedly when moving
 *                           over the drop area.
 *   {function} drop         Method called when the drag image is
 *                           dropped on this drop area.
 *                           The method is called with an event object
 *                           as parameter. The event object contains a
 *                           parameter 'data' which can contain data
 *                           provided by the drag area.
 *
 * Created draggable or doppable areas are registed in the drag and
 * drop module. To remove a draggable or droppable area, the
 * following methods can be used respectively:
 *   dnd.removeDraggable(element);
 *   dnd.removeDroppable(element);
 *
 * which removes all drag and drop functionality from the concerning
 * element.
 *
 *
 * EXAMPLE
 *
 *   var drag = document.getElementById('drag');
 *   dnd.makeDraggable(drag, {
 *     'dragStart': function (event) {
 *       event.data = 'Hello World!'; // data can be any type
 *     }
 *   });
 *
 *   var drop = document.getElementById('drop');
 *   dnd.makeDroppable(drop, {
 *     'drop': function (event) {
 *       alert(event.data);                     // will alert 'Hello World!'
 *     },
 *     'dragEnter': function (event) {
 *       drop.style.backgroundColor = 'yellow'; // set visual effect
 *     },
 *     'dragLeave': function (event) {
 *       drop.style.backgroundColor = '';       // remove visual effect
 *     }
 *   });
 *
 *
 * @license
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 * Copyright (c) 2011-2012 Almende B.V. <http://www.almende.com>
 *
 * @author Jos de Jong <jos@almende.org>
 * @date   2012-02-09
 */
links.dnd = function () {
    var dragAreas = [];              // all registered drag areas
    var dropAreas = [];              // all registered drop areas

    var dragArea = undefined;        // currently dragged area
    var dragImage = undefined;
    var dragImageOffsetX = 0;
    var dragImageOffsetY = 0;
    var dragEvent = {};              // object with event properties, passed to each event
    var mouseMove = undefined;
    var mouseUp = undefined;
    var originalCursor = undefined;

    function isDragging() {
        return (dragArea != undefined);
    }

    /**
     * Make an HTML element draggable
     * @param {Element} element
     * @param {Object} options. available parameters:
     *                 {String} effectAllowed
     *                 {String or HTML DOM} dragImage
     *                 {function} dragStart
     *                 {function} dragEnd
     */
    function makeDraggable (element, options) {
        // create an object holding the dragarea and options
        var newDragArea = {
            'element': element
        };
        if (options) {
            links.TreeGrid.extend(newDragArea, options);
        }
        dragAreas.push(newDragArea);

        var mouseDown = function (event) {
            event = event || window.event;
            var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
            if (!leftButtonDown) {
                return;
            }

            // create mousemove listener
            mouseMove = function (event) {
                if (!isDragging()) {
                    dragStart(event, newDragArea);
                }
                dragOver(event);

                preventDefault(event);
            };
            addEventListener(document, 'mousemove', mouseMove);

            // create mouseup listener
            mouseUp = function (event) {
                if (isDragging()) {
                    dragEnd(event);
                }

                // remove event listeners
                if (mouseMove) {
                    removeEventListener(document, 'mousemove', mouseMove);
                    mouseMove = undefined;
                }
                if (mouseUp) {
                    removeEventListener(document, 'mouseup', mouseUp);
                    mouseUp = undefined;
                }

                preventDefault(event);
            };
            addEventListener(document, 'mouseup', mouseUp);

            preventDefault(event);
        };
        addEventListener(element, 'mousedown', mouseDown);

        newDragArea.mouseDown = mouseDown;
    }


    /**
     * Make an HTML element droppable
     * @param {Element} element
     * @param {Object} options. available parameters:
     *                 {String} dropEffect
     *                 {function} dragEnter
     *                 {function} dragLeave
     *                 {function} drop
     */
    function makeDroppable (element, options) {
        var newDropArea = {
            'element': element,
            'mouseOver': false
        };
        if (options) {
            links.TreeGrid.extend(newDropArea, options);
        }
        if (!newDropArea.dropEffect) {
            newDropArea.dropEffect = 'link';
        }

        dropAreas.push(newDropArea);
    }

    /**
     * Remove draggable functionality from element
     * @param {Element} element
     */
    function removeDraggable (element) {
        var i = 0;
        while (i < dragAreas.length) {
            var d = dragAreas[i];
            if (d.element == element) {
                removeEventListener(d.element, 'mousedown', d.mouseDown);
                dragAreas.splice(i, 1);
            }
            else {
                i++;
            }
        }
    }

    /**
     * Remove droppabe functionality from element
     * @param {Element} element
     */
    function removeDroppable (element) {
        var i = 0;
        while (i < dropAreas.length) {
            if (dropAreas[i].element == element) {
                dropAreas.splice(i, 1);
            }
            else {
                i++;
            }
        }
    }

    function dragStart(event, newDragArea) {
        // register the dragarea
        if (dragArea) {
            return;
        }
        dragArea = newDragArea;

        // trigger event
        var proceed = true;
        if (dragArea.dragStart) {
            var data = {};
            dragEvent = {
                'dataTransfer' : {
                    'dragArea': dragArea.element,
                    'dropArea': undefined,
                    'data': data,
                    'getData': function (key) {
                        return data[key];
                    },
                    'setData': function (key, value) {
                        data[key] = value;
                    },
                    'clearData': function (key) {
                        delete data[key];
                    }
                },
                'clientX': event.clientX,
                'clientY': event.clientY
            };

            var ret = dragArea.dragStart(dragEvent);
            proceed = (ret !== false);
        }

        if (!proceed) {
            // cancel dragevent
            dragArea = undefined;
            return;
        }

        // create dragImage
        var clone = undefined;
        dragImage = document.createElement('div');
        dragImage.style.position = 'absolute';
        if (typeof(dragArea.dragImage) == 'string') {
            // create dragImage from HTML string
            dragImage.innerHTML = dragArea.dragImage;
            dragImageOffsetX = -dragArea.dragImageOffsetX || 0;
            dragImageOffsetY = -dragArea.dragImageOffsetY || 0;
        }
        else if (dragArea.dragImage) {
            // create dragImage from HTML DOM element
            dragImage.appendChild(dragArea.dragImage);
            dragImageOffsetX = -dragArea.dragImageOffsetX || 0;
            dragImageOffsetY = -dragArea.dragImageOffsetY || 0;
        }
        else {
            // clone the drag area
            clone = dragArea.element.cloneNode(true);
            dragImageOffsetX = (event.clientX || 0) - getAbsoluteLeft(dragArea.element);
            dragImageOffsetY = (event.clientY || 0) - getAbsoluteTop(dragArea.element);
            clone.style.left = '0px';
            clone.style.top = '0px';
            clone.style.opacity = '0.7';
            clone.style.filter = 'alpha(opacity=70)';

            dragImage.appendChild(clone);
        }
        document.body.appendChild(dragImage);

        // adjust the cursor
        if (originalCursor == undefined) {
            originalCursor = document.body.style.cursor;
        }
        document.body.style.cursor = 'move';
    }

    function dragOver (event) {
        if (!dragImage) {
            return;
        }

        // adjust position of the dragImage
        if (dragImage) {
            dragImage.style.left = (event.clientX - dragImageOffsetX) + 'px';
            dragImage.style.top = (event.clientY - dragImageOffsetY) + 'px';
        }

        // adjust event properties
        dragEvent.clientX = event.clientX;
        dragEvent.clientY = event.clientY;

        // find center of the drag area
        var left = (event.clientX - dragImageOffsetX);
        var top = (event.clientY - dragImageOffsetY);
        var width = dragImage.clientWidth || dragArea.element.clientWidth;
        var height = dragImage.clientHeight || dragArea.element.clientHeight;
        var x = left + width / 2;
        var y = top + height / 2;
        // console.log(dragImageOffsetX, x, left, width, dragImageOffsetY, y, top, height)

        // check if there is a droparea overlapping with current dragarea
        var currentDropArea = undefined;
        for (var i = 0; i < dropAreas.length; i++) {
            var dropArea = dropAreas[i];
            var left = getAbsoluteLeft(dropArea.element);
            var top = getAbsoluteTop(dropArea.element);
            var width = dropArea.element.clientWidth;
            var height = dropArea.element.clientHeight;

            if (x > left && x < left + width && y > top && y < top + height &&
                !currentDropArea &&
                getAllowedDropEffect(dragArea.effectAllowed, dropArea.dropEffect)) {
                // on droparea
                currentDropArea = dropArea;

                if (!dropArea.mouseOver) {
                    if (dropArea.dragEnter) {
                        dragEvent.dataTransfer.dropArea = dropArea;
                        dragEvent.dataTransfer.dropEffect = undefined;
                        dropArea.dragEnter(dragEvent);
                    }
                    dropArea.mouseOver = true;
                }
            }
            else {
                // not on droparea
                if (dropArea.mouseOver) {
                    if (dropArea.dragLeave) {
                        dragEvent.dataTransfer.dropArea = dropArea;
                        dragEvent.dataTransfer.dropEffect = undefined;
                        dropArea.dragLeave(dragEvent);
                    }
                    dropArea.mouseOver = false;
                }
            }
        }

        // adjust event properties
        if (currentDropArea) {
            dragEvent.dataTransfer.dropArea = currentDropArea.element;
            dragEvent.dataTransfer.dropEffect =
                getAllowedDropEffect(dragArea.effectAllowed, currentDropArea.dropEffect);

            if (currentDropArea.dragOver) {
                currentDropArea.dragOver(dragEvent);

                // TODO
                // // dropEffecct may be changed during dragOver
                //currentDropArea.dropEffect = dragEvent.dataTransfer.dropEffect;
            }
        }
        else {
            dragEvent.dataTransfer.dropArea = undefined;
            dragEvent.dataTransfer.dropEffect = undefined;
        }

        if (dragArea.drag) {
            // dragEvent.dataTransfer.effectAllowed = dragArea.effectAllowed;

            dragArea.drag(dragEvent);

            // TODO
            // // effectAllowed may be changed during drag
            // dragArea.effectAllowed = dragEvent.dataTransfer.effectAllowed;
        }
    }

    function dragEnd (event) {
        // remove the dragImage
        if (dragImage && dragImage.parentNode) {
            dragImage.parentNode.removeChild(dragImage);
        }
        dragImage = undefined;

        // restore cursor
        document.body.style.cursor = originalCursor || '';
        originalCursor = undefined;

        var currentDropArea = undefined;
        for (var i = 0; i < dropAreas.length; i++) {
            var dropArea = dropAreas[i];

            // perform mouse leave event
            if (dropArea.mouseOver) {
                if (dropArea.dragLeave) {
                    dropArea.dragLeave(dragEvent);
                }
                dropArea.mouseOver = false;

                // perform drop event
                if (!currentDropArea) {
                    currentDropArea = dropArea;
                }
            }
        }

        if (currentDropArea) {
            // adjust event properties
            dragEvent.dataTransfer.dropArea = currentDropArea.element;
            dragEvent.dataTransfer.dropEffect =
                getAllowedDropEffect(dragArea.effectAllowed, currentDropArea.dropEffect);

            // trigger drop event
            if (dragEvent.dataTransfer.dropEffect) {
                if (currentDropArea.drop) {
                    currentDropArea.drop(dragEvent);
                }
            }
        }
        else {
            dragEvent.dataTransfer.dropArea = undefined;
            dragEvent.dataTransfer.dropEffect = undefined;
        }

        // trigger dragEnd event
        if (dragArea.dragEnd) {
            dragArea.dragEnd(dragEvent);
        }

        // remove the dragArea
        dragArea = undefined;

        // clear event data
        dragEvent = {};
    }

    /**
     * Return the current dropEffect, taking into account the allowed drop effects
     * @param {String} effectAllowed
     * @param {String} dropEffect
     * @return allowedDropEffect      the allowed dropEffect, or undefined when
     *                                not allowed
     */
    function getAllowedDropEffect (effectAllowed, dropEffect) {
        if (!dropEffect || dropEffect == 'none') {
            // none
            return undefined;
        }

        if (!effectAllowed || effectAllowed.toLowerCase() == 'all') {
            // all
            return dropEffect;
        }

        if (effectAllowed.toLowerCase().indexOf(dropEffect.toLowerCase()) != -1 ) {
            return dropEffect;
        }

        return undefined;
    }

    /**
     * Add and event listener. Works for all browsers
     * @param {Element}     element    An html element
     * @param {string}      action     The action, for example 'click',
     *                                 without the prefix 'on'
     * @param {function}    listener   The callback function to be executed
     * @param {boolean}     useCapture
     */
    function addEventListener (element, action, listener, useCapture) {
        if (element.addEventListener) {
            if (useCapture === undefined) {
                useCapture = false;
            }

            if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) {
                action = 'DOMMouseScroll';  // For Firefox
            }

            element.addEventListener(action, listener, useCapture);
        } else {
            element.attachEvent('on' + action, listener);  // IE browsers
        }
    }

    /**
     * Remove an event listener from an element
     * @param {Element}      element   An html dom element
     * @param {string}       action    The name of the event, for example 'mousedown'
     * @param {function}     listener  The listener function
     * @param {boolean}      useCapture
     */
    function removeEventListener (element, action, listener, useCapture) {
        if (element.removeEventListener) {
            // non-IE browsers
            if (useCapture === undefined) {
                useCapture = false;
            }

            if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) {
                action = 'DOMMouseScroll';  // For Firefox
            }

            element.removeEventListener(action, listener, useCapture);
        } else {
            // IE browsers
            element.detachEvent('on' + action, listener);
        }
    }

    /**
     * Stop event propagation
     */
    function stopPropagation (event) {
        if (!event)
            event = window.event;

        if (event.stopPropagation) {
            event.stopPropagation();  // non-IE browsers
        }
        else {
            event.cancelBubble = true;  // IE browsers
        }
    }

    /**
     * Cancels the event if it is cancelable, without stopping further propagation of the event.
     */
    function preventDefault (event) {
        if (!event)
            event = window.event;

        if (event.preventDefault) {
            event.preventDefault();  // non-IE browsers
        }
        else {
            event.returnValue = false;  // IE browsers
        }
    }


    /**
     * Retrieve the absolute left value of a DOM element
     * @param {Element} elem    A dom element, for example a div
     * @return {number} left        The absolute left position of this element
     *                              in the browser page.
     */
    function getAbsoluteLeft (elem) {
        var left = 0;
        while( elem != null ) {
            left += elem.offsetLeft;
            //left -= elem.srcollLeft;  // TODO: adjust for scroll positions. check if it works in IE too
            elem = elem.offsetParent;
        }
        return left;
    }

    /**
     * Retrieve the absolute top value of a DOM element
     * @param {Element} elem    A dom element, for example a div
     * @return {number} top        The absolute top position of this element
     *                              in the browser page.
     */
    function getAbsoluteTop (elem) {
        var top = 0;
        while( elem != null ) {
            top += elem.offsetTop;
            //left -= elem.srcollLeft;  // TODO: adjust for scroll positions. check if it works in IE too
            elem = elem.offsetParent;
        }
        return top;
    }

    // return public methods
    return {
        'makeDraggable': makeDraggable,
        'makeDroppable': makeDroppable,
        'removeDraggable': removeDraggable,
        'removeDroppable': removeDroppable
    };
}();