diff --git a/question/type/ddwtos/amd/build/ddwtos.min.js b/question/type/ddwtos/amd/build/ddwtos.min.js new file mode 100644 index 00000000000..388b76148ec --- /dev/null +++ b/question/type/ddwtos/amd/build/ddwtos.min.js @@ -0,0 +1 @@ +define(["jquery","core/dragdrop","core/key_codes"],function(a,b,c){"use strict";function d(a,b){this.containerId=a,b&&this.getRoot().addClass("qtype_ddwtos-readonly"),this.resizeAllDragsAndDrops(),this.cloneDrags(),this.positionDrags()}d.prototype.resizeAllDragsAndDrops=function(){var a=this;this.getRoot().find(".answercontainer > div").each(function(b){a.resizeAllDragsAndDropsInGroup(b+1)})},d.prototype.resizeAllDragsAndDropsInGroup=function(a){var b=this,c=this.getRoot().find(".draggrouphomes"+a+" span.draghome"),d=0,e=0;c.each(function(a,b){d=Math.max(d,Math.ceil(b.offsetWidth)),e=Math.max(e,Math.ceil(0+b.offsetHeight))}),d+=8,e+=2,c.each(function(a,c){b.setElementSize(c,d,e)}),this.getRoot().find("span.drop.group"+a).each(function(a,c){b.setElementSize(c,d,e)})},d.prototype.setElementSize=function(b,c,d){a(b).width(c).height(d).css("lineHeight",d+"px")},d.prototype.cloneDrags=function(){var b=this;this.getRoot().find("span.draghome").each(function(c,d){b.cloneDragsForOneChoice(a(d))})},d.prototype.cloneDragsForOneChoice=function(a){if(a.hasClass("infinite"))for(var b=this.noOfDropsInGroup(this.getGroup(a)),c=0;c1;)c--,d=this.getUnplacedChoice(a,c);return d},d.prototype.animateTo=function(a,b){var c=a.offset(),d=b.offset();a.addClass("beingdragged"),a.animate({left:parseInt(a.css("left"))+d.left-c.left,top:parseInt(a.css("top"))+d.top-c.top},{duration:"fast",done:function(){a.removeClass("beingdragged"),a.offset(d)}})},d.prototype.isPointInDrop=function(a,b,c){var d=c.offset();return a>=d.left&&a=d.top&&b. + +/** + * JavaScript to make drag-drop into text questions work. + * + * Some vocabulary to help understand this code: + * + * The question text contains 'drops' - blanks into which the 'drags', the missing + * words, can be put. + * + * The thing that can be moved into the drops are called 'drags'. There may be + * multiple copies of the 'same' drag which does not really cause problems. + * Each drag has a 'choice' number which is the value set on the drop's hidden + * input when this drag is placed in a drop. + * + * These may be in separate 'groups', distinguished by colour. + * Things can only interact with other things in the same group. + * The groups are numbered from 1. + * + * The place where a given drag started from is called its 'home'. + * + * @module qtype_ddwtos/ddwtos + * @package qtype_ddwtos + * @copyright 2018 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 3.6 + */ +define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys) { + + "use strict"; + + /** + * Object to handle one drag-drop into text question. + * + * @param {String} containerId id of the outer div for this question. + * @param {boolean} readOnly whether the question is being displayed read-only. + * @constructor + */ + function DragDropToTextQuestion(containerId, readOnly) { + this.containerId = containerId; + if (readOnly) { + this.getRoot().addClass('qtype_ddwtos-readonly'); + } + this.resizeAllDragsAndDrops(); + this.cloneDrags(); + this.positionDrags(); + } + + /** + * In each group, resize all the items to be the same size. + */ + DragDropToTextQuestion.prototype.resizeAllDragsAndDrops = function() { + var thisQ = this; + this.getRoot().find('.answercontainer > div').each(function(i) { + thisQ.resizeAllDragsAndDropsInGroup(i + 1); + }); + }; + + /** + * In a given group, set all the drags and drops to be the same size. + * + * @param {int} group the group number. + */ + DragDropToTextQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) { + var thisQ = this, + dragHomes = this.getRoot().find('.draggrouphomes' + group + ' span.draghome'), + maxWidth = 0, + maxHeight = 0; + + // Find the maximum size of any drag in this groups. + dragHomes.each(function(i, drag) { + maxWidth = Math.max(maxWidth, Math.ceil(drag.offsetWidth)); + maxHeight = Math.max(maxHeight, Math.ceil(0 + drag.offsetHeight)); + }); + + // The size we will want to set is a bit bigger than this. + maxWidth += 8; + maxHeight += 2; + + // Set each drag home to that size. + dragHomes.each(function(i, drag) { + thisQ.setElementSize(drag, maxWidth, maxHeight); + }); + + // Set each drop to that size. + this.getRoot().find('span.drop.group' + group).each(function(i, drop) { + thisQ.setElementSize(drop, maxWidth, maxHeight); + }); + }; + + /** + * Set a given DOM element to be a particular size. + * + * @param {HTMLElement} element + * @param {int} width + * @param {int} height + */ + DragDropToTextQuestion.prototype.setElementSize = function(element, width, height) { + $(element).width(width).height(height).css('lineHeight', height + 'px'); + }; + + /** + * Invisible 'drag homes' are output by the renderer. These have the same properties + * as the drag items but are invisible. We clone these invisible elements to make the + * actual drag items. + */ + DragDropToTextQuestion.prototype.cloneDrags = function() { + var thisQ = this; + this.getRoot().find('span.draghome').each(function(index, draghome) { + thisQ.cloneDragsForOneChoice($(draghome)); + }); + }; + + /** + * Clone drag item for one choice. + * + * @param {jQuery} dragHome the drag home to clone. + */ + DragDropToTextQuestion.prototype.cloneDragsForOneChoice = function(dragHome) { + if (dragHome.hasClass('infinite')) { + var noOfDrags = this.noOfDropsInGroup(this.getGroup(dragHome)); + for (var i = 0; i < noOfDrags; i++) { + this.cloneDrag(dragHome); + } + } else { + this.cloneDrag(dragHome); + } + }; + + /** + * Clone drag item. + * + * @param {jQuery} dragHome + */ + DragDropToTextQuestion.prototype.cloneDrag = function(dragHome) { + var drag = dragHome.clone(); + drag.removeClass('draghome') + .addClass('drag unplaced moodle-has-zindex') + .offset(dragHome.offset()); + this.getRoot().find('div.drags').append(drag); + }; + + /** + * Update the position of drags. + */ + DragDropToTextQuestion.prototype.positionDrags = function() { + var thisQ = this, + root = this.getRoot(); + + // First move all items back home. + root.find('span.drag').each(function(i, dragNode) { + var drag = $(dragNode), + currentPlace = thisQ.getClassnameNumericSuffix(drag, 'inplace'); + drag.addClass('unplaced') + .removeClass('placed') + .offset(thisQ.getDragHome(thisQ.getGroup(drag), thisQ.getChoice(drag)).offset()); + if (currentPlace !== null) { + drag.removeClass('inplace' + currentPlace); + } + }); + + // Then place the once that should be placed. + root.find('input.placeinput').each(function(i, inputNode) { + var input = $(inputNode), + choice = input.val(); + if (choice === '0') { + // No item in this place. + return; + } + + var place = thisQ.getPlace(input); + thisQ.getUnplacedChoice(thisQ.getGroup(input), choice) + .removeClass('unplaced') + .addClass('placed inplace' + place) + .offset(root.find('.drop.place' + place).offset()); + }); + }; + + /** + * Handles the start of dragging an item. + * + * @param {Event} e the touch start or mouse down event. + */ + DragDropToTextQuestion.prototype.handleDragStart = function(e) { + var thisQ = this, + drag = $(e.target).closest('.drag'); + + var info = dragDrop.prepare(e); + if (!info.start) { + return; + } + + var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace'); + if (currentPlace !== null) { + this.setInputValue(currentPlace, 0); + drag.removeClass('inplace' + currentPlace); + } + + drag.addClass('beingdragged'); + dragDrop.start(e, drag, function(x, y, drag) { + thisQ.dragMove(x, y, drag); + }, function(x, y, drag) { + thisQ.dragEnd(x, y, drag); + }); + }; + + /** + * Called whenever the currently dragged items moves. + * + * @param {Number} pageX the x position. + * @param {Number} pageY the y position. + * @param {jQuery} drag the item being moved. + */ + DragDropToTextQuestion.prototype.dragMove = function(pageX, pageY, drag) { + var thisQ = this; + this.getRoot().find('span.drop.group' + this.getGroup(drag)).each(function(i, dropNode) { + var drop = $(dropNode); + if (thisQ.isPointInDrop(pageX, pageY, drop)) { + drop.addClass('valid-drag-over-drop'); + } else { + drop.removeClass('valid-drag-over-drop'); + } + }); + }; + + /** + * Called when user drops a drag item. + * + * @param {Number} pageX the x position. + * @param {Number} pageY the y position. + * @param {jQuery} drag the item being moved. + */ + DragDropToTextQuestion.prototype.dragEnd = function(pageX, pageY, drag) { + var thisQ = this, + root = this.getRoot(), + placed = false; + root.find('span.drop.group' + this.getGroup(drag)).each(function(i, dropNode) { + var drop = $(dropNode); + if (!thisQ.isPointInDrop(pageX, pageY, drop)) { + // Not this drop. + return true; + } + + // Now put this drag into the drop. + drop.removeClass('valid-drag-over-drop'); + thisQ.sendDragToDrop(drag, drop); + placed = true; + return false; // Stop the each() here. + }); + + if (!placed) { + this.sendDragHome(drag); + } + }; + + /** + * Animate a drag item into a given place (or back home). + * + * @param {jQuery|null} drag the item to place. If null, clear the place. + * @param {jQuery} drop the place to put it. + */ + DragDropToTextQuestion.prototype.sendDragToDrop = function(drag, drop) { + // Is there already a drag in this drop? if so, evict it. + var oldDrag = this.getCurrentDragInPlace(this.getPlace(drop)); + if (oldDrag.length !== 0) { + this.sendDragHome(oldDrag); + } + + if (drag.length === 0) { + this.setInputValue(this.getPlace(drop), 0); + } else { + this.setInputValue(this.getPlace(drop), this.getChoice(drag)); + drag.removeClass('unplaced') + .addClass('placed inplace' + this.getPlace(drop)); + this.animateTo(drag, drop); + } + }; + + /** + * Animate a drag back to its home. + * + * @param {jQuery} drag the item being moved. + */ + DragDropToTextQuestion.prototype.sendDragHome = function(drag) { + drag.removeClass('placed').addClass('unplaced'); + var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace'); + if (currentPlace !== null) { + drag.removeClass('inplace' + currentPlace); + } + + this.animateTo(drag, this.getDragHome(this.getGroup(drag), this.getChoice(drag))); + }; + + /** + * Handles keyboard events on drops. + * + * Drops are focusable. Once focused, right/down/space switches to the next choice, and + * left/up switches to the previous. Escape clear. + * + * @param {KeyboardEvent} e + */ + DragDropToTextQuestion.prototype.handleKeyPress = function(e) { + var drop = $(e.target).closest('.drop'), + currentDrag = this.getCurrentDragInPlace(this.getPlace(drop)), + nextDrag = $(); + + switch (e.keyCode) { + case keys.space: + case keys.arrowRight: + case keys.arrowDown: + nextDrag = this.getNextDrag(this.getGroup(drop), currentDrag); + break; + + case keys.arrowLeft: + case keys.arrowUp: + nextDrag = this.getPreviousDrag(this.getGroup(drop), currentDrag); + break; + + case keys.escape: + break; + + default: + return; // To avoid the preventDefault below. + } + + e.preventDefault(); + this.sendDragToDrop(nextDrag, drop); + }; + + /** + * Choose the next drag in a group. + * + * @param {int} group which group. + * @param {jQuery} drag current choice (empty jQuery if there isn't one). + * @return {jQuery} the next drag in that group, or null if there wasn't one. + */ + DragDropToTextQuestion.prototype.getNextDrag = function(group, drag) { + var choice, + numChoices = this.noOfChoicesInGroup(group); + + if (drag.length === 0) { + choice = 1; // Was empty, so we want to select the first choice. + } else { + choice = this.getChoice(drag) + 1; + } + + var next = this.getUnplacedChoice(group, choice); + while (next.length === 0 && choice < numChoices) { + choice++; + next = this.getUnplacedChoice(group, choice); + } + + return next; + }; + + /** + * Choose the previous drag in a group. + * + * @param {int} group which group. + * @param {jQuery} drag current choice (empty jQuery if there isn't one). + * @return {jQuery} the next drag in that group, or null if there wasn't one. + */ + DragDropToTextQuestion.prototype.getPreviousDrag = function(group, drag) { + var choice; + + if (drag.length === 0) { + choice = this.noOfChoicesInGroup(group); + } else { + choice = this.getChoice(drag) - 1; + } + + var previous = this.getUnplacedChoice(group, choice); + while (previous.length === 0 && choice > 1) { + choice--; + previous = this.getUnplacedChoice(group, choice); + } + + // Does this choice exist? + return previous; + }; + + /** + * Animate an object to the given destination. + * + * @param {jQuery} drag the element to be animated. + * @param {jQuery} target element marking the place to move it to. + */ + DragDropToTextQuestion.prototype.animateTo = function(drag, target) { + var currentPos = drag.offset(), + targetPos = target.offset(); + drag.addClass('beingdragged'); + + // Animate works in terms of CSS position, whereas locating an object + // on the page works best with jQuery offset() function. So, to get + // the right target position, we work out the required change in + // offset() and then add that to the current CSS position. + drag.animate( + { + left: parseInt(drag.css('left')) + targetPos.left - currentPos.left, + top: parseInt(drag.css('top')) + targetPos.top - currentPos.top + }, + { + duration: 'fast', + done: function() { + drag.removeClass('beingdragged'); + // It seems that the animation sometimes leaves the drag + // one pixel out of position. Put it in exactly the right place. + drag.offset(targetPos); + } + } + ); + }; + + /** + * Detect if a point is inside a given DOM node. + * + * @param {Number} pageX the x position. + * @param {Number} pageY the y position. + * @param {jQuery} drop the node to check (typically a drop). + * @return {boolean} whether the point is inside the node. + */ + DragDropToTextQuestion.prototype.isPointInDrop = function(pageX, pageY, drop) { + var position = drop.offset(); + return pageX >= position.left && pageX < position.left + drop.width() + && pageY >= position.top && pageY < position.top + drop.height(); + }; + + /** + * Set the value of the hidden input for a place, to record what is currently there. + * + * @param {int} place which place to set the input value for. + * @param {int} choice the value to set. + */ + DragDropToTextQuestion.prototype.setInputValue = function(place, choice) { + this.getRoot().find('input.placeinput.place' + place).val(choice); + }; + + /** + * Get the outer div for this question. + * + * @returns {jQuery} containing that div. + */ + DragDropToTextQuestion.prototype.getRoot = function() { + return $(document.getElementById(this.containerId)); + }; + + /** + * Get drag home for a given choice. + * + * @param {int} group the group. + * @param {int} choice the choice number. + * @returns {jQuery} containing that div. + */ + DragDropToTextQuestion.prototype.getDragHome = function(group, choice) { + return this.getRoot().find('.draghome.group' + group + '.choice' + choice); + }; + + /** + * Get an unplaced choice for a particular group. + * + * @param {int} group the group. + * @param {int} choice the choice number. + * @returns {jQuery} jQuery wrapping the unplaced choice. If there isn't one, the jQuery will be empty. + */ + DragDropToTextQuestion.prototype.getUnplacedChoice = function(group, choice) { + return this.getRoot().find('.drag.group' + group + '.choice' + choice + '.unplaced').slice(0, 1); + }; + + /** + * Get the drag that is currently in a given place. + * + * @param {int} place the place number. + * @return {jQuery} the current drag (or an empty jQuery if none). + */ + DragDropToTextQuestion.prototype.getCurrentDragInPlace = function(place) { + return this.getRoot().find('span.drag.inplace' + place); + }; + + /** + * Return the number of blanks in a given group. + * + * @param {int} group the group number. + * @returns {int} the number of drops. + */ + DragDropToTextQuestion.prototype.noOfDropsInGroup = function(group) { + return this.getRoot().find('.drop.group' + group).length; + }; + + /** + * Return the number of choices in a given group. + * + * @param {int} group the group number. + * @returns {int} the number of choices. + */ + DragDropToTextQuestion.prototype.noOfChoicesInGroup = function(group) { + return this.getRoot().find('.draghome.group' + group).length; + }; + + /** + * Return the number at the end of the CSS class name with the given prefix. + * + * @param {jQuery} node + * @param {String} prefix name prefix + * @returns {Number|null} the suffix if found, else null. + */ + DragDropToTextQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) { + var classes = node.attr('class'); + if (classes !== '') { + var classesArr = classes.split(' '); + for (var index = 0; index < classesArr.length; index++) { + var patt1 = new RegExp('^' + prefix + '([0-9])+$'); + if (patt1.test(classesArr[index])) { + var patt2 = new RegExp('([0-9])+$'); + var match = patt2.exec(classesArr[index]); + return Number(match[0]); + } + } + } + return null; + }; + + /** + * Get the choice number of a drag. + * + * @param {jQuery} drag the drag. + * @returns {Number} the choice number. + */ + DragDropToTextQuestion.prototype.getChoice = function(drag) { + return this.getClassnameNumericSuffix(drag, 'choice'); + }; + + /** + * Given a DOM node that is significant to this question + * (drag, drop, ...) get the group it belongs to. + * + * @param {jQuery} node a DOM node. + * @returns {Number} the group it belongs to. + */ + DragDropToTextQuestion.prototype.getGroup = function(node) { + return this.getClassnameNumericSuffix(node, 'group'); + }; + + /** + * Get the place number of a drop, or its corresponding hidden input. + * + * @param {jQuery} node the DOM node. + * @returns {Number} the place number. + */ + DragDropToTextQuestion.prototype.getPlace = function(node) { + return this.getClassnameNumericSuffix(node, 'place'); + }; + + /** + * Singleton that tracks all the DragDropToTextQuestions on this page, and deals + * with event dispatching. + * + * @type {Object} + */ + var questionManager = { + /** + * {boolean} used to ensure the event handlers are only initialised once per page. + */ + eventHandlersInitialised: false, + + /** + * {DragDropToTextQuestion[]} all the questions on this page, indexed by containerId (id on the .que div). + */ + questions: {}, + + /** + * Initialise questions. + * + * @param {String} containerId id of the outer div for this question. + * @param {boolean} readOnly whether the question is being displayed read-only. + */ + init: function(containerId, readOnly) { + questionManager.questions[containerId] = new DragDropToTextQuestion(containerId, readOnly); + if (!questionManager.eventHandlersInitialised) { + questionManager.setupEventHandlers(); + questionManager.eventHandlersInitialised = true; + } + }, + + /** + * Set up the event handlers that make this question type work. (Done once per page.) + */ + setupEventHandlers: function() { + $('body').on('mousedown touchstart', + '.que.ddwtos:not(.qtype_ddwtos-readonly) span.drag', + questionManager.handleDragStart) + .on('keydown', + '.que.ddwtos:not(.qtype_ddwtos-readonly) span.drop', + questionManager.handleKeyPress); + + $(window).on('resize', questionManager.handleWindowResize); + }, + + /** + * Handle mouse down / touch start on drags. + * @param {Event} e the DOM event. + */ + handleDragStart: function(e) { + e.preventDefault(); + var question = questionManager.getQuestionForEvent(e); + if (question) { + question.handleDragStart(e); + } + }, + + /** + * Handle key down / press on drops. + * @param {KeyboardEvent} e + */ + handleKeyPress: function(e) { + var question = questionManager.getQuestionForEvent(e); + if (question) { + question.handleKeyPress(e); + } + }, + + /** + * Handle when the window is resized. + */ + handleWindowResize: function() { + for (var containerId in questionManager.questions) { + if (questionManager.questions.hasOwnProperty(containerId)) { + questionManager.questions[containerId].positionDrags(); + } + } + }, + + /** + * Given an event, work out which question it affects. + * + * @param {Event} e the event. + * @returns {DragDropToTextQuestion|undefined} The question, or undefined. + */ + getQuestionForEvent: function(e) { + var containerId = $(e.currentTarget).closest('.que.ddwtos').attr('id'); + return questionManager.questions[containerId]; + } + }; + + /** + * @alias module:qtype_ddwtos/ddwtos + */ + return { + /** + * Initialise one drag-drop into text question. + * + * @param {String} containerId id of the outer div for this question. + * @param {boolean} readOnly whether the question is being displayed read-only. + */ + init: questionManager.init + }; +}); diff --git a/question/type/ddwtos/renderer.php b/question/type/ddwtos/renderer.php index 7425082df2a..5e53cbf9331 100644 --- a/question/type/ddwtos/renderer.php +++ b/question/type/ddwtos/renderer.php @@ -36,31 +36,14 @@ require_once($CFG->dirroot . '/question/type/gapselect/rendererbase.php'); */ class qtype_ddwtos_renderer extends qtype_elements_embedded_in_question_text_renderer { - protected function qtext_classname() { - return 'qtext ddwtos_questionid_for_javascript'; - } - public function formulation_and_controls(question_attempt $qa, question_display_options $options) { global $PAGE; $result = parent::formulation_and_controls($qa, $options); - $inputids = array(); - $question = $qa->get_question(); - foreach ($question->places as $placeno => $place) { - $inputids[$placeno] = $this->box_id($qa, $question->field($placeno)); - } - - $params = array( - 'inputids' => $inputids, - 'topnode' => 'div.que.ddwtos#q' . $qa->get_slot(), - 'readonly' => $options->readonly - ); - - $PAGE->requires->yui_module('moodle-qtype_ddwtos-dd', - 'M.qtype_ddwtos.init_question', array($params)); - + $PAGE->requires->js_call_amd('qtype_ddwtos/ddwtos', 'init', + ['q' . $qa->get_slot(), $options->readonly]); return $result; } @@ -76,17 +59,13 @@ class qtype_ddwtos_renderer extends qtype_elements_embedded_in_question_text_ren } $classes = array('answercontainer'); - if (!$options->readonly) { - $classes[] = 'notreadonly'; - } else { + if ($options->readonly) { $classes[] = 'readonly'; } $result .= html_writer::tag('div', $dragboxs, array('class' => implode(' ', $classes))); $classes = array('drags'); - if (!$options->readonly) { - $classes[] = 'notreadonly'; - } else { + if ($options->readonly) { $classes[] = 'readonly'; } $result .= html_writer::tag('div', '', array('class' => implode(' ', $classes))); @@ -155,8 +134,12 @@ class qtype_ddwtos_renderer extends qtype_elements_embedded_in_question_text_ren } /** - * Actually, this question type abuses this method to always ouptut the + * Actually, this question type abuses this method to always output the * hidden fields it needs. + * + * @param question_attempt $qa the question attempt. + * @param bool $reallyclear whether we are really clearing the responses, or just outputting them. + * @return string HTML to output. */ public function clear_wrong(question_attempt $qa, $reallyclear = true) { $question = $qa->get_question(); @@ -172,30 +155,37 @@ class qtype_ddwtos_renderer extends qtype_elements_embedded_in_question_text_ren foreach ($question->places as $place => $group) { $fieldname = $question->field($place); if (array_key_exists($fieldname, $response)) { - $value = $response[$fieldname]; + $value = (string) $response[$fieldname]; } else { $value = '0'; } if (array_key_exists($fieldname, $cleanresponse)) { - $cleanvalue = $cleanresponse[$fieldname]; + $cleanvalue = (string) $cleanresponse[$fieldname]; } else { $cleanvalue = '0'; } - if ($cleanvalue != $value) { + if ($cleanvalue === $value) { + // Normal case: just one hidden input, to store the + // current value and be the value submitted. $output .= html_writer::empty_tag('input', array( 'type' => 'hidden', 'id' => $this->box_id($qa, 'p' . $place), + 'class' => 'placeinput place' . $place . ' group' . $group, + 'name' => $qa->get_qt_field_name($fieldname), + 'value' => s($value))); + } else { + // The case, which only happens when the question is read-only, where + // we want to show the drag item in a given place (first hidden input), + // but when submitted, we want it to go to a different place (second input). + $output .= html_writer::empty_tag('input', array( + 'type' => 'hidden', + 'id' => $this->box_id($qa, 'p' . $place), + 'class' => 'placeinput place' . $place . ' group' . $group, 'value' => s($value))) . html_writer::empty_tag('input', array( 'type' => 'hidden', 'name' => $qa->get_qt_field_name($fieldname), 'value' => s($cleanvalue))); - } else { - $output .= html_writer::empty_tag('input', array( - 'type' => 'hidden', - 'id' => $this->box_id($qa, 'p' . $place), - 'name' => $qa->get_qt_field_name($fieldname), - 'value' => s($value))); } } return $output; diff --git a/question/type/ddwtos/styles.css b/question/type/ddwtos/styles.css index aa7b8ab3732..b0292889543 100644 --- a/question/type/ddwtos/styles.css +++ b/question/type/ddwtos/styles.css @@ -18,16 +18,15 @@ margin-bottom: 2px; } +.que.ddwtos .drags { + height: 0; +} + .que.ddwtos .draghome, .que.ddwtos .drag { display: inline-block; text-align: center; background: transparent; - border: 0; -} - -.que.ddwtos .draghome, -.que.ddwtos .drag.unplaced { border: 1px solid #000; } @@ -36,21 +35,8 @@ } .que.ddwtos .drag { + position: absolute; z-index: 2; -} - -.que.ddwtos .drag.yui3-dd-dragging { - z-index: 3; - box-shadow: 3px 3px 4px #000; -} - -.que.ddwtos .drop:focus, -.que.ddwtos .drop.yui3-dd-drop-over.yui3-dd-drop-active-valid { - border-color: #0a0; - box-shadow: 0 0 5px 5px rgba(255, 255, 150, 1); -} - -.que.ddwtos .notreadonly .drag { cursor: move; } @@ -58,6 +44,17 @@ cursor: default; } +.que.ddwtos .drag.beingdragged { + z-index: 3; + box-shadow: 3px 3px 4px #000; +} + +.que.ddwtos .drop:focus, +.que.ddwtos .drop.valid-drag-over-drop { + border-color: #0a0; + box-shadow: 0 0 5px 5px rgba(255, 255, 150, 1); +} + .que.ddwtos span.incorrect { background-color: #faa; } diff --git a/question/type/ddwtos/tests/behat/preview.feature b/question/type/ddwtos/tests/behat/preview.feature index 46652534a87..33740931e5b 100644 --- a/question/type/ddwtos/tests/behat/preview.feature +++ b/question/type/ddwtos/tests/behat/preview.feature @@ -30,7 +30,7 @@ Feature: Preview a drag-drop into text question And I switch to "questionpreview" window # Increase window size and wait 2 seconds to ensure elements are placed properly by js. # Keep window large else drag will scroll the window to find element. - And I change window size to "large" + And I change window size to "medium" And I wait "2" seconds And I drag "quick" to space "1" in the drag and drop into text question And I drag "fox" to space "2" in the drag and drop into text question diff --git a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-debug.js b/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-debug.js deleted file mode 100644 index eaad12d19a3..00000000000 --- a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-debug.js +++ /dev/null @@ -1,445 +0,0 @@ -YUI.add('moodle-qtype_ddwtos-dd', function (Y, NAME) { - -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * JavaScript code for the ddwtos question type. - * - * @package qtype - * @subpackage ddwtos - * @copyright 2011 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -var DDWTOSDDNAME = 'ddwtos_dd'; -var DDWTOS_DD = function() { - DDWTOS_DD.superclass.constructor.apply(this, arguments); -}; -/** - * This is the class for ddwtos question rendering. - * A DDWTOS_DD class is created for each question. - */ -Y.extend(DDWTOS_DD, Y.Base, { - selectors: null, - passiveSupported: false, - initializer: function() { - var pendingid = 'qtype_ddwtos-' + Math.random().toString(36).slice(2); // Random string. - M.util.js_pending(pendingid); - this.selectors = this.css_selectors(this.get('topnode')); - this.set_padding_sizes_all(); - this.clone_drag_items(); - this.initial_place_of_drag_items(); - this.make_drop_zones(); - if (!this.get('readonly')) { - Y.later(500, this, this.position_drag_items, [pendingid, true]); - } else { - Y.later(500, this, this.position_drag_items, [pendingid, 3]); - Y.one('window').on('resize', function() { - this.position_drag_items(pendingid); - }, this); - } - this.checkPassiveSupported(); - }, - /** - * put all our selectors in the same place so we can quickly find and change them later - * if the structure of the document changes. - */ - css_selectors: function(topnode) { - return { - top_node: function() { - return topnode; - }, - drag_container: function() { - return topnode + ' div.drags'; - }, - drags: function() { - return this.drag_container() + ' span.drag'; - }, - drag: function(no) { - return this.drags() + '.no' + no; - }, - drags_in_group: function(groupno) { - return this.drags() + '.group' + groupno; - }, - unplaced_drags_in_group: function(groupno) { - return this.drags_in_group(groupno) + '.unplaced'; - }, - drags_for_choice_in_group: function(choiceno, groupno) { - return this.drags_in_group(groupno) + '.choice' + choiceno; - }, - unplaced_drags_for_choice_in_group: function(choiceno, groupno) { - return this.unplaced_drags_in_group(groupno) + '.choice' + choiceno; - }, - drops: function() { - return topnode + ' span.drop'; - }, - drop_for_place: function(placeno) { - return this.drops() + '.place' + placeno; - }, - drops_in_group: function(groupno) { - return this.drops() + '.group' + groupno; - }, - drag_homes: function() { - return topnode + ' span.draghome'; - }, - drag_homes_group: function(groupno) { - return topnode + ' .draggrouphomes' + groupno + ' span.draghome'; - }, - drag_home: function(groupno, choiceno) { - return topnode + ' .draggrouphomes' + groupno + ' span.draghome.choice' + choiceno; - }, - drops_group: function(groupno) { - return topnode + ' span.drop.group' + groupno; - } - }; - }, - set_padding_sizes_all: function() { - for (var groupno = 1; groupno <= 8; groupno++) { - this.set_padding_size_for_group(groupno); - } - }, - set_padding_size_for_group: function(groupno) { - var groupitems = Y.all(this.selectors.drag_homes_group(groupno)); - if (groupitems.size() !== 0) { - var maxwidth = 0; - var maxheight = 0; - // find max height and width - groupitems.each(function(item) { - maxwidth = Math.max(maxwidth, Math.ceil(item.get('offsetWidth'))); - maxheight = Math.max(maxheight, Math.ceil(item.get('offsetHeight'))); - }, this); - maxwidth += 8; - maxheight += 2; - groupitems.each(function(item) { - this.pad_to_width_height(item, maxwidth, maxheight); - }, this); - Y.all(this.selectors.drops_group(groupno)).each(function(item) { - this.pad_to_width_height(item, maxwidth + 2, maxheight + 2); - }, this); - } - }, - pad_to_width_height: function(node, width, height) { - node.setStyle('width', width + 'px').setStyle('height', height + 'px') - .setStyle('lineHeight', height + 'px'); - }, - - /** - * Invisible 'drag homes' are output by the renderer. These have the same properties - * as the drag items but are invisible. We clone these invisible elements to make the - * actual drag items. - */ - clone_drag_items: function() { - Y.all(this.selectors.drag_homes()).each(this.clone_drag_items_for_one_choice, this); - }, - clone_drag_items_for_one_choice: function(draghome) { - if (draghome.hasClass('infinite')) { - var groupno = this.get_group(draghome); - var noofdrags = Y.all(this.selectors.drops_in_group(groupno)).size(); - for (var i = 0; i < noofdrags; i++) { - this.clone_drag_item(draghome); - } - } else { - this.clone_drag_item(draghome); - } - }, - nextdragitemno: 1, - clone_drag_item: function(draghome) { - var drag = draghome.cloneNode(true); - drag.removeClass('draghome'); - drag.addClass('drag'); - drag.addClass('no' + this.nextdragitemno); - this.nextdragitemno++; - drag.setStyles({'visibility': 'visible', 'position': 'absolute'}); - Y.one(this.selectors.drag_container()).appendChild(drag); - if (!this.get('readonly')) { - this.make_draggable(drag); - } - }, - get_classname_numeric_suffix: function(node, prefix) { - var classes = node.getAttribute('class'); - if (classes !== '') { - var classesarr = classes.split(' '); - for (var index = 0; index < classesarr.length; index++) { - var patt1 = new RegExp('^' + prefix + '([0-9])+$'); - if (patt1.test(classesarr[index])) { - var patt2 = new RegExp('([0-9])+$'); - var match = patt2.exec(classesarr[index]); - return Number(match[0]); - } - } - } - throw 'Prefix "' + prefix + '" not found in class names.'; - }, - get_choice: function(node) { - return this.get_classname_numeric_suffix(node, 'choice'); - }, - get_group: function(node) { - return this.get_classname_numeric_suffix(node, 'group'); - }, - get_place: function(node) { - return this.get_classname_numeric_suffix(node, 'place'); - }, - get_no: function(node) { - return this.get_classname_numeric_suffix(node, 'no'); - }, - placed: null, - initial_place_of_drag_items: function() { - Y.all(this.selectors.drags()).addClass('unplaced'); - this.placed = []; - for (var placeno in this.get('inputids')) { - var inputid = this.get('inputids')[placeno]; - var inputnode = Y.one('input#' + inputid); - var choiceno = Number(inputnode.get('value')); - if (choiceno !== 0) { - var drop = Y.one(this.selectors.drop_for_place(placeno)); - var groupno = this.get_group(drop); - var drag = - Y.one(this.selectors.unplaced_drags_for_choice_in_group(choiceno, groupno)); - this.place_drag_in_drop(drag, drop); - this.position_drag_item(drag); - } - } - }, - make_draggable: function(drag) { - new Y.DD.Drag({ - node: drag, - groups: [this.get_group(drag)], - dragMode: 'point' - }).plug(Y.Plugin.DDConstrained, {constrain2node: this.selectors.top_node()}); - - // Prevent scrolling whilst dragging on Adroid devices. - this.prevent_touchmove_from_scrolling(drag); - }, - - /** - * prevent_touchmove_from_scrolling allows users of touch screen devices to - * use drag and drop and normal scrolling at the same time. I.e. when - * touching and dragging a draggable item, the screen does not scroll, but - * you can scroll by touching other area of the screen apart from the - * draggable items. - */ - prevent_touchmove_from_scrolling: function(drag) { - var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; - var eventHandler = function(event) { - event.preventDefault(); - }; - var dragId = drag.get('id'); - var el = document.getElementById(dragId); - // Note do not dynamically add events within another event, as this causes issues on iOS11.3. - // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and - // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. - el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); - }, - - /** - * Some older browsers do not support passing an options object to addEventListener. - * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. - */ - checkPassiveSupported: function() { - try { - var options = Object.defineProperty({}, 'passive', { - get: function() { - this.passiveSupported = true; - }.bind(this) - }); - window.addEventListener('test', options, options); - window.removeEventListener('test', options, options); - } catch (err) { - this.passiveSupported = false; - } - }, - - make_drop_zones: function() { - Y.all(this.selectors.drops()).each(this.make_drop_zone, this); - }, - make_drop_zone: function(drop) { - var dropdd = new Y.DD.Drop({ - node: drop, - groups: [this.get_group(drop)]}); - dropdd.on('drop:hit', function(e) { - var drag = e.drag.get('node'); - var drop = e.drop.get('node'); - if (this.get_group(drop) === this.get_group(drag)) { - this.place_drag_in_drop(drag, drop); - } - }, this); - if (!this.get('readonly')) { - drop.on('dragchange', this.drop_zone_key_press, this); - } - }, - place_drag_in_drop: function(drag, drop) { - var placeno = this.get_place(drop); - var inputid = this.get('inputids')[placeno]; - var inputnode = Y.one('input#' + inputid); - if (drag !== null) { - inputnode.set('value', this.get_choice(drag)); - } else { - inputnode.set('value', '0'); - } - for (var alreadytheredragno in this.placed) { - if (this.placed[alreadytheredragno] === placeno) { - delete this.placed[alreadytheredragno]; - var alreadytheredrag = Y.one(this.selectors.drag(alreadytheredragno)); - if (alreadytheredrag && alreadytheredrag.dd) { - alreadytheredrag.dd.detach('drag:start'); - } - } - } - if (drag !== null) { - this.placed[this.get_no(drag)] = placeno; - if (drag.dd) { - drag.dd.once('drag:start', function(e, inputnode, drag) { - inputnode.set('value', 0); - delete this.placed[this.get_no(drag)]; - drag.addClass('unplaced'); - }, this, inputnode, drag); - } - } - }, - remove_drag_from_drop: function(drop) { - this.place_drag_in_drop(null, drop); - }, - - /** - * Postition, or reposition, all the drag items. - * @param pendingid (optional) if given, then mark the js task complete after the - * items are all positioned. - * @param dotimeout (optional) if true, continually re-position the items so - * they stay in place. Else, if an integer, reposition this many times before stopping. - */ - position_drag_items: function(pendingid, dotimeout) { - Y.all(this.selectors.drags()).each(this.position_drag_item, this); - M.util.js_complete(pendingid); - if (dotimeout === true || dotimeout > 0) { - if (dotimeout !== true) { - dotimeout -= 1; - } - Y.later(500, this, this.position_drag_items, [pendingid, dotimeout]); - } - }, - position_drag_item: function(drag) { - if (!drag.hasClass('yui3-dd-dragging')) { - if (!this.placed[this.get_no(drag)]) { - var groupno = this.get_group(drag); - var choiceno = this.get_choice(drag); - var home = Y.one(this.selectors.drag_home(groupno, choiceno)); - drag.setXY(home.getXY()); - drag.addClass('unplaced'); - } else { - var placeno = this.placed[this.get_no(drag)]; - var drop = Y.one(this.selectors.drop_for_place(placeno)); - drag.setXY([drop.getX() + 2, drop.getY() + 2]); - drag.removeClass('unplaced'); - } - } - }, - - drop_zone_key_press: function(e) { - switch (e.direction) { - case 'next' : - this.place_next_drag_in(e.target); - break; - case 'previous' : - this.place_previous_drag_in(e.target); - break; - case 'remove' : - this.remove_drag_from_drop(e.target); - break; - } - e.preventDefault(); - }, - place_next_drag_in: function(drop) { - this.choose_next_choice_for_drop(drop, 1); - }, - place_previous_drag_in: function(drop) { - this.choose_next_choice_for_drop(drop, -1); - }, - choose_next_choice_for_drop: function(drop, direction) { - var next; - var groupno = this.get_group(drop); - var current = this.current_choice_in_drop(drop); - var unplaceddragsingroup = Y.all(this.selectors.unplaced_drags_in_group(groupno)); - if (0 === current) { - if (direction === 1) { - next = 1; - } else { - var lastdrag = unplaceddragsingroup.pop(); - next = this.get_choice(lastdrag); - } - } else { - next = current + direction; - } - var drag; - do { - drag = Y.one(this.selectors.unplaced_drags_for_choice_in_group(next, groupno)); - if (Y.one(this.selectors.drags_for_choice_in_group(next, groupno)) === null) { - this.remove_drag_from_drop(drop); - return; - } - next = next + direction; - } while (drag === null); - this.place_drag_in_drop(drag, drop); - }, - current_choice_in_drop: function(drop) { - var inputid = this.get('inputids')[this.get_place(drop)]; - var inputnode = Y.one('input#' + inputid); - return Number(inputnode.get('value')); - } -}, { - NAME: DDWTOSDDNAME, - ATTRS: { - readonly: {value: false}, - topnode: {value: null}, - inputids: {value: null} - } -}); - -Y.Event.define('dragchange', { - // Webkit and IE repeat keydown when you hold down arrow keys. - // Opera links keypress to page scroll; others keydown. - // Firefox prevents page scroll via preventDefault() on either - // keydown or keypress. - _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress', - - _keys: { - '32': 'next', // Space - '37': 'previous', // Left arrow - '38': 'previous', // Up arrow - '39': 'next', // Right arrow - '40': 'next', // Down arrow - '27': 'remove' // Escape - }, - - _keyHandler: function(e, notifier) { - if (this._keys[e.keyCode]) { - e.direction = this._keys[e.keyCode]; - notifier.fire(e); - } - }, - - on: function(node, sub, notifier) { - sub._detacher = node.on(this._event, this._keyHandler, - this, notifier); - } -}); - -M.qtype_ddwtos = M.qtype_ddwtos || {}; -M.qtype_ddwtos.init_question = function(config) { - return new DDWTOS_DD(config); -}; - - -}, '@VERSION@', {"requires": ["node", "dd", "dd-drop", "dd-constrain"]}); diff --git a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-min.js b/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-min.js deleted file mode 100644 index ff5bf8c1fde..00000000000 --- a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-min.js +++ /dev/null @@ -1,2 +0,0 @@ -YUI.add("moodle-qtype_ddwtos-dd",function(e,t){var n="ddwtos_dd",r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,e.Base,{selectors:null,passiveSupported:!1,initializer:function(){var t="qtype_ddwtos-"+Math.random().toString(36).slice(2);M.util.js_pending(t),this.selectors=this.css_selectors(this.get("topnode")),this.set_padding_sizes_all(),this.clone_drag_items(),this.initial_place_of_drag_items(),this.make_drop_zones(),this.get("readonly")?(e.later(500,this,this.position_drag_items,[t,3]),e.one("window").on("resize",function(){this.position_drag_items(t)},this)):e.later(500,this,this.position_drag_items,[t,!0]),this.checkPassiveSupported()},css_selectors:function(e){return{top_node:function(){return e},drag_container:function(){return e+" div.drags"},drags:function(){return this.drag_container()+" span.drag"},drag:function(e){return this.drags()+".no"+e},drags_in_group:function(e){return this.drags()+".group"+e},unplaced_drags_in_group:function(e){return this.drags_in_group(e)+".unplaced"},drags_for_choice_in_group:function(e,t){return this.drags_in_group(t)+".choice"+e},unplaced_drags_for_choice_in_group:function(e,t){return this.unplaced_drags_in_group(t)+".choice"+e},drops:function(){return e+" span.drop"},drop_for_place:function(e){return this.drops()+".place"+e},drops_in_group:function(e){return this.drops()+".group"+e},drag_homes:function(){return e+" span.draghome"},drag_homes_group:function(t){return e+" .draggrouphomes"+t+" span.draghome"},drag_home:function(t,n){return e+" .draggrouphomes"+t+" span.draghome.choice"+n},drops_group:function(t){return e+" span.drop.group"+t}}},set_padding_sizes_all:function(){for(var e=1;e<=8;e++)this.set_padding_size_for_group(e)},set_padding_size_for_group:function(t){var n=e.all(this.selectors.drag_homes_group(t));if(n.size()!==0){var r=0,i=0;n.each(function(e){r=Math.max(r,Math.ceil(e.get("offsetWidth"))),i=Math.max(i,Math.ceil(e.get("offsetHeight")))},this),r+=8,i+=2,n.each(function(e){this.pad_to_width_height(e,r,i)},this),e.all(this.selectors.drops_group(t)).each(function(e){this.pad_to_width_height(e,r+2,i+2)},this)}},pad_to_width_height:function(e,t,n){e.setStyle("width",t+"px").setStyle("height",n+"px").setStyle("lineHeight",n+"px")},clone_drag_items:function(){e.all(this.selectors.drag_homes()).each(this.clone_drag_items_for_one_choice,this)},clone_drag_items_for_one_choice:function(t){if(t.hasClass("infinite")){var n=this.get_group(t),r=e.all(this.selectors.drops_in_group(n)).size();for(var i=0;i0)n!==!0&&(n-=1),e.later(500,this,this.position_drag_items,[t,n])},position_drag_item:function(t){if(!t.hasClass("yui3-dd-dragging"))if(!this.placed[this.get_no(t)]){var n=this.get_group(t),r=this.get_choice(t),i=e.one(this.selectors.drag_home(n,r));t.setXY(i.getXY()),t.addClass("unplaced")}else{var s=this -.placed[this.get_no(t)],o=e.one(this.selectors.drop_for_place(s));t.setXY([o.getX()+2,o.getY()+2]),t.removeClass("unplaced")}},drop_zone_key_press:function(e){switch(e.direction){case"next":this.place_next_drag_in(e.target);break;case"previous":this.place_previous_drag_in(e.target);break;case"remove":this.remove_drag_from_drop(e.target)}e.preventDefault()},place_next_drag_in:function(e){this.choose_next_choice_for_drop(e,1)},place_previous_drag_in:function(e){this.choose_next_choice_for_drop(e,-1)},choose_next_choice_for_drop:function(t,n){var r,i=this.get_group(t),s=this.current_choice_in_drop(t),o=e.all(this.selectors.unplaced_drags_in_group(i));if(0===s)if(n===1)r=1;else{var u=o.pop();r=this.get_choice(u)}else r=s+n;var a;do{a=e.one(this.selectors.unplaced_drags_for_choice_in_group(r,i));if(e.one(this.selectors.drags_for_choice_in_group(r,i))===null){this.remove_drag_from_drop(t);return}r+=n}while(a===null);this.place_drag_in_drop(a,t)},current_choice_in_drop:function(t){var n=this.get("inputids")[this.get_place(t)],r=e.one("input#"+n);return Number(r.get("value"))}},{NAME:n,ATTRS:{readonly:{value:!1},topnode:{value:null},inputids:{value:null}}}),e.Event.define("dragchange",{_event:e.UA.webkit||e.UA.ie?"keydown":"keypress",_keys:{32:"next",37:"previous",38:"previous",39:"next",40:"next",27:"remove"},_keyHandler:function(e,t){this._keys[e.keyCode]&&(e.direction=this._keys[e.keyCode],t.fire(e))},on:function(e,t,n){t._detacher=e.on(this._event,this._keyHandler,this,n)}}),M.qtype_ddwtos=M.qtype_ddwtos||{},M.qtype_ddwtos.init_question=function(e){return new r(e)}},"@VERSION@",{requires:["node","dd","dd-drop","dd-constrain"]}); diff --git a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js b/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js deleted file mode 100644 index eaad12d19a3..00000000000 --- a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js +++ /dev/null @@ -1,445 +0,0 @@ -YUI.add('moodle-qtype_ddwtos-dd', function (Y, NAME) { - -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * JavaScript code for the ddwtos question type. - * - * @package qtype - * @subpackage ddwtos - * @copyright 2011 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -var DDWTOSDDNAME = 'ddwtos_dd'; -var DDWTOS_DD = function() { - DDWTOS_DD.superclass.constructor.apply(this, arguments); -}; -/** - * This is the class for ddwtos question rendering. - * A DDWTOS_DD class is created for each question. - */ -Y.extend(DDWTOS_DD, Y.Base, { - selectors: null, - passiveSupported: false, - initializer: function() { - var pendingid = 'qtype_ddwtos-' + Math.random().toString(36).slice(2); // Random string. - M.util.js_pending(pendingid); - this.selectors = this.css_selectors(this.get('topnode')); - this.set_padding_sizes_all(); - this.clone_drag_items(); - this.initial_place_of_drag_items(); - this.make_drop_zones(); - if (!this.get('readonly')) { - Y.later(500, this, this.position_drag_items, [pendingid, true]); - } else { - Y.later(500, this, this.position_drag_items, [pendingid, 3]); - Y.one('window').on('resize', function() { - this.position_drag_items(pendingid); - }, this); - } - this.checkPassiveSupported(); - }, - /** - * put all our selectors in the same place so we can quickly find and change them later - * if the structure of the document changes. - */ - css_selectors: function(topnode) { - return { - top_node: function() { - return topnode; - }, - drag_container: function() { - return topnode + ' div.drags'; - }, - drags: function() { - return this.drag_container() + ' span.drag'; - }, - drag: function(no) { - return this.drags() + '.no' + no; - }, - drags_in_group: function(groupno) { - return this.drags() + '.group' + groupno; - }, - unplaced_drags_in_group: function(groupno) { - return this.drags_in_group(groupno) + '.unplaced'; - }, - drags_for_choice_in_group: function(choiceno, groupno) { - return this.drags_in_group(groupno) + '.choice' + choiceno; - }, - unplaced_drags_for_choice_in_group: function(choiceno, groupno) { - return this.unplaced_drags_in_group(groupno) + '.choice' + choiceno; - }, - drops: function() { - return topnode + ' span.drop'; - }, - drop_for_place: function(placeno) { - return this.drops() + '.place' + placeno; - }, - drops_in_group: function(groupno) { - return this.drops() + '.group' + groupno; - }, - drag_homes: function() { - return topnode + ' span.draghome'; - }, - drag_homes_group: function(groupno) { - return topnode + ' .draggrouphomes' + groupno + ' span.draghome'; - }, - drag_home: function(groupno, choiceno) { - return topnode + ' .draggrouphomes' + groupno + ' span.draghome.choice' + choiceno; - }, - drops_group: function(groupno) { - return topnode + ' span.drop.group' + groupno; - } - }; - }, - set_padding_sizes_all: function() { - for (var groupno = 1; groupno <= 8; groupno++) { - this.set_padding_size_for_group(groupno); - } - }, - set_padding_size_for_group: function(groupno) { - var groupitems = Y.all(this.selectors.drag_homes_group(groupno)); - if (groupitems.size() !== 0) { - var maxwidth = 0; - var maxheight = 0; - // find max height and width - groupitems.each(function(item) { - maxwidth = Math.max(maxwidth, Math.ceil(item.get('offsetWidth'))); - maxheight = Math.max(maxheight, Math.ceil(item.get('offsetHeight'))); - }, this); - maxwidth += 8; - maxheight += 2; - groupitems.each(function(item) { - this.pad_to_width_height(item, maxwidth, maxheight); - }, this); - Y.all(this.selectors.drops_group(groupno)).each(function(item) { - this.pad_to_width_height(item, maxwidth + 2, maxheight + 2); - }, this); - } - }, - pad_to_width_height: function(node, width, height) { - node.setStyle('width', width + 'px').setStyle('height', height + 'px') - .setStyle('lineHeight', height + 'px'); - }, - - /** - * Invisible 'drag homes' are output by the renderer. These have the same properties - * as the drag items but are invisible. We clone these invisible elements to make the - * actual drag items. - */ - clone_drag_items: function() { - Y.all(this.selectors.drag_homes()).each(this.clone_drag_items_for_one_choice, this); - }, - clone_drag_items_for_one_choice: function(draghome) { - if (draghome.hasClass('infinite')) { - var groupno = this.get_group(draghome); - var noofdrags = Y.all(this.selectors.drops_in_group(groupno)).size(); - for (var i = 0; i < noofdrags; i++) { - this.clone_drag_item(draghome); - } - } else { - this.clone_drag_item(draghome); - } - }, - nextdragitemno: 1, - clone_drag_item: function(draghome) { - var drag = draghome.cloneNode(true); - drag.removeClass('draghome'); - drag.addClass('drag'); - drag.addClass('no' + this.nextdragitemno); - this.nextdragitemno++; - drag.setStyles({'visibility': 'visible', 'position': 'absolute'}); - Y.one(this.selectors.drag_container()).appendChild(drag); - if (!this.get('readonly')) { - this.make_draggable(drag); - } - }, - get_classname_numeric_suffix: function(node, prefix) { - var classes = node.getAttribute('class'); - if (classes !== '') { - var classesarr = classes.split(' '); - for (var index = 0; index < classesarr.length; index++) { - var patt1 = new RegExp('^' + prefix + '([0-9])+$'); - if (patt1.test(classesarr[index])) { - var patt2 = new RegExp('([0-9])+$'); - var match = patt2.exec(classesarr[index]); - return Number(match[0]); - } - } - } - throw 'Prefix "' + prefix + '" not found in class names.'; - }, - get_choice: function(node) { - return this.get_classname_numeric_suffix(node, 'choice'); - }, - get_group: function(node) { - return this.get_classname_numeric_suffix(node, 'group'); - }, - get_place: function(node) { - return this.get_classname_numeric_suffix(node, 'place'); - }, - get_no: function(node) { - return this.get_classname_numeric_suffix(node, 'no'); - }, - placed: null, - initial_place_of_drag_items: function() { - Y.all(this.selectors.drags()).addClass('unplaced'); - this.placed = []; - for (var placeno in this.get('inputids')) { - var inputid = this.get('inputids')[placeno]; - var inputnode = Y.one('input#' + inputid); - var choiceno = Number(inputnode.get('value')); - if (choiceno !== 0) { - var drop = Y.one(this.selectors.drop_for_place(placeno)); - var groupno = this.get_group(drop); - var drag = - Y.one(this.selectors.unplaced_drags_for_choice_in_group(choiceno, groupno)); - this.place_drag_in_drop(drag, drop); - this.position_drag_item(drag); - } - } - }, - make_draggable: function(drag) { - new Y.DD.Drag({ - node: drag, - groups: [this.get_group(drag)], - dragMode: 'point' - }).plug(Y.Plugin.DDConstrained, {constrain2node: this.selectors.top_node()}); - - // Prevent scrolling whilst dragging on Adroid devices. - this.prevent_touchmove_from_scrolling(drag); - }, - - /** - * prevent_touchmove_from_scrolling allows users of touch screen devices to - * use drag and drop and normal scrolling at the same time. I.e. when - * touching and dragging a draggable item, the screen does not scroll, but - * you can scroll by touching other area of the screen apart from the - * draggable items. - */ - prevent_touchmove_from_scrolling: function(drag) { - var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; - var eventHandler = function(event) { - event.preventDefault(); - }; - var dragId = drag.get('id'); - var el = document.getElementById(dragId); - // Note do not dynamically add events within another event, as this causes issues on iOS11.3. - // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and - // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. - el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); - }, - - /** - * Some older browsers do not support passing an options object to addEventListener. - * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. - */ - checkPassiveSupported: function() { - try { - var options = Object.defineProperty({}, 'passive', { - get: function() { - this.passiveSupported = true; - }.bind(this) - }); - window.addEventListener('test', options, options); - window.removeEventListener('test', options, options); - } catch (err) { - this.passiveSupported = false; - } - }, - - make_drop_zones: function() { - Y.all(this.selectors.drops()).each(this.make_drop_zone, this); - }, - make_drop_zone: function(drop) { - var dropdd = new Y.DD.Drop({ - node: drop, - groups: [this.get_group(drop)]}); - dropdd.on('drop:hit', function(e) { - var drag = e.drag.get('node'); - var drop = e.drop.get('node'); - if (this.get_group(drop) === this.get_group(drag)) { - this.place_drag_in_drop(drag, drop); - } - }, this); - if (!this.get('readonly')) { - drop.on('dragchange', this.drop_zone_key_press, this); - } - }, - place_drag_in_drop: function(drag, drop) { - var placeno = this.get_place(drop); - var inputid = this.get('inputids')[placeno]; - var inputnode = Y.one('input#' + inputid); - if (drag !== null) { - inputnode.set('value', this.get_choice(drag)); - } else { - inputnode.set('value', '0'); - } - for (var alreadytheredragno in this.placed) { - if (this.placed[alreadytheredragno] === placeno) { - delete this.placed[alreadytheredragno]; - var alreadytheredrag = Y.one(this.selectors.drag(alreadytheredragno)); - if (alreadytheredrag && alreadytheredrag.dd) { - alreadytheredrag.dd.detach('drag:start'); - } - } - } - if (drag !== null) { - this.placed[this.get_no(drag)] = placeno; - if (drag.dd) { - drag.dd.once('drag:start', function(e, inputnode, drag) { - inputnode.set('value', 0); - delete this.placed[this.get_no(drag)]; - drag.addClass('unplaced'); - }, this, inputnode, drag); - } - } - }, - remove_drag_from_drop: function(drop) { - this.place_drag_in_drop(null, drop); - }, - - /** - * Postition, or reposition, all the drag items. - * @param pendingid (optional) if given, then mark the js task complete after the - * items are all positioned. - * @param dotimeout (optional) if true, continually re-position the items so - * they stay in place. Else, if an integer, reposition this many times before stopping. - */ - position_drag_items: function(pendingid, dotimeout) { - Y.all(this.selectors.drags()).each(this.position_drag_item, this); - M.util.js_complete(pendingid); - if (dotimeout === true || dotimeout > 0) { - if (dotimeout !== true) { - dotimeout -= 1; - } - Y.later(500, this, this.position_drag_items, [pendingid, dotimeout]); - } - }, - position_drag_item: function(drag) { - if (!drag.hasClass('yui3-dd-dragging')) { - if (!this.placed[this.get_no(drag)]) { - var groupno = this.get_group(drag); - var choiceno = this.get_choice(drag); - var home = Y.one(this.selectors.drag_home(groupno, choiceno)); - drag.setXY(home.getXY()); - drag.addClass('unplaced'); - } else { - var placeno = this.placed[this.get_no(drag)]; - var drop = Y.one(this.selectors.drop_for_place(placeno)); - drag.setXY([drop.getX() + 2, drop.getY() + 2]); - drag.removeClass('unplaced'); - } - } - }, - - drop_zone_key_press: function(e) { - switch (e.direction) { - case 'next' : - this.place_next_drag_in(e.target); - break; - case 'previous' : - this.place_previous_drag_in(e.target); - break; - case 'remove' : - this.remove_drag_from_drop(e.target); - break; - } - e.preventDefault(); - }, - place_next_drag_in: function(drop) { - this.choose_next_choice_for_drop(drop, 1); - }, - place_previous_drag_in: function(drop) { - this.choose_next_choice_for_drop(drop, -1); - }, - choose_next_choice_for_drop: function(drop, direction) { - var next; - var groupno = this.get_group(drop); - var current = this.current_choice_in_drop(drop); - var unplaceddragsingroup = Y.all(this.selectors.unplaced_drags_in_group(groupno)); - if (0 === current) { - if (direction === 1) { - next = 1; - } else { - var lastdrag = unplaceddragsingroup.pop(); - next = this.get_choice(lastdrag); - } - } else { - next = current + direction; - } - var drag; - do { - drag = Y.one(this.selectors.unplaced_drags_for_choice_in_group(next, groupno)); - if (Y.one(this.selectors.drags_for_choice_in_group(next, groupno)) === null) { - this.remove_drag_from_drop(drop); - return; - } - next = next + direction; - } while (drag === null); - this.place_drag_in_drop(drag, drop); - }, - current_choice_in_drop: function(drop) { - var inputid = this.get('inputids')[this.get_place(drop)]; - var inputnode = Y.one('input#' + inputid); - return Number(inputnode.get('value')); - } -}, { - NAME: DDWTOSDDNAME, - ATTRS: { - readonly: {value: false}, - topnode: {value: null}, - inputids: {value: null} - } -}); - -Y.Event.define('dragchange', { - // Webkit and IE repeat keydown when you hold down arrow keys. - // Opera links keypress to page scroll; others keydown. - // Firefox prevents page scroll via preventDefault() on either - // keydown or keypress. - _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress', - - _keys: { - '32': 'next', // Space - '37': 'previous', // Left arrow - '38': 'previous', // Up arrow - '39': 'next', // Right arrow - '40': 'next', // Down arrow - '27': 'remove' // Escape - }, - - _keyHandler: function(e, notifier) { - if (this._keys[e.keyCode]) { - e.direction = this._keys[e.keyCode]; - notifier.fire(e); - } - }, - - on: function(node, sub, notifier) { - sub._detacher = node.on(this._event, this._keyHandler, - this, notifier); - } -}); - -M.qtype_ddwtos = M.qtype_ddwtos || {}; -M.qtype_ddwtos.init_question = function(config) { - return new DDWTOS_DD(config); -}; - - -}, '@VERSION@', {"requires": ["node", "dd", "dd-drop", "dd-constrain"]}); diff --git a/question/type/ddwtos/yui/src/ddwtos/build.json b/question/type/ddwtos/yui/src/ddwtos/build.json deleted file mode 100644 index d67dd9a123b..00000000000 --- a/question/type/ddwtos/yui/src/ddwtos/build.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "moodle-qtype_ddwtos-dd", - "builds": { - "moodle-qtype_ddwtos-dd": { - "jsfiles": [ - "ddwtos.js" - ] - } - } -} diff --git a/question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js b/question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js deleted file mode 100644 index 79fd922e000..00000000000 --- a/question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js +++ /dev/null @@ -1,440 +0,0 @@ -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * JavaScript code for the ddwtos question type. - * - * @package qtype - * @subpackage ddwtos - * @copyright 2011 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -var DDWTOSDDNAME = 'ddwtos_dd'; -var DDWTOS_DD = function() { - DDWTOS_DD.superclass.constructor.apply(this, arguments); -}; -/** - * This is the class for ddwtos question rendering. - * A DDWTOS_DD class is created for each question. - */ -Y.extend(DDWTOS_DD, Y.Base, { - selectors: null, - passiveSupported: false, - initializer: function() { - var pendingid = 'qtype_ddwtos-' + Math.random().toString(36).slice(2); // Random string. - M.util.js_pending(pendingid); - this.selectors = this.css_selectors(this.get('topnode')); - this.set_padding_sizes_all(); - this.clone_drag_items(); - this.initial_place_of_drag_items(); - this.make_drop_zones(); - if (!this.get('readonly')) { - Y.later(500, this, this.position_drag_items, [pendingid, true]); - } else { - Y.later(500, this, this.position_drag_items, [pendingid, 3]); - Y.one('window').on('resize', function() { - this.position_drag_items(pendingid); - }, this); - } - this.checkPassiveSupported(); - }, - /** - * put all our selectors in the same place so we can quickly find and change them later - * if the structure of the document changes. - */ - css_selectors: function(topnode) { - return { - top_node: function() { - return topnode; - }, - drag_container: function() { - return topnode + ' div.drags'; - }, - drags: function() { - return this.drag_container() + ' span.drag'; - }, - drag: function(no) { - return this.drags() + '.no' + no; - }, - drags_in_group: function(groupno) { - return this.drags() + '.group' + groupno; - }, - unplaced_drags_in_group: function(groupno) { - return this.drags_in_group(groupno) + '.unplaced'; - }, - drags_for_choice_in_group: function(choiceno, groupno) { - return this.drags_in_group(groupno) + '.choice' + choiceno; - }, - unplaced_drags_for_choice_in_group: function(choiceno, groupno) { - return this.unplaced_drags_in_group(groupno) + '.choice' + choiceno; - }, - drops: function() { - return topnode + ' span.drop'; - }, - drop_for_place: function(placeno) { - return this.drops() + '.place' + placeno; - }, - drops_in_group: function(groupno) { - return this.drops() + '.group' + groupno; - }, - drag_homes: function() { - return topnode + ' span.draghome'; - }, - drag_homes_group: function(groupno) { - return topnode + ' .draggrouphomes' + groupno + ' span.draghome'; - }, - drag_home: function(groupno, choiceno) { - return topnode + ' .draggrouphomes' + groupno + ' span.draghome.choice' + choiceno; - }, - drops_group: function(groupno) { - return topnode + ' span.drop.group' + groupno; - } - }; - }, - set_padding_sizes_all: function() { - for (var groupno = 1; groupno <= 8; groupno++) { - this.set_padding_size_for_group(groupno); - } - }, - set_padding_size_for_group: function(groupno) { - var groupitems = Y.all(this.selectors.drag_homes_group(groupno)); - if (groupitems.size() !== 0) { - var maxwidth = 0; - var maxheight = 0; - // find max height and width - groupitems.each(function(item) { - maxwidth = Math.max(maxwidth, Math.ceil(item.get('offsetWidth'))); - maxheight = Math.max(maxheight, Math.ceil(item.get('offsetHeight'))); - }, this); - maxwidth += 8; - maxheight += 2; - groupitems.each(function(item) { - this.pad_to_width_height(item, maxwidth, maxheight); - }, this); - Y.all(this.selectors.drops_group(groupno)).each(function(item) { - this.pad_to_width_height(item, maxwidth + 2, maxheight + 2); - }, this); - } - }, - pad_to_width_height: function(node, width, height) { - node.setStyle('width', width + 'px').setStyle('height', height + 'px') - .setStyle('lineHeight', height + 'px'); - }, - - /** - * Invisible 'drag homes' are output by the renderer. These have the same properties - * as the drag items but are invisible. We clone these invisible elements to make the - * actual drag items. - */ - clone_drag_items: function() { - Y.all(this.selectors.drag_homes()).each(this.clone_drag_items_for_one_choice, this); - }, - clone_drag_items_for_one_choice: function(draghome) { - if (draghome.hasClass('infinite')) { - var groupno = this.get_group(draghome); - var noofdrags = Y.all(this.selectors.drops_in_group(groupno)).size(); - for (var i = 0; i < noofdrags; i++) { - this.clone_drag_item(draghome); - } - } else { - this.clone_drag_item(draghome); - } - }, - nextdragitemno: 1, - clone_drag_item: function(draghome) { - var drag = draghome.cloneNode(true); - drag.removeClass('draghome'); - drag.addClass('drag'); - drag.addClass('no' + this.nextdragitemno); - this.nextdragitemno++; - drag.setStyles({'visibility': 'visible', 'position': 'absolute'}); - Y.one(this.selectors.drag_container()).appendChild(drag); - if (!this.get('readonly')) { - this.make_draggable(drag); - } - }, - get_classname_numeric_suffix: function(node, prefix) { - var classes = node.getAttribute('class'); - if (classes !== '') { - var classesarr = classes.split(' '); - for (var index = 0; index < classesarr.length; index++) { - var patt1 = new RegExp('^' + prefix + '([0-9])+$'); - if (patt1.test(classesarr[index])) { - var patt2 = new RegExp('([0-9])+$'); - var match = patt2.exec(classesarr[index]); - return Number(match[0]); - } - } - } - throw 'Prefix "' + prefix + '" not found in class names.'; - }, - get_choice: function(node) { - return this.get_classname_numeric_suffix(node, 'choice'); - }, - get_group: function(node) { - return this.get_classname_numeric_suffix(node, 'group'); - }, - get_place: function(node) { - return this.get_classname_numeric_suffix(node, 'place'); - }, - get_no: function(node) { - return this.get_classname_numeric_suffix(node, 'no'); - }, - placed: null, - initial_place_of_drag_items: function() { - Y.all(this.selectors.drags()).addClass('unplaced'); - this.placed = []; - for (var placeno in this.get('inputids')) { - var inputid = this.get('inputids')[placeno]; - var inputnode = Y.one('input#' + inputid); - var choiceno = Number(inputnode.get('value')); - if (choiceno !== 0) { - var drop = Y.one(this.selectors.drop_for_place(placeno)); - var groupno = this.get_group(drop); - var drag = - Y.one(this.selectors.unplaced_drags_for_choice_in_group(choiceno, groupno)); - this.place_drag_in_drop(drag, drop); - this.position_drag_item(drag); - } - } - }, - make_draggable: function(drag) { - new Y.DD.Drag({ - node: drag, - groups: [this.get_group(drag)], - dragMode: 'point' - }).plug(Y.Plugin.DDConstrained, {constrain2node: this.selectors.top_node()}); - - // Prevent scrolling whilst dragging on Adroid devices. - this.prevent_touchmove_from_scrolling(drag); - }, - - /** - * prevent_touchmove_from_scrolling allows users of touch screen devices to - * use drag and drop and normal scrolling at the same time. I.e. when - * touching and dragging a draggable item, the screen does not scroll, but - * you can scroll by touching other area of the screen apart from the - * draggable items. - */ - prevent_touchmove_from_scrolling: function(drag) { - var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; - var eventHandler = function(event) { - event.preventDefault(); - }; - var dragId = drag.get('id'); - var el = document.getElementById(dragId); - // Note do not dynamically add events within another event, as this causes issues on iOS11.3. - // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and - // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. - el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); - }, - - /** - * Some older browsers do not support passing an options object to addEventListener. - * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. - */ - checkPassiveSupported: function() { - try { - var options = Object.defineProperty({}, 'passive', { - get: function() { - this.passiveSupported = true; - }.bind(this) - }); - window.addEventListener('test', options, options); - window.removeEventListener('test', options, options); - } catch (err) { - this.passiveSupported = false; - } - }, - - make_drop_zones: function() { - Y.all(this.selectors.drops()).each(this.make_drop_zone, this); - }, - make_drop_zone: function(drop) { - var dropdd = new Y.DD.Drop({ - node: drop, - groups: [this.get_group(drop)]}); - dropdd.on('drop:hit', function(e) { - var drag = e.drag.get('node'); - var drop = e.drop.get('node'); - if (this.get_group(drop) === this.get_group(drag)) { - this.place_drag_in_drop(drag, drop); - } - }, this); - if (!this.get('readonly')) { - drop.on('dragchange', this.drop_zone_key_press, this); - } - }, - place_drag_in_drop: function(drag, drop) { - var placeno = this.get_place(drop); - var inputid = this.get('inputids')[placeno]; - var inputnode = Y.one('input#' + inputid); - if (drag !== null) { - inputnode.set('value', this.get_choice(drag)); - } else { - inputnode.set('value', '0'); - } - for (var alreadytheredragno in this.placed) { - if (this.placed[alreadytheredragno] === placeno) { - delete this.placed[alreadytheredragno]; - var alreadytheredrag = Y.one(this.selectors.drag(alreadytheredragno)); - if (alreadytheredrag && alreadytheredrag.dd) { - alreadytheredrag.dd.detach('drag:start'); - } - } - } - if (drag !== null) { - this.placed[this.get_no(drag)] = placeno; - if (drag.dd) { - drag.dd.once('drag:start', function(e, inputnode, drag) { - inputnode.set('value', 0); - delete this.placed[this.get_no(drag)]; - drag.addClass('unplaced'); - }, this, inputnode, drag); - } - } - }, - remove_drag_from_drop: function(drop) { - this.place_drag_in_drop(null, drop); - }, - - /** - * Postition, or reposition, all the drag items. - * @param pendingid (optional) if given, then mark the js task complete after the - * items are all positioned. - * @param dotimeout (optional) if true, continually re-position the items so - * they stay in place. Else, if an integer, reposition this many times before stopping. - */ - position_drag_items: function(pendingid, dotimeout) { - Y.all(this.selectors.drags()).each(this.position_drag_item, this); - M.util.js_complete(pendingid); - if (dotimeout === true || dotimeout > 0) { - if (dotimeout !== true) { - dotimeout -= 1; - } - Y.later(500, this, this.position_drag_items, [pendingid, dotimeout]); - } - }, - position_drag_item: function(drag) { - if (!drag.hasClass('yui3-dd-dragging')) { - if (!this.placed[this.get_no(drag)]) { - var groupno = this.get_group(drag); - var choiceno = this.get_choice(drag); - var home = Y.one(this.selectors.drag_home(groupno, choiceno)); - drag.setXY(home.getXY()); - drag.addClass('unplaced'); - } else { - var placeno = this.placed[this.get_no(drag)]; - var drop = Y.one(this.selectors.drop_for_place(placeno)); - drag.setXY([drop.getX() + 2, drop.getY() + 2]); - drag.removeClass('unplaced'); - } - } - }, - - drop_zone_key_press: function(e) { - switch (e.direction) { - case 'next' : - this.place_next_drag_in(e.target); - break; - case 'previous' : - this.place_previous_drag_in(e.target); - break; - case 'remove' : - this.remove_drag_from_drop(e.target); - break; - } - e.preventDefault(); - }, - place_next_drag_in: function(drop) { - this.choose_next_choice_for_drop(drop, 1); - }, - place_previous_drag_in: function(drop) { - this.choose_next_choice_for_drop(drop, -1); - }, - choose_next_choice_for_drop: function(drop, direction) { - var next; - var groupno = this.get_group(drop); - var current = this.current_choice_in_drop(drop); - var unplaceddragsingroup = Y.all(this.selectors.unplaced_drags_in_group(groupno)); - if (0 === current) { - if (direction === 1) { - next = 1; - } else { - var lastdrag = unplaceddragsingroup.pop(); - next = this.get_choice(lastdrag); - } - } else { - next = current + direction; - } - var drag; - do { - drag = Y.one(this.selectors.unplaced_drags_for_choice_in_group(next, groupno)); - if (Y.one(this.selectors.drags_for_choice_in_group(next, groupno)) === null) { - this.remove_drag_from_drop(drop); - return; - } - next = next + direction; - } while (drag === null); - this.place_drag_in_drop(drag, drop); - }, - current_choice_in_drop: function(drop) { - var inputid = this.get('inputids')[this.get_place(drop)]; - var inputnode = Y.one('input#' + inputid); - return Number(inputnode.get('value')); - } -}, { - NAME: DDWTOSDDNAME, - ATTRS: { - readonly: {value: false}, - topnode: {value: null}, - inputids: {value: null} - } -}); - -Y.Event.define('dragchange', { - // Webkit and IE repeat keydown when you hold down arrow keys. - // Opera links keypress to page scroll; others keydown. - // Firefox prevents page scroll via preventDefault() on either - // keydown or keypress. - _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress', - - _keys: { - '32': 'next', // Space - '37': 'previous', // Left arrow - '38': 'previous', // Up arrow - '39': 'next', // Right arrow - '40': 'next', // Down arrow - '27': 'remove' // Escape - }, - - _keyHandler: function(e, notifier) { - if (this._keys[e.keyCode]) { - e.direction = this._keys[e.keyCode]; - notifier.fire(e); - } - }, - - on: function(node, sub, notifier) { - sub._detacher = node.on(this._event, this._keyHandler, - this, notifier); - } -}); - -M.qtype_ddwtos = M.qtype_ddwtos || {}; -M.qtype_ddwtos.init_question = function(config) { - return new DDWTOS_DD(config); -}; diff --git a/question/type/ddwtos/yui/src/ddwtos/meta/ddwtos.json b/question/type/ddwtos/yui/src/ddwtos/meta/ddwtos.json deleted file mode 100644 index 6e7953d7c73..00000000000 --- a/question/type/ddwtos/yui/src/ddwtos/meta/ddwtos.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "moodle-qtype_ddwtos-dd": { - "requires": [ - "node", - "dd", - "dd-drop", - "dd-constrain" - ] - } -} diff --git a/question/type/gapselect/rendererbase.php b/question/type/gapselect/rendererbase.php index 0577a219251..3bca12b1c77 100644 --- a/question/type/gapselect/rendererbase.php +++ b/question/type/gapselect/rendererbase.php @@ -59,8 +59,7 @@ abstract class qtype_elements_embedded_in_question_text_renderer } $result = ''; - $result .= html_writer::tag('div', $questiontext, - array('class' => $this->qtext_classname(), 'id' => $this->qtext_id($qa))); + $result .= html_writer::tag('div', $questiontext, array('class' => 'qtext')); $result .= $this->post_qtext_elements($qa, $options); @@ -99,10 +98,6 @@ abstract class qtype_elements_embedded_in_question_text_renderer return $glues; } - protected function qtext_classname() { - return 'qtext'; - } - protected function qtext_id($qa) { return str_replace(':', '_', $qa->get_qt_field_name('')); }