Commit 108b0fc3 by Valera Rozuvan Committed by Alexander Kryklia

Refactoring and code documentation.

parent f2e930a3
...@@ -8,53 +8,25 @@ define(['logme'], function (logme) { ...@@ -8,53 +8,25 @@ define(['logme'], function (logme) {
return BaseImage; return BaseImage;
function BaseImage(state) { function BaseImage(state) {
var targetImgSrc, baseImageElContainer, mouseMoveDiv; var baseImageElContainer;
targetImgSrc = state.config.imageDir + '/' + state.config.base_image;
baseImageElContainer = $( baseImageElContainer = $(
'<div ' + '<div ' +
'class="base_image_container" ' + 'class="base_image_container" ' +
'style=" ' + 'style=" ' +
'position: relative; ' + 'position: relative; ' +
'margin-bottom: 25px; ' +
'" ' + '" ' +
'></div>' '></div>'
); );
state.baseImageEl = $( state.baseImageEl = $(
'<img ' + '<img ' +
'src="' + targetImgSrc + '" ' + 'src="' + state.config.imageDir + '/' + state.config.base_image + '" ' +
'/>' '/>'
); );
state.baseImageEl.appendTo(baseImageElContainer); state.baseImageEl.appendTo(baseImageElContainer);
state.baseImageElWidth = null;
$('<img/>') // 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 = $(
'<div ' +
'style=" ' +
'clear: both; ' +
'width: auto; ' +
'height: 25px; ' +
'text-align: center; ' +
'" ' +
'></div>'
);
mouseMoveDiv.appendTo(baseImageElContainer);
baseImageElContainer.appendTo(state.containerEl); baseImageElContainer.appendTo(state.containerEl);
} }
}); });
......
...@@ -78,9 +78,9 @@ define(['logme'], function (logme) { ...@@ -78,9 +78,9 @@ define(['logme'], function (logme) {
if (typeof config.target_outline === 'string') { if (typeof config.target_outline === 'string') {
if (config.target_outline.toLowerCase() === 'true') { if (config.target_outline.toLowerCase() === 'true') {
state.config.target_outline = true; state.config.targetOutline = true;
} else if (config.target_outline.toLowerCase() === 'false') { } else if (config.target_outline.toLowerCase() === 'false') {
state.config.target_outline = false; state.config.targetOutline = false;
} else { } else {
logme('ERROR: Property config.target_outline can either be "true", or "false".'); logme('ERROR: Property config.target_outline can either be "true", or "false".');
returnStatus = false; returnStatus = false;
......
...@@ -8,22 +8,13 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -8,22 +8,13 @@ define(['logme', 'update_input'], function (logme, updateInput) {
return Draggables; return Draggables;
function Draggables(state) { function Draggables(state) {
var _draggables, numDraggables; var c1;
numDraggables = state.config.draggables.length;
_draggables = [];
state.draggables = []; state.draggables = [];
(function (i) { for (c1 = 0; c1 < state.config.draggables.length; c1 += 1) {
while (i < numDraggables) { processDraggable(state.config.draggables[c1], c1 + 1);
processDraggable(state.config.draggables[i], i + 1); }
i += 1;
}
if (state.individualTargets === false) {
updateInput(state, true);
}
}(0));
state.currentMovingDraggable = null; state.currentMovingDraggable = null;
...@@ -39,8 +30,8 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -39,8 +30,8 @@ define(['logme', 'update_input'], function (logme, updateInput) {
return; return;
function processDraggable(obj, objIndex) { function processDraggable(obj, objIndex) {
var draggableContainerEl, imgEl, inContainer, ousePressed, var draggableContainerEl, inContainer, mousePressed, onTarget,
onTarget, draggableObj, marginCss; draggableObj, marginCss;
draggableContainerEl = $( draggableContainerEl = $(
'<div ' + '<div ' +
...@@ -51,20 +42,16 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -51,20 +42,16 @@ define(['logme', 'update_input'], function (logme, updateInput) {
'float: left; ' + 'float: left; ' +
'overflow: hidden; ' + 'overflow: hidden; ' +
'z-index: ' + objIndex + '; ' + 'z-index: ' + objIndex + '; ' +
'border: 1px solid gray; ' + 'border: 1px solid #CCC; ' +
'" ' + '" ' +
'data-draggable-position-index="' + objIndex + '" ' + 'data-draggable-position-index="' + objIndex + '" ' +
'></div>' '></div>'
); );
if (obj.icon.length > 0) { if (obj.icon.length > 0) {
imgEl = $( draggableContainerEl.append(
'<img ' + $('<img src="' + state.config.imageDir + '/' + obj.icon + '" />')
'src="' + state.config.imageDir + '/' + obj.icon + '" ' +
'/>'
); );
draggableContainerEl.append(imgEl);
} }
if (obj.label.length > 0) { if (obj.label.length > 0) {
...@@ -80,7 +67,6 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -80,7 +67,6 @@ define(['logme', 'update_input'], function (logme, updateInput) {
} }
draggableContainerEl.appendTo(state.sliderEl); draggableContainerEl.appendTo(state.sliderEl);
_draggables.push(draggableContainerEl);
inContainer = true; inContainer = true;
mousePressed = false; mousePressed = false;
...@@ -102,12 +88,6 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -102,12 +88,6 @@ define(['logme', 'update_input'], function (logme, updateInput) {
draggableContainerEl.mouseup(mouseUp); draggableContainerEl.mouseup(mouseUp);
draggableContainerEl.mousemove(mouseMove); draggableContainerEl.mousemove(mouseMove);
if (objIndex + 1 === numDraggables) {
state.draggablesLoaded = true;
state.updateArrowOpacity();
}
return; return;
function mouseDown(event) { function mouseDown(event) {
...@@ -134,10 +114,9 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -134,10 +114,9 @@ define(['logme', 'update_input'], function (logme, updateInput) {
} }
} }
function mouseUp(event) { function mouseUp() {
if (mousePressed === true) { if (mousePressed === true) {
state.currentMovingDraggable = null; state.currentMovingDraggable = null;
normalizeEvent(event);
checkLandingElement(event); checkLandingElement(event);
} }
...@@ -150,14 +129,30 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -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; var offsetDE, indexes, DEindex, targetFound;
mousePressed = false; mousePressed = false;
offsetDE = draggableContainerEl.position(); offsetDE = draggableContainerEl.position();
if (state.individualTargets === false) { if (state.individualTargets === true) {
targetFound = false;
checkIfOnTarget();
if (targetFound === true) {
correctZIndexes();
} else {
moveBackToSlider();
removeObjIdFromTarget();
}
} else {
if ( if (
(offsetDE.left < 0) || (offsetDE.left < 0) ||
(offsetDE.left + 100 > state.baseImageEl.width()) || (offsetDE.left + 100 > state.baseImageEl.width()) ||
...@@ -174,21 +169,9 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -174,21 +169,9 @@ define(['logme', 'update_input'], function (logme, updateInput) {
draggableObj.x = offsetDE.left + 50; draggableObj.x = offsetDE.left + 50;
draggableObj.y = offsetDE.top + 50; draggableObj.y = offsetDE.top + 50;
} }
} else if (state.individualTargets === true) {
targetFound = false;
checkIfOnTarget();
if (targetFound === true) {
correctZIndexes();
} else {
moveBackToSlider();
removeObjIdFromTarget();
}
} }
state.updateArrowOpacity(); state.updateArrowOpacity();
updateInput(state); updateInput(state);
return; return;
...@@ -197,25 +180,26 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -197,25 +180,26 @@ define(['logme', 'update_input'], function (logme, updateInput) {
var c1; var c1;
if (onTarget !== null) { if (onTarget !== null) {
c1 = 0; for (c1 = 0; c1 < onTarget.draggable.length; c1 += 1) {
while (c1 < onTarget.draggable.length) {
if (onTarget.draggable[c1] === obj.id) { if (onTarget.draggable[c1] === obj.id) {
onTarget.draggable.splice(c1, 1); onTarget.draggable.splice(c1, 1);
break; break;
} }
c1 += 1;
} }
onTarget = null; 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() { function checkIfOnTarget() {
var c1, target; var c1, target;
for (c1 = 0; c1 < state.targets.length; c1++) { for (c1 = 0; c1 < state.targets.length; c1 += 1) {
target = state.targets[c1]; target = state.targets[c1];
if (offsetDE.top + 50 < target.offset.top) { if (offsetDE.top + 50 < target.offset.top) {
...@@ -241,10 +225,21 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -241,10 +225,21 @@ define(['logme', 'update_input'], function (logme, updateInput) {
targetFound = true; targetFound = true;
removeObjIdFromTarget(); // If the draggable was moved from one target to
onTarget = target; // 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); snapToTarget(target);
break; break;
...@@ -256,30 +251,47 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -256,30 +251,47 @@ define(['logme', 'update_input'], function (logme, updateInput) {
draggableContainerEl.css('top', target.offset.top + 0.5 * target.h - 50); 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() { function correctZIndexes() {
var c1; var c1;
c1 = 0; for (c1 = 0; c1 < state.draggables.length; c1++) {
if (
while (c1 < _draggables.length) { parseInt(draggableContainerEl.attr('data-old-z-index'), 10) <
if (parseInt(draggableContainerEl.attr('data-old-z-index'), 10) < parseInt(_draggables[c1].css('z-index'), 10)) { parseInt(state.draggables[c1].el.css('z-index'), 10)
_draggables[c1].css('z-index', parseInt(_draggables[c1].css('z-index'), 10) - 1); ) {
state.draggables[c1].el.css(
'z-index',
parseInt(state.draggables[c1].el.css('z-index'), 10) - 1
);
} }
c1 += 1;
} }
draggableContainerEl.css('z-index', c1); 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() { function moveBackToSlider() {
var c1; var c1;
draggableContainerEl.detach(); draggableContainerEl.detach();
draggableContainerEl.css('position', 'static'); draggableContainerEl.css('position', 'static');
// Get the position indexes of all draggables that are
// currently in the slider, along with the corresponding
// jQuery element.
indexes = []; indexes = [];
DEindex = parseInt(draggableContainerEl.attr('data-draggable-position-index'), 10);
state.sliderEl.children().each(function (index, value) { state.sliderEl.children().each(function (index, value) {
indexes.push({ indexes.push({
'index': parseInt($(value).attr('data-draggable-position-index'), 10), 'index': parseInt($(value).attr('data-draggable-position-index'), 10),
...@@ -287,27 +299,39 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -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)) { if ((inContainer === false) && (indexes[c1].index > DEindex)) {
indexes[c1].el.before(draggableContainerEl); indexes[c1].el.before(draggableContainerEl);
inContainer = true; 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) { if (inContainer === false) {
draggableContainerEl.appendTo(state.sliderEl); draggableContainerEl.appendTo(state.sliderEl);
inContainer = true;
} }
inContainer = true;
draggableContainerEl.css('border', '1px solid gray'); draggableContainerEl.css('border', '1px solid gray');
} }
} }
} }
// In firefox the event does not have a proper pageX and pageY
// coordinates.
function normalizeEvent(event) { function normalizeEvent(event) {
if(!event.offsetX) { if(!event.offsetX) {
event.offsetX = (event.pageX - $(event.target).offset().left); event.offsetX = (event.pageX - $(event.target).offset().left);
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
(function (requirejs, require, define) { (function (requirejs, require, define) {
define( define(
['logme', 'state', 'config_parser', 'container', 'base_image', 'scroller', 'draggables', 'targets'], ['logme', 'state', 'config_parser', 'container', 'base_image', 'scroller',
function (logme, State, configParser, Container, BaseImage, Scroller, Draggables, Targets) { 'draggables', 'targets', 'update_input'],
function (logme, State, configParser, Container, BaseImage, Scroller,
Draggables, Targets, updateInput) {
return Main; return Main;
function Main() { function Main() {
...@@ -58,12 +60,13 @@ define( ...@@ -58,12 +60,13 @@ define(
Container(state); Container(state);
BaseImage(state); BaseImage(state);
Targets(state);
Scroller(state); Scroller(state);
Draggables(state); Draggables(state);
Targets(state);
logme('config', config); // Update the input element, checking first that it is not filled with
logme('state', state); // an answer from the server.
updateInput(state, true);
} }
}); });
......
...@@ -61,14 +61,20 @@ define(['logme'], function (logme) { ...@@ -61,14 +61,20 @@ define(['logme'], function (logme) {
moveLeftEl.mousemove(function (event) { event.preventDefault(); }); moveLeftEl.mousemove(function (event) { event.preventDefault(); });
moveLeftEl.mousedown(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) { moveLeftEl.mouseup(function (event) {
event.preventDefault(); event.preventDefault();
// When there are no more hidden draggables, prevent from
// scrolling infinitely.
if (showElLeftMargin > -102) { if (showElLeftMargin > -102) {
return; return;
} }
showElLeftMargin += 102; showElLeftMargin += 102;
// We scroll by changing the 'margin-left' CSS property smoothly.
state.sliderEl.animate({ state.sliderEl.animate({
'margin-left': showElLeftMargin + 'px' 'margin-left': showElLeftMargin + 'px'
}, 100, function () { }, 100, function () {
...@@ -91,6 +97,10 @@ define(['logme'], function (logme) { ...@@ -91,6 +97,10 @@ define(['logme'], function (logme) {
showElLeftMargin = 0; 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 = $( state.sliderEl = $(
'<div ' + '<div ' +
'style=" ' + 'style=" ' +
...@@ -141,15 +151,20 @@ define(['logme'], function (logme) { ...@@ -141,15 +151,20 @@ define(['logme'], function (logme) {
moveRightEl.mousemove(function (event) { event.preventDefault(); }); moveRightEl.mousemove(function (event) { event.preventDefault(); });
moveRightEl.mousedown(function (event) { event.preventDefault(); }); moveRightEl.mousedown(function (event) { event.preventDefault(); });
// This event will be responsible for moving the scroller right.
// Hidden draggables will be shown.
moveRightEl.mouseup(function (event) { moveRightEl.mouseup(function (event) {
event.preventDefault(); event.preventDefault();
// When there are no more hidden draggables, prevent from
// scrolling infinitely.
if (showElLeftMargin < -102 * (state.sliderEl.children().length - 6)) { if (showElLeftMargin < -102 * (state.sliderEl.children().length - 6)) {
return; return;
} }
showElLeftMargin -= 102; showElLeftMargin -= 102;
// We scroll by changing the 'margin-left' CSS property smoothly.
state.sliderEl.animate({ state.sliderEl.animate({
'margin-left': showElLeftMargin + 'px' 'margin-left': showElLeftMargin + 'px'
}, 100, function () { }, 100, function () {
...@@ -159,6 +174,16 @@ define(['logme'], function (logme) { ...@@ -159,6 +174,16 @@ define(['logme'], function (logme) {
parentEl.appendTo(state.containerEl); parentEl.appendTo(state.containerEl);
// Make the function available throughout the application. We need to
// call it in several places:
//
// 1.) When initially reading answer from server, if draggables will be
// positioned on the base image, the scroller's right and left arrows
// opacity must be updated.
//
// 2.) When creating draggable elements, the scroller's right and left
// arrows opacity must be updated according to the number of
// draggables.
state.updateArrowOpacity = updateArrowOpacity; state.updateArrowOpacity = updateArrowOpacity;
return; return;
......
...@@ -11,6 +11,8 @@ define([], function () { ...@@ -11,6 +11,8 @@ define([], function () {
return { return {
'problemId': problemId, 'problemId': problemId,
// Will indicate when all targetsand draggables have been loaded,
// processed, and postioned intially.
'targetsLoaded': false, 'targetsLoaded': false,
'draggablesLoaded': false 'draggablesLoaded': false
}; };
......
...@@ -4,48 +4,29 @@ ...@@ -4,48 +4,29 @@
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system // See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) { (function (requirejs, require, define) {
define(['logme', 'update_input'], function (logme, updateInput) { define(['logme'], function (logme) {
return Targets; return Targets;
function Targets(state) { function Targets(state) {
var numTargets; var c1;
numTargets = state.config.targets.length;
state.targets = []; state.targets = [];
(function (c1) { for (c1 = 0; c1 < state.config.targets.length; c1++) {
while (c1 < numTargets) { processTarget(state.config.targets[c1]);
processTarget(state.config.targets[c1], c1); }
c1 += 1;
}
if (state.individualTargets === true) {
updateInput(state, true);
}
}(0));
return; return;
function processTarget(obj, objIndex) { function processTarget(obj) {
var baseImageElOffset, tEl, left, borderCss; var targetEl, borderCss;
// if (state.baseImageElWidth === null) {
// window.setTimeout(function () {
// processTarget(obj);
// }, 50);
//
// return;
// }
// left = obj.x + 0.5 * (state.baseImageEl.parent().width() - state.baseImageElWidth);
left = obj.x;
borderCss = ''; borderCss = '';
if (state.config.target_outline === true) { if (state.config.targetOutline === true) {
borderCss = 'border: 1px dashed gray; '; borderCss = 'border: 1px dashed gray; ';
} }
tEl = $( targetEl = $(
'<div ' + '<div ' +
'style=" ' + 'style=" ' +
'display: block; ' + 'display: block; ' +
...@@ -53,27 +34,26 @@ define(['logme', 'update_input'], function (logme, updateInput) { ...@@ -53,27 +34,26 @@ define(['logme', 'update_input'], function (logme, updateInput) {
'width: ' + obj.w + 'px; ' + 'width: ' + obj.w + 'px; ' +
'height: ' + obj.h + 'px; ' + 'height: ' + obj.h + 'px; ' +
'top: ' + obj.y + 'px; ' + 'top: ' + obj.y + 'px; ' +
'left: ' + left + 'px; ' + 'left: ' + obj.x + 'px; ' +
borderCss + borderCss +
'" ' + '" ' +
'data-target-id="' + obj.id + '" ' + 'data-target-id="' + obj.id + '" ' +
'></div>' '></div>'
); );
tEl.appendTo(state.baseImageEl.parent()); targetEl.appendTo(state.baseImageEl.parent());
state.targets.push({ state.targets.push({
'id': obj.id, 'id': obj.id,
'offset': tEl.position(),
'w': obj.w, 'w': obj.w,
'h': obj.h, 'h': obj.h,
'el': tEl,
'el': targetEl,
'offset': targetEl.position(),
'draggable': [] 'draggable': []
}); });
if (objIndex + 1 === numTargets) {
state.targetsLoaded = true;
}
} }
} }
}); });
......
...@@ -54,6 +54,8 @@ define(['logme'], function (logme) { ...@@ -54,6 +54,8 @@ define(['logme'], function (logme) {
inputEl = $('#input_' + state.problemId); inputEl = $('#input_' + state.problemId);
inputEl.val(stateStr); inputEl.val(stateStr);
logme(inputEl.val());
return; return;
// Check if input has an answer from server. If yes, then position // Check if input has an answer from server. If yes, then position
...@@ -75,16 +77,7 @@ define(['logme'], function (logme) { ...@@ -75,16 +77,7 @@ define(['logme'], function (logme) {
var draggableId, draggable, targetId, target, draggablePosition, var draggableId, draggable, targetId, target, draggablePosition,
c1; c1;
if ( logme(answer);
((state.individualTargets === true) && (state.targetsLoaded === false)) ||
(state.draggablesLoaded === false)
) {
window.setTimeout(function () {
repositionDraggables(answer);
}, 50);
return;
}
if ( if (
((typeof answer.use_targets === 'boolean') && (answer.use_targets === true)) || ((typeof answer.use_targets === 'boolean') && (answer.use_targets === true)) ||
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment