diff --git a/common/static/js/capa/drag_and_drop/draggables.js b/common/static/js/capa/drag_and_drop/draggables.js
index d95b0e706f..5065e7193b 100644
--- a/common/static/js/capa/drag_and_drop/draggables.js
+++ b/common/static/js/capa/drag_and_drop/draggables.js
@@ -5,189 +5,170 @@
(function (requirejs, require, define) {
define(['logme', 'update_input'], function (logme, updateInput) {
- return Draggables;
+ return {
+ 'init': init
+ };
- function Draggables(state) {
- var c1;
+ function init(state) {
+ logme('Draggables.init; state = ', state);
state.draggables = [];
state.numDraggablesInSlider = 0;
-
- for (c1 = 0; c1 < state.config.draggables.length; c1 += 1) {
- processDraggable(state.config.draggables[c1], c1 + 1);
- }
-
- state.updateArrowOpacity();
state.currentMovingDraggable = null;
+ (function (c1) {
+ while (c1 < state.config.draggables.length) {
+ processDraggable(state, state.config.draggables[c1], c1 + 1);
+ c1 += 1
+ }
+ }(0));
+
+ state.updateArrowOpacity();
+
$(document).mousemove(function (event) {
- if (state.currentMovingDraggable !== null) {
- state.currentMovingDraggable.iconEl.css(
+ documentMouseMove(state, event);
+ });
+ }
+
+ function documentMouseMove(state, event) {
+ if (state.currentMovingDraggable !== null) {
+ state.currentMovingDraggable.iconEl.css(
+ 'left',
+ event.pageX -
+ state.baseImageEl.offset().left -
+ state.currentMovingDraggable.iconWidth * 0.5
+ - state.currentMovingDraggable.iconElLeftOffset
+ );
+ state.currentMovingDraggable.iconEl.css(
+ 'top',
+ event.pageY -
+ state.baseImageEl.offset().top -
+ state.currentMovingDraggable.iconHeight * 0.5
+ );
+
+ if (state.currentMovingDraggable.labelEl !== null) {
+ state.currentMovingDraggable.labelEl.css(
'left',
event.pageX -
state.baseImageEl.offset().left -
- state.currentMovingDraggable.iconWidth * 0.5
- - state.currentMovingDraggable.iconElLeftOffset
+ state.currentMovingDraggable.labelWidth * 0.5
+ - 9 // Account for padding, border.
);
- state.currentMovingDraggable.iconEl.css(
+ state.currentMovingDraggable.labelEl.css(
'top',
event.pageY -
- state.baseImageEl.offset().top -
- state.currentMovingDraggable.iconHeight * 0.5
+ state.baseImageEl.offset().top +
+ state.currentMovingDraggable.iconHeight * 0.5 +
+ 5
+ );
+ }
+ }
+ }
+
+ function processDraggable(state, obj, objIndex) {
+ var draggableObj;
+
+ logme('processDraggable; state = ', state);
+
+ logme('Processing draggable #' + objIndex);
+
+ draggableObj = {
+ 'zIndex': objIndex,
+ 'oldZIndex': objIndex,
+ 'labelEl': null,
+ 'hasLoaded': false,
+ 'inContainer': true,
+ 'mousePressed': false,
+ 'onTarget': null,
+
+ 'state': state,
+
+ 'mouseDown': mouseDown,
+ 'mouseUp': mouseUp,
+ 'mouseMove': mouseMove,
+ 'checkLandingElement': checkLandingElement,
+ 'removeObjIdFromTarget': removeObjIdFromTarget,
+ 'checkIfOnTarget': checkIfOnTarget,
+ 'snapToTarget': snapToTarget,
+ 'correctZIndexes': correctZIndexes,
+ 'moveBackToSlider': moveBackToSlider
+ };
+
+ draggableObj.containerEl = $(
+ '
'
);
- draggableObj.iconEl.appendTo(draggableObj.containerEl);
+ draggableObj.labelEl.appendTo(
+ draggableObj.containerEl
+ );
- draggableObj.iconWidth = draggableObj.iconEl.width();
- draggableObj.iconHeight = draggableObj.iconEl.height();
- draggableObj.iconWidthSmall = draggableObj.iconWidth;
- draggableObj.iconHeightSmall = draggableObj.iconHeight;
+ draggableObj.labelWidth = draggableObj.labelEl.width();
- draggableObj.iconEl.css(
+ draggableObj.labelEl.css(
'left',
- 50 - draggableObj.iconWidthSmall * 0.5
+ 50 - draggableObj.labelWidth * 0.5
);
- draggableObj.iconEl.css(
+ draggableObj.labelEl.css(
'top',
- 50 - draggableObj.iconHeightSmall * 0.5
+ 5 + draggableObj.iconHeightSmall + 5
);
- } else {
- // If no icon and no label, don't create a draggable.
- return;
+
+ draggableObj.labelEl.mousedown(function (event) {
+ draggableObj.mouseDown.call(draggableObj, event);
+ });
+ draggableObj.labelEl.mouseup(function (event) {
+ draggableObj.mouseUp.call(draggableObj, event);
+ });
+ draggableObj.labelEl.mousemove(function (event) {
+ draggableObj.mouseMove.call(draggableObj, event);
+ });
}
- }
- draggableObj.iconEl.mousedown(mouseDown);
- draggableObj.iconEl.mouseup(mouseUp);
- draggableObj.iconEl.mousemove(mouseMove);
-
- draggableObj.containerEl.mousedown(mouseDown);
- draggableObj.containerEl.mouseup(mouseUp);
- draggableObj.containerEl.mousemove(mouseMove);
-
- inContainer = true;
- mousePressed = false;
-
- onTarget = null;
-
- draggableObj.id = obj.id;
- draggableObj.x = -1;
- draggableObj.y = -1;
-
- draggableObj.setInContainer = function (val) {
- inContainer = val;
- };
- draggableObj.setOnTarget = function (val) {
- onTarget = val;
- };
-
- state.draggables.push(draggableObj);
-
- state.numDraggablesInSlider += 1;
-
- if (obj.icon.length === 0) {
draggableObj.hasLoaded = true;
- }
+ });
+ } else {
+ // To make life easier, if there is no icon, but there is a
+ // label, we will create a label and store it as if it was an
+ // icon. All the existing code will work, and the user will
+ // see a label instead of an icon.
+ if (obj.label.length > 0) {
+ draggableObj.iconElBGColor = state.config.labelBgColor;
+ draggableObj.iconElPadding = 8;
+ draggableObj.iconElBorder = '1px solid black';
+ draggableObj.iconElLeftOffset = 9;
- return;
+ draggableObj.iconEl = $(
+ '
' +
+ obj.label +
+ '
'
+ );
- function mouseDown(event) {
- if (mousePressed === false) {
- // So that the browser does not perform a default drag.
- // If we don't do this, each drag operation will
- // potentially cause the highlghting of the dragged element.
- event.preventDefault();
+ draggableObj.iconEl.appendTo(draggableObj.containerEl);
- // If this draggable is just being dragged out of the
- // container, we must perform some additional tasks.
- if (inContainer === true) {
- draggableObj.containerEl.hide();
-
- draggableObj.iconEl.detach();
- draggableObj.iconEl.css(
- 'background-color', draggableObj.iconElBGColor
- );
- draggableObj.iconEl.css(
- 'padding-left', draggableObj.iconElPadding
- );
- draggableObj.iconEl.css(
- 'padding-right', draggableObj.iconElPadding
- );
- draggableObj.iconEl.css(
- 'border', draggableObj.iconElBorder
- );
- draggableObj.iconEl.css(
- 'width',
- draggableObj.iconWidth
- );
- draggableObj.iconEl.css(
- 'height',
- draggableObj.iconHeight
- );
- draggableObj.iconEl.css(
- 'left',
- event.pageX -
- state.baseImageEl.offset().left -
- draggableObj.iconWidth * 0.5
- - draggableObj.iconElLeftOffset
- );
- draggableObj.iconEl.css(
- 'top',
- event.pageY -
- state.baseImageEl.offset().top -
- draggableObj.iconHeight * 0.5
- );
- draggableObj.iconEl.appendTo(
- state.baseImageEl.parent()
- );
-
- if (draggableObj.labelEl !== null) {
- draggableObj.labelEl.detach();
- draggableObj.labelEl.css(
- 'background-color', state.config.labelBgColor
- );
- draggableObj.labelEl.css(
- 'padding-left', 8
- );
- draggableObj.labelEl.css(
- 'padding-right', 8
- );
- draggableObj.labelEl.css(
- 'border', '1px solid black'
- );
- draggableObj.labelEl.css(
- 'left',
- event.pageX -
- state.baseImageEl.offset().left -
- draggableObj.labelWidth * 0.5
- - 9 // Account for padding, border.
- );
- draggableObj.labelEl.css(
- 'top',
- event.pageY -
- state.baseImageEl.offset().top +
- draggableObj.iconHeight * 0.5 + 5
- );
- draggableObj.labelEl.appendTo(
- state.baseImageEl.parent()
- );
- }
-
- inContainer = false;
- state.numDraggablesInSlider -= 1;
- }
-
- draggableObj.oldZIndex = draggableObj.zIndex;
- draggableObj.zIndex = 1000;
- draggableObj.iconEl.css('z-index', '1000');
- if (draggableObj.labelEl !== null) {
- draggableObj.labelEl.css('z-index', '1000');
- }
-
- mousePressed = true;
- state.currentMovingDraggable = draggableObj;
- }
- }
-
- function mouseUp() {
- if (mousePressed === true) {
- state.currentMovingDraggable = null;
-
- checkLandingElement();
- }
- }
-
- function mouseMove() {
- if (mousePressed === true) {
- // Because we have also attached a 'mousemove' event to the
- // 'document' (that will do the same thing), let's tell the
- // browser not to bubble up this event. The attached event
- // on the 'document' will only be triggered when the mouse
- // pointer leaves the draggable while it is in the middle
- // of a drag operation (user moves the mouse very quickly).
- event.stopPropagation();
-
- draggableObj.iconEl.css(
- 'left',
- event.pageX -
- state.baseImageEl.offset().left -
- draggableObj.iconWidth * 0.5
- - draggableObj.iconElLeftOffset
- );
- draggableObj.iconEl.css(
- 'top',
- event.pageY -
- state.baseImageEl.offset().top -
- draggableObj.iconHeight * 0.5
- );
-
- if (draggableObj.labelEl !== null) {
- draggableObj.labelEl.css(
- 'left',
- event.pageX -
- state.baseImageEl.offset().left -
- draggableObj.labelWidth * 0.5
- - 9 // Acoount for padding, border.
- );
- draggableObj.labelEl.css(
- 'top',
- event.pageY -
- state.baseImageEl.offset().top +
- draggableObj.iconHeight * 0.5 +
- 5
- );
- }
- }
- }
-
- // At this point the mouse was realeased, and we need to check
- // where the draggable eneded up. Based on several things, we
- // will either move the draggable back to the slider, or update
- // the input with the user's answer (X-Y position of the draggable,
- // or the ID of the target where it landed.
- function checkLandingElement() {
- var positionIE, targetFound;
-
- mousePressed = false;
- positionIE = draggableObj.iconEl.position();
-
- if (state.individualTargets === true) {
- targetFound = false;
-
- checkIfOnTarget();
-
- if (targetFound === true) {
- correctZIndexes();
- } else {
- moveBackToSlider();
- removeObjIdFromTarget();
-
- state.numDraggablesInSlider += 1;
- }
- } else {
- if (
- (positionIE.left < 0) ||
- (
- positionIE.left + draggableObj.iconWidth >
- state.baseImageEl.width()
- ) ||
- (positionIE.top < 0) ||
- (
- positionIE.top + draggableObj.iconHeight >
- state.baseImageEl.height()
- )
- ) {
- moveBackToSlider();
-
- draggableObj.x = -1;
- draggableObj.y = -1;
-
- state.numDraggablesInSlider += 1;
- } else {
- correctZIndexes();
-
- draggableObj.x =
- positionIE.left + draggableObj.iconWidth * 0.5;
- draggableObj.y =
- positionIE.top + draggableObj.iconHeight * 0.5;
- }
- }
-
- state.updateArrowOpacity();
- updateInput(state);
+ draggableObj.iconWidth = draggableObj.iconEl.width();
+ draggableObj.iconHeight = draggableObj.iconEl.height();
+ draggableObj.iconWidthSmall = draggableObj.iconWidth;
+ draggableObj.iconHeightSmall = draggableObj.iconHeight;
+ draggableObj.iconEl.css(
+ 'left',
+ 50 - draggableObj.iconWidthSmall * 0.5
+ );
+ draggableObj.iconEl.css(
+ 'top',
+ 50 - draggableObj.iconHeightSmall * 0.5
+ );
+ } else {
+ // If no icon and no label, don't create a draggable.
return;
-
- function removeObjIdFromTarget() {
- var c1;
-
- if (onTarget !== null) {
- for (c1 = 0; c1 < onTarget.draggable.length; c1 += 1) {
- if (onTarget.draggable[c1] === draggableObj.id) {
- onTarget.draggable.splice(c1, 1);
-
- break;
- }
- }
-
- if (onTarget.numTextEl !== null) {
- onTarget.updateNumTextEl();
- }
-
- onTarget = null;
- }
- }
-
- //
- // Determine if a draggable, after it was relased, ends up on a
- // target. We do this by iterating over all of the targets, and
- // for each one we check whether the draggable's center is
- // within the target's dimensions.
- //
- // positionIE is the object as returned by
- //
- // draggableObj.iconEl.position()
- //
- function checkIfOnTarget() {
- var c1, target;
-
- for (c1 = 0; c1 < state.targets.length; c1 += 1) {
- target = state.targets[c1];
-
- // If only one draggable per target is allowed, and
- // the current target already has a draggable on it
- // (with an ID different from the one we are checking
- // against), then go to next target.
- if (
- (state.config.one_per_target === true) &&
- (target.draggable.length === 1) &&
- (target.draggable[0] !== draggableObj.id)
- ) {
- continue;
- }
-
- // Check if the draggable's center coordinate is within
- // the target's dimensions. If not, go to next target.
- if (
- positionIE.top + draggableObj.iconHeight * 0.5 <
- target.offset.top
- ) {
- continue;
- }
- if (
- positionIE.top + draggableObj.iconHeight * 0.5 >
- target.offset.top + target.h
- ) {
- continue;
- }
- if (
- positionIE.left + draggableObj.iconWidth * 0.5 <
- target.offset.left
- ) {
- continue;
- }
- if (
- positionIE.left + draggableObj.iconWidth * 0.5 >
- target.offset.left + target.w
- ) {
- continue;
- }
-
- // If we got here, then our draggable is on top of a
- // target.
- targetFound = true;
-
- // If the draggable was moved from one target to
- // another, then we need to remove it's ID from the
- // previous target's draggables list, and add it to the
- // new target's draggables list.
- if (
- (onTarget !== null) &&
- (onTarget.id !== target.id)
- ) {
- removeObjIdFromTarget();
- onTarget = target;
- target.draggable.push(draggableObj.id);
- }
- // If the draggable was moved from the slider to a
- // target, remember the target, and add ID to the
- // target's draggables list.
- else if (onTarget === null) {
- onTarget = target;
- target.draggable.push(draggableObj.id);
- }
-
- if (target.numTextEl !== null) {
- target.updateNumTextEl();
- }
-
- // Reposition the draggable so that it's center
- // coincides with the center of the target.
- snapToTarget(target);
-
- break;
- }
- }
-
- function snapToTarget(target) {
- var offset;
-
- offset = 0;
- if (state.config.targetOutline === true) {
- offset = 1;
- }
-
- draggableObj.iconEl.css(
- 'left',
- target.offset.left + 0.5 * target.w -
- draggableObj.iconWidth * 0.5 + offset
- - draggableObj.iconElLeftOffset
- );
- draggableObj.iconEl.css(
- 'top',
- target.offset.top + 0.5 * target.h -
- draggableObj.iconHeight * 0.5 + offset
- );
-
- if (draggableObj.labelEl !== null) {
- draggableObj.labelEl.css(
- 'left',
- target.offset.left + 0.5 * target.w -
- draggableObj.labelWidth * 0.5 + offset
- - 9 // Acoount for padding, border.
- );
- draggableObj.labelEl.css(
- 'top',
- target.offset.top + 0.5 * target.h +
- draggableObj.iconHeight * 0.5 + 5 + offset
- );
- }
- }
-
- // Go through all of the draggables subtract 1 from the z-index
- // of all whose z-index is higher than the old z-index of the
- // current element. After, set the z-index of the current
- // element to 1 + N (where N is the number of draggables - i.e.
- // the highest z-index possible).
- //
- // This will make sure that after releasing a draggable, it
- // will be on top of all of the other draggables. Also, the
- // ordering of the visibility (z-index) of the other draggables
- // will not change.
- function correctZIndexes() {
- var draggablesInMe, c1, c2, highestZIndex;
-
- if (state.individualTargets === true) {
-
- logme('In correctZIndexes.');
-
- if (onTarget.draggable.length > 0) {
- logme('onTarget.draggable.length > 0');
-
- draggablesInMe = [];
-
- for (c1 = 0; c1 < onTarget.draggable.length; c1 += 1) {
- for (c2 = 0; c2 < state.draggables.length; c2 += 1) {
- if (
- onTarget.draggable[c1] ===
- state.draggables[c2].id
- ) {
- draggablesInMe.push(state.draggables[c2]);
- }
- }
- }
-
- highestZIndex = -10000;
-
- for (c1 = 0; c1 < draggablesInMe.length; c1 += 1) {
- if (
- (draggablesInMe[c1].zIndex > highestZIndex) &&
- (draggablesInMe[c1].zIndex !== 1000)
- ) {
- highestZIndex = draggablesInMe[c1].zIndex;
- }
- }
-
- if (highestZIndex === -10000) {
- highestZIndex = onTarget.draggable.length;
- } else if (highestZIndex < draggableObj.oldZIndex) {
- highestZIndex = draggableObj.oldZIndex;
- } else {
- for (c1 = 0; c1 < draggablesInMe.length; c1 += 1) {
- logme('draggablesInMe[' + c1 + '].id = ' + draggablesInMe[c1].id);
- logme('draggablesInMe[' + c1 + '].zIndex = ' + draggablesInMe[c1].zIndex);
- logme('draggablesInMe[' + c1 + '].oldZIndex = ' + draggablesInMe[c1].oldZIndex);
- }
-
- logme('----------------');
- logme('highestZIndex = ' + highestZIndex);
-
- for (c1 = 0; c1 < draggablesInMe.length; c1 += 1) {
- draggablesInMe[c1].zIndex -= 1;
- draggablesInMe[c1].oldZIndex -= 1;
-
- draggablesInMe[c1].iconEl.css(
- 'z-index',
- draggablesInMe[c1].zIndex
- );
- if (draggablesInMe[c1].labelEl !== null) {
- draggablesInMe[c1].labelEl.css(
- 'z-index',
- draggablesInMe[c1].zIndex
- );
- }
- }
- }
- } else {
- logme('highestZIndex = onTarget.draggable.length');
- highestZIndex = onTarget.draggable.length;
- }
-
- draggableObj.zIndex = highestZIndex;
- draggableObj.oldZIndex = highestZIndex;
-
- draggableObj.iconEl.css(
- 'z-index',
- draggableObj.zIndex
- );
- if (draggableObj.labelEl !== null) {
- draggableObj.labelEl.css(
- 'z-index',
- draggableObj.zIndex
- );
- }
- } else {
- for (c1 = 0; c1 < state.draggables.length; c1++) {
- if (
- draggableObj.oldZIndex <
- state.draggables[c1].zIndex
- ) {
- state.draggables[c1].zIndex -= 1;
- state.draggables[c1].oldZIndex = state.draggables[c1].zIndex;
- state.draggables[c1].iconEl.css(
- 'z-index',
- state.draggables[c1].zIndex
- );
-
- if (state.draggables[c1].labelEl !== null) {
- state.draggables[c1].labelEl.css(
- 'z-index',
- state.draggables[c1].zIndex
- );
- }
- }
- }
-
- draggableObj.zIndex = c1;
- draggableObj.oldZIndex = c1;
- draggableObj.iconEl.css('z-index', c1);
-
- if (draggableObj.labelEl !== null) {
- draggableObj.labelEl.css('z-index', c1);
- }
- }
- }
-
- // If a draggable was released in a wrong positione, we will
- // move it back to the slider, placing it in the same position
- // that it was dragged out of.
- function moveBackToSlider() {
- draggableObj.containerEl.show();
-
- draggableObj.zIndex = draggableObj.oldZIndex;
-
- draggableObj.iconEl.detach();
- draggableObj.iconEl.css('border', 'none');
- draggableObj.iconEl.css('background-color', 'transparent');
- draggableObj.iconEl.css('padding-left', 0);
- draggableObj.iconEl.css('padding-right', 0);
- draggableObj.iconEl.css('z-index', draggableObj.zIndex);
- draggableObj.iconEl.css(
- 'width',
- draggableObj.iconWidthSmall
- );
- draggableObj.iconEl.css(
- 'height',
- draggableObj.iconHeightSmall
- );
- draggableObj.iconEl.css(
- 'left',
- 50 - draggableObj.iconWidthSmall * 0.5
- );
- if (draggableObj.labelEl !== null) {
- draggableObj.iconEl.css('top', 5);
- } else {
- draggableObj.iconEl.css(
- 'top',
- 50 - draggableObj.iconHeightSmall * 0.5
- );
- }
-
- draggableObj.iconEl.appendTo(draggableObj.containerEl);
-
- if (draggableObj.labelEl !== null) {
- draggableObj.labelEl.detach();
- draggableObj.labelEl.css('border', 'none');
- draggableObj.labelEl.css('background-color', 'transparent');
- draggableObj.labelEl.css('padding-left', 0);
- draggableObj.labelEl.css('padding-right', 0);
- draggableObj.labelEl.css('z-index', draggableObj.zIndex);
- draggableObj.labelEl.css(
- 'left',
- 50 - draggableObj.labelWidth * 0.5
- );
- draggableObj.labelEl.css(
- 'top',
- 5 + draggableObj.iconHeightSmall + 5
- );
- draggableObj.labelEl.appendTo(
- draggableObj.containerEl
- );
- }
-
- inContainer = true;
- }
}
}
+
+ draggableObj.iconEl.mousedown(function (event) {
+ draggableObj.mouseDown.call(draggableObj, event);
+ });
+ draggableObj.iconEl.mouseup(function (event) {
+ draggableObj.mouseUp.call(draggableObj, event);
+ });
+ draggableObj.iconEl.mousemove(function (event) {
+ draggableObj.mouseMove.call(draggableObj, event);
+ });
+
+ draggableObj.containerEl.mousedown(function (event) {
+ draggableObj.mouseDown.call(draggableObj, event);
+ });
+ draggableObj.containerEl.mouseup(function (event) {
+ draggableObj.mouseUp.call(draggableObj, event);
+ });
+ draggableObj.containerEl.mousemove(function (event) {
+ draggableObj.mouseMove.call(draggableObj, event);
+ });
+
+ draggableObj.id = obj.id;
+ draggableObj.x = -1;
+ draggableObj.y = -1;
+
+ state.numDraggablesInSlider += 1;
+
+ if (obj.icon.length === 0) {
+ draggableObj.hasLoaded = true;
+ }
+
+ state.draggables.push(draggableObj);
+ }
+
+ function mouseDown(event) {
+ if (this.mousePressed === false) {
+ // So that the browser does not perform a default drag.
+ // If we don't do this, each drag operation will
+ // potentially cause the highlghting of the dragged element.
+ event.preventDefault();
+
+ // If this draggable is just being dragged out of the
+ // container, we must perform some additional tasks.
+ if (this.inContainer === true) {
+ this.containerEl.hide();
+
+ this.iconEl.detach();
+ this.iconEl.css(
+ 'background-color', this.iconElBGColor
+ );
+ this.iconEl.css(
+ 'padding-left', this.iconElPadding
+ );
+ this.iconEl.css(
+ 'padding-right', this.iconElPadding
+ );
+ this.iconEl.css(
+ 'border', this.iconElBorder
+ );
+ this.iconEl.css(
+ 'width',
+ this.iconWidth
+ );
+ this.iconEl.css(
+ 'height',
+ this.iconHeight
+ );
+ this.iconEl.css(
+ 'left',
+ event.pageX -
+ this.state.baseImageEl.offset().left -
+ this.iconWidth * 0.5
+ - this.iconElLeftOffset
+ );
+ this.iconEl.css(
+ 'top',
+ event.pageY -
+ this.state.baseImageEl.offset().top -
+ this.iconHeight * 0.5
+ );
+ this.iconEl.appendTo(
+ state.baseImageEl.parent()
+ );
+
+ if (this.labelEl !== null) {
+ this.labelEl.detach();
+ this.labelEl.css(
+ 'background-color', this.state.config.labelBgColor
+ );
+ this.labelEl.css(
+ 'padding-left', 8
+ );
+ this.labelEl.css(
+ 'padding-right', 8
+ );
+ this.labelEl.css(
+ 'border', '1px solid black'
+ );
+ this.labelEl.css(
+ 'left',
+ event.pageX -
+ this.state.baseImageEl.offset().left -
+ this.labelWidth * 0.5
+ - 9 // Account for padding, border.
+ );
+ this.labelEl.css(
+ 'top',
+ event.pageY -
+ this.state.baseImageEl.offset().top +
+ this.iconHeight * 0.5 + 5
+ );
+ this.labelEl.appendTo(
+ this.state.baseImageEl.parent()
+ );
+ }
+
+ this.inContainer = false;
+ this.state.numDraggablesInSlider -= 1;
+ }
+
+ this.oldZIndex = this.zIndex;
+ this.zIndex = 1000;
+ this.iconEl.css('z-index', '1000');
+ if (this.labelEl !== null) {
+ this.labelEl.css('z-index', '1000');
+ }
+
+ this.mousePressed = true;
+ this.state.currentMovingDraggable = this;
+ }
+ }
+
+ function mouseUp() {
+ if (this.mousePressed === true) {
+ this.state.currentMovingDraggable = null;
+
+ this.checkLandingElement();
+ }
+ }
+
+ function mouseMove() {
+ if (this.mousePressed === true) {
+ // Because we have also attached a 'mousemove' event to the
+ // 'document' (that will do the same thing), let's tell the
+ // browser not to bubble up this event. The attached event
+ // on the 'document' will only be triggered when the mouse
+ // pointer leaves the draggable while it is in the middle
+ // of a drag operation (user moves the mouse very quickly).
+ event.stopPropagation();
+
+ this.iconEl.css(
+ 'left',
+ event.pageX -
+ this.state.baseImageEl.offset().left -
+ this.iconWidth * 0.5
+ - this.iconElLeftOffset
+ );
+ this.iconEl.css(
+ 'top',
+ event.pageY -
+ this.state.baseImageEl.offset().top -
+ this.iconHeight * 0.5
+ );
+
+ if (this.labelEl !== null) {
+ this.labelEl.css(
+ 'left',
+ event.pageX -
+ this.state.baseImageEl.offset().left -
+ this.labelWidth * 0.5
+ - 9 // Acoount for padding, border.
+ );
+ this.labelEl.css(
+ 'top',
+ event.pageY -
+ this.state.baseImageEl.offset().top +
+ this.iconHeight * 0.5 +
+ 5
+ );
+ }
+ }
+ }
+
+ // At this point the mouse was realeased, and we need to check
+ // where the draggable eneded up. Based on several things, we
+ // will either move the draggable back to the slider, or update
+ // the input with the user's answer (X-Y position of the draggable,
+ // or the ID of the target where it landed.
+ function checkLandingElement() {
+ var positionIE;
+
+ this.mousePressed = false;
+ positionIE = this.iconEl.position();
+
+ if (this.state.individualTargets === true) {
+ if (this.checkIfOnTarget(positionIE) === true) {
+ this.correctZIndexes();
+ } else {
+ this.moveBackToSlider();
+ this.removeObjIdFromTarget();
+
+ this.state.numDraggablesInSlider += 1;
+ }
+ } else {
+ if (
+ (positionIE.left < 0) ||
+ (
+ positionIE.left + this.iconWidth >
+ this.state.baseImageEl.width()
+ ) ||
+ (positionIE.top < 0) ||
+ (
+ positionIE.top + this.iconHeight >
+ this.state.baseImageEl.height()
+ )
+ ) {
+ this.moveBackToSlider();
+
+ this.x = -1;
+ this.y = -1;
+
+ this.state.numDraggablesInSlider += 1;
+ } else {
+ this.correctZIndexes();
+
+ this.x =
+ positionIE.left + this.iconWidth * 0.5;
+ this.y =
+ positionIE.top + this.iconHeight * 0.5;
+ }
+ }
+
+ this.state.updateArrowOpacity();
+ updateInput(this.state);
+ }
+
+ function removeObjIdFromTarget() {
+ var c1;
+
+ if (this.onTarget !== null) {
+ for (c1 = 0; c1 < this.onTarget.draggable.length; c1 += 1) {
+ if (this.onTarget.draggable[c1] === this.id) {
+ this.onTarget.draggable.splice(c1, 1);
+
+ break;
+ }
+ }
+
+ if (this.onTarget.numTextEl !== null) {
+ this.onTarget.updateNumTextEl();
+ }
+
+ this.onTarget = null;
+ }
+ }
+
+ //
+ // Determine if a draggable, after it was relased, ends up on a
+ // target. We do this by iterating over all of the targets, and
+ // for each one we check whether the draggable's center is
+ // within the target's dimensions.
+ //
+ // positionIE is the object as returned by
+ //
+ // this.iconEl.position()
+ //
+ function checkIfOnTarget(positionIE) {
+ var c1, target, targetFound;
+
+ targetFound = false;
+
+ for (c1 = 0; c1 < this.state.targets.length; c1 += 1) {
+ target = this.state.targets[c1];
+
+ // If only one draggable per target is allowed, and
+ // the current target already has a draggable on it
+ // (with an ID different from the one we are checking
+ // against), then go to next target.
+ if (
+ (this.state.config.one_per_target === true) &&
+ (target.draggable.length === 1) &&
+ (target.draggable[0] !== this.id)
+ ) {
+ continue;
+ }
+
+ // Check if the draggable's center coordinate is within
+ // the target's dimensions. If not, go to next target.
+ if (
+ positionIE.top + this.iconHeight * 0.5 <
+ target.offset.top
+ ) {
+ continue;
+ }
+ if (
+ positionIE.top + this.iconHeight * 0.5 >
+ target.offset.top + target.h
+ ) {
+ continue;
+ }
+ if (
+ positionIE.left + this.iconWidth * 0.5 <
+ target.offset.left
+ ) {
+ continue;
+ }
+ if (
+ positionIE.left + this.iconWidth * 0.5 >
+ target.offset.left + target.w
+ ) {
+ continue;
+ }
+
+ // If we got here, then our draggable is on top of a
+ // target.
+ targetFound = true;
+
+ // If the draggable was moved from one target to
+ // another, then we need to remove it's ID from the
+ // previous target's draggables list, and add it to the
+ // new target's draggables list.
+ if (
+ (this.onTarget !== null) &&
+ (this.onTarget.id !== target.id)
+ ) {
+ this.removeObjIdFromTarget();
+ this.onTarget = target;
+ target.draggable.push(this.id);
+ }
+ // If the draggable was moved from the slider to a
+ // target, remember the target, and add ID to the
+ // target's draggables list.
+ else if (this.onTarget === null) {
+ this.onTarget = target;
+ target.draggable.push(this.id);
+ }
+
+ if (target.numTextEl !== null) {
+ target.updateNumTextEl();
+ }
+
+ // Reposition the draggable so that it's center
+ // coincides with the center of the target.
+ this.snapToTarget(target);
+
+ break;
+ }
+
+ return targetFound;
+ }
+
+ function snapToTarget(target) {
+ var offset;
+
+ offset = 0;
+ if (this.state.config.targetOutline === true) {
+ offset = 1;
+ }
+
+ this.iconEl.css(
+ 'left',
+ target.offset.left + 0.5 * target.w -
+ this.iconWidth * 0.5 + offset
+ - this.iconElLeftOffset
+ );
+ this.iconEl.css(
+ 'top',
+ target.offset.top + 0.5 * target.h -
+ this.iconHeight * 0.5 + offset
+ );
+
+ if (this.labelEl !== null) {
+ this.labelEl.css(
+ 'left',
+ target.offset.left + 0.5 * target.w -
+ this.labelWidth * 0.5 + offset
+ - 9 // Acoount for padding, border.
+ );
+ this.labelEl.css(
+ 'top',
+ target.offset.top + 0.5 * target.h +
+ this.iconHeight * 0.5 + 5 + offset
+ );
+ }
+ }
+
+ // Go through all of the draggables subtract 1 from the z-index
+ // of all whose z-index is higher than the old z-index of the
+ // current element. After, set the z-index of the current
+ // element to 1 + N (where N is the number of draggables - i.e.
+ // the highest z-index possible).
+ //
+ // This will make sure that after releasing a draggable, it
+ // will be on top of all of the other draggables. Also, the
+ // ordering of the visibility (z-index) of the other draggables
+ // will not change.
+ function correctZIndexes() {
+ var draggablesInMe, c1, c2, highestZIndex;
+
+ if (this.state.individualTargets === true) {
+ if (this.onTarget.draggable.length > 0) {
+ draggablesInMe = [];
+
+ for (c1 = 0; c1 < this.onTarget.draggable.length; c1 += 1) {
+ for (c2 = 0; c2 < this.state.draggables.length; c2 += 1) {
+ if (
+ this.onTarget.draggable[c1] ===
+ this.state.draggables[c2].id
+ ) {
+ draggablesInMe.push(this.state.draggables[c2]);
+ }
+ }
+ }
+
+ highestZIndex = -10000;
+
+ for (c1 = 0; c1 < draggablesInMe.length; c1 += 1) {
+ if (
+ (draggablesInMe[c1].zIndex > highestZIndex) &&
+ (draggablesInMe[c1].zIndex !== 1000)
+ ) {
+ highestZIndex = draggablesInMe[c1].zIndex;
+ }
+ }
+
+ if (highestZIndex === -10000) {
+ highestZIndex = this.onTarget.draggable.length;
+ } else if (highestZIndex < this.oldZIndex) {
+ highestZIndex = this.oldZIndex;
+ } else {
+ for (c1 = 0; c1 < draggablesInMe.length; c1 += 1) {
+ draggablesInMe[c1].zIndex -= 1;
+ draggablesInMe[c1].oldZIndex -= 1;
+
+ draggablesInMe[c1].iconEl.css(
+ 'z-index',
+ draggablesInMe[c1].zIndex
+ );
+ if (draggablesInMe[c1].labelEl !== null) {
+ draggablesInMe[c1].labelEl.css(
+ 'z-index',
+ draggablesInMe[c1].zIndex
+ );
+ }
+ }
+ }
+ } else {
+ highestZIndex = this.onTarget.draggable.length;
+ }
+
+ this.zIndex = highestZIndex;
+ this.oldZIndex = highestZIndex;
+
+ this.iconEl.css(
+ 'z-index',
+ this.zIndex
+ );
+ if (this.labelEl !== null) {
+ this.labelEl.css(
+ 'z-index',
+ this.zIndex
+ );
+ }
+ } else {
+ for (c1 = 0; c1 < this.state.draggables.length; c1++) {
+ if (
+ this.oldZIndex <
+ this.state.draggables[c1].zIndex
+ ) {
+ this.state.draggables[c1].zIndex -= 1;
+ this.state.draggables[c1].oldZIndex = this.state.draggables[c1].zIndex;
+ this.state.draggables[c1].iconEl.css(
+ 'z-index',
+ this.state.draggables[c1].zIndex
+ );
+
+ if (this.state.draggables[c1].labelEl !== null) {
+ this.state.draggables[c1].labelEl.css(
+ 'z-index',
+ this.state.draggables[c1].zIndex
+ );
+ }
+ }
+ }
+
+ this.zIndex = c1;
+ this.oldZIndex = c1;
+ this.iconEl.css('z-index', c1);
+
+ if (this.labelEl !== null) {
+ this.labelEl.css('z-index', c1);
+ }
+ }
+ }
+
+ // If a draggable was released in a wrong positione, we will
+ // move it back to the slider, placing it in the same position
+ // that it was dragged out of.
+ function moveBackToSlider() {
+ this.containerEl.show();
+
+ this.zIndex = this.oldZIndex;
+
+ this.iconEl.detach();
+ this.iconEl.css('border', 'none');
+ this.iconEl.css('background-color', 'transparent');
+ this.iconEl.css('padding-left', 0);
+ this.iconEl.css('padding-right', 0);
+ this.iconEl.css('z-index', this.zIndex);
+ this.iconEl.css(
+ 'width',
+ this.iconWidthSmall
+ );
+ this.iconEl.css(
+ 'height',
+ this.iconHeightSmall
+ );
+ this.iconEl.css(
+ 'left',
+ 50 - this.iconWidthSmall * 0.5
+ );
+ if (this.labelEl !== null) {
+ this.iconEl.css('top', 5);
+ } else {
+ this.iconEl.css(
+ 'top',
+ 50 - this.iconHeightSmall * 0.5
+ );
+ }
+
+ this.iconEl.appendTo(this.containerEl);
+
+ if (this.labelEl !== null) {
+ this.labelEl.detach();
+ this.labelEl.css('border', 'none');
+ this.labelEl.css('background-color', 'transparent');
+ this.labelEl.css('padding-left', 0);
+ this.labelEl.css('padding-right', 0);
+ this.labelEl.css('z-index', this.zIndex);
+ this.labelEl.css(
+ 'left',
+ 50 - this.labelWidth * 0.5
+ );
+ this.labelEl.css(
+ 'top',
+ 5 + this.iconHeightSmall + 5
+ );
+ this.labelEl.appendTo(
+ this.containerEl
+ );
+ }
+
+ this.inContainer = true;
}
});
diff --git a/common/static/js/capa/drag_and_drop/main.js b/common/static/js/capa/drag_and_drop/main.js
index 24cc8cbdcb..6b1164b6df 100644
--- a/common/static/js/capa/drag_and_drop/main.js
+++ b/common/static/js/capa/drag_and_drop/main.js
@@ -63,7 +63,9 @@ define(
Targets(state);
Scroller(state);
- Draggables(state);
+ Draggables.init(state);
+
+ logme('After Draggables.init(state); state = ', state);
// Update the input element, checking first that it is not filled with
// an answer from the server.
diff --git a/common/static/js/capa/drag_and_drop/targets.js b/common/static/js/capa/drag_and_drop/targets.js
index 0bc246e49f..af27c186f2 100644
--- a/common/static/js/capa/drag_and_drop/targets.js
+++ b/common/static/js/capa/drag_and_drop/targets.js
@@ -119,15 +119,6 @@ define(['logme'], function (logme) {
highestZIndex = -10000;
lowestZIndex = 10000;
- for (c1 = 0; c1 < draggablesInMe.length; c1 += 1) {
- logme(
- 'draggablesInMe[' + c1 + '].id = ' + draggablesInMe[c1].id,
- 'draggablesInMe[' + c1 + '].zIndex = ' + draggablesInMe[c1].zIndex,
- 'draggablesInMe[' + c1 + '].oldZIndex = ' + draggablesInMe[c1].oldZIndex
- );
- }
- logme('------------------');
-
for (c1 = 0; c1 < draggablesInMe.length; c1 += 1) {
if (draggablesInMe[c1].zIndex < lowestZIndex) {
lowestZIndex = draggablesInMe[c1].zIndex;
diff --git a/common/static/js/capa/drag_and_drop/update_input.js b/common/static/js/capa/drag_and_drop/update_input.js
index 3b5053d5d2..14534f1240 100644
--- a/common/static/js/capa/drag_and_drop/update_input.js
+++ b/common/static/js/capa/drag_and_drop/update_input.js
@@ -10,6 +10,8 @@ define(['logme'], function (logme) {
function updateInput(state, checkFirst) {
var inputEl, stateStr, targets, draggables, c1, c2, tempObj;
+ logme('updateInput; state = ', state);
+
if (checkFirst === true) {
if (checkIfHasAnswer() === true) {
return;
@@ -126,7 +128,7 @@ define(['logme'], function (logme) {
return;
}
- draggable.setInContainer(false);
+ draggable.inContainer = false;
draggable.containerEl.hide();
draggable.iconEl.detach();
@@ -196,7 +198,7 @@ define(['logme'], function (logme) {
);
}
- draggable.setOnTarget(target);
+ draggable.onTarget = target;
target.draggable.push(draggableId);
if (target.numTextEl !== null) {
@@ -244,7 +246,7 @@ define(['logme'], function (logme) {
return;
}
- draggable.setInContainer(false);
+ draggable.inContainer = false;
draggable.containerEl.hide();
draggable.iconEl.detach();