diff --git a/common/static/js/capa/drag_and_drop/base_image.js b/common/static/js/capa/drag_and_drop/base_image.js index 185325512c..9cee42feb2 100644 --- a/common/static/js/capa/drag_and_drop/base_image.js +++ b/common/static/js/capa/drag_and_drop/base_image.js @@ -8,53 +8,25 @@ define(['logme'], function (logme) { return BaseImage; function BaseImage(state) { - var targetImgSrc, baseImageElContainer, mouseMoveDiv; - - targetImgSrc = state.config.imageDir + '/' + state.config.base_image; + var baseImageElContainer; baseImageElContainer = $( '
' ); state.baseImageEl = $( '' ); state.baseImageEl.appendTo(baseImageElContainer); - state.baseImageElWidth = null; - $('') // Make in memory copy of image to avoid css issues. - .attr('src', state.baseImageEl.attr('src')) - .load(function () { - state.baseImageElWidth = this.width; - }); - - // state.baseImageEl.mousemove( - // function (event) { - // mouseMoveDiv.html( - // '[' + event.offsetX + ', ' + event.offsetY + ']' - // ); - // } - // ); - - mouseMoveDiv = $( - '
' - ); - mouseMoveDiv.appendTo(baseImageElContainer); - baseImageElContainer.appendTo(state.containerEl); } }); diff --git a/common/static/js/capa/drag_and_drop/config_parser.js b/common/static/js/capa/drag_and_drop/config_parser.js index 58f6114009..664324e0b9 100644 --- a/common/static/js/capa/drag_and_drop/config_parser.js +++ b/common/static/js/capa/drag_and_drop/config_parser.js @@ -78,9 +78,9 @@ define(['logme'], function (logme) { if (typeof config.target_outline === 'string') { if (config.target_outline.toLowerCase() === 'true') { - state.config.target_outline = true; + state.config.targetOutline = true; } else if (config.target_outline.toLowerCase() === 'false') { - state.config.target_outline = false; + state.config.targetOutline = false; } else { logme('ERROR: Property config.target_outline can either be "true", or "false".'); returnStatus = false; diff --git a/common/static/js/capa/drag_and_drop/draggables.js b/common/static/js/capa/drag_and_drop/draggables.js index 714fe19597..faf0f7b3f3 100644 --- a/common/static/js/capa/drag_and_drop/draggables.js +++ b/common/static/js/capa/drag_and_drop/draggables.js @@ -8,22 +8,13 @@ define(['logme', 'update_input'], function (logme, updateInput) { return Draggables; function Draggables(state) { - var _draggables, numDraggables; + var c1; - numDraggables = state.config.draggables.length; - _draggables = []; state.draggables = []; - (function (i) { - while (i < numDraggables) { - processDraggable(state.config.draggables[i], i + 1); - i += 1; - } - - if (state.individualTargets === false) { - updateInput(state, true); - } - }(0)); + for (c1 = 0; c1 < state.config.draggables.length; c1 += 1) { + processDraggable(state.config.draggables[c1], c1 + 1); + } state.currentMovingDraggable = null; @@ -39,8 +30,8 @@ define(['logme', 'update_input'], function (logme, updateInput) { return; function processDraggable(obj, objIndex) { - var draggableContainerEl, imgEl, inContainer, ousePressed, - onTarget, draggableObj, marginCss; + var draggableContainerEl, inContainer, mousePressed, onTarget, + draggableObj, marginCss; draggableContainerEl = $( '
' ); if (obj.icon.length > 0) { - imgEl = $( - '' + draggableContainerEl.append( + $('') ); - - draggableContainerEl.append(imgEl); } if (obj.label.length > 0) { @@ -80,7 +67,6 @@ define(['logme', 'update_input'], function (logme, updateInput) { } draggableContainerEl.appendTo(state.sliderEl); - _draggables.push(draggableContainerEl); inContainer = true; mousePressed = false; @@ -102,12 +88,6 @@ define(['logme', 'update_input'], function (logme, updateInput) { draggableContainerEl.mouseup(mouseUp); draggableContainerEl.mousemove(mouseMove); - if (objIndex + 1 === numDraggables) { - state.draggablesLoaded = true; - - state.updateArrowOpacity(); - } - return; function mouseDown(event) { @@ -134,10 +114,9 @@ define(['logme', 'update_input'], function (logme, updateInput) { } } - function mouseUp(event) { + function mouseUp() { if (mousePressed === true) { state.currentMovingDraggable = null; - normalizeEvent(event); checkLandingElement(event); } @@ -150,14 +129,30 @@ define(['logme', 'update_input'], function (logme, updateInput) { } } - function checkLandingElement(event) { + // 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 offsetDE, indexes, DEindex, targetFound; mousePressed = false; offsetDE = draggableContainerEl.position(); - if (state.individualTargets === false) { + if (state.individualTargets === true) { + targetFound = false; + + checkIfOnTarget(); + + if (targetFound === true) { + correctZIndexes(); + } else { + moveBackToSlider(); + removeObjIdFromTarget(); + } + } else { if ( (offsetDE.left < 0) || (offsetDE.left + 100 > state.baseImageEl.width()) || @@ -174,21 +169,9 @@ define(['logme', 'update_input'], function (logme, updateInput) { draggableObj.x = offsetDE.left + 50; draggableObj.y = offsetDE.top + 50; } - } else if (state.individualTargets === true) { - targetFound = false; - - checkIfOnTarget(); - - if (targetFound === true) { - correctZIndexes(); - } else { - moveBackToSlider(); - removeObjIdFromTarget(); - } } state.updateArrowOpacity(); - updateInput(state); return; @@ -197,25 +180,26 @@ define(['logme', 'update_input'], function (logme, updateInput) { var c1; if (onTarget !== null) { - c1 = 0; - - while (c1 < onTarget.draggable.length) { + for (c1 = 0; c1 < onTarget.draggable.length; c1 += 1) { if (onTarget.draggable[c1] === obj.id) { onTarget.draggable.splice(c1, 1); break; } - c1 += 1; } 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. function checkIfOnTarget() { var c1, target; - for (c1 = 0; c1 < state.targets.length; c1++) { + for (c1 = 0; c1 < state.targets.length; c1 += 1) { target = state.targets[c1]; if (offsetDE.top + 50 < target.offset.top) { @@ -241,10 +225,21 @@ define(['logme', 'update_input'], function (logme, updateInput) { targetFound = true; - removeObjIdFromTarget(); - onTarget = target; + // 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(obj.id); + } else if (onTarget === null) { + onTarget = target; + target.draggable.push(obj.id); + } - target.draggable.push(obj.id); + // Reposition the draggable so that it's center + // coincides with the center of the target. snapToTarget(target); break; @@ -256,30 +251,47 @@ define(['logme', 'update_input'], function (logme, updateInput) { draggableContainerEl.css('top', target.offset.top + 0.5 * target.h - 50); } + // 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 c1; - c1 = 0; - - while (c1 < _draggables.length) { - if (parseInt(draggableContainerEl.attr('data-old-z-index'), 10) < parseInt(_draggables[c1].css('z-index'), 10)) { - _draggables[c1].css('z-index', parseInt(_draggables[c1].css('z-index'), 10) - 1); + for (c1 = 0; c1 < state.draggables.length; c1++) { + if ( + parseInt(draggableContainerEl.attr('data-old-z-index'), 10) < + parseInt(state.draggables[c1].el.css('z-index'), 10) + ) { + state.draggables[c1].el.css( + 'z-index', + parseInt(state.draggables[c1].el.css('z-index'), 10) - 1 + ); } - c1 += 1; } draggableContainerEl.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() { var c1; draggableContainerEl.detach(); draggableContainerEl.css('position', 'static'); + // Get the position indexes of all draggables that are + // currently in the slider, along with the corresponding + // jQuery element. indexes = []; - DEindex = parseInt(draggableContainerEl.attr('data-draggable-position-index'), 10); - state.sliderEl.children().each(function (index, value) { indexes.push({ 'index': parseInt($(value).attr('data-draggable-position-index'), 10), @@ -287,27 +299,39 @@ define(['logme', 'update_input'], function (logme, updateInput) { }); }); - c1 = 0; + // Get the position index of the element that we are + // inserting back into the slider. + DEindex = parseInt(draggableContainerEl.attr('data-draggable-position-index'), 10); - while (c1 < indexes.length) { + // Starting from the first position index that we + // retrieved, and going up, if we find a position index + // that is more than 'DEindex', we know that we must insert + // the current element before the element with the greater + // position index. + for (c1 = 0; c1 < indexes.length; c1 += 1) { if ((inContainer === false) && (indexes[c1].index > DEindex)) { indexes[c1].el.before(draggableContainerEl); inContainer = true; } - - c1 += 1; } + // If we did not find a greater postion index, then either + // there are no elements in the slider, or all of them + // have a lesser position index. In both cases we add the + // current draggable to the end. if (inContainer === false) { draggableContainerEl.appendTo(state.sliderEl); - inContainer = true; } + inContainer = true; + draggableContainerEl.css('border', '1px solid gray'); } } } + // In firefox the event does not have a proper pageX and pageY + // coordinates. function normalizeEvent(event) { if(!event.offsetX) { event.offsetX = (event.pageX - $(event.target).offset().left); diff --git a/common/static/js/capa/drag_and_drop/main.js b/common/static/js/capa/drag_and_drop/main.js index 9027f55d01..0ccfb47051 100644 --- a/common/static/js/capa/drag_and_drop/main.js +++ b/common/static/js/capa/drag_and_drop/main.js @@ -5,8 +5,10 @@ (function (requirejs, require, define) { define( - ['logme', 'state', 'config_parser', 'container', 'base_image', 'scroller', 'draggables', 'targets'], - function (logme, State, configParser, Container, BaseImage, Scroller, Draggables, Targets) { + ['logme', 'state', 'config_parser', 'container', 'base_image', 'scroller', + 'draggables', 'targets', 'update_input'], + function (logme, State, configParser, Container, BaseImage, Scroller, + Draggables, Targets, updateInput) { return Main; function Main() { @@ -58,12 +60,13 @@ define( Container(state); BaseImage(state); + Targets(state); Scroller(state); Draggables(state); - Targets(state); - logme('config', config); - logme('state', state); + // Update the input element, checking first that it is not filled with + // an answer from the server. + updateInput(state, true); } }); diff --git a/common/static/js/capa/drag_and_drop/scroller.js b/common/static/js/capa/drag_and_drop/scroller.js index 46af42bb1b..d3a4d23eab 100644 --- a/common/static/js/capa/drag_and_drop/scroller.js +++ b/common/static/js/capa/drag_and_drop/scroller.js @@ -61,14 +61,20 @@ define(['logme'], function (logme) { moveLeftEl.mousemove(function (event) { event.preventDefault(); }); moveLeftEl.mousedown(function (event) { event.preventDefault(); }); + // This event will be responsible for moving the scroller left. + // Hidden draggables will be shown. moveLeftEl.mouseup(function (event) { event.preventDefault(); + // When there are no more hidden draggables, prevent from + // scrolling infinitely. if (showElLeftMargin > -102) { return; } showElLeftMargin += 102; + + // We scroll by changing the 'margin-left' CSS property smoothly. state.sliderEl.animate({ 'margin-left': showElLeftMargin + 'px' }, 100, function () { @@ -91,6 +97,10 @@ define(['logme'], function (logme) { showElLeftMargin = 0; + // Element where the draggables will be contained. It is very long + // so that any SANE number of draggables will fit in a single row. It + // will be contained in a parent element whose 'overflow' CSS value + // will be hidden, preventing the long row from fully being visible. state.sliderEl = $( '
' ); - tEl.appendTo(state.baseImageEl.parent()); + targetEl.appendTo(state.baseImageEl.parent()); state.targets.push({ 'id': obj.id, - 'offset': tEl.position(), + 'w': obj.w, 'h': obj.h, - 'el': tEl, + + 'el': targetEl, + 'offset': targetEl.position(), + 'draggable': [] }); - - if (objIndex + 1 === numTargets) { - state.targetsLoaded = true; - } } } }); 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 7b5cd6acfa..6ebf62ef11 100644 --- a/common/static/js/capa/drag_and_drop/update_input.js +++ b/common/static/js/capa/drag_and_drop/update_input.js @@ -54,6 +54,8 @@ define(['logme'], function (logme) { inputEl = $('#input_' + state.problemId); inputEl.val(stateStr); + logme(inputEl.val()); + return; // Check if input has an answer from server. If yes, then position @@ -75,16 +77,7 @@ define(['logme'], function (logme) { var draggableId, draggable, targetId, target, draggablePosition, c1; - if ( - ((state.individualTargets === true) && (state.targetsLoaded === false)) || - (state.draggablesLoaded === false) - ) { - window.setTimeout(function () { - repositionDraggables(answer); - }, 50); - - return; - } + logme(answer); if ( ((typeof answer.use_targets === 'boolean') && (answer.use_targets === true)) ||