How to use drag method in fMBT

Best Python code snippet using fMBT_python

ddwtos.js

Source:ddwtos.js Github

copy

Full Screen

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 };...

Full Screen

Full Screen

dnd.js

Source:dnd.js Github

copy

Full Screen

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);...

Full Screen

Full Screen

abstractdragdrop.js

Source:abstractdragdrop.js Github

copy

Full Screen

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; ...

Full Screen

Full Screen

draglistgroup.js

Source:draglistgroup.js Github

copy

Full Screen

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}; ...

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run fMBT automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful