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 0b64a7fd65..cd74267690 100644 --- a/common/static/js/capa/drag_and_drop/base_image.js +++ b/common/static/js/capa/drag_and_drop/base_image.js @@ -26,7 +26,7 @@ define(['logme'], function (logme) { state.baseImageEl.attr( 'src', - state.config.base_image + state.config.baseImage ); state.baseImageEl.load(function () { baseImageElContainer.css('width', this.width); @@ -43,11 +43,11 @@ define(['logme'], function (logme) { }); state.baseImageEl.error(function () { logme( - 'ERROR: Image "' + state.config.base_image + '" was not found!' + 'ERROR: Image "' + state.config.baseImage + '" was not found!' ); baseImageElContainer.html( '' + - 'ERROR: Image "' + state.config.base_image + '" was not found!' + + 'ERROR: Image "' + state.config.baseImage + '" was not found!' + '' ); 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 b87f618bf4..b154a81826 100644 --- a/common/static/js/capa/drag_and_drop/config_parser.js +++ b/common/static/js/capa/drag_and_drop/config_parser.js @@ -8,154 +8,218 @@ define(['logme'], function (logme) { return configParser; function configParser(config, state) { - var returnStatus; - - returnStatus = true; - state.config = { 'draggables': [], + 'baseImage': '', 'targets': [], - 'base_image': '' + 'onePerTarget': null, + 'targetOutline': true, + 'labelBgColor': '#d6d6d6', + + 'individualTargets': null, + + 'errors': 0 // Number of errors found while parsing config. }; - if ($.isArray(config.draggables) === true) { + getDraggables(state, config); + getBaseImage(state, config); + getTargets(state, config); + getOnePerTarget(state, config); + getTargetOutline(state, config); + getLabelBgColor(state, config); + + setIndividualTargets(state); + + if (state.config.errors !== 0) { + return false; + } + + return true; + } + + function getDraggables(state, config) { + if (config.hasOwnProperty('draggables') === false) { + logme('ERROR: "config" does not have a property "draggables".'); + state.config.errors += 1; + } else if ($.isArray(config.draggables) === true) { (function (i) { while (i < config.draggables.length) { - if (processDraggable(config.draggables[i]) !== true) { - returnStatus = false; + if (processDraggable(state, config.draggables[i]) !== true) { + state.config.errors += 1; } i += 1; } }(0)); } else if ($.isPlainObject(config.draggables) === true) { - if (processDraggable(config.draggables) !== true) { - returnStatus = false; + if (processDraggable(state, config.draggables) !== true) { + state.config.errors += 1; } } else { logme('ERROR: The type of config.draggables is no supported.'); - returnStatus = false; + state.config.errors += 1; } + } - if (typeof config.base_image === 'string') { - state.config.base_image = config.base_image; + function getBaseImage(state, config) { + if (config.hasOwnProperty('base_image') === false) { + logme('ERROR: "config" does not have a property "base_image".'); + state.config.errors += 1; + } else if (typeof config.base_image === 'string') { + state.config.baseImage = config.base_image; } else { logme('ERROR: Property config.base_image is not of type "string".'); - returnStatus = false; + state.config.errors += 1; } + } - if ($.isArray(config.targets) === true) { + function getTargets(state, config) { + if (config.hasOwnProperty('targets') === false) { + // It is possible that no "targets" were specified. This is not an error. + // In this case the default value of "[]" (empty array) will be used. + // Draggables can be positioned anywhere on the image, and the server will + // get an answer in the form of (x, y) coordinates for each draggable. + } else if ($.isArray(config.targets) === true) { (function (i) { while (i < config.targets.length) { - if (processTarget(config.targets[i]) !== true) { - returnStatus = false; + if (processTarget(state, config.targets[i]) !== true) { + state.config.errors += 1; } i += 1; } }(0)); } else if ($.isPlainObject(config.targets) === true) { - if (processTarget(config.targets) !== true) { - returnStatus = false; + if (processTarget(state, config.targets) !== true) { + state.config.errors += 1; } - } else if (typeof config.targets !== 'undefined') { + } else { logme('ERROR: Property config.targets is not of a supported type.'); - returnStatus = false; + state.config.errors += 1; } + } - if (typeof config.one_per_target === 'string') { + function getOnePerTarget(state, config) { + if (config.hasOwnProperty('one_per_target') === false) { + logme('ERROR: "config" does not have a property "one_per_target".'); + state.config.errors += 1; + } else if (typeof config.one_per_target === 'string') { if (config.one_per_target.toLowerCase() === 'true') { - state.config.one_per_target = true; + state.config.onePerTarget = true; } else if (config.one_per_target.toLowerCase() === 'false') { - state.config.one_per_target = false; + state.config.onePerTarget = false; } else { logme('ERROR: Property config.one_per_target can either be "true", or "false".'); - returnStatus = false; + state.config.errors += 1; } - } else if (typeof config.one_per_target !== 'undefined') { + } else { logme('ERROR: Property config.one_per_target is not of a supported type.'); - returnStatus = false; + state.config.errors += 1; } + } - if (typeof config.target_outline === 'string') { + function getTargetOutline(state, config) { + if (config.hasOwnProperty('target_outline') === false) { + // It is possible that no "target_outline" was specified. This is not an error. + // In this case the default value of 'true' (boolean) will be used. + } else if (typeof config.target_outline === 'string') { if (config.target_outline.toLowerCase() === 'true') { state.config.targetOutline = true; } else if (config.target_outline.toLowerCase() === 'false') { state.config.targetOutline = false; } else { logme('ERROR: Property config.target_outline can either be "true", or "false".'); - returnStatus = false; + state.config.errors += 1; } - } else if (typeof config.target_outline !== 'undefined') { + } else { logme('ERROR: Property config.target_outline is not of a supported type.'); - returnStatus = false; + state.config.errors += 1; } + } - state.config.labelBgColor = '#d6d6d6'; - if (typeof config.label_bg_color === 'string') { + function getLabelBgColor(state, config) { + if (config.hasOwnProperty('label_bg_color') === false) { + // It is possible that no "label_bg_color" was specified. This is not an error. + // In this case the default value of '#d6d6d6' (string) will be used. + } else if (typeof config.label_bg_color === 'string') { state.config.labelBgColor = config.label_bg_color; - } else if (typeof config.label_bg_color !== 'undefined') { + } else { logme('ERROR: Property config.label_bg_color is not of a supported type.'); returnStatus = false; } + } + function setIndividualTargets(state) { if (state.config.targets.length === 0) { - state.individualTargets = false; + state.config.individualTargets = false; } else { - state.individualTargets = true; + state.config.individualTargets = true; + } + } + + function processDraggable(state, obj) { + if (!attrIsString(obj, 'id')) { + return false; + } + + if (!attrIsString(obj, 'icon')) { + return false; + } + if (!attrIsString(obj, 'label')) { + return false; + } + + state.config.draggables.push(obj); + + return true; + } + + function processTarget(state, obj) { + if (!attrIsString(obj, 'id')) { + return false; + } + + if (!attrIsInteger(obj, 'w')) { + return false; + } + if (!attrIsInteger(obj, 'h')) { + return false; + } + + if (!attrIsInteger(obj, 'x')) { + return false; + } + if (!attrIsInteger(obj, 'y')) { + return false; + } + + state.config.targets.push(obj); + + return true; + } + + function attrIsString(obj, attr) { + if (typeof obj[attr] !== 'string') { + logme('ERROR: Attribute "obj.' + attr + '" is not a string.'); + + return false; } return true; + } - function processDraggable(obj) { - if (!attrIsString(obj, 'id')) { return false; } + function attrIsInteger(obj, attr) { + var tempInt; - if (!attrIsString(obj, 'icon')) { return false; } - if (!attrIsString(obj, 'label')) { return false; } + tempInt = parseInt(obj[attr], 10); - state.config.draggables.push(obj); + if (isFinite(tempInt) === false) { + logme('ERROR: Attribute "obj.' + attr + '" is not an integer.'); - true; + return false; } - function processTarget(obj) { - if (!attrIsString(obj, 'id')) { return false; } + obj[attr] = tempInt; - if (!attrIsInteger(obj, 'w')) { return false; } - if (!attrIsInteger(obj, 'h')) { return false; } - - if (!attrIsInteger(obj, 'x')) { return false; } - if (!attrIsInteger(obj, 'y')) { return false; } - - state.config.targets.push(obj); - - true; - - } - - function attrIsString(obj, attr) { - if (typeof obj[attr] !== 'string') { - logme('ERROR: Attribute "obj.' + attr + '" is not a string.'); - - return false; - } - - return true; - } - - function attrIsInteger(obj, attr) { - var tempInt; - - tempInt = parseInt(obj[attr], 10); - - if (isFinite(tempInt) === false) { - logme('ERROR: Attribute "obj.' + attr + '" is not an integer.'); - - return false; - } - - obj[attr] = tempInt; - - return true; - } + return true; } }); diff --git a/common/static/js/capa/drag_and_drop/draggables.js b/common/static/js/capa/drag_and_drop/draggables.js index 5065e7193b..4c4585d121 100644 --- a/common/static/js/capa/drag_and_drop/draggables.js +++ b/common/static/js/capa/drag_and_drop/draggables.js @@ -10,8 +10,6 @@ define(['logme', 'update_input'], function (logme, updateInput) { }; function init(state) { - logme('Draggables.init; state = ', state); - state.draggables = []; state.numDraggablesInSlider = 0; state.currentMovingDraggable = null; @@ -65,13 +63,137 @@ define(['logme', 'update_input'], function (logme, updateInput) { } } + function moveDraggableToXY(newPosition) { + var self, offset; + + if (this.hasLoaded === false) { + self = this; + + setTimeout(function () { + self.moveDraggableToXY(newPosition); + }, 50); + + return; + } + + offset = 0; + if (this.state.config.targetOutline === true) { + offset = 1; + } + + this.inContainer = false; + 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', + newPosition.x - this.iconWidth * 0.5 + offset - this.iconElLeftOffset + ); + this.iconEl.css( + 'top', + newPosition.y - this.iconHeight * 0.5 + offset + ); + this.iconEl.appendTo(this.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', + newPosition.x - this.labelWidth * 0.5 + offset - 9 // Account for padding, border. + ); + this.labelEl.css( + 'top', + newPosition.y - this.iconHeight * 0.5 + this.iconHeight + 5 + offset + ); + this.labelEl.appendTo(this.state.baseImageEl.parent()); + } + + this.x = newPosition.x; + this.y = newPosition.y; + + this.state.numDraggablesInSlider -= 1; + this.state.updateArrowOpacity(); + } + + function moveDraggableToTarget(target) { + var self, offset; + + if (this.hasLoaded === false) { + self = this; + + setTimeout(function () { + self.moveDraggableToTarget(target); + }, 50); + + return; + } + + offset = 0; + if (this.state.config.targetOutline === true) { + offset = 1; + } + + this.inContainer = false; + 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', + 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 + ); + this.iconEl.appendTo(this.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', + target.offset.left + 0.5 * target.w - this.labelWidth * 0.5 + offset - 9 // Account for padding, border. + ); + this.labelEl.css( + 'top', + target.offset.top + 0.5 * target.h + this.iconHeight * 0.5 + 5 + offset + ); + this.labelEl.appendTo(this.state.baseImageEl.parent()); + } + + this.onTarget = target; + target.draggable.push(this.id); + + if (target.numTextEl !== null) { + target.updateNumTextEl(); + } + + this.state.numDraggablesInSlider -= 1; + this.state.updateArrowOpacity(); + } + function processDraggable(state, obj, objIndex) { var draggableObj; - logme('processDraggable; state = ', state); - - logme('Processing draggable #' + objIndex); - draggableObj = { 'zIndex': objIndex, 'oldZIndex': objIndex, @@ -91,7 +213,10 @@ define(['logme', 'update_input'], function (logme, updateInput) { 'checkIfOnTarget': checkIfOnTarget, 'snapToTarget': snapToTarget, 'correctZIndexes': correctZIndexes, - 'moveBackToSlider': moveBackToSlider + 'moveBackToSlider': moveBackToSlider, + + 'moveDraggableToTarget': moveDraggableToTarget, + 'moveDraggableToXY': moveDraggableToXY }; draggableObj.containerEl = $( @@ -334,7 +459,7 @@ define(['logme', 'update_input'], function (logme, updateInput) { this.iconHeight * 0.5 ); this.iconEl.appendTo( - state.baseImageEl.parent() + this.state.baseImageEl.parent() ); if (this.labelEl !== null) { @@ -447,7 +572,7 @@ define(['logme', 'update_input'], function (logme, updateInput) { this.mousePressed = false; positionIE = this.iconEl.position(); - if (this.state.individualTargets === true) { + if (this.state.config.individualTargets === true) { if (this.checkIfOnTarget(positionIE) === true) { this.correctZIndexes(); } else { @@ -486,7 +611,7 @@ define(['logme', 'update_input'], function (logme, updateInput) { } this.state.updateArrowOpacity(); - updateInput(this.state); + updateInput.update(this.state); } function removeObjIdFromTarget() { @@ -532,7 +657,7 @@ define(['logme', 'update_input'], function (logme, updateInput) { // (with an ID different from the one we are checking // against), then go to next target. if ( - (this.state.config.one_per_target === true) && + (this.state.config.onePerTarget === true) && (target.draggable.length === 1) && (target.draggable[0] !== this.id) ) { @@ -652,7 +777,7 @@ define(['logme', 'update_input'], function (logme, updateInput) { function correctZIndexes() { var draggablesInMe, c1, c2, highestZIndex; - if (this.state.individualTargets === true) { + if (this.state.config.individualTargets === true) { if (this.onTarget.draggable.length > 0) { draggablesInMe = []; diff --git a/common/static/js/capa/drag_and_drop/main.js b/common/static/js/capa/drag_and_drop/main.js index 6b1164b6df..900ea34e15 100644 --- a/common/static/js/capa/drag_and_drop/main.js +++ b/common/static/js/capa/drag_and_drop/main.js @@ -65,11 +65,11 @@ define( Scroller(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. - updateInput(state, true); + if (updateInput.check(state) === false) { + updateInput.update(state); + } }()); } }); diff --git a/common/static/js/capa/drag_and_drop/targets.js b/common/static/js/capa/drag_and_drop/targets.js index af27c186f2..dbec5e199d 100644 --- a/common/static/js/capa/drag_and_drop/targets.js +++ b/common/static/js/capa/drag_and_drop/targets.js @@ -45,7 +45,7 @@ define(['logme'], function (logme) { event.preventDefault(); }); - if (state.config.one_per_target === false) { + if (state.config.onePerTarget === false) { numTextEl = $( '