Best JavaScript code snippet using playwright-internal
ddwtos.js
Source:ddwtos.js  
1// This file is part of Moodle - http://moodle.org/2//3// Moodle is free software: you can redistribute it and/or modify4// it under the terms of the GNU General Public License as published by5// the Free Software Foundation, either version 3 of the License, or6// (at your option) any later version.7//8// Moodle is distributed in the hope that it will be useful,9// but WITHOUT ANY WARRANTY; without even the implied warranty of10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the11// GNU General Public License for more details.12//13// You should have received a copy of the GNU General Public License14// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.15/**16 * JavaScript to make drag-drop into text questions work.17 *18 * Some vocabulary to help understand this code:19 *20 * The question text contains 'drops' - blanks into which the 'drags', the missing21 * words, can be put.22 *23 * The thing that can be moved into the drops are called 'drags'. There may be24 * multiple copies of the 'same' drag which does not really cause problems.25 * Each drag has a 'choice' number which is the value set on the drop's hidden26 * input when this drag is placed in a drop.27 *28 * These may be in separate 'groups', distinguished by colour.29 * Things can only interact with other things in the same group.30 * The groups are numbered from 1.31 *32 * The place where a given drag started from is called its 'home'.33 *34 * @module     qtype_ddwtos/ddwtos35 * @copyright  2018 The Open University36 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later37 * @since      3.638 */39define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys) {40    "use strict";41    /**42     * Object to handle one drag-drop into text question.43     *44     * @param {String} containerId id of the outer div for this question.45     * @param {boolean} readOnly whether the question is being displayed read-only.46     * @constructor47     */48    function DragDropToTextQuestion(containerId, readOnly) {49        this.containerId = containerId;50        if (readOnly) {51            this.getRoot().addClass('qtype_ddwtos-readonly');52        }53        this.resizeAllDragsAndDrops();54        this.cloneDrags();55        this.positionDrags();56    }57    /**58     * In each group, resize all the items to be the same size.59     */60    DragDropToTextQuestion.prototype.resizeAllDragsAndDrops = function() {61        var thisQ = this;62        this.getRoot().find('.answercontainer > div').each(function(i, node) {63            thisQ.resizeAllDragsAndDropsInGroup(64                thisQ.getClassnameNumericSuffix($(node), 'draggrouphomes'));65        });66    };67    /**68     * In a given group, set all the drags and drops to be the same size.69     *70     * @param {int} group the group number.71     */72    DragDropToTextQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {73        var thisQ = this,74            dragHomes = this.getRoot().find('.draggrouphomes' + group + ' span.draghome'),75            maxWidth = 0,76            maxHeight = 0;77        // Find the maximum size of any drag in this groups.78        dragHomes.each(function(i, drag) {79            maxWidth = Math.max(maxWidth, Math.ceil(drag.offsetWidth));80            maxHeight = Math.max(maxHeight, Math.ceil(0 + drag.offsetHeight));81        });82        // The size we will want to set is a bit bigger than this.83        maxWidth += 8;84        maxHeight += 2;85        // Set each drag home to that size.86        dragHomes.each(function(i, drag) {87            thisQ.setElementSize(drag, maxWidth, maxHeight);88        });89        // Set each drop to that size.90        this.getRoot().find('span.drop.group' + group).each(function(i, drop) {91            thisQ.setElementSize(drop, maxWidth, maxHeight);92        });93    };94    /**95     * Set a given DOM element to be a particular size.96     *97     * @param {HTMLElement} element98     * @param {int} width99     * @param {int} height100     */101    DragDropToTextQuestion.prototype.setElementSize = function(element, width, height) {102        $(element).width(width).height(height).css('lineHeight', height + 'px');103    };104    /**105     * Invisible 'drag homes' are output by the renderer. These have the same properties106     * as the drag items but are invisible. We clone these invisible elements to make the107     * actual drag items.108     */109    DragDropToTextQuestion.prototype.cloneDrags = function() {110        var thisQ = this;111        thisQ.getRoot().find('span.draghome').each(function(index, draghome) {112            var drag = $(draghome);113            var placeHolder = drag.clone();114            placeHolder.removeClass();115            placeHolder.addClass('draghome choice' +116                thisQ.getChoice(drag) + ' group' +117                thisQ.getGroup(drag) + ' dragplaceholder');118            drag.before(placeHolder);119        });120    };121    /**122     * Update the position of drags.123     */124    DragDropToTextQuestion.prototype.positionDrags = function() {125        var thisQ = this,126            root = this.getRoot();127        // First move all items back home.128        root.find('span.draghome').not('.dragplaceholder').each(function(i, dragNode) {129            var drag = $(dragNode),130                currentPlace = thisQ.getClassnameNumericSuffix(drag, 'inplace');131            drag.addClass('unplaced')132                .removeClass('placed');133            drag.removeAttr('tabindex');134            if (currentPlace !== null) {135                drag.removeClass('inplace' + currentPlace);136            }137        });138        // Then place the once that should be placed.139        root.find('input.placeinput').each(function(i, inputNode) {140            var input = $(inputNode),141                choice = input.val(),142                place = thisQ.getPlace(input);143            // Record the last known position of the drop.144            var drop = root.find('.drop.place' + place),145                dropPosition = drop.offset();146            drop.data('prev-top', dropPosition.top).data('prev-left', dropPosition.left);147            if (choice === '0') {148                // No item in this place.149                return;150            }151            // Get the unplaced drag.152            var unplacedDrag = thisQ.getUnplacedChoice(thisQ.getGroup(input), choice);153            // Get the clone of the drag.154            var hiddenDrag = thisQ.getDragClone(unplacedDrag);155            if (hiddenDrag.length) {156                if (unplacedDrag.hasClass('infinite')) {157                    var noOfDrags = thisQ.noOfDropsInGroup(thisQ.getGroup(unplacedDrag));158                    var cloneDrags = thisQ.getInfiniteDragClones(unplacedDrag, false);159                    if (cloneDrags.length < noOfDrags) {160                        var cloneDrag = unplacedDrag.clone();161                        hiddenDrag.after(cloneDrag);162                        questionManager.addEventHandlersToDrag(cloneDrag);163                    } else {164                        hiddenDrag.addClass('active');165                    }166                } else {167                    hiddenDrag.addClass('active');168                }169            }170            // Send the drag to drop.171            thisQ.sendDragToDrop(thisQ.getUnplacedChoice(thisQ.getGroup(input), choice), drop);172        });173    };174    /**175     * Handles the start of dragging an item.176     *177     * @param {Event} e the touch start or mouse down event.178     */179    DragDropToTextQuestion.prototype.handleDragStart = function(e) {180        var thisQ = this,181            drag = $(e.target).closest('.draghome');182        var info = dragDrop.prepare(e);183        if (!info.start) {184            return;185        }186        drag.addClass('beingdragged');187        var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');188        if (currentPlace !== null) {189            this.setInputValue(currentPlace, 0);190            drag.removeClass('inplace' + currentPlace);191            var hiddenDrop = thisQ.getDrop(drag, currentPlace);192            if (hiddenDrop.length) {193                hiddenDrop.addClass('active');194                drag.offset(hiddenDrop.offset());195            }196        } else {197            var hiddenDrag = thisQ.getDragClone(drag);198            if (hiddenDrag.length) {199                if (drag.hasClass('infinite')) {200                    var noOfDrags = this.noOfDropsInGroup(this.getGroup(drag));201                    var cloneDrags = this.getInfiniteDragClones(drag, false);202                    if (cloneDrags.length < noOfDrags) {203                        var cloneDrag = drag.clone();204                        cloneDrag.removeClass('beingdragged');205                        hiddenDrag.after(cloneDrag);206                        questionManager.addEventHandlersToDrag(cloneDrag);207                        drag.offset(cloneDrag.offset());208                    } else {209                        hiddenDrag.addClass('active');210                        drag.offset(hiddenDrag.offset());211                    }212                } else {213                    hiddenDrag.addClass('active');214                    drag.offset(hiddenDrag.offset());215                }216            }217        }218        dragDrop.start(e, drag, function(x, y, drag) {219            thisQ.dragMove(x, y, drag);220        }, function(x, y, drag) {221            thisQ.dragEnd(x, y, drag);222        });223    };224    /**225     * Called whenever the currently dragged items moves.226     *227     * @param {Number} pageX the x position.228     * @param {Number} pageY the y position.229     * @param {jQuery} drag the item being moved.230     */231    DragDropToTextQuestion.prototype.dragMove = function(pageX, pageY, drag) {232        var thisQ = this;233        this.getRoot().find('span.drop.group' + this.getGroup(drag)).each(function(i, dropNode) {234            var drop = $(dropNode);235            if (thisQ.isPointInDrop(pageX, pageY, drop)) {236                drop.addClass('valid-drag-over-drop');237            } else {238                drop.removeClass('valid-drag-over-drop');239            }240        });241        this.getRoot().find('span.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {242            var drop = $(dropNode);243            if (thisQ.isPointInDrop(pageX, pageY, drop) && !thisQ.isDragSameAsDrop(drag, drop)) {244                drop.addClass('valid-drag-over-drop');245            } else {246                drop.removeClass('valid-drag-over-drop');247            }248        });249    };250    /**251     * Called when user drops a drag item.252     *253     * @param {Number} pageX the x position.254     * @param {Number} pageY the y position.255     * @param {jQuery} drag the item being moved.256     */257    DragDropToTextQuestion.prototype.dragEnd = function(pageX, pageY, drag) {258        var thisQ = this,259            root = this.getRoot(),260            placed = false;261        root.find('span.drop.group' + this.getGroup(drag)).each(function(i, dropNode) {262            var drop = $(dropNode);263            if (!thisQ.isPointInDrop(pageX, pageY, drop)) {264                // Not this drop.265                return true;266            }267            // Now put this drag into the drop.268            drop.removeClass('valid-drag-over-drop');269            thisQ.sendDragToDrop(drag, drop);270            placed = true;271            return false; // Stop the each() here.272        });273        root.find('span.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, placedNode) {274            var placedDrag = $(placedNode);275            if (!thisQ.isPointInDrop(pageX, pageY, placedDrag) || thisQ.isDragSameAsDrop(drag, placedDrag)) {276                // Not this placed drag.277                return true;278            }279            // Now put this drag into the drop.280            placedDrag.removeClass('valid-drag-over-drop');281            var currentPlace = thisQ.getClassnameNumericSuffix(placedDrag, 'inplace');282            var drop = thisQ.getDrop(drag, currentPlace);283            thisQ.sendDragToDrop(drag, drop);284            placed = true;285            return false; // Stop the each() here.286        });287        if (!placed) {288            this.sendDragHome(drag);289        }290    };291    /**292     * Animate a drag item into a given place (or back home).293     *294     * @param {jQuery|null} drag the item to place. If null, clear the place.295     * @param {jQuery} drop the place to put it.296     */297    DragDropToTextQuestion.prototype.sendDragToDrop = function(drag, drop) {298        // Is there already a drag in this drop? if so, evict it.299        var oldDrag = this.getCurrentDragInPlace(this.getPlace(drop));300        if (oldDrag.length !== 0) {301            var currentPlace = this.getClassnameNumericSuffix(oldDrag, 'inplace');302            var hiddenDrop = this.getDrop(oldDrag, currentPlace);303            hiddenDrop.addClass('active');304            oldDrag.addClass('beingdragged');305            oldDrag.offset(hiddenDrop.offset());306            this.sendDragHome(oldDrag);307        }308        if (drag.length === 0) {309            this.setInputValue(this.getPlace(drop), 0);310            if (drop.data('isfocus')) {311                drop.focus();312            }313        } else {314            this.setInputValue(this.getPlace(drop), this.getChoice(drag));315            drag.removeClass('unplaced')316                .addClass('placed inplace' + this.getPlace(drop));317            drag.attr('tabindex', 0);318            this.animateTo(drag, drop);319        }320    };321    /**322     * Animate a drag back to its home.323     *324     * @param {jQuery} drag the item being moved.325     */326    DragDropToTextQuestion.prototype.sendDragHome = function(drag) {327        var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');328        if (currentPlace !== null) {329            drag.removeClass('inplace' + currentPlace);330        }331        drag.data('unplaced', true);332        this.animateTo(drag, this.getDragHome(this.getGroup(drag), this.getChoice(drag)));333    };334    /**335     * Handles keyboard events on drops.336     *337     * Drops are focusable. Once focused, right/down/space switches to the next choice, and338     * left/up switches to the previous. Escape clear.339     *340     * @param {KeyboardEvent} e341     */342    DragDropToTextQuestion.prototype.handleKeyPress = function(e) {343        var drop = $(e.target).closest('.drop');344        if (drop.length === 0) {345            var placedDrag = $(e.target);346            var currentPlace = this.getClassnameNumericSuffix(placedDrag, 'inplace');347            if (currentPlace !== null) {348                drop = this.getDrop(placedDrag, currentPlace);349            }350        }351        var currentDrag = this.getCurrentDragInPlace(this.getPlace(drop)),352            nextDrag = $();353        switch (e.keyCode) {354            case keys.space:355            case keys.arrowRight:356            case keys.arrowDown:357                nextDrag = this.getNextDrag(this.getGroup(drop), currentDrag);358                break;359            case keys.arrowLeft:360            case keys.arrowUp:361                nextDrag = this.getPreviousDrag(this.getGroup(drop), currentDrag);362                break;363            case keys.escape:364                break;365            default:366                questionManager.isKeyboardNavigation = false;367                return; // To avoid the preventDefault below.368        }369        if (nextDrag.length) {370            nextDrag.data('isfocus', true);371            nextDrag.addClass('beingdragged');372            var hiddenDrag = this.getDragClone(nextDrag);373            if (hiddenDrag.length) {374                if (nextDrag.hasClass('infinite')) {375                    var noOfDrags = this.noOfDropsInGroup(this.getGroup(nextDrag));376                    var cloneDrags = this.getInfiniteDragClones(nextDrag, false);377                    if (cloneDrags.length < noOfDrags) {378                        var cloneDrag = nextDrag.clone();379                        cloneDrag.removeClass('beingdragged');380                        cloneDrag.removeAttr('tabindex');381                        hiddenDrag.after(cloneDrag);382                        questionManager.addEventHandlersToDrag(cloneDrag);383                        nextDrag.offset(cloneDrag.offset());384                    } else {385                        hiddenDrag.addClass('active');386                        nextDrag.offset(hiddenDrag.offset());387                    }388                } else {389                    hiddenDrag.addClass('active');390                    nextDrag.offset(hiddenDrag.offset());391                }392            }393        } else {394            drop.data('isfocus', true);395        }396        e.preventDefault();397        this.sendDragToDrop(nextDrag, drop);398    };399    /**400     * Choose the next drag in a group.401     *402     * @param {int} group which group.403     * @param {jQuery} drag current choice (empty jQuery if there isn't one).404     * @return {jQuery} the next drag in that group, or null if there wasn't one.405     */406    DragDropToTextQuestion.prototype.getNextDrag = function(group, drag) {407        var choice,408            numChoices = this.noOfChoicesInGroup(group);409        if (drag.length === 0) {410            choice = 1; // Was empty, so we want to select the first choice.411        } else {412            choice = this.getChoice(drag) + 1;413        }414        var next = this.getUnplacedChoice(group, choice);415        while (next.length === 0 && choice < numChoices) {416            choice++;417            next = this.getUnplacedChoice(group, choice);418        }419        return next;420    };421    /**422     * Choose the previous drag in a group.423     *424     * @param {int} group which group.425     * @param {jQuery} drag current choice (empty jQuery if there isn't one).426     * @return {jQuery} the next drag in that group, or null if there wasn't one.427     */428    DragDropToTextQuestion.prototype.getPreviousDrag = function(group, drag) {429        var choice;430        if (drag.length === 0) {431            choice = this.noOfChoicesInGroup(group);432        } else {433            choice = this.getChoice(drag) - 1;434        }435        var previous = this.getUnplacedChoice(group, choice);436        while (previous.length === 0 && choice > 1) {437            choice--;438            previous = this.getUnplacedChoice(group, choice);439        }440        // Does this choice exist?441        return previous;442    };443    /**444     * Animate an object to the given destination.445     *446     * @param {jQuery} drag the element to be animated.447     * @param {jQuery} target element marking the place to move it to.448     */449    DragDropToTextQuestion.prototype.animateTo = function(drag, target) {450        var currentPos = drag.offset(),451            targetPos = target.offset(),452            thisQ = this;453        M.util.js_pending('qtype_ddwtos-animate-' + thisQ.containerId);454        // Animate works in terms of CSS position, whereas locating an object455        // on the page works best with jQuery offset() function. So, to get456        // the right target position, we work out the required change in457        // offset() and then add that to the current CSS position.458        drag.animate(459            {460                left: parseInt(drag.css('left')) + targetPos.left - currentPos.left,461                top: parseInt(drag.css('top')) + targetPos.top - currentPos.top462            },463            {464                duration: 'fast',465                done: function() {466                    $('body').trigger('qtype_ddwtos-dragmoved', [drag, target, thisQ]);467                    M.util.js_complete('qtype_ddwtos-animate-' + thisQ.containerId);468                }469            }470        );471    };472    /**473     * Detect if a point is inside a given DOM node.474     *475     * @param {Number} pageX the x position.476     * @param {Number} pageY the y position.477     * @param {jQuery} drop the node to check (typically a drop).478     * @return {boolean} whether the point is inside the node.479     */480    DragDropToTextQuestion.prototype.isPointInDrop = function(pageX, pageY, drop) {481        var position = drop.offset();482        return pageX >= position.left && pageX < position.left + drop.width()483                && pageY >= position.top && pageY < position.top + drop.height();484    };485    /**486     * Set the value of the hidden input for a place, to record what is currently there.487     *488     * @param {int} place which place to set the input value for.489     * @param {int} choice the value to set.490     */491    DragDropToTextQuestion.prototype.setInputValue = function(place, choice) {492        this.getRoot().find('input.placeinput.place' + place).val(choice);493    };494    /**495     * Get the outer div for this question.496     *497     * @returns {jQuery} containing that div.498     */499    DragDropToTextQuestion.prototype.getRoot = function() {500        return $(document.getElementById(this.containerId));501    };502    /**503     * Get drag home for a given choice.504     *505     * @param {int} group the group.506     * @param {int} choice the choice number.507     * @returns {jQuery} containing that div.508     */509    DragDropToTextQuestion.prototype.getDragHome = function(group, choice) {510        if (!this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice).is(':visible')) {511            return this.getRoot().find('.draggrouphomes' + group +512                ' span.draghome.infinite' +513                '.choice' + choice +514                '.group' + group);515        }516        return this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice);517    };518    /**519     * Get an unplaced choice for a particular group.520     *521     * @param {int} group the group.522     * @param {int} choice the choice number.523     * @returns {jQuery} jQuery wrapping the unplaced choice. If there isn't one, the jQuery will be empty.524     */525    DragDropToTextQuestion.prototype.getUnplacedChoice = function(group, choice) {526        return this.getRoot().find('.draghome.group' + group + '.choice' + choice + '.unplaced').slice(0, 1);527    };528    /**529     * Get the drag that is currently in a given place.530     *531     * @param {int} place the place number.532     * @return {jQuery} the current drag (or an empty jQuery if none).533     */534    DragDropToTextQuestion.prototype.getCurrentDragInPlace = function(place) {535        return this.getRoot().find('span.draghome.inplace' + place);536    };537    /**538     * Return the number of blanks in a given group.539     *540     * @param {int} group the group number.541     * @returns {int} the number of drops.542     */543    DragDropToTextQuestion.prototype.noOfDropsInGroup = function(group) {544        return this.getRoot().find('.drop.group' + group).length;545    };546    /**547     * Return the number of choices in a given group.548     *549     * @param {int} group the group number.550     * @returns {int} the number of choices.551     */552    DragDropToTextQuestion.prototype.noOfChoicesInGroup = function(group) {553        return this.getRoot().find('.draghome.group' + group).length;554    };555    /**556     * Return the number at the end of the CSS class name with the given prefix.557     *558     * @param {jQuery} node559     * @param {String} prefix name prefix560     * @returns {Number|null} the suffix if found, else null.561     */562    DragDropToTextQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {563        var classes = node.attr('class');564        if (classes !== '') {565            var classesArr = classes.split(' ');566            for (var index = 0; index < classesArr.length; index++) {567                var patt1 = new RegExp('^' + prefix + '([0-9])+$');568                if (patt1.test(classesArr[index])) {569                    var patt2 = new RegExp('([0-9])+$');570                    var match = patt2.exec(classesArr[index]);571                    return Number(match[0]);572                }573            }574        }575        return null;576    };577    /**578     * Get the choice number of a drag.579     *580     * @param {jQuery} drag the drag.581     * @returns {Number} the choice number.582     */583    DragDropToTextQuestion.prototype.getChoice = function(drag) {584        return this.getClassnameNumericSuffix(drag, 'choice');585    };586    /**587     * Given a DOM node that is significant to this question588     * (drag, drop, ...) get the group it belongs to.589     *590     * @param {jQuery} node a DOM node.591     * @returns {Number} the group it belongs to.592     */593    DragDropToTextQuestion.prototype.getGroup = function(node) {594        return this.getClassnameNumericSuffix(node, 'group');595    };596    /**597     * Get the place number of a drop, or its corresponding hidden input.598     *599     * @param {jQuery} node the DOM node.600     * @returns {Number} the place number.601     */602    DragDropToTextQuestion.prototype.getPlace = function(node) {603        return this.getClassnameNumericSuffix(node, 'place');604    };605    /**606     * Get drag clone for a given drag.607     *608     * @param {jQuery} drag the drag.609     * @returns {jQuery} the drag's clone.610     */611    DragDropToTextQuestion.prototype.getDragClone = function(drag) {612        return this.getRoot().find('.draggrouphomes' +613            this.getGroup(drag) +614            ' span.draghome' +615            '.choice' + this.getChoice(drag) +616            '.group' + this.getGroup(drag) +617            '.dragplaceholder');618    };619    /**620     * Get infinite drag clones for given drag.621     *622     * @param {jQuery} drag the drag.623     * @param {Boolean} inHome in the home area or not.624     * @returns {jQuery} the drag's clones.625     */626    DragDropToTextQuestion.prototype.getInfiniteDragClones = function(drag, inHome) {627        if (inHome) {628            return this.getRoot().find('.draggrouphomes' +629                this.getGroup(drag) +630                ' span.draghome' +631                '.choice' + this.getChoice(drag) +632                '.group' + this.getGroup(drag) +633                '.infinite').not('.dragplaceholder');634        }635        return this.getRoot().find('span.draghome' +636            '.choice' + this.getChoice(drag) +637            '.group' + this.getGroup(drag) +638            '.infinite').not('.dragplaceholder');639    };640    /**641     * Get drop for a given drag and place.642     *643     * @param {jQuery} drag the drag.644     * @param {Integer} currentPlace the current place of drag.645     * @returns {jQuery} the drop's clone.646     */647    DragDropToTextQuestion.prototype.getDrop = function(drag, currentPlace) {648        return this.getRoot().find('.drop.group' + this.getGroup(drag) + '.place' + currentPlace);649    };650    /**651     * Check that the drag is drop to it's clone.652     *653     * @param {jQuery} drag The drag.654     * @param {jQuery} drop The drop.655     * @returns {boolean}656     */657    DragDropToTextQuestion.prototype.isDragSameAsDrop = function(drag, drop) {658        return this.getChoice(drag) === this.getChoice(drop) && this.getGroup(drag) === this.getGroup(drop);659    };660    /**661     * Singleton that tracks all the DragDropToTextQuestions on this page, and deals662     * with event dispatching.663     *664     * @type {Object}665     */666    var questionManager = {667        /**668         * {boolean} used to ensure the event handlers are only initialised once per page.669         */670        eventHandlersInitialised: false,671        /**672         * {boolean} is keyboard navigation or not.673         */674        isKeyboardNavigation: false,675        /**676         * {DragDropToTextQuestion[]} all the questions on this page, indexed by containerId (id on the .que div).677         */678        questions: {},679        /**680         * Initialise questions.681         *682         * @param {String} containerId id of the outer div for this question.683         * @param {boolean} readOnly whether the question is being displayed read-only.684         */685        init: function(containerId, readOnly) {686            questionManager.questions[containerId] = new DragDropToTextQuestion(containerId, readOnly);687            if (!questionManager.eventHandlersInitialised) {688                questionManager.setupEventHandlers();689                questionManager.eventHandlersInitialised = true;690            }691        },692        /**693         * Set up the event handlers that make this question type work. (Done once per page.)694         */695        setupEventHandlers: function() {696            // We do not use the body event here to prevent the other event on Mobile device, such as scroll event.697            questionManager.addEventHandlersToDrag($('.que.ddwtos:not(.qtype_ddwtos-readonly) span.draghome'));698            $('body')699                .on('keydown',700                    '.que.ddwtos:not(.qtype_ddwtos-readonly) span.drop',701                    questionManager.handleKeyPress)702                .on('keydown',703                    '.que.ddwtos:not(.qtype_ddwtos-readonly) span.draghome.placed:not(.beingdragged)',704                    questionManager.handleKeyPress)705                .on('qtype_ddwtos-dragmoved', questionManager.handleDragMoved);706        },707        /**708         * Binding the drag/touch event again for newly created element.709         *710         * @param {jQuery} element Element to bind the event711         */712        addEventHandlersToDrag: function(element) {713            // Unbind all the mousedown and touchstart events to prevent double binding.714            element.unbind('mousedown touchstart');715            element.on('mousedown touchstart', questionManager.handleDragStart);716        },717        /**718         * Handle mouse down / touch start on drags.719         * @param {Event} e the DOM event.720         */721        handleDragStart: function(e) {722            e.preventDefault();723            var question = questionManager.getQuestionForEvent(e);724            if (question) {725                question.handleDragStart(e);726            }727        },728        /**729         * Handle key down / press on drops.730         * @param {KeyboardEvent} e731         */732        handleKeyPress: function(e) {733            if (questionManager.isKeyboardNavigation) {734                return;735            }736            questionManager.isKeyboardNavigation = true;737            var question = questionManager.getQuestionForEvent(e);738            if (question) {739                question.handleKeyPress(e);740            }741        },742        /**743         * Given an event, work out which question it affects.744         *745         * @param {Event} e the event.746         * @returns {DragDropToTextQuestion|undefined} The question, or undefined.747         */748        getQuestionForEvent: function(e) {749            var containerId = $(e.currentTarget).closest('.que.ddwtos').attr('id');750            return questionManager.questions[containerId];751        },752        /**753         * Handle when drag moved.754         *755         * @param {Event} e the event.756         * @param {jQuery} drag the drag757         * @param {jQuery} target the target758         * @param {DragDropToTextQuestion} thisQ the question.759         */760        handleDragMoved: function(e, drag, target, thisQ) {761            drag.removeClass('beingdragged');762            drag.css('top', '').css('left', '');763            target.after(drag);764            target.removeClass('active');765            if (typeof drag.data('unplaced') !== 'undefined' && drag.data('unplaced') === true) {766                drag.removeClass('placed').addClass('unplaced');767                drag.removeAttr('tabindex');768                drag.removeData('unplaced');769                if (drag.hasClass('infinite') && thisQ.getInfiniteDragClones(drag, true).length > 1) {770                    thisQ.getInfiniteDragClones(drag, true).first().remove();771                }772            }773            if (typeof drag.data('isfocus') !== 'undefined' && drag.data('isfocus') === true) {774                drag.focus();775                drag.removeData('isfocus');776            }777            if (typeof target.data('isfocus') !== 'undefined' && target.data('isfocus') === true) {778                target.removeData('isfocus');779            }780            if (questionManager.isKeyboardNavigation) {781                questionManager.isKeyboardNavigation = false;782            }783        }784    };785    /**786     * @alias module:qtype_ddwtos/ddwtos787     */788    return {789        /**790         * Initialise one drag-drop into text question.791         *792         * @param {String} containerId id of the outer div for this question.793         * @param {boolean} readOnly whether the question is being displayed read-only.794         */795        init: questionManager.init796    };...dnd.js
Source:dnd.js  
1// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-2const Clutter = imports.gi.Clutter;3const GLib = imports.gi.GLib;4const Gtk = imports.gi.Gtk;5const St = imports.gi.St;6const Lang = imports.lang;7const Meta = imports.gi.Meta;8const Shell = imports.gi.Shell;9const Signals = imports.signals;10const Tweener = imports.ui.tweener;11const Main = imports.ui.main;12const Params = imports.misc.params;13// Time to scale down to maxDragActorSize14const SCALE_ANIMATION_TIME = 0.25;15// Time to animate to original position on cancel16const SNAP_BACK_ANIMATION_TIME = 0.25;17// Time to animate to original position on success18const REVERT_ANIMATION_TIME = 0.75;19const DragMotionResult = {20    NO_DROP:   0,21    COPY_DROP: 1,22    MOVE_DROP: 2,23    CONTINUE:  324};25const DRAG_CURSOR_MAP = {26    0: Meta.Cursor.DND_UNSUPPORTED_TARGET,27    1: Meta.Cursor.DND_COPY,28    2: Meta.Cursor.DND_MOVE29};30const DragDropResult = {31    FAILURE:  0,32    SUCCESS:  1,33    CONTINUE: 234};35let eventHandlerActor = null;36let currentDraggable = null;37let dragMonitors = [];38function _getEventHandlerActor() {39    if (!eventHandlerActor) {40        eventHandlerActor = new Clutter.Actor({ width: 0, height: 0 });41        Main.uiGroup.add_actor(eventHandlerActor);42        // We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen43        // when you've grabbed the pointer.44        eventHandlerActor.connect('event',45                                  function(actor, event) {46                                      return currentDraggable._onEvent(actor, event);47                                  });48    }49    return eventHandlerActor;50}51function addDragMonitor(monitor) {52    dragMonitors.push(monitor);53}54function removeDragMonitor(monitor) {55    for (let i = 0; i < dragMonitors.length; i++)56        if (dragMonitors[i] == monitor) {57            dragMonitors.splice(i, 1);58            return;59        }60}61const _Draggable = new Lang.Class({62    Name: 'Draggable',63    _init : function(actor, params) {64        params = Params.parse(params, { manualMode: false,65                                        restoreOnSuccess: false,66                                        dragActorMaxSize: undefined,67                                        dragActorOpacity: undefined });68        this.actor = actor;69        if (!params.manualMode)70            this.actor.connect('button-press-event',71                               Lang.bind(this, this._onButtonPress));72        this.actor.connect('destroy', Lang.bind(this, function() {73            this._actorDestroyed = true;74            if (this._dragInProgress && this._dragCancellable)75                this._cancelDrag(global.get_current_time());76            this.disconnectAll();77        }));78        this._onEventId = null;79        this._restoreOnSuccess = params.restoreOnSuccess;80        this._dragActorMaxSize = params.dragActorMaxSize;81        this._dragActorOpacity = params.dragActorOpacity;82        this._buttonDown = false; // The mouse button has been pressed and has not yet been released.83        this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet.84        this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting).85        this._dragCancellable = true;86        this._eventsGrabbed = false;87    },88    _onButtonPress : function (actor, event) {89        if (event.get_button() != 1)90            return Clutter.EVENT_PROPAGATE;91        if (Tweener.getTweenCount(actor))92            return Clutter.EVENT_PROPAGATE;93        this._buttonDown = true;94        this._grabActor();95        let [stageX, stageY] = event.get_coords();96        this._dragStartX = stageX;97        this._dragStartY = stageY;98        return Clutter.EVENT_PROPAGATE;99    },100    _grabActor: function() {101        Clutter.grab_pointer(this.actor);102        this._onEventId = this.actor.connect('event',103                                             Lang.bind(this, this._onEvent));104    },105    _ungrabActor: function() {106        if (!this._onEventId)107            return;108        Clutter.ungrab_pointer();109        this.actor.disconnect(this._onEventId);110        this._onEventId = null;111    },112    _grabEvents: function() {113        if (!this._eventsGrabbed) {114            this._eventsGrabbed = Main.pushModal(_getEventHandlerActor());115            if (this._eventsGrabbed)116                Clutter.grab_pointer(_getEventHandlerActor());117        }118    },119    _ungrabEvents: function() {120        if (this._eventsGrabbed) {121            Clutter.ungrab_pointer();122            Main.popModal(_getEventHandlerActor());123            this._eventsGrabbed = false;124        }125    },126    _onEvent: function(actor, event) {127        // We intercept BUTTON_RELEASE event to know that the button was released in case we128        // didn't start the drag, to drop the draggable in case the drag was in progress, and129        // to complete the drag and ensure that whatever happens to be under the pointer does130        // not get triggered if the drag was cancelled with Esc.131        if (event.type() == Clutter.EventType.BUTTON_RELEASE) {132            this._buttonDown = false;133            if (this._dragInProgress) {134                return this._dragActorDropped(event);135            } else if (this._dragActor != null && !this._animationInProgress) {136                // Drag must have been cancelled with Esc.137                this._dragComplete();138                return Clutter.EVENT_STOP;139            } else {140                // Drag has never started.141                this._ungrabActor();142                return Clutter.EVENT_PROPAGATE;143            }144        // We intercept MOTION event to figure out if the drag has started and to draw145        // this._dragActor under the pointer when dragging is in progress146        } else if (event.type() == Clutter.EventType.MOTION) {147            if (this._dragInProgress) {148                return this._updateDragPosition(event);149            } else if (this._dragActor == null) {150                return this._maybeStartDrag(event);151            }152        // We intercept KEY_PRESS event so that we can process Esc key press to cancel153        // dragging and ignore all other key presses.154        } else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragInProgress) {155            let symbol = event.get_key_symbol();156            if (symbol == Clutter.Escape) {157                this._cancelDrag(event.get_time());158                return Clutter.EVENT_STOP;159            }160        }161        return Clutter.EVENT_PROPAGATE;162    },163    /**164     * fakeRelease:165     *166     * Fake a release event.167     * Must be called if you want to intercept release events on draggable168     * actors for other purposes (for example if you're using169     * PopupMenu.ignoreRelease())170     */171    fakeRelease: function() {172        this._buttonDown = false;173        this._ungrabActor();174    },175    /**176     * startDrag:177     * @stageX: X coordinate of event178     * @stageY: Y coordinate of event179     * @time: Event timestamp180     *181     * Directly initiate a drag and drop operation from the given actor.182     * This function is useful to call if you've specified manualMode183     * for the draggable.184     */185    startDrag: function (stageX, stageY, time) {186        currentDraggable = this;187        this._dragInProgress = true;188        // Special-case St.Button: the pointer grab messes with the internal189        // state, so force a reset to a reasonable state here190        if (this.actor instanceof St.Button) {191            this.actor.fake_release();192            this.actor.hover = false;193        }194        this.emit('drag-begin', time);195        if (this._onEventId)196            this._ungrabActor();197        this._grabEvents();198        global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG);199        this._dragX = this._dragStartX = stageX;200        this._dragY = this._dragStartY = stageY;201        if (this.actor._delegate && this.actor._delegate.getDragActor) {202            this._dragActor = this.actor._delegate.getDragActor();203            Main.uiGroup.add_child(this._dragActor);204            this._dragActor.raise_top();205            Shell.util_set_hidden_from_pick(this._dragActor, true);206            // Drag actor does not always have to be the same as actor. For example drag actor207            // can be an image that's part of the actor. So to perform "snap back" correctly we need208            // to know what was the drag actor source.209            if (this.actor._delegate.getDragActorSource) {210                this._dragActorSource = this.actor._delegate.getDragActorSource();211                // If the user dragged from the source, then position212                // the dragActor over it. Otherwise, center it213                // around the pointer214                let [sourceX, sourceY] = this._dragActorSource.get_transformed_position();215                let x, y;216                if (stageX > sourceX && stageX <= sourceX + this._dragActor.width &&217                    stageY > sourceY && stageY <= sourceY + this._dragActor.height) {218                    x = sourceX;219                    y = sourceY;220                } else {221                    x = stageX - this._dragActor.width / 2;222                    y = stageY - this._dragActor.height / 2;223                }224                this._dragActor.set_position(x, y);225            } else {226                this._dragActorSource = this.actor;227            }228            this._dragOrigParent = undefined;229            this._dragOffsetX = this._dragActor.x - this._dragStartX;230            this._dragOffsetY = this._dragActor.y - this._dragStartY;231        } else {232            this._dragActor = this.actor;233            this._dragActorSource = undefined;234            this._dragOrigParent = this.actor.get_parent();235            this._dragOrigX = this._dragActor.x;236            this._dragOrigY = this._dragActor.y;237            this._dragOrigScale = this._dragActor.scale_x;238            // Set the actor's scale such that it will keep the same239            // transformed size when it's reparented to the uiGroup240            let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();241            this._dragActor.set_scale(scaledWidth / this.actor.width,242                                      scaledHeight / this.actor.height);243            let [actorStageX, actorStageY] = this.actor.get_transformed_position();244            this._dragOffsetX = actorStageX - this._dragStartX;245            this._dragOffsetY = actorStageY - this._dragStartY;246            this._dragOrigParent.remove_actor(this._dragActor);247            Main.uiGroup.add_child(this._dragActor);248            this._dragActor.raise_top();249            Shell.util_set_hidden_from_pick(this._dragActor, true);250        }251        this._dragOrigOpacity = this._dragActor.opacity;252        if (this._dragActorOpacity != undefined)253            this._dragActor.opacity = this._dragActorOpacity;254        this._snapBackX = this._dragStartX + this._dragOffsetX;255        this._snapBackY = this._dragStartY + this._dragOffsetY;256        this._snapBackScale = this._dragActor.scale_x;257        if (this._dragActorMaxSize != undefined) {258            let [scaledWidth, scaledHeight] = this._dragActor.get_transformed_size();259            let currentSize = Math.max(scaledWidth, scaledHeight);260            if (currentSize > this._dragActorMaxSize) {261                let scale = this._dragActorMaxSize / currentSize;262                let origScale =  this._dragActor.scale_x;263                let origDragOffsetX = this._dragOffsetX;264                let origDragOffsetY = this._dragOffsetY;265                // The position of the actor changes as we scale266                // around the drag position, but we can't just tween267                // to the final position because that tween would268                // fight with updates as the user continues dragging269                // the mouse; instead we do the position computations in270                // an onUpdate() function.271                Tweener.addTween(this._dragActor,272                                 { scale_x: scale * origScale,273                                   scale_y: scale * origScale,274                                   time: SCALE_ANIMATION_TIME,275                                   transition: 'easeOutQuad',276                                   onUpdate: function() {277                                       let currentScale = this._dragActor.scale_x / origScale;278                                       this._dragOffsetX = currentScale * origDragOffsetX;279                                       this._dragOffsetY = currentScale * origDragOffsetY;280                                       this._dragActor.set_position(this._dragX + this._dragOffsetX,281                                                                    this._dragY + this._dragOffsetY);282                                   },283                                   onUpdateScope: this });284            }285        }286    },287    _maybeStartDrag:  function(event) {288        let [stageX, stageY] = event.get_coords();289        // See if the user has moved the mouse enough to trigger a drag290        let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;291        if ((Math.abs(stageX - this._dragStartX) > threshold ||292             Math.abs(stageY - this._dragStartY) > threshold)) {293                this.startDrag(stageX, stageY, event.get_time());294                this._updateDragPosition(event);295        }296        return true;297    },298    _updateDragHover : function () {299        this._updateHoverId = 0;300        let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,301                                                                  this._dragX, this._dragY);302        let dragEvent = {303            x: this._dragX,304            y: this._dragY,305            dragActor: this._dragActor,306            source: this.actor._delegate,307            targetActor: target308        };309        for (let i = 0; i < dragMonitors.length; i++) {310            let motionFunc = dragMonitors[i].dragMotion;311            if (motionFunc) {312                let result = motionFunc(dragEvent);313                if (result != DragMotionResult.CONTINUE) {314                    global.screen.set_cursor(DRAG_CURSOR_MAP[result]);315                    return GLib.SOURCE_REMOVE;316                }317            }318        }319        while (target) {320            if (target._delegate && target._delegate.handleDragOver) {321                let [r, targX, targY] = target.transform_stage_point(this._dragX, this._dragY);322                // We currently loop through all parents on drag-over even if one of the children has handled it.323                // We can check the return value of the function and break the loop if it's true if we don't want324                // to continue checking the parents.325                let result = target._delegate.handleDragOver(this.actor._delegate,326                                                             this._dragActor,327                                                             targX,328                                                             targY,329                                                             0);330                if (result != DragMotionResult.CONTINUE) {331                    global.screen.set_cursor(DRAG_CURSOR_MAP[result]);332                    return GLib.SOURCE_REMOVE;333                }334            }335            target = target.get_parent();336        }337        global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG);338        return GLib.SOURCE_REMOVE;339    },340    _queueUpdateDragHover: function() {341        if (this._updateHoverId)342            return;343        this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT,344                                            Lang.bind(this, this._updateDragHover));345        GLib.Source.set_name_by_id(this._updateHoverId, '[gnome-shell] this._updateDragHover');346    },347    _updateDragPosition : function (event) {348        let [stageX, stageY] = event.get_coords();349        this._dragX = stageX;350        this._dragY = stageY;351        this._dragActor.set_position(stageX + this._dragOffsetX,352                                     stageY + this._dragOffsetY);353        this._queueUpdateDragHover();354        return true;355    },356    _dragActorDropped: function(event) {357        let [dropX, dropY] = event.get_coords();358        let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,359                                                                  dropX, dropY);360        // We call observers only once per motion with the innermost361        // target actor. If necessary, the observer can walk the362        // parent itself.363        let dropEvent = {364            dropActor: this._dragActor,365            targetActor: target,366            clutterEvent: event367        };368        for (let i = 0; i < dragMonitors.length; i++) {369            let dropFunc = dragMonitors[i].dragDrop;370            if (dropFunc)371                switch (dropFunc(dropEvent)) {372                    case DragDropResult.FAILURE:373                    case DragDropResult.SUCCESS:374                        return true;375                    case DragDropResult.CONTINUE:376                        continue;377                }378        }379        // At this point it is too late to cancel a drag by destroying380        // the actor, the fate of which is decided by acceptDrop and its381        // side-effects382        this._dragCancellable = false;383        while (target) {384            if (target._delegate && target._delegate.acceptDrop) {385                let [r, targX, targY] = target.transform_stage_point(dropX, dropY);386                if (target._delegate.acceptDrop(this.actor._delegate,387                                                this._dragActor,388                                                targX,389                                                targY,390                                                event.get_time())) {391                    // If it accepted the drop without taking the actor,392                    // handle it ourselves.393                    if (this._dragActor.get_parent() == Main.uiGroup) {394                        if (this._restoreOnSuccess) {395                            this._restoreDragActor(event.get_time());396                            return true;397                        } else398                            this._dragActor.destroy();399                    }400                    this._dragInProgress = false;401                    global.screen.set_cursor(Meta.Cursor.DEFAULT);402                    this.emit('drag-end', event.get_time(), true);403                    this._dragComplete();404                    return true;405                }406            }407            target = target.get_parent();408        }409        this._cancelDrag(event.get_time());410        return true;411    },412    _getRestoreLocation: function() {413        let x, y, scale;414        if (this._dragActorSource && this._dragActorSource.visible) {415            // Snap the clone back to its source416            [x, y] = this._dragActorSource.get_transformed_position();417            let [sourceScaledWidth, sourceScaledHeight] = this._dragActorSource.get_transformed_size();418            scale = this._dragActor.width / sourceScaledWidth;419        } else if (this._dragOrigParent) {420            // Snap the actor back to its original position within421            // its parent, adjusting for the fact that the parent422            // may have been moved or scaled423            let [parentX, parentY] = this._dragOrigParent.get_transformed_position();424            let [parentWidth, parentHeight] = this._dragOrigParent.get_size();425            let [parentScaledWidth, parentScaledHeight] = this._dragOrigParent.get_transformed_size();426            let parentScale = 1.0;427            if (parentWidth != 0)428                parentScale = parentScaledWidth / parentWidth;429            x = parentX + parentScale * this._dragOrigX;430            y = parentY + parentScale * this._dragOrigY;431            scale = this._dragOrigScale * parentScale;432        } else {433            // Snap back actor to its original stage position434            x = this._snapBackX;435            y = this._snapBackY;436            scale = this._snapBackScale;437        }438        return [x, y, scale];439    },440    _cancelDrag: function(eventTime) {441        this.emit('drag-cancelled', eventTime);442        this._dragInProgress = false;443        let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();444        if (this._actorDestroyed) {445            global.screen.set_cursor(Meta.Cursor.DEFAULT);446            if (!this._buttonDown)447                this._dragComplete();448            this.emit('drag-end', eventTime, false);449            if (!this._dragOrigParent)450                this._dragActor.destroy();451            return;452        }453        this._animationInProgress = true;454        // No target, so snap back455        Tweener.addTween(this._dragActor,456                         { x: snapBackX,457                           y: snapBackY,458                           scale_x: snapBackScale,459                           scale_y: snapBackScale,460                           opacity: this._dragOrigOpacity,461                           time: SNAP_BACK_ANIMATION_TIME,462                           transition: 'easeOutQuad',463                           onComplete: this._onAnimationComplete,464                           onCompleteScope: this,465                           onCompleteParams: [this._dragActor, eventTime]466                         });467    },468    _restoreDragActor: function(eventTime) {469        this._dragInProgress = false;470        [restoreX, restoreY, restoreScale] = this._getRestoreLocation();471        // fade the actor back in at its original location472        this._dragActor.set_position(restoreX, restoreY);473        this._dragActor.set_scale(restoreScale, restoreScale);474        this._dragActor.opacity = 0;475        this._animationInProgress = true;476        Tweener.addTween(this._dragActor,477                         { opacity: this._dragOrigOpacity,478                           time: REVERT_ANIMATION_TIME,479                           transition: 'easeOutQuad',480                           onComplete: this._onAnimationComplete,481                           onCompleteScope: this,482                           onCompleteParams: [this._dragActor, eventTime]483                         });484    },485    _onAnimationComplete : function (dragActor, eventTime) {486        if (this._dragOrigParent) {487            Main.uiGroup.remove_child(this._dragActor);488            this._dragOrigParent.add_actor(this._dragActor);489            dragActor.set_scale(this._dragOrigScale, this._dragOrigScale);490            dragActor.set_position(this._dragOrigX, this._dragOrigY);491        } else {492            dragActor.destroy();493        }494        global.screen.set_cursor(Meta.Cursor.DEFAULT);495        this.emit('drag-end', eventTime, false);496        this._animationInProgress = false;497        if (!this._buttonDown)498            this._dragComplete();499    },500    _dragComplete: function() {501        if (!this._actorDestroyed)502            Shell.util_set_hidden_from_pick(this._dragActor, false);503        this._ungrabEvents();504        global.sync_pointer();505        if (this._updateHoverId) {506            GLib.source_remove(this._updateHoverId);507            this._updateHoverId = 0;508        }509        this._dragActor = undefined;510        currentDraggable = null;511    }512});513Signals.addSignalMethods(_Draggable.prototype);514/**515 * makeDraggable:516 * @actor: Source actor517 * @params: (optional) Additional parameters518 *519 * Create an object which controls drag and drop for the given actor.520 *521 * If %manualMode is %true in @params, do not automatically start522 * drag and drop on click523 *524 * If %dragActorMaxSize is present in @params, the drag actor will525 * be scaled down to be no larger than that size in pixels.526 *527 * If %dragActorOpacity is present in @params, the drag actor will528 * will be set to have that opacity during the drag.529 *530 * Note that when the drag actor is the source actor and the drop531 * succeeds, the actor scale and opacity aren't reset; if the drop532 * target wants to reuse the actor, it's up to the drop target to533 * reset these values.534 */535function makeDraggable(actor, params) {536    return new _Draggable(actor, params);...abstractdragdrop.js
Source:abstractdragdrop.js  
1goog.provide('goog.fx.AbstractDragDrop'); 2goog.provide('goog.fx.AbstractDragDrop.EventType'); 3goog.provide('goog.fx.DragDropEvent'); 4goog.provide('goog.fx.DragDropItem'); 5goog.require('goog.dom'); 6goog.require('goog.dom.classes'); 7goog.require('goog.events'); 8goog.require('goog.events.Event'); 9goog.require('goog.events.EventTarget'); 10goog.require('goog.events.EventType'); 11goog.require('goog.fx.Dragger'); 12goog.require('goog.fx.Dragger.EventType'); 13goog.require('goog.math.Box'); 14goog.require('goog.math.Coordinate'); 15goog.require('goog.style'); 16goog.fx.AbstractDragDrop = function() { 17  this.items_ =[]; 18  this.targets_ =[]; 19  this.scrollableContainers_ =[]; 20}; 21goog.inherits(goog.fx.AbstractDragDrop, goog.events.EventTarget); 22goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ = 10; 23goog.fx.AbstractDragDrop.prototype.isSource_ = false; 24goog.fx.AbstractDragDrop.prototype.isTarget_ = false; 25goog.fx.AbstractDragDrop.prototype.subtargetFunction_; 26goog.fx.AbstractDragDrop.prototype.activeSubtarget_; 27goog.fx.AbstractDragDrop.prototype.dragClass_; 28goog.fx.AbstractDragDrop.prototype.sourceClass_; 29goog.fx.AbstractDragDrop.prototype.targetClass_; 30goog.fx.AbstractDragDrop.prototype.scrollTarget_; 31goog.fx.AbstractDragDrop.prototype.dummyTarget_; 32goog.fx.AbstractDragDrop.prototype.initialized_ = false; 33goog.fx.AbstractDragDrop.EventType = { 34  DRAGOVER: 'dragover', 35  DRAGOUT: 'dragout', 36  DRAG: 'drag', 37  DROP: 'drop', 38  DRAGSTART: 'dragstart', 39  DRAGEND: 'dragend' 40}; 41goog.fx.AbstractDragDrop.initDragDistanceThreshold = 5; 42goog.fx.AbstractDragDrop.prototype.setDragClass = function(className) { 43  this.dragClass_ = className; 44}; 45goog.fx.AbstractDragDrop.prototype.setSourceClass = function(className) { 46  this.sourceClass_ = className; 47}; 48goog.fx.AbstractDragDrop.prototype.setTargetClass = function(className) { 49  this.targetClass_ = className; 50}; 51goog.fx.AbstractDragDrop.prototype.isInitialized = function() { 52  return this.initialized_; 53}; 54goog.fx.AbstractDragDrop.prototype.addItem = function(item) { 55  throw Error('Call to pure virtual method'); 56}; 57goog.fx.AbstractDragDrop.prototype.addTarget = function(target) { 58  this.targets_.push(target); 59  target.isTarget_ = true; 60  this.isSource_ = true; 61}; 62goog.fx.AbstractDragDrop.prototype.setScrollTarget = function(scrollTarget) { 63  this.scrollTarget_ = scrollTarget; 64}; 65goog.fx.AbstractDragDrop.prototype.init = function() { 66  if(this.initialized_) { 67    return; 68  } 69  for(var item, i = 0; item = this.items_[i]; i ++) { 70    this.initItem(item); 71  } 72  this.initialized_ = true; 73}; 74goog.fx.AbstractDragDrop.prototype.initItem = function(item) { 75  if(this.isSource_) { 76    goog.events.listen(item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false, item); 77    if(this.sourceClass_) { 78      goog.dom.classes.add(item.element, this.sourceClass_); 79    } 80  } 81  if(this.isTarget_ && this.targetClass_) { 82    goog.dom.classes.add(item.element, this.targetClass_); 83  } 84}; 85goog.fx.AbstractDragDrop.prototype.disposeItem = function(item) { 86  if(this.isSource_) { 87    goog.events.unlisten(item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false, item); 88    if(this.sourceClass_) { 89      goog.dom.classes.remove(item.element, this.sourceClass_); 90    } 91  } 92  if(this.isTarget_ && this.targetClass_) { 93    goog.dom.classes.remove(item.element, this.targetClass_); 94  } 95}; 96goog.fx.AbstractDragDrop.prototype.removeItems = function() { 97  for(var item, i = 0; item = this.items_[i]; i ++) { 98    this.disposeItem(item); 99  } 100  this.items_.length = 0; 101}; 102goog.fx.AbstractDragDrop.prototype.maybeStartDrag = function(event, item) { 103  item.maybeStartDrag_(event, item.element); 104}; 105goog.fx.AbstractDragDrop.prototype.startDrag = function(event, item) { 106  if(this.dragItem_) { 107    return; 108  } 109  this.dragItem_ = item; 110  var dragStartEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DRAGSTART, this, this.dragItem_); 111  if(this.dispatchEvent(dragStartEvent) == false) { 112    dragStartEvent.dispose(); 113    this.dragItem_ = null; 114    return; 115  } 116  dragStartEvent.dispose(); 117  var el = item.getCurrentDragElement(); 118  this.dragEl_ = this.createDragElement(el); 119  var doc = goog.dom.getOwnerDocument(el); 120  doc.body.appendChild(this.dragEl_); 121  this.dragger_ = this.createDraggerFor(el, this.dragEl_, event); 122  this.dragger_.setScrollTarget(this.scrollTarget_); 123  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false, this); 124  goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this); 125  goog.events.listen(doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_); 126  this.recalculateDragTargets(); 127  this.activeTarget_ = null; 128  this.initScrollableContainers_(); 129  this.dragger_.startDrag(event); 130  event.preventDefault(); 131}; 132goog.fx.AbstractDragDrop.prototype.recalculateDragTargets = function() { 133  this.targetList_ =[]; 134  for(var target, i = 0; target = this.targets_[i]; i ++) { 135    for(var itm, j = 0; itm = target.items_[j]; j ++) { 136      this.addDragTarget_(target, itm); 137    } 138  } 139  if(! this.targetBox_) { 140    this.targetBox_ = new goog.math.Box(0, 0, 0, 0); 141  } 142}; 143goog.fx.AbstractDragDrop.prototype.createDraggerFor = function(sourceEl, el, event) { 144  var pos = this.getDragElementPosition(sourceEl, el, event); 145  el.style.position = 'absolute'; 146  el.style.left = pos.x + 'px'; 147  el.style.top = pos.y + 'px'; 148  return new goog.fx.Dragger(el); 149}; 150goog.fx.AbstractDragDrop.prototype.endDrag = function(event) { 151  var activeTarget = event.dragCanceled ? null: this.activeTarget_; 152  if(activeTarget && activeTarget.target_) { 153    var clientX = event.clientX; 154    var clientY = event.clientY; 155    var scroll = this.getScrollPos(); 156    var x = clientX + scroll.x; 157    var y = clientY + scroll.y; 158    var subtarget; 159    if(this.subtargetFunction_) { 160      subtarget = this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y); 161    } 162    var dragEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_, activeTarget.target_, activeTarget.item_, activeTarget.element_, clientX, clientY, x, y); 163    this.dispatchEvent(dragEvent); 164    dragEvent.dispose(); 165    var dropEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DROP, this, this.dragItem_, activeTarget.target_, activeTarget.item_, activeTarget.element_, clientX, clientY, x, y, subtarget); 166    activeTarget.target_.dispatchEvent(dropEvent); 167    dropEvent.dispose(); 168  } 169  var dragEndEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DRAGEND, this, this.dragItem_); 170  this.dispatchEvent(dragEndEvent); 171  dragEndEvent.dispose(); 172  goog.events.unlisten(this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false, this); 173  goog.events.unlisten(this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this); 174  var doc = goog.dom.getOwnerDocument(this.dragItem_.getCurrentDragElement()); 175  goog.events.unlisten(doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_); 176  this.afterEndDrag(this.activeTarget_ ? this.activeTarget_.item_: null); 177}; 178goog.fx.AbstractDragDrop.prototype.afterEndDrag = function(opt_dropTarget) { 179  this.disposeDrag(); 180}; 181goog.fx.AbstractDragDrop.prototype.disposeDrag = function() { 182  this.disposeScrollableContainerListeners_(); 183  this.dragger_.dispose(); 184  goog.dom.removeNode(this.dragEl_); 185  delete this.dragItem_; 186  delete this.dragEl_; 187  delete this.dragger_; 188  delete this.targetList_; 189  delete this.activeTarget_; 190}; 191goog.fx.AbstractDragDrop.prototype.moveDrag_ = function(event) { 192  var x = event.clientX; 193  var y = event.clientY; 194  var scroll = this.getScrollPos(); 195  x += scroll.x; 196  y += scroll.y; 197  var activeTarget = this.activeTarget_; 198  var subtarget; 199  if(activeTarget) { 200    if(this.subtargetFunction_ && activeTarget.target_) { 201      subtarget = this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y); 202    } 203    if(this.isInside_(x, y, activeTarget.box_) && subtarget == this.activeSubtarget_) { 204      return; 205    } 206    if(activeTarget.target_) { 207      var sourceDragOutEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_, activeTarget.target_, activeTarget.item_, activeTarget.element_); 208      this.dispatchEvent(sourceDragOutEvent); 209      sourceDragOutEvent.dispose(); 210      var targetDragOutEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_, activeTarget.target_, activeTarget.item_, activeTarget.element_, undefined, undefined, undefined, undefined, this.activeSubtarget_); 211      activeTarget.target_.dispatchEvent(targetDragOutEvent); 212      targetDragOutEvent.dispose(); 213    } 214    this.activeSubtarget_ = subtarget; 215    this.activeTarget_ = null; 216  } 217  if(this.isInside_(x, y, this.targetBox_)) { 218    activeTarget = this.activeTarget_ = this.getTargetFromPosition_(x, y); 219    if(activeTarget && activeTarget.target_) { 220      if(this.subtargetFunction_) { 221        subtarget = this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y); 222      } 223      var sourceDragOverEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_, activeTarget.target_, activeTarget.item_, activeTarget.element_); 224      sourceDragOverEvent.subtarget = subtarget; 225      this.dispatchEvent(sourceDragOverEvent); 226      sourceDragOverEvent.dispose(); 227      var targetDragOverEvent = new goog.fx.DragDropEvent(goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_, activeTarget.target_, activeTarget.item_, activeTarget.element_, event.clientX, event.clientY, undefined, undefined, subtarget); 228      activeTarget.target_.dispatchEvent(targetDragOverEvent); 229      targetDragOverEvent.dispose(); 230    } else if(! activeTarget) { 231      this.activeTarget_ = this.maybeCreateDummyTargetForPosition_(x, y); 232    } 233  } 234}; 235goog.fx.AbstractDragDrop.prototype.suppressSelect_ = function(event) { 236  return false; 237}; 238goog.fx.AbstractDragDrop.prototype.initScrollableContainers_ = function() { 239  var container, i, j, target; 240  for(i = 0; container = this.scrollableContainers_[i]; i ++) { 241    goog.events.listen(container.element_, goog.events.EventType.SCROLL, this.containerScrollHandler_, false, this); 242    container.containedTargets_ =[]; 243    container.savedScrollLeft_ = container.element_.scrollLeft; 244    container.savedScrollTop_ = container.element_.scrollTop; 245    var pos = goog.style.getPageOffset(container.element_); 246    var size = goog.style.getSize(container.element_); 247    container.box_ = new goog.math.Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x); 248  } 249  for(i = 0; target = this.targetList_[i]; i ++) { 250    for(j = 0; container = this.scrollableContainers_[j]; j ++) { 251      if(goog.dom.contains(container.element_, target.element_)) { 252        container.containedTargets_.push(target); 253        target.scrollableContainer_ = container; 254      } 255    } 256  } 257}; 258goog.fx.AbstractDragDrop.prototype.disposeScrollableContainerListeners_ = function() { 259  for(var i = 0, container; container = this.scrollableContainers_[i]; i ++) { 260    goog.events.unlisten(container.element_, 'scroll', this.containerScrollHandler_, false, this); 261    container.containedTargets_ =[]; 262  } 263}; 264goog.fx.AbstractDragDrop.prototype.addScrollableContainer = function(element) { 265  this.scrollableContainers_.push(new goog.fx.ScrollableContainer_(element)); 266}; 267goog.fx.AbstractDragDrop.prototype.containerScrollHandler_ = function(e) { 268  for(var i = 0, container; container = this.scrollableContainers_[i]; i ++) { 269    if(e.target == container.element_) { 270      var deltaTop = container.savedScrollTop_ - container.element_.scrollTop; 271      var deltaLeft = container.savedScrollLeft_ - container.element_.scrollLeft; 272      container.savedScrollTop_ = container.element_.scrollTop; 273      container.savedScrollLeft_ = container.element_.scrollLeft; 274      for(var j = 0, target; target = container.containedTargets_[j]; j ++) { 275        var box = target.box_; 276        box.top += deltaTop; 277        box.left += deltaLeft; 278        box.bottom += deltaTop; 279        box.right += deltaLeft; 280        this.calculateTargetBox_(box); 281      } 282    } 283  } 284}; 285goog.fx.AbstractDragDrop.prototype.setSubtargetFunction = function(f) { 286  this.subtargetFunction_ = f; 287}; 288goog.fx.AbstractDragDrop.prototype.createDragElement = function(sourceEl) { 289  var dragEl = this.cloneNode_(sourceEl); 290  if(this.dragClass_) { 291    goog.dom.classes.add(dragEl, this.dragClass_); 292  } 293  return dragEl; 294}; 295goog.fx.AbstractDragDrop.prototype.getDragElementPosition = function(el, dragEl, event) { 296  var pos = goog.style.getPageOffset(el); 297  var marginBox = goog.style.getMarginBox(el); 298  pos.x +=(marginBox.left || 0) * 2; 299  pos.y +=(marginBox.top || 0) * 2; 300  return pos; 301}; 302goog.fx.AbstractDragDrop.prototype.getDragger = function() { 303  return this.dragger_; 304}; 305goog.fx.AbstractDragDrop.prototype.cloneNode_ = function(sourceEl) { 306  var clonedEl =(sourceEl.cloneNode(true)); 307  switch(sourceEl.tagName.toLowerCase()) { 308    case 'tr': 309      return goog.dom.createDom('table', null, goog.dom.createDom('tbody', null, clonedEl)); 310    case 'td': 311    case 'th': 312      return goog.dom.createDom('table', null, goog.dom.createDom('tbody', null, goog.dom.createDom('tr', null, clonedEl))); 313    default: 314      return clonedEl; 315  } 316}; 317goog.fx.AbstractDragDrop.prototype.addDragTarget_ = function(target, item) { 318  var draggableElements = item.getDraggableElements(); 319  var targetList = this.targetList_; 320  for(var i = 0; i < draggableElements.length; i ++) { 321    var draggableElement = draggableElements[i]; 322    var pos = goog.style.getPageOffset(draggableElement); 323    var size = goog.style.getSize(draggableElement); 324    var box = new goog.math.Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x); 325    targetList.push(new goog.fx.ActiveDropTarget_(box, target, item, draggableElement)); 326    this.calculateTargetBox_(box); 327  } 328}; 329goog.fx.AbstractDragDrop.prototype.calculateTargetBox_ = function(box) { 330  if(this.targetList_.length == 1) { 331    this.targetBox_ = new goog.math.Box(box.top, box.right, box.bottom, box.left); 332  } else { 333    var tb = this.targetBox_; 334    tb.left = Math.min(box.left, tb.left); 335    tb.right = Math.max(box.right, tb.right); 336    tb.top = Math.min(box.top, tb.top); 337    tb.bottom = Math.max(box.bottom, tb.bottom); 338  } 339}; 340goog.fx.AbstractDragDrop.prototype.maybeCreateDummyTargetForPosition_ = function(x, y) { 341  if(! this.dummyTarget_) { 342    this.dummyTarget_ = new goog.fx.ActiveDropTarget_(this.targetBox_.clone()); 343  } 344  var fakeTargetBox = this.dummyTarget_.box_; 345  fakeTargetBox.top = this.targetBox_.top; 346  fakeTargetBox.right = this.targetBox_.right; 347  fakeTargetBox.bottom = this.targetBox_.bottom; 348  fakeTargetBox.left = this.targetBox_.left; 349  for(var i = 0, target; target = this.targetList_[i]; i ++) { 350    var box = target.box_; 351    var horizontalClip = - 1; 352    if(x >= box.right) { 353      horizontalClip = box.right > fakeTargetBox.left ? box.right: fakeTargetBox.left; 354    } else if(x < box.left) { 355      horizontalClip = box.left < fakeTargetBox.right ? box.left: fakeTargetBox.right; 356    } 357    var verticalClip = - 1; 358    if(y >= box.bottom) { 359      verticalClip = box.bottom > fakeTargetBox.top ? box.bottom: fakeTargetBox.top; 360    } else if(y < box.top) { 361      verticalClip = box.top < fakeTargetBox.bottom ? box.top: fakeTargetBox.bottom; 362    } 363    if(horizontalClip >= 0 && verticalClip >= 0) { 364      if(Math.abs(horizontalClip - x) > Math.abs(verticalClip - y)) { 365        verticalClip = - 1; 366      } else { 367        horizontalClip = - 1; 368      } 369    } 370    if(horizontalClip >= 0) { 371      if(horizontalClip <= x) { 372        fakeTargetBox.left = horizontalClip; 373      } else { 374        fakeTargetBox.right = horizontalClip; 375      } 376    } else if(verticalClip >= 0) { 377      if(verticalClip <= y) { 378        fakeTargetBox.top = verticalClip; 379      } else { 380        fakeTargetBox.bottom = verticalClip; 381      } 382    } 383  } 384  return(fakeTargetBox.right - fakeTargetBox.left) *(fakeTargetBox.bottom - fakeTargetBox.top) >= goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ ? this.dummyTarget_: null; 385}; 386goog.fx.AbstractDragDrop.prototype.getTargetFromPosition_ = function(x, y) { 387  for(var target, i = 0; target = this.targetList_[i]; i ++) { 388    if(this.isInside_(x, y, target.box_)) { 389      if(target.scrollableContainer_) { 390        var box = target.scrollableContainer_.box_; 391        if(this.isInside_(x, y, box)) { 392          return target; 393        } 394      } else { 395        return target; 396      } 397    } 398  } 399  return null; 400}; 401goog.fx.AbstractDragDrop.prototype.isInside_ = function(x, y, box) { 402  return x >= box.left && x < box.right && y >= box.top && y < box.bottom; 403}; 404goog.fx.AbstractDragDrop.prototype.getScrollPos = function() { 405  return goog.dom.getDomHelper(this.dragEl_).getDocumentScroll(); 406}; 407goog.fx.AbstractDragDrop.prototype.disposeInternal = function() { 408  goog.fx.AbstractDragDrop.superClass_.disposeInternal.call(this); 409  this.removeItems(); 410}; 411goog.fx.DragDropEvent = function(type, source, sourceItem, opt_target, opt_targetItem, opt_targetElement, opt_clientX, opt_clientY, opt_x, opt_y, opt_subtarget) { 412  goog.events.Event.call(this, type); 413  this.dragSource = source; 414  this.dragSourceItem = sourceItem; 415  this.dropTarget = opt_target; 416  this.dropTargetItem = opt_targetItem; 417  this.dropTargetElement = opt_targetElement; 418  this.clientX = opt_clientX; 419  this.clientY = opt_clientY; 420  this.viewportX = opt_x; 421  this.viewportY = opt_y; 422  this.subtarget = opt_subtarget; 423}; 424goog.inherits(goog.fx.DragDropEvent, goog.events.Event); 425goog.fx.DragDropEvent.prototype.disposeInternal = function() { 426  goog.fx.DragDropEvent.superClass_.disposeInternal.call(this); 427  delete this.dragSource; 428  delete this.dragSourceItem; 429  delete this.dropTarget; 430  delete this.dropTargetItem; 431  delete this.dropTargetElement; 432}; 433goog.fx.DragDropItem = function(element, opt_data) { 434  this.element = goog.dom.getElement(element); 435  this.data = opt_data; 436  this.parent_ = null; 437  if(! this.element) { 438    throw Error('Invalid argument'); 439  } 440}; 441goog.inherits(goog.fx.DragDropItem, goog.events.EventTarget); 442goog.fx.DragDropItem.prototype.currentDragElement_ = null; 443goog.fx.DragDropItem.prototype.getData = function() { 444  return this.data; 445}; 446goog.fx.DragDropItem.prototype.getDraggableElement = function(target) { 447  return target; 448}; 449goog.fx.DragDropItem.prototype.getCurrentDragElement = function() { 450  return this.currentDragElement_; 451}; 452goog.fx.DragDropItem.prototype.getDraggableElements = function() { 453  return[this.element]; 454}; 455goog.fx.DragDropItem.prototype.mouseDown_ = function(event) { 456  var element = this.getDraggableElement((event.target)); 457  if(element) { 458    this.maybeStartDrag_(event, element); 459  } 460}; 461goog.fx.DragDropItem.prototype.setParent = function(parent) { 462  this.parent_ = parent; 463}; 464goog.fx.DragDropItem.prototype.maybeStartDrag_ = function(event, element) { 465  goog.events.listen(element, goog.events.EventType.MOUSEMOVE, this.mouseMove_, false, this); 466  goog.events.listen(element, goog.events.EventType.MOUSEOUT, this.mouseMove_, false, this); 467  goog.events.listen(element, goog.events.EventType.MOUSEUP, this.mouseUp_, false, this); 468  this.currentDragElement_ = element; 469  this.startPosition_ = new goog.math.Coordinate(event.clientX, event.clientY); 470  event.preventDefault(); 471}; 472goog.fx.DragDropItem.prototype.mouseMove_ = function(event) { 473  var distance = Math.abs(event.clientX - this.startPosition_.x) + Math.abs(event.clientY - this.startPosition_.y); 474  if(distance > goog.fx.AbstractDragDrop.initDragDistanceThreshold) { 475    var currentDragElement = this.currentDragElement_; 476    goog.events.unlisten(currentDragElement, goog.events.EventType.MOUSEMOVE, this.mouseMove_, false, this); 477    goog.events.unlisten(currentDragElement, goog.events.EventType.MOUSEOUT, this.mouseMove_, false, this); 478    goog.events.unlisten(currentDragElement, goog.events.EventType.MOUSEUP, this.mouseUp_, false, this); 479    this.parent_.startDrag(event, this); 480  } 481}; 482goog.fx.DragDropItem.prototype.mouseUp_ = function(event) { 483  var currentDragElement = this.currentDragElement_; 484  goog.events.unlisten(currentDragElement, goog.events.EventType.MOUSEMOVE, this.mouseMove_, false, this); 485  goog.events.unlisten(currentDragElement, goog.events.EventType.MOUSEOUT, this.mouseMove_, false, this); 486  goog.events.unlisten(currentDragElement, goog.events.EventType.MOUSEUP, this.mouseUp_, false, this); 487  delete this.startPosition_; 488  this.currentDragElement_ = null; 489}; 490goog.fx.ActiveDropTarget_ = function(box, opt_target, opt_item, opt_element) { 491  this.box_ = box; 492  this.target_ = opt_target; 493  this.item_ = opt_item; 494  this.element_ = opt_element; 495}; 496goog.fx.ActiveDropTarget_.prototype.scrollableContainer_ = null; 497goog.fx.ScrollableContainer_ = function(element) { 498  this.containedTargets_ =[]; 499  this.element_ = element; 500  this.savedScrollLeft_ = 0; 501  this.savedScrollTop_ = 0; 502  this.box_ = null; ...draglistgroup.js
Source:draglistgroup.js  
1goog.provide('goog.fx.DragListDirection'); 2goog.provide('goog.fx.DragListGroup'); 3goog.provide('goog.fx.DragListGroup.EventType'); 4goog.provide('goog.fx.DragListGroupEvent'); 5goog.require('goog.asserts'); 6goog.require('goog.dom'); 7goog.require('goog.dom.NodeType'); 8goog.require('goog.dom.classes'); 9goog.require('goog.events.Event'); 10goog.require('goog.events.EventHandler'); 11goog.require('goog.events.EventTarget'); 12goog.require('goog.events.EventType'); 13goog.require('goog.fx.Dragger'); 14goog.require('goog.fx.Dragger.EventType'); 15goog.require('goog.math.Coordinate'); 16goog.require('goog.style'); 17goog.fx.DragListGroup = function() { 18  goog.events.EventTarget.call(this); 19  this.dragLists_ =[]; 20  this.dragItems_ =[]; 21  this.dragItemForHandle_ = { }; 22  this.eventHandler_ = new goog.events.EventHandler(this); 23  this.isInitialized_ = false; 24  this.isCurrDragItemAlwaysDisplayed_ = false; 25  this.updateWhileDragging_ = true; 26}; 27goog.inherits(goog.fx.DragListGroup, goog.events.EventTarget); 28goog.fx.DragListDirection = { 29  DOWN: 0, 30  RIGHT: 2, 31  LEFT: 3, 32  RIGHT_2D: 4, 33  LEFT_2D: 5 34}; 35goog.fx.DragListGroup.EventType = { 36  BEFOREDRAGSTART: 'beforedragstart', 37  DRAGSTART: 'dragstart', 38  BEFOREDRAGMOVE: 'beforedragmove', 39  DRAGMOVE: 'dragmove', 40  BEFOREDRAGEND: 'beforedragend', 41  DRAGEND: 'dragend' 42}; 43goog.fx.DragListGroup.prototype.dragItemHoverClasses_; 44goog.fx.DragListGroup.prototype.dragItemHandleHoverClasses_; 45goog.fx.DragListGroup.prototype.currDragItemClasses_; 46goog.fx.DragListGroup.prototype.draggerElClass_; 47goog.fx.DragListGroup.prototype.currDragItem_; 48goog.fx.DragListGroup.prototype.currHoverList_; 49goog.fx.DragListGroup.prototype.origList_; 50goog.fx.DragListGroup.prototype.origNextItem_; 51goog.fx.DragListGroup.prototype.currHoverItem_; 52goog.fx.DragListGroup.prototype.draggerEl_; 53goog.fx.DragListGroup.prototype.dragger_; 54goog.fx.DragListGroup.prototype.setIsCurrDragItemAlwaysDisplayed = function() { 55  this.isCurrDragItemAlwaysDisplayed_ = true; 56}; 57goog.fx.DragListGroup.prototype.setNoUpdateWhileDragging = function() { 58  this.updateWhileDragging_ = false; 59}; 60goog.fx.DragListGroup.prototype.addDragList = function(dragListElement, growthDirection, opt_unused, opt_dragHoverClass) { 61  goog.asserts.assert(! this.isInitialized_); 62  dragListElement.dlgGrowthDirection_ = growthDirection; 63  dragListElement.dlgDragHoverClass_ = opt_dragHoverClass; 64  this.dragLists_.push(dragListElement); 65}; 66goog.fx.DragListGroup.prototype.setFunctionToGetHandleForDragItem = function(getHandleForDragItemFn) { 67  goog.asserts.assert(! this.isInitialized_); 68  this.getHandleForDragItem_ = getHandleForDragItemFn; 69}; 70goog.fx.DragListGroup.prototype.setDragItemHoverClass = function(var_args) { 71  goog.asserts.assert(! this.isInitialized_); 72  this.dragItemHoverClasses_ = goog.array.slice(arguments, 0); 73}; 74goog.fx.DragListGroup.prototype.setDragItemHandleHoverClass = function(var_args) { 75  goog.asserts.assert(! this.isInitialized_); 76  this.dragItemHandleHoverClasses_ = goog.array.slice(arguments, 0); 77}; 78goog.fx.DragListGroup.prototype.setCurrDragItemClass = function(var_args) { 79  goog.asserts.assert(! this.isInitialized_); 80  this.currDragItemClasses_ = goog.array.slice(arguments, 0); 81}; 82goog.fx.DragListGroup.prototype.setDraggerElClass = function(draggerElClass) { 83  goog.asserts.assert(! this.isInitialized_); 84  this.draggerElClass_ = draggerElClass; 85}; 86goog.fx.DragListGroup.prototype.init = function() { 87  if(this.isInitialized_) { 88    return; 89  } 90  for(var i = 0, numLists = this.dragLists_.length; i < numLists; i ++) { 91    var dragList = this.dragLists_[i]; 92    var dragItems = goog.dom.getChildren(dragList); 93    for(var j = 0, numItems = dragItems.length; j < numItems; ++ j) { 94      var dragItem = dragItems[j]; 95      var dragItemHandle = this.getHandleForDragItem_(dragItem); 96      var uid = goog.getUid(dragItemHandle); 97      this.dragItemForHandle_[uid]= dragItem; 98      if(this.dragItemHoverClasses_) { 99        this.eventHandler_.listen(dragItem, goog.events.EventType.MOUSEOVER, this.handleDragItemMouseover_); 100        this.eventHandler_.listen(dragItem, goog.events.EventType.MOUSEOUT, this.handleDragItemMouseout_); 101      } 102      if(this.dragItemHandleHoverClasses_) { 103        this.eventHandler_.listen(dragItemHandle, goog.events.EventType.MOUSEOVER, this.handleDragItemHandleMouseover_); 104        this.eventHandler_.listen(dragItemHandle, goog.events.EventType.MOUSEOUT, this.handleDragItemHandleMouseout_); 105      } 106      this.dragItems_.push(dragItem); 107      this.eventHandler_.listen(dragItemHandle, goog.events.EventType.MOUSEDOWN, this.handleDragStart_); 108    } 109  } 110  this.isInitialized_ = true; 111}; 112goog.fx.DragListGroup.prototype.disposeInternal = function() { 113  this.eventHandler_.dispose(); 114  for(var i = 0, n = this.dragLists_.length; i < n; i ++) { 115    var dragList = this.dragLists_[i]; 116    dragList.dlgGrowthDirection_ = undefined; 117    dragList.dlgDragHoverClass_ = undefined; 118  } 119  this.dragLists_.length = 0; 120  this.dragItems_.length = 0; 121  this.dragItemForHandle_ = null; 122  this.cleanupDragDom_(); 123  goog.fx.DragListGroup.superClass_.disposeInternal.call(this); 124}; 125goog.fx.DragListGroup.prototype.recacheListAndItemBounds_ = function(currDragItem) { 126  for(var i = 0, n = this.dragLists_.length; i < n; i ++) { 127    var dragList = this.dragLists_[i]; 128    dragList.dlgBounds_ = goog.style.getBounds(dragList); 129  } 130  for(var i = 0, n = this.dragItems_.length; i < n; i ++) { 131    var dragItem = this.dragItems_[i]; 132    if(dragItem != currDragItem) { 133      dragItem.dlgBounds_ = goog.style.getBounds(dragItem); 134    } 135  } 136}; 137goog.fx.DragListGroup.prototype.handleDragStart_ = function(e) { 138  if(! e.isMouseActionButton()) { 139    e.preventDefault(); 140    return; 141  } 142  var uid = goog.getUid((e.currentTarget)); 143  var currDragItem =(this.dragItemForHandle_[uid]); 144  var rv = this.dispatchEvent(new goog.fx.DragListGroupEvent(goog.fx.DragListGroup.EventType.BEFOREDRAGSTART, this, e, currDragItem, null, null)); 145  if(! rv) { 146    e.preventDefault(); 147    return; 148  } 149  this.currDragItem_ = currDragItem; 150  this.origList_ =(currDragItem.parentNode); 151  this.origNextItem_ = goog.dom.getNextElementSibling(currDragItem); 152  this.currHoverItem_ = this.origNextItem_; 153  this.currHoverList_ = this.origList_; 154  var draggerEl = this.cloneNode_(currDragItem); 155  this.draggerEl_ = draggerEl; 156  if(this.currDragItemClasses_) { 157    goog.dom.classes.add.apply(null, goog.array.concat(currDragItem, this.currDragItemClasses_)); 158  } else { 159    currDragItem.style.visibility = 'hidden'; 160  } 161  if(this.draggerElClass_) { 162    goog.dom.classes.add(draggerEl, this.draggerElClass_); 163  } 164  draggerEl.style.margin = '0px'; 165  draggerEl.style.position = 'absolute'; 166  goog.dom.getOwnerDocument(currDragItem).body.appendChild(draggerEl); 167  var currDragItemPos = goog.style.getPageOffset(currDragItem); 168  goog.style.setPageOffset(draggerEl, currDragItemPos); 169  var draggerElSize = goog.style.getSize(draggerEl); 170  draggerEl.halfWidth = draggerElSize.width / 2; 171  draggerEl.halfHeight = draggerElSize.height / 2; 172  if(this.updateWhileDragging_) { 173    currDragItem.style.display = 'none'; 174  } 175  this.recacheListAndItemBounds_(currDragItem); 176  currDragItem.style.display = ''; 177  this.dragger_ = new goog.fx.Dragger(draggerEl); 178  this.eventHandler_.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG, this.handleDragMove_); 179  this.eventHandler_.listen(this.dragger_, goog.fx.Dragger.EventType.END, this.handleDragEnd_); 180  this.dragger_.startDrag(e); 181  this.dispatchEvent(new goog.fx.DragListGroupEvent(goog.fx.DragListGroup.EventType.DRAGSTART, this, e, currDragItem, draggerEl, this.dragger_)); 182}; 183goog.fx.DragListGroup.prototype.handleDragMove_ = function(dragEvent) { 184  var draggerElPos = goog.style.getPageOffset(this.draggerEl_); 185  var draggerElCenter = new goog.math.Coordinate(draggerElPos.x + this.draggerEl_.halfWidth, draggerElPos.y + this.draggerEl_.halfHeight); 186  var hoverList = this.getHoverDragList_(draggerElCenter); 187  var hoverNextItem = hoverList ? this.getHoverNextItem_(hoverList, draggerElCenter): null; 188  var rv = this.dispatchEvent(new goog.fx.DragListGroupEvent(goog.fx.DragListGroup.EventType.BEFOREDRAGMOVE, this, dragEvent, this.currDragItem_, this.draggerEl_, this.dragger_, draggerElCenter, hoverList, hoverNextItem)); 189  if(! rv) { 190    return false; 191  } 192  if(hoverList) { 193    if(this.updateWhileDragging_) { 194      this.insertCurrDragItem_(hoverList, hoverNextItem); 195    } else { 196      this.updateCurrHoverItem(hoverNextItem, draggerElCenter); 197    } 198    this.currDragItem_.style.display = ''; 199    if(hoverList.dlgDragHoverClass_) { 200      goog.dom.classes.add(hoverList, hoverList.dlgDragHoverClass_); 201    } 202  } else { 203    if(! this.isCurrDragItemAlwaysDisplayed_) { 204      this.currDragItem_.style.display = 'none'; 205    } 206    for(var i = 0, n = this.dragLists_.length; i < n; i ++) { 207      var dragList = this.dragLists_[i]; 208      if(dragList.dlgDragHoverClass_) { 209        goog.dom.classes.remove(dragList, dragList.dlgDragHoverClass_); 210      } 211    } 212  } 213  if(hoverList != this.currHoverList_) { 214    this.currHoverList_ = hoverList; 215    this.recacheListAndItemBounds_(this.currDragItem_); 216  } 217  this.dispatchEvent(new goog.fx.DragListGroupEvent(goog.fx.DragListGroup.EventType.DRAGMOVE, this, dragEvent,(this.currDragItem_), this.draggerEl_, this.dragger_, draggerElCenter, hoverList, hoverNextItem)); 218  return false; 219}; 220goog.fx.DragListGroup.prototype.handleDragEnd_ = function(dragEvent) { 221  var rv = this.dispatchEvent(new goog.fx.DragListGroupEvent(goog.fx.DragListGroup.EventType.BEFOREDRAGEND, this, dragEvent,(this.currDragItem_), this.draggerEl_, this.dragger_)); 222  if(! rv) { 223    return false; 224  } 225  if(! this.updateWhileDragging_) { 226    this.insertCurrHoverItem(); 227  } 228  this.cleanupDragDom_(); 229  this.dispatchEvent(new goog.fx.DragListGroupEvent(goog.fx.DragListGroup.EventType.DRAGEND, this, dragEvent, this.currDragItem_, this.draggerEl_, this.dragger_)); 230  this.currDragItem_ = null; 231  this.currHoverList_ = null; 232  this.origList_ = null; 233  this.origNextItem_ = null; 234  this.draggerEl_ = null; 235  this.dragger_ = null; 236  for(var i = 0, n = this.dragLists_.length; i < n; i ++) { 237    this.dragLists_[i].dlgBounds_ = null; 238  } 239  for(var i = 0, n = this.dragItems_.length; i < n; i ++) { 240    this.dragItems_[i].dlgBounds_ = null; 241  } 242  return true; 243}; 244goog.fx.DragListGroup.prototype.cleanupDragDom_ = function() { 245  goog.dispose(this.dragger_); 246  if(this.draggerEl_) { 247    goog.dom.removeNode(this.draggerEl_); 248  } 249  if(this.currDragItem_ && this.currDragItem_.style.display == 'none') { 250    this.origList_.insertBefore(this.currDragItem_, this.origNextItem_); 251    this.currDragItem_.style.display = ''; 252  } 253  if(this.currDragItemClasses_ && this.currDragItem_) { 254    goog.dom.classes.remove.apply(null, goog.array.concat(this.currDragItem_, this.currDragItemClasses_)); 255  } else if(this.currDragItem_) { 256    this.currDragItem_.style.visibility = 'visible'; 257  } 258  for(var i = 0, n = this.dragLists_.length; i < n; i ++) { 259    var dragList = this.dragLists_[i]; 260    if(dragList.dlgDragHoverClass_) { 261      goog.dom.classes.remove(dragList, dragList.dlgDragHoverClass_); 262    } 263  } 264}; 265goog.fx.DragListGroup.prototype.getHandleForDragItem_ = function(dragItem) { 266  return dragItem; 267}; 268goog.fx.DragListGroup.prototype.handleDragItemMouseover_ = function(e) { 269  goog.dom.classes.add.apply(null, goog.array.concat((e.currentTarget), this.dragItemHoverClasses_)); 270}; 271goog.fx.DragListGroup.prototype.handleDragItemMouseout_ = function(e) { 272  goog.dom.classes.remove.apply(null, goog.array.concat((e.currentTarget), this.dragItemHoverClasses_)); 273}; 274goog.fx.DragListGroup.prototype.handleDragItemHandleMouseover_ = function(e) { 275  goog.dom.classes.add.apply(null, goog.array.concat((e.currentTarget), this.dragItemHandleHoverClasses_)); 276}; 277goog.fx.DragListGroup.prototype.handleDragItemHandleMouseout_ = function(e) { 278  goog.dom.classes.remove.apply(null, goog.array.concat((e.currentTarget), this.dragItemHandleHoverClasses_)); 279}; 280goog.fx.DragListGroup.prototype.getHoverDragList_ = function(draggerElCenter) { 281  var prevHoverList = null; 282  if(this.currDragItem_.style.display != 'none') { 283    prevHoverList =(this.currDragItem_.parentNode); 284    var prevHoverListBounds = goog.style.getBounds(prevHoverList); 285    if(this.isInRect_(draggerElCenter, prevHoverListBounds)) { 286      return prevHoverList; 287    } 288  } 289  for(var i = 0, n = this.dragLists_.length; i < n; i ++) { 290    var dragList = this.dragLists_[i]; 291    if(dragList == prevHoverList) { 292      continue; 293    } 294    if(this.isInRect_(draggerElCenter, dragList.dlgBounds_)) { 295      return dragList; 296    } 297  } 298  return null; 299}; 300goog.fx.DragListGroup.prototype.isInRect_ = function(pos, rect) { 301  return pos.x > rect.left && pos.x < rect.left + rect.width && pos.y > rect.top && pos.y < rect.top + rect.height; 302}; 303goog.fx.DragListGroup.prototype.updateCurrHoverItem = function(hoverNextItem, opt_draggerElCenter) { 304  if(goog.isDefAndNotNull(hoverNextItem)) { 305    this.currHoverItem_ = hoverNextItem; 306  } 307}; 308goog.fx.DragListGroup.prototype.insertCurrHoverItem = function() { 309  this.origList_.insertBefore(this.currDragItem_, this.currHoverItem_); 310}; 311goog.fx.DragListGroup.prototype.getHoverNextItem_ = function(hoverList, draggerElCenter) { 312  if(hoverList == null) { 313    throw Error('getHoverNextItem_ called with null hoverList.'); 314  } 315  var relevantCoord; 316  var getRelevantBoundFn; 317  var isBeforeFn; 318  var pickClosestRow = false; 319  var distanceToClosestRow = undefined; 320  switch(hoverList.dlgGrowthDirection_) { 321    case goog.fx.DragListDirection.DOWN: 322      relevantCoord = draggerElCenter.y; 323      getRelevantBoundFn = goog.fx.DragListGroup.getBottomBound_; 324      isBeforeFn = goog.fx.DragListGroup.isLessThan_; 325      break; 326    case goog.fx.DragListDirection.RIGHT_2D: 327      pickClosestRow = true; 328    case goog.fx.DragListDirection.RIGHT: 329      relevantCoord = draggerElCenter.x; 330      getRelevantBoundFn = goog.fx.DragListGroup.getRightBound_; 331      isBeforeFn = goog.fx.DragListGroup.isLessThan_; 332      break; 333    case goog.fx.DragListDirection.LEFT_2D: 334      pickClosestRow = true; 335    case goog.fx.DragListDirection.LEFT: 336      relevantCoord = draggerElCenter.x; 337      getRelevantBoundFn = goog.fx.DragListGroup.getLeftBound_; 338      isBeforeFn = goog.fx.DragListGroup.isGreaterThan_; 339      break; 340  } 341  var earliestAfterItem = null; 342  var earliestAfterItemRelevantBound; 343  var hoverListItems = goog.dom.getChildren(hoverList); 344  for(var i = 0, n = hoverListItems.length; i < n; i ++) { 345    var item = hoverListItems[i]; 346    if(item == this.currDragItem_) { 347      continue; 348    } 349    var relevantBound = getRelevantBoundFn(item.dlgBounds_); 350    if(pickClosestRow) { 351      var distanceToRow = goog.fx.DragListGroup.verticalDistanceFromItem_(item, draggerElCenter); 352      if(! goog.isDef(distanceToClosestRow)) { 353        distanceToClosestRow = distanceToRow; 354      } 355      if(isBeforeFn(relevantCoord, relevantBound) &&(earliestAfterItemRelevantBound == undefined ||(distanceToRow < distanceToClosestRow) ||((distanceToRow == distanceToClosestRow) &&(isBeforeFn(relevantBound, earliestAfterItemRelevantBound) || relevantBound == earliestAfterItemRelevantBound)))) { 356        earliestAfterItem = item; 357        earliestAfterItemRelevantBound = relevantBound; 358      } 359      if(distanceToRow < distanceToClosestRow) { 360        distanceToClosestRow = distanceToRow; 361      } 362    } else if(isBeforeFn(relevantCoord, relevantBound) &&(earliestAfterItemRelevantBound == undefined || isBeforeFn(relevantBound, earliestAfterItemRelevantBound))) { 363      earliestAfterItem = item; 364      earliestAfterItemRelevantBound = relevantBound; 365    } 366  } 367  if(! goog.isNull(earliestAfterItem) && goog.fx.DragListGroup.verticalDistanceFromItem_(earliestAfterItem, draggerElCenter) > distanceToClosestRow) { 368    return null; 369  } else { 370    return earliestAfterItem; 371  } 372}; 373goog.fx.DragListGroup.verticalDistanceFromItem_ = function(item, target) { 374  var itemBounds = item.dlgBounds_; 375  var itemCenterY = itemBounds.top +(itemBounds.height - 1) / 2; 376  return Math.abs(target.y - itemCenterY); 377}; 378goog.fx.DragListGroup.getBottomBound_ = function(itemBounds) { 379  return itemBounds.top + itemBounds.height - 1; 380}; 381goog.fx.DragListGroup.getRightBound_ = function(itemBounds) { 382  return itemBounds.left + itemBounds.width - 1; 383}; 384goog.fx.DragListGroup.getLeftBound_ = function(itemBounds) { 385  return itemBounds.left || 0; 386}; 387goog.fx.DragListGroup.isLessThan_ = function(a, b) { 388  return a < b; 389}; 390goog.fx.DragListGroup.isGreaterThan_ = function(a, b) { 391  return a > b; 392}; 393goog.fx.DragListGroup.prototype.insertCurrDragItem_ = function(hoverList, hoverNextItem) { 394  if(this.currDragItem_.parentNode != hoverList || goog.dom.getNextElementSibling(this.currDragItem_) != hoverNextItem) { 395    hoverList.insertBefore(this.currDragItem_, hoverNextItem); 396  } 397}; 398goog.fx.DragListGroup.prototype.cloneNode_ = function(sourceEl) { 399  var clonedEl =(sourceEl.cloneNode(true)); 400  switch(sourceEl.tagName.toLowerCase()) { 401    case 'tr': 402      return goog.dom.createDom('table', null, goog.dom.createDom('tbody', null, clonedEl)); 403    case 'td': 404    case 'th': 405      return goog.dom.createDom('table', null, goog.dom.createDom('tbody', null, goog.dom.createDom('tr', null, clonedEl))); 406    default: 407      return clonedEl; 408  } 409}; 410goog.fx.DragListGroupEvent = function(type, dragListGroup, event, currDragItem, draggerEl, dragger, opt_draggerElCenter, opt_hoverList, opt_hoverNextItem) { 411  goog.events.Event.call(this, type); 412  this.dragListGroup = dragListGroup; 413  this.event = event; 414  this.currDragItem = currDragItem; 415  this.draggerEl = draggerEl; 416  this.dragger = dragger; 417  this.draggerElCenter = opt_draggerElCenter; 418  this.hoverList = opt_hoverList; 419  this.hoverNextItem = opt_hoverNextItem; 420}; ...MultiDrag.js
Source:MultiDrag.js  
1import {2	toggleClass,3	getRect,4	index,5	closest,6	on,7	off,8	clone,9	css,10	setRect,11	unsetRect,12	matrix,13	expando14} from '../../src/utils.js';15import dispatchEvent from '../../src/EventDispatcher.js';16let multiDragElements = [],17	multiDragClones = [],18	lastMultiDragSelect, // for selection with modifier key down (SHIFT)19	multiDragSortable,20	initialFolding = false, // Initial multi-drag fold when drag started21	folding = false, // Folding any other time22	dragStarted = false,23	dragEl,24	clonesFromRect,25	clonesHidden;26function MultiDragPlugin() {27	function MultiDrag(sortable) {28		// Bind all private methods29		for (let fn in this) {30			if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {31				this[fn] = this[fn].bind(this);32			}33		}34		if (sortable.options.supportPointer) {35			on(document, 'pointerup', this._deselectMultiDrag);36		} else {37			on(document, 'mouseup', this._deselectMultiDrag);38			on(document, 'touchend', this._deselectMultiDrag);39		}40		on(document, 'keydown', this._checkKeyDown);41		on(document, 'keyup', this._checkKeyUp);42		this.defaults = {43			selectedClass: 'sortable-selected',44			multiDragKey: null,45			setData(dataTransfer, dragEl) {46				let data = '';47				if (multiDragElements.length && multiDragSortable === sortable) {48					multiDragElements.forEach((multiDragElement, i) => {49						data += (!i ? '' : ', ') + multiDragElement.textContent;50					});51				} else {52					data = dragEl.textContent;53				}54				dataTransfer.setData('Text', data);55			}56		};57	}58	MultiDrag.prototype = {59		multiDragKeyDown: false,60		isMultiDrag: false,61		delayStartGlobal({ dragEl: dragged }) {62			dragEl = dragged;63		},64		delayEnded() {65			this.isMultiDrag = ~multiDragElements.indexOf(dragEl);66		},67		setupClone({ sortable, cancel }) {68			if (!this.isMultiDrag) return;69			for (let i = 0; i < multiDragElements.length; i++) {70				multiDragClones.push(clone(multiDragElements[i]));71				multiDragClones[i].sortableIndex = multiDragElements[i].sortableIndex;72				multiDragClones[i].draggable = false;73				multiDragClones[i].style['will-change'] = '';74				toggleClass(multiDragClones[i], this.options.selectedClass, false);75				multiDragElements[i] === dragEl && toggleClass(multiDragClones[i], this.options.chosenClass, false);76			}77			sortable._hideClone();78			cancel();79		},80		clone({ sortable, rootEl, dispatchSortableEvent, cancel }) {81			if (!this.isMultiDrag) return;82			if (!this.options.removeCloneOnHide) {83				if (multiDragElements.length && multiDragSortable === sortable) {84					insertMultiDragClones(true, rootEl);85					dispatchSortableEvent('clone');86					cancel();87				}88			}89		},90		showClone({ cloneNowShown, rootEl, cancel }) {91			if (!this.isMultiDrag) return;92			insertMultiDragClones(false, rootEl);93			multiDragClones.forEach(clone => {94				css(clone, 'display', '');95			});96			cloneNowShown();97			clonesHidden = false;98			cancel();99		},100		hideClone({ sortable, cloneNowHidden, cancel }) {101			if (!this.isMultiDrag) return;102			multiDragClones.forEach(clone => {103				css(clone, 'display', 'none');104				if (this.options.removeCloneOnHide && clone.parentNode) {105					clone.parentNode.removeChild(clone);106				}107			});108			cloneNowHidden();109			clonesHidden = true;110			cancel();111		},112		dragStartGlobal({ sortable }) {113			if (!this.isMultiDrag && multiDragSortable) {114				multiDragSortable.multiDrag._deselectMultiDrag();115			}116			multiDragElements.forEach(multiDragElement => {117				multiDragElement.sortableIndex = index(multiDragElement);118			});119			// Sort multi-drag elements120			multiDragElements = multiDragElements.sort(function(a, b) {121				return a.sortableIndex - b.sortableIndex;122			});123			dragStarted = true;124		},125		dragStarted({ sortable }) {126			if (!this.isMultiDrag) return;127			if (this.options.sort) {128				// Capture rects,129				// hide multi drag elements (by positioning them absolute),130				// set multi drag elements rects to dragRect,131				// show multi drag elements,132				// animate to rects,133				// unset rects & remove from DOM134				sortable.captureAnimationState();135				if (this.options.animation) {136					multiDragElements.forEach(multiDragElement => {137						if (multiDragElement === dragEl) return;138						css(multiDragElement, 'position', 'absolute');139					});140					let dragRect = getRect(dragEl, false, true, true);141					multiDragElements.forEach(multiDragElement => {142						if (multiDragElement === dragEl) return;143						setRect(multiDragElement, dragRect);144					});145					folding = true;146					initialFolding = true;147				}148			}149			sortable.animateAll(() => {150				folding = false;151				initialFolding = false;152				if (this.options.animation) {153					multiDragElements.forEach(multiDragElement => {154						unsetRect(multiDragElement);155					});156				}157				// Remove all auxiliary multidrag items from el, if sorting enabled158				if (this.options.sort) {159					removeMultiDragElements();160				}161			});162		},163		dragOver({ target, completed, cancel }) {164			if (folding && ~multiDragElements.indexOf(target)) {165				completed(false);166				cancel();167			}168		},169		revert({ fromSortable, rootEl, sortable, dragRect }) {170			if (multiDragElements.length > 1) {171				// Setup unfold animation172				multiDragElements.forEach(multiDragElement => {173					sortable.addAnimationState({174						target: multiDragElement,175						rect: folding ? getRect(multiDragElement) : dragRect176					});177					unsetRect(multiDragElement);178					multiDragElement.fromRect = dragRect;179					fromSortable.removeAnimationState(multiDragElement);180				});181				folding = false;182				insertMultiDragElements(!this.options.removeCloneOnHide, rootEl);183			}184		},185		dragOverCompleted({ sortable, isOwner, insertion, activeSortable, parentEl, putSortable }) {186			let options = this.options;187			if (insertion) {188				// Clones must be hidden before folding animation to capture dragRectAbsolute properly189				if (isOwner) {190					activeSortable._hideClone();191				}192				initialFolding = false;193				// If leaving sort:false root, or already folding - Fold to new location194				if (options.animation && multiDragElements.length > 1 && (folding || !isOwner && !activeSortable.options.sort && !putSortable)) {195					// Fold: Set all multi drag elements's rects to dragEl's rect when multi-drag elements are invisible196					let dragRectAbsolute = getRect(dragEl, false, true, true);197					multiDragElements.forEach(multiDragElement => {198						if (multiDragElement === dragEl) return;199						setRect(multiDragElement, dragRectAbsolute);200						// Move element(s) to end of parentEl so that it does not interfere with multi-drag clones insertion if they are inserted201						// while folding, and so that we can capture them again because old sortable will no longer be fromSortable202						parentEl.appendChild(multiDragElement);203					});204					folding = true;205				}206				// Clones must be shown (and check to remove multi drags) after folding when interfering multiDragElements are moved out207				if (!isOwner) {208					// Only remove if not folding (folding will remove them anyways)209					if (!folding) {210						removeMultiDragElements();211					}212					if (multiDragElements.length > 1) {213						let clonesHiddenBefore = clonesHidden;214						activeSortable._showClone(sortable);215						// Unfold animation for clones if showing from hidden216						if (activeSortable.options.animation && !clonesHidden && clonesHiddenBefore) {217							multiDragClones.forEach(clone => {218								activeSortable.addAnimationState({219									target: clone,220									rect: clonesFromRect221								});222								clone.fromRect = clonesFromRect;223								clone.thisAnimationDuration = null;224							});225						}226					} else {227						activeSortable._showClone(sortable);228					}229				}230			}231		},232		dragOverAnimationCapture({ dragRect, isOwner, activeSortable }) {233			multiDragElements.forEach(multiDragElement => {234				multiDragElement.thisAnimationDuration = null;235			});236			if (activeSortable.options.animation && !isOwner && activeSortable.multiDrag.isMultiDrag) {237				clonesFromRect = Object.assign({}, dragRect);238				let dragMatrix = matrix(dragEl, true);239				clonesFromRect.top -= dragMatrix.f;240				clonesFromRect.left -= dragMatrix.e;241			}242		},243		dragOverAnimationComplete() {244			if (folding) {245				folding = false;246				removeMultiDragElements();247			}248		},249		drop({ originalEvent: evt, rootEl, parentEl, sortable, dispatchSortableEvent, oldIndex, putSortable }) {250			let toSortable = (putSortable || this.sortable);251			if (!evt) return;252			let options = this.options,253				children = parentEl.children;254			// Multi-drag selection255			if (!dragStarted) {256				if (options.multiDragKey && !this.multiDragKeyDown) {257					this._deselectMultiDrag();258				}259				toggleClass(dragEl, options.selectedClass, !~multiDragElements.indexOf(dragEl));260				if (!~multiDragElements.indexOf(dragEl)) {261					multiDragElements.push(dragEl);262					dispatchEvent({263						sortable,264						rootEl,265						name: 'select',266						targetEl: dragEl,267						originalEvt: evt268					});269					// Modifier activated, select from last to dragEl270					if (evt.shiftKey && lastMultiDragSelect && sortable.el.contains(lastMultiDragSelect)) {271						let lastIndex = index(lastMultiDragSelect),272							currentIndex = index(dragEl);273						if (~lastIndex && ~currentIndex && lastIndex !== currentIndex) {274							// Must include lastMultiDragSelect (select it), in case modified selection from no selection275							// (but previous selection existed)276							let n, i;277							if (currentIndex > lastIndex) {278								i = lastIndex;279								n = currentIndex;280							} else {281								i = currentIndex;282								n = lastIndex + 1;283							}284							for (; i < n; i++) {285								if (~multiDragElements.indexOf(children[i])) continue;286								toggleClass(children[i], options.selectedClass, true);287								multiDragElements.push(children[i]);288								dispatchEvent({289									sortable,290									rootEl,291									name: 'select',292									targetEl: children[i],293									originalEvt: evt294								});295							}296						}297					} else {298						lastMultiDragSelect = dragEl;299					}300					multiDragSortable = toSortable;301				} else {302					multiDragElements.splice(multiDragElements.indexOf(dragEl), 1);303					lastMultiDragSelect = null;304					dispatchEvent({305						sortable,306						rootEl,307						name: 'deselect',308						targetEl: dragEl,309						originalEvt: evt310					});311				}312			}313			// Multi-drag drop314			if (dragStarted && this.isMultiDrag) {315				// Do not "unfold" after around dragEl if reverted316				if ((parentEl[expando].options.sort || parentEl !== rootEl) && multiDragElements.length > 1) {317					let dragRect = getRect(dragEl),318						multiDragIndex = index(dragEl, ':not(.' + this.options.selectedClass + ')');319					if (!initialFolding && options.animation) dragEl.thisAnimationDuration = null;320					toSortable.captureAnimationState();321					if (!initialFolding) {322						if (options.animation) {323							dragEl.fromRect = dragRect;324							multiDragElements.forEach(multiDragElement => {325								multiDragElement.thisAnimationDuration = null;326								if (multiDragElement !== dragEl) {327									let rect = folding ? getRect(multiDragElement) : dragRect;328									multiDragElement.fromRect = rect;329									// Prepare unfold animation330									toSortable.addAnimationState({331										target: multiDragElement,332										rect: rect333									});334								}335							});336						}337						// Multi drag elements are not necessarily removed from the DOM on drop, so to reinsert338						// properly they must all be removed339						removeMultiDragElements();340						multiDragElements.forEach(multiDragElement => {341							if (children[multiDragIndex]) {342								parentEl.insertBefore(multiDragElement, children[multiDragIndex]);343							} else {344								parentEl.appendChild(multiDragElement);345							}346							multiDragIndex++;347						});348						// If initial folding is done, the elements may have changed position because they are now349						// unfolding around dragEl, even though dragEl may not have his index changed, so update event350						// must be fired here as Sortable will not.351						if (oldIndex === index(dragEl)) {352							let update = false;353							multiDragElements.forEach(multiDragElement => {354								if (multiDragElement.sortableIndex !== index(multiDragElement)) {355									update = true;356									return;357								}358							});359							if (update) {360								dispatchSortableEvent('update');361							}362						}363					}364					// Must be done after capturing individual rects (scroll bar)365					multiDragElements.forEach(multiDragElement => {366						unsetRect(multiDragElement);367					});368					toSortable.animateAll();369				}370				multiDragSortable = toSortable;371			}372			// Remove clones if necessary373			if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) {374				multiDragClones.forEach(clone => {375					clone.parentNode && clone.parentNode.removeChild(clone);376				});377			}378		},379		nullingGlobal() {380			this.isMultiDrag =381			dragStarted = false;382			multiDragClones.length = 0;383		},384		destroyGlobal() {385			this._deselectMultiDrag();386			off(document, 'pointerup', this._deselectMultiDrag);387			off(document, 'mouseup', this._deselectMultiDrag);388			off(document, 'touchend', this._deselectMultiDrag);389			off(document, 'keydown', this._checkKeyDown);390			off(document, 'keyup', this._checkKeyUp);391		},392		_deselectMultiDrag(evt) {393			if (typeof dragStarted !== "undefined" && dragStarted) return;394			// Only deselect if selection is in this sortable395			if (multiDragSortable !== this.sortable) return;396			// Only deselect if target is not item in this sortable397			if (evt && closest(evt.target, this.options.draggable, this.sortable.el, false)) return;398			// Only deselect if left click399			if (evt && evt.button !== 0) return;400			while (multiDragElements.length) {401				let el = multiDragElements[0];402				toggleClass(el, this.options.selectedClass, false);403				multiDragElements.shift();404				dispatchEvent({405					sortable: this.sortable,406					rootEl: this.sortable.el,407					name: 'deselect',408					targetEl: el,409					originalEvt: evt410				});411			}412		},413		_checkKeyDown(evt) {414			if (evt.key === this.options.multiDragKey) {415				this.multiDragKeyDown = true;416			}417		},418		_checkKeyUp(evt) {419			if (evt.key === this.options.multiDragKey) {420				this.multiDragKeyDown = false;421			}422		}423	};424	return Object.assign(MultiDrag, {425		// Static methods & properties426		pluginName: 'multiDrag',427		utils: {428			/**429			 * Selects the provided multi-drag item430			 * @param  {HTMLElement} el    The element to be selected431			 */432			select(el) {433				let sortable = el.parentNode[expando];434				if (!sortable || !sortable.options.multiDrag || ~multiDragElements.indexOf(el)) return;435				if (multiDragSortable && multiDragSortable !== sortable) {436					multiDragSortable.multiDrag._deselectMultiDrag();437					multiDragSortable = sortable;438				}439				toggleClass(el, sortable.options.selectedClass, true);440				multiDragElements.push(el);441			},442			/**443			 * Deselects the provided multi-drag item444			 * @param  {HTMLElement} el    The element to be deselected445			 */446			deselect(el) {447				let sortable = el.parentNode[expando],448					index = multiDragElements.indexOf(el);449				if (!sortable || !sortable.options.multiDrag || !~index) return;450				toggleClass(el, sortable.options.selectedClass, false);451				multiDragElements.splice(index, 1);452			}453		},454		eventProperties() {455			const oldIndicies = [],456				newIndicies = [];457			multiDragElements.forEach(multiDragElement => {458				oldIndicies.push({459					multiDragElement,460					index: multiDragElement.sortableIndex461				});462				// multiDragElements will already be sorted if folding463				let newIndex;464				if (folding && multiDragElement !== dragEl) {465					newIndex = -1;466				} else if (folding) {467					newIndex = index(multiDragElement, ':not(.' + this.options.selectedClass + ')');468				} else {469					newIndex = index(multiDragElement);470				}471				newIndicies.push({472					multiDragElement,473					index: newIndex474				});475			});476			return {477				items: [...multiDragElements],478				clones: [...multiDragClones],479				oldIndicies,480				newIndicies481			};482		},483		optionListeners: {484			multiDragKey(key) {485				key = key.toLowerCase();486				if (key === 'ctrl') {487					key = 'Control';488				} else if (key.length > 1) {489					key = key.charAt(0).toUpperCase() + key.substr(1);490				}491				return key;492			}493		}494	});495}496function insertMultiDragElements(clonesInserted, rootEl) {497	multiDragElements.forEach((multiDragElement, i) => {498		let target = rootEl.children[multiDragElement.sortableIndex + (clonesInserted ? Number(i) : 0)];499		if (target) {500			rootEl.insertBefore(multiDragElement, target);501		} else {502			rootEl.appendChild(multiDragElement);503		}504	});505}506/**507 * Insert multi-drag clones508 * @param  {[Boolean]} elementsInserted  Whether the multi-drag elements are inserted509 * @param  {HTMLElement} rootEl510 */511function insertMultiDragClones(elementsInserted, rootEl) {512	multiDragClones.forEach((clone, i) => {513		let target = rootEl.children[clone.sortableIndex + (elementsInserted ? Number(i) : 0)];514		if (target) {515			rootEl.insertBefore(clone, target);516		} else {517			rootEl.appendChild(clone);518		}519	});520}521function removeMultiDragElements() {522	multiDragElements.forEach(multiDragElement => {523		if (multiDragElement === dragEl) return;524		multiDragElement.parentNode && multiDragElement.parentNode.removeChild(multiDragElement);525	});526}...drag.js
Source:drag.js  
1/* global $q, $create, Blyde */2"use strict";3{4	let mouseX = 0, mouseY = 0;5	window.on('mousemove', (e) => {6		mouseX = e.clientX;7		mouseY = e.clientY;8	});9	window.on('touchmove', (e) => {10		mouseX = e.touches[0].clientX;11		mouseY = e.touches[0].clientY;12	});13	window.on('touchstart', (e) => {14		mouseX = e.touches[0].clientX;15		mouseY = e.touches[0].clientY;16	});1718	const createCase = (node) => {19		let dragCase = $create('dragcase');20		let dragHolder = $create('dragHolder');21		let clientStyle = node.getBoundingClientRect();22		let computedStyle = window.getComputedStyle(node);23		dragCase.dragger = {24			posX: clientStyle.left,25			posY: clientStyle.top,26			deg: 027		};28		dragCase.style.position = 'fixed';29		dragCase.style.display = 'block';30		dragCase.style.left = dragCase.dragger.posX + 'px';31		dragCase.style.top = dragCase.dragger.posY + 'px';32		dragHolder.style.display = computedStyle.display;33		dragHolder.style.float = computedStyle.float;34		dragCase.append(dragHolder);35		node.dragCase = dragCase;36		node.dragHolder = dragHolder;37		return dragCase;38	};3940	const drag = function() {41		let diffX = 0, diffY = 0;4243		if (this.dragging) {44			diffX = mouseX - this.clientWidth/2 - this.dragger.posX;45			diffY = mouseY - this.dragger.posY  + 40;46		} else {47			diffX = this.dragger.targetX - this.dragger.posX;48			diffY = this.dragger.targetY - this.dragger.posY;49			if ((Math.pow(diffX, 2) + Math.pow(diffY, 2)) < 0.05) {50				this.style.left = this.dragger.targetX + 'px';51				this.style.top = this.dragger.targetY + 'px';52				this.style.transform = `rotate(0deg)`;53				window.cancelAnimationFrame(this.dragID);54				this.dragger.dragID = null;55				if (this.dragger.callback) this.dragger.callback();56				return;57			}58		}5960		let diffDeg = Math.tan(diffX/(Math.abs(this.dragger.posY - mouseY) + Math.sqrt(Math.pow(this.clientHeight, 2) + Math.pow(this.clientWidth, 2))*2 + 100 + Math.abs(diffX)))/Math.PI*180;61		if (diffDeg > 60) {62			diffDeg = 60;63		} else if (diffDeg < -60) {64			diffDeg = -60;65		}66		diffDeg = diffDeg - this.dragger.deg;67		this.dragger.posX = this.dragger.posX + diffX/8;68		this.dragger.posY = this.dragger.posY + diffY/8;69		this.dragger.deg = this.dragger.deg + diffDeg/8;70		this.style.left = this.dragger.posX + 'px';71		this.style.top = this.dragger.posY + 'px';72		this.style.transform = `rotate(${this.dragger.deg}deg)`;7374		this.dragger.dragID = window.requestAnimationFrame(drag.bind(this));75	};7677	Blyde.fn('Dragjs', {78		node: {79			startDrag(width, height) {80				if (this.parentNode.dragging || this.tagName.toUpperCase() === 'DRAGCASE') return this;81				let dragCase = '';82				if (this.parentNode.tagName.toUpperCase() === 'DRAGCASE') {83					dragCase = this.parentNode;84				} else {85					dragCase = createCase(this);86					if (width) dragCase.style.width = width;87					if (height) dragCase.style.height = height;88					this.swap(this.dragHolder);89				}90				$q('body').appendChild(dragCase);91				dragCase.dragging = true;92				if (!dragCase.dragger.dragID) {93					drag.call(dragCase);94				}95				return this;96			},97			dragRelease(cb) {98				if (this.tagName.toUpperCase() === 'DRAGCASE') return this;99				if (this.parentNode.tagName.toUpperCase() === 'DRAGCASE' && this.parentNode.dragging === true) {100					this.dragCase.dragger.callback = cb;101					this.dragCase.dragger.targetX = mouseX - this.dragCase.clientWidth/2;102					this.dragCase.dragger.targetY = mouseY;103					this.dragCase.dragging = false;104				}105				return this;106			},107			stopDrag(cb) {108				if (this.tagName.toUpperCase() === 'DRAGCASE') return this;109				if (this.dragCase) {110					if (this.dragCase.dragger.dragID) {111						window.cancelAnimationFrame(this.dragCase.dragger.dragID);112					}113					if (cb) {114						cb();115					} else {116						this.dragHolder.swap(this);117					}118					this.dragHolder.remove();119					this.dragCase.remove();120					this.dragHolder = null;121					this.dragCase = null;122				}123				return this;124			},125			moveTo(x, y, cb) {126				if (this.tagName.toUpperCase() === 'DRAGCASE') return this;127				this.startDrag().dragRelease(cb);128				this.dragCase.dragger.targetX = x;129				this.dragCase.dragger.targetY = y;130				return this;131			}132		}133	}, true);
...DragAndDrop.js
Source:DragAndDrop.js  
1/*2	Copyright (c) 2004-2006, The Dojo Foundation3	All Rights Reserved.4	Licensed under the Academic Free License version 2.1 or above OR the5	modified BSD license. For more information on Dojo licensing, see:6		http://dojotoolkit.org/community/licensing.shtml7*/8dojo.require("dojo.lang.common");9dojo.require("dojo.lang.declare");10dojo.provide("dojo.dnd.DragAndDrop");11dojo.declare("dojo.dnd.DragSource", null, {12	type: "",13	onDragEnd: function(){14	},15	onDragStart: function(){16	},17	/*18	 * This function gets called when the DOM element was 19	 * selected for dragging by the HtmlDragAndDropManager.20	 */21	onSelected: function(){22	},23	unregister: function(){24		dojo.dnd.dragManager.unregisterDragSource(this);25	},26	reregister: function(){27		dojo.dnd.dragManager.registerDragSource(this);28	}29}, function(){30	//dojo.profile.start("DragSource");31	var dm = dojo.dnd.dragManager;32	if(dm["registerDragSource"]){ // side-effect prevention33		dm.registerDragSource(this);34	}35	//dojo.profile.end("DragSource");36});37dojo.declare("dojo.dnd.DragObject", null, {38	type: "",39	onDragStart: function(){40		// gets called directly after being created by the DragSource41		// default action is to clone self as icon42	},43	onDragMove: function(){44		// this changes the UI for the drag icon45		//	"it moves itself"46	},47	onDragOver: function(){48	},49	onDragOut: function(){50	},51	onDragEnd: function(){52	},53	// normal aliases54	onDragLeave: this.onDragOut,55	onDragEnter: this.onDragOver,56	// non-camel aliases57	ondragout: this.onDragOut,58	ondragover: this.onDragOver59}, function(){60	var dm = dojo.dnd.dragManager;61	if(dm["registerDragObject"]){ // side-effect prevention62		dm.registerDragObject(this);63	}64});65dojo.declare("dojo.dnd.DropTarget", null, {66	acceptsType: function(type){67		if(!dojo.lang.inArray(this.acceptedTypes, "*")){ // wildcard68			if(!dojo.lang.inArray(this.acceptedTypes, type)) { return false; }69		}70		return true;71	},72	accepts: function(dragObjects){73		if(!dojo.lang.inArray(this.acceptedTypes, "*")){ // wildcard74			for (var i = 0; i < dragObjects.length; i++) {75				if (!dojo.lang.inArray(this.acceptedTypes,76					dragObjects[i].type)) { return false; }77			}78		}79		return true;80	},81	unregister: function(){82		dojo.dnd.dragManager.unregisterDropTarget(this);83	},84	onDragOver: function(){85	},86	onDragOut: function(){87	},88	onDragMove: function(){89	},90	onDropStart: function(){91	},92	onDrop: function(){93	},94	onDropEnd: function(){95	}96}, function(){97	if (this.constructor == dojo.dnd.DropTarget) { return; } // need to be subclassed98	this.acceptedTypes = [];99	dojo.dnd.dragManager.registerDropTarget(this);100});101// NOTE: this interface is defined here for the convenience of the DragManager102// implementor. It is expected that in most cases it will be satisfied by103// extending a native event (DOM event in HTML and SVG).104dojo.dnd.DragEvent = function(){105	this.dragSource = null;106	this.dragObject = null;107	this.target = null;108	this.eventStatus = "success";109	//110	// can be one of:111	//	[	"dropSuccess", "dropFailure", "dragMove",112	//		"dragStart", "dragEnter", "dragLeave"]113	//114}115/*116 *	The DragManager handles listening for low-level events and dispatching117 *	them to higher-level primitives like drag sources and drop targets. In118 *	order to do this, it must keep a list of the items.119 */120dojo.declare("dojo.dnd.DragManager", null, {121	selectedSources: [],122	dragObjects: [],123	dragSources: [],124	registerDragSource: function(){},125	dropTargets: [],126	registerDropTarget: function(){},127	lastDragTarget: null,128	currentDragTarget: null,129	onKeyDown: function(){},130	onMouseOut: function(){},131	onMouseMove: function(){},132	onMouseUp: function(){}133});134// NOTE: despite the existance of the DragManager class, there will be a135// singleton drag manager provided by the renderer-specific D&D support code.136// It is therefore sane for us to assign instance variables to the DragManager137// prototype138// The renderer-specific file will define the following object:...HtmlDragCopy.js
Source:HtmlDragCopy.js  
1/*2	Copyright (c) 2004-2006, The Dojo Foundation3	All Rights Reserved.4	Licensed under the Academic Free License version 2.1 or above OR the5	modified BSD license. For more information on Dojo licensing, see:6		http://dojotoolkit.org/community/licensing.shtml7*/8dojo.provide("dojo.dnd.HtmlDragCopy");9dojo.require("dojo.dnd.*");10dojo.declare("dojo.dnd.HtmlDragCopySource", dojo.dnd.HtmlDragSource,11function(node, type, copyOnce){12		this.copyOnce = copyOnce;13		this.makeCopy = true;14},15{16	onDragStart: function(){17		var dragObj = new dojo.dnd.HtmlDragCopyObject(this.dragObject, this.type, this);18		if(this.dragClass) { dragObj.dragClass = this.dragClass; }19		if (this.constrainToContainer) {20			dragObj.constrainTo(this.constrainingContainer || this.domNode.parentNode);21		}22		return dragObj;23	},24	onSelected: function() {25		for (var i=0; i<this.dragObjects.length; i++) {26			dojo.dnd.dragManager.selectedSources.push(new dojo.dnd.HtmlDragCopySource(this.dragObjects[i]));27		}28	}29});30dojo.declare("dojo.dnd.HtmlDragCopyObject", dojo.dnd.HtmlDragObject,31function(dragObject, type, source){32		this.copySource = source;33},34{35	onDragStart: function(e) {36		dojo.dnd.HtmlDragCopyObject.superclass.onDragStart.apply(this, arguments);37		if(this.copySource.makeCopy) {38			this.sourceNode = this.domNode;39			this.domNode    = this.domNode.cloneNode(true);40		}41	},42	onDragEnd: function(e){43		switch(e.dragStatus){44			case "dropFailure": // slide back to the start45				var startCoords = dojo.html.getAbsolutePosition(this.dragClone, true);46				// offset the end so the effect can be seen47				var endCoords = { left: this.dragStartPosition.x + 1,48					top: this.dragStartPosition.y + 1};49				// animate50				var anim = dojo.lfx.slideTo(this.dragClone, endCoords, 500, dojo.lfx.easeOut);51				var dragObject = this;52				dojo.event.connect(anim, "onEnd", function (e) {53					// pause for a second (not literally) and disappear54					dojo.lang.setTimeout(function() {55							dojo.html.removeNode(dragObject.dragClone);56							dragObject.dragClone = null;57							if(dragObject.copySource.makeCopy) {58								dojo.html.removeNode(dragObject.domNode);59								dragObject.domNode = dragObject.sourceNode;60								dragObject.sourceNode = null;61							}62						},63						200);64				});65				anim.play();66				dojo.event.topic.publish('dragEnd', { source: this } );67				return;68		}69		dojo.dnd.HtmlDragCopyObject.superclass.onDragEnd.apply(this, arguments);70		this.copySource.dragObject = this.domNode;71		if(this.copySource.copyOnce){72			this.copySource.makeCopy = false;73		}74		new dojo.dnd.HtmlDragCopySource(this.sourceNode, this.type, this.copySource.copyOnce);75		this.sourceNode = null;76	}...Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.drag('#tsf > div:nth-child(2) > div > div.RNNXgb > div > div.a4bIc > input', 100, 100);7  await page.screenshot({ path: 'drag.png' });8  await browser.close();9})();10const { chromium } = require('playwright');11(async () => {12  const browser = await chromium.launch();13  const context = await browser.newContext();14  const page = await context.newPage();15  await page.drag('#tsf > div:nth-child(2) > div > div.RNNXgb > div > div.a4bIc > input', 100, 100);16  await page.screenshot({ path: 'drag.png' });17  await browser.close();18})();19const { chromium } = require('playwright');20(async () => {21  const browser = await chromium.launch();22  const context = await browser.newContext();23  const page = await context.newPage();24  await page.drag('#tsf > div:nth-child(2) > div > div.RNNXgb > div > div.a4bIc > input', 100, 100);25  await page.screenshot({ path: 'drag.png' });26  await browser.close();27})();28const { chromium } = require('playwright');29(async () => {30  const browser = await chromium.launch();31  const context = await browser.newContext();32  const page = await context.newPage();33  await page.drag('#tsf > div:nth-child(2) > div > div.RNNXgb > div > div.a4bIc > input', 100, 100);34  await page.screenshot({ path: 'drag.png' });35  await browser.close();36})();Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.drag('#tsf > div:nth-child(2) > div.A8SBwf.emcav > div.RNNXgb > div > div.a4bIc > input', 100, 100);7  await page.screenshot({ path: `drag.png` });8  await browser.close();9})();10const { chromium } = require('playwright');11(async () => {12  const browser = await chromium.launch();13  const context = await browser.newContext();14  const page = await context.newPage();15  await page.dragAndDrop('#tsf > div:nth-child(2) > div.A8SBwf.emcav > div.RNNXgb > div > div.a4bIc > input', '#tsf > div:nth-child(2) > div.A8SBwf.emcav > div.RNNXgb > div > div.a4bIc > input');16  await page.screenshot({ path: `dragAndDrop.png` });17  await browser.close();18})();19const { chromium } = require('playwright');20(async () => {21  const browser = await chromium.launch();22  const context = await browser.newContext();23  const page = await context.newPage();24  await page.drop('#tsf > div:nth-child(2) > div.A8SBwf.emcav > div.RNNXgb > div > div.a4bIc > input');25  await page.screenshot({ path: `drop.png` });26  await browser.close();27})();Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.click('input[aria-label="Search"]');7  await page.keyboard.type('Playwright');8  await page.keyboard.press('Enter');9  await page.waitForSelector('text=Playwright: Node.js library to automate');10  await page.click('text=Playwright: Node.js library to automate');11  await page.waitForSelector('#search');12  await page.click('#search');13  await page.keyboard.type('drag');14  await page.keyboard.press('Enter');15  await page.waitForSelector('text=Drag and Drop');16  await page.click('text=Drag and Drop');17  await page.waitForSelector('text=Drag and Drop');18  await page.waitForSelector('#drag');19  const drag = await page.$('#drag');20  await drag.dragAndDrop({ x: 100, y: 100 });21  await browser.close();22})();23const { chromium } = require('playwright');24(async () => {25  const browser = await chromium.launch();26  const context = await browser.newContext();27  const page = await context.newPage();28  await page.click('input[aria-label="Search"]');29  await page.keyboard.type('Playwright');30  await page.keyboard.press('Enter');31  await page.waitForSelector('text=Playwright: Node.js library to automate');32  await page.click('text=Playwright: Node.js library to automate');33  await page.waitForSelector('#search');34  await page.click('#search');35  await page.keyboard.type('drag');36  await page.keyboard.press('Enter');37  await page.waitForSelector('text=Drag and Drop');38  await page.click('text=Drag and Drop');39  await page.waitForSelector('text=Drag and Drop');40  await page.waitForSelector('#drag');41  const drag = await page.$('#drag');42  await drag.dragAndDrop({ x: 100, y: 100 });43  await browser.close();44})();45const { chromium } = require('playwright');46(async () => {47  const browser = await chromium.launch();48  const context = await browser.newContext();Using AI Code Generation
1const {chromium} = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.drag('#drag', 100, 100);7  await page.screenshot({ path: 'drag.png' });8  await browser.close();9})();Using AI Code Generation
1const {chromium} = require('playwright');2(async() => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.drag('#tsf > div:nth-child(2) > div > div.FPdoLc.VlcLAe > center > input[type="submit"]:nth-child(1)', {delay: 1000, steps: 10});7  await page.screenshot({path: 'screenshot.png'});8  await browser.close();9})();10const {chromium} = require('playwright');11(async() => {12  const browser = await chromium.launch();13  const context = await browser.newContext();14  const page = await context.newPage();15  await page.dragAndDrop('#tsf > div:nth-child(2) > div > div.FPdoLc.VlcLAe > center > input[type="submit"]:nth-child(1)', {delay: 1000, steps: 10});16  await page.screenshot({path: 'screenshot.png'});17  await browser.close();18})();Using AI Code Generation
1const { devices } = require('playwright');2const iPhone = devices['iPhone 6'];3(async () => {4  const browser = await chromium.launch();5  const context = await browser.newContext({ ...iPhone });6  const page = await context.newPage();7  await page.drag('#gbqfbb', { steps: 5 });8  await page.screenshot({ path: 'drag.png' });9  await browser.close();10})();11const { devices } = require('playwright');12const iPhone = devices['iPhone 6'];13(async () => {14  const browser = await chromium.launch();15  const context = await browser.newContext({ ...iPhone });16  const page = await context.newPage();17  await page.dragAndDrop('#gbqfbb', { steps: 5 });18  await page.screenshot({ path: 'dragAndDrop.png' });19  await browser.close();20})();21const { devices } = require('playwright');22const iPhone = devices['iPhone 6'];23(async () => {24  const browser = await chromium.launch();25  const context = await browser.newContext({ ...iPhone });26  const page = await context.newPage();27  await page.$eval('input[name="q"]', (el) => el.value = 'Hello World');28  await page.screenshot({ path: 'hello-world.png' });29  await browser.close();30})();31const { devices } = require('playwright');32const iPhone = devices['iPhone 6'];33(async () => {Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const page = await browser.newPage();5  const source = await page.$('input[name="q"]');6  const target = await page.$('input[name="btnK"]');7  await source.dragAndDrop(target);8  await browser.close();9})();Using AI Code Generation
1const { dragAndDrop } = require('playwright-internal');2const { chromium } = require('playwright');3(async () => {4  const browser = await chromium.launch({ headless: false });5  const context = await browser.newContext();6  const page = await context.newPage();7  const aHandle = await page.$('#column-a');8  const bHandle = await page.$('#column-b');9  await dragAndDrop(page, aHandle, bHandle);10  await page.screenshot({ path: `drag.png` });11  await browser.close();12})();Using AI Code Generation
1const { drag } = require('@playwright/test');2await drag('#source', { steps: 10, by: { x: 100, y: 100 } });3await drag('#source', { steps: 10, to: '#target' });4await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'] });5await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'], delay: 1000 });6await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'], delay: 1000, button: 'left' });7await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'], delay: 1000, button: 'left', position: 'center' });8await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'], delay: 1000, button: 'left', position: 'topLeft' });9await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'], delay: 1000, button: 'left', position: 'topRight' });10await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'], delay: 1000, button: 'left', position: 'bottomLeft' });11await drag('#source', { steps: 10, by: { x: 100, y: 100 }, modifiers: ['Shift'], delay:LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!
