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