Best Python code snippet using fMBT_python
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}; ...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
