MDL-79863 qtype_ordering: Ordering qtype: rewrite JavaScript and add keyboard interaction

* This rewrite was mainly to add keyboard support, which is important
for accessibility.
* At the same time, the CSS was changed, to make the question appear
visually much more like the other core drag-drop question types.
* The new drag-drop code is based on the same underlying libraries that
the code drag-drop question types have been using since Moodle 3.6
* Unfortunately, so far we have only been able to make this work back to
Moodle 2.9.
This commit is contained in:
M Kassaei
2024-04-04 10:59:27 +08:00
committed by Mathew May
parent e885659edf
commit 0fdf194fab
10 changed files with 385 additions and 262 deletions
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/dragdrop","core/key_codes"],function(a,b,c){return function(d){var e=null,f=null,g=null,h=null,i=null,j=function(c,h){e={time:(new Date).getTime(),x:h.x,y:h.y},g=a(c.currentTarget).closest(d.itemInPage),"undefined"!=typeof d.reorderStart&&d.reorderStart(g.closest(d.list),g),f=r(),i=a(d.proxyHtml.replace("%%ITEM_HTML%%",g.html()).replace("%%ITEM_CLASS_NAME%%",g.attr("class"))),a(document.body).append(i),i.css("position","absolute"),i.css(g.offset()),i.width(g.outerWidth()),i.height(g.outerHeight()),g.addClass(d.itemMovingClass),b.start(c,i,k,m)},k=function(){var b=g.closest(d.list),c=null,e=null;b.find(d.item).each(function(b,d){var f=q(d,i);(null===c||f<e)&&(c=a(d),e=f)}),c[0]!==g[0]&&(p(i)<p(c)?g.insertBefore(c):g.insertAfter(c))},l=function(a,b){var c=[];return a.split(",").forEach(function(a){b.split(",").forEach(function(b){c.push(a.trim()+" "+b.trim())})}),c.join(", ")},m=function(a,b){"undefined"!=typeof d.reorderEnd&&d.reorderEnd(g.closest(d.list),g);var c=r();s(f,c)?(new Date).getTime()-e.time<500&&Math.abs(e.x-a)<10&&Math.abs(e.y-b)<10&&g[0].focus():d.reorderDone(g.closest(d.list),g,c),i.remove(),i=null,g.removeClass(d.itemMovingClass),g=null,e=null},n=function(a,b){var d;switch(a.keyCode){case c.space:case c.arrowRight:case c.arrowDown:a.preventDefault(),a.stopPropagation(),d=document.getElementById(b.attr("id"));var e=d.nextSibling;null!==e&&e.parentNode.insertBefore(e,e.previousSibling);break;case c.arrowLeft:case c.arrowUp:a.preventDefault(),a.stopPropagation(),d=document.getElementById(b.attr("id"));var f=d.previousSibling;null!==f&&f.parentNode.insertBefore(f,f.nextSibling.nextSibling)}},o=function(a){return a.offset().left+a.outerWidth()/2},p=function(a){return a.offset().top+a.outerHeight()/2},q=function(b,c){var d=a(b),e=a(c),f=o(d)-o(e),g=p(d)-p(e);return Math.sqrt(f*f+g*g)},r=function(){return(g||h).closest(d.list).find(d.item).map(function(a,b){return d.idGetter(b)}).get()},s=function(a,b){return a.length===b.length&&a.every(function(a,c){return a===b[c]})};d.itemInPage=l(d.list,d.item),a(d.list).on("mousedown touchstart",d.item,function(a){var c=b.prepare(a);c.start&&j(a,c)}),a(d.list).on("keydown",d.item,function(b){h=a(b.currentTarget).closest(d.itemInPage),f=r(),n(b,h);var c=r();s(f,c)||d.reorderDone(h.closest(d.list),h,c)}),a(d.itemInPage).attr("tabindex","0")}});
@@ -1 +0,0 @@
!function(a){"function"==typeof define&&define.amd?define(["jquery","jqueryui"],a):a(window.jQuery)}(function(a){function b(a){var b=window.pageXOffset,c=window.pageYOffset,d=a.clientX,e=a.clientY;return 0===a.pageY&&Math.floor(e)>Math.floor(a.pageY)||0===a.pageX&&Math.floor(d)>Math.floor(a.pageX)?(d-=b,e-=c):(e<a.pageY-c||d<a.pageX-b)&&(d=a.pageX-b,e=a.pageY-c),{clientX:d,clientY:e}}function c(c,e){if(!(!d&&c.originalEvent.touches.length>1||d&&!c.isPrimary)){var f=d?c.originalEvent:c.originalEvent.changedTouches[0],g=document.createEvent("MouseEvents"),h=b(f);a(f.target).is("input")||a(f.target).is("textarea")?c.stopPropagation():c.preventDefault(),g.initMouseEvent(e,!0,!0,window,1,c.screenX||f.screenX,c.screenY||f.screenY,c.clientX||h.clientX,c.clientY||h.clientY,!1,!1,!1,!1,0,null),c.target.dispatchEvent(g)}}var d=window.navigator.pointerEnabled||window.navigator.msPointerEnabled;if(a.support.touch="ontouchend"in document||d,a.support.touch&&a.ui.mouse){var e,f=a.ui.mouse.prototype,g=f._mouseInit;f._touchStart=function(a){var b=this;e||!d&&!b._mouseCapture(a.originalEvent.changedTouches[0])||(e=!0,b._touchMoved=!1,c(a,"mouseover"),c(a,"mousemove"),c(a,"mousedown"))},f._touchMove=function(a){e&&(this._touchMoved=!0,c(a,"mousemove"))},f._touchEnd=function(a){e&&(c(a,"mouseup"),c(a,"mouseout"),this._touchMoved||c(a,"click"),e=!1)},f._mouseInit=function(){var b=this;b.element.on({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd"),pointerDown:a.proxy(b,"_touchStart"),pointerMove:a.proxy(b,"_touchMove"),pointerUp:a.proxy(b,"_touchEnd"),MSPointerDown:a.proxy(b,"_touchStart"),MSPointerMove:a.proxy(b,"_touchMove"),MSPointerUp:a.proxy(b,"_touchEnd")}),g.call(b)}}});
-1
View File
@@ -1 +0,0 @@
define(["jquery","jqueryui","qtype_ordering/jquery.ui.touch-punch-improved"],function(a){return{init:function(b,c,d,e){a("#"+b).sortable({axis:e,containment:a("#"+b),opacity:.6,update:function(){var b=a(this).sortable("toArray").toString();a("#"+c).attr("value",b)}})}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","qtype_ordering/drag_reorder"],function(a,b){return{init:function(c,d){new b({list:"ul#"+c,item:"li.sortableitem",proxyHtml:'<div class="que ordering dragproxy"><ul class="sortablelist"><li class="%%ITEM_CLASS_NAME%% item-moving">%%ITEM_HTML%%</li></ul></div>',itemMovingClass:"current-drop",idGetter:function(b){return a(b).attr("id")},nameGetter:function(b){return a(b).text},reorderStart:function(){},reorderEnd:function(){},reorderDone:function(b,c,e){a("input#"+d)[0].value=e.join(",")}})}}});
@@ -0,0 +1,309 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Generic library to allow things in a vertical list to be re-ordered using drag and drop.
*
* To make a set of things draggable, create a new instance of this object passing the
* necessary config, as explained in the comment on the constructor.
*
* @package qtype_ordering
* @copyright 2018 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @module qtype_ordering/drag_reorder
*/
define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, drag, keys) {
/**
* Constructor.
*
* To make a list draggable, create a new instance of this object, passing the necessary config.
* For example:
* {
* // Selector for the list (or lists) to be reordered.
* list: 'ul.my-list',
*
* // Selector, relative to the list selector, for the items that can be moved.
* item: '> li',
*
* // The user actually drags a proxy object, which is constructed from this string,
* // and then added directly as a child of <body>. The token %%ITEM_HTML%% is
* // replaced with the innerHtml of the item being dragged. The token %%ITEM_CLASS_NAME%%
* // is replaced with the class attribute of the item being dragged. Because of this,
* // the styling of the contents of your list item needs to work for the proxy, as well as
* // for items in place in the context of the list. Your CSS also needs to ensure
* // that this proxy has position: absolute. You probably want other styles, like a
* // drop shadow. Using class osep-itemmoving might be all you need to do.
* proxyHtml: '<div class="osep-itemmoving %%ITEM_CLASS_NAME%%">%%ITEM_HTML%%</div>,
*
* // While the proxy is being dragged, this class is added to the item being moved.
* // You can probably use "osep-itemmoving" here.
* itemMovingClass: "osep-itemmoving",
*
* // This is a callback which, when called with the DOM node for an item,
* // returns the string that uniquely identifies each item.
* // Therefore, the result of the drag action will be represented by the array
* // obtained by calling this method on each item in the list in order.
* idGetter: function(item) { return $(node).data('id'); },
*
* // This is a callback which, when called with the DOM node for an item,
* // returns a string that is the name of the item.
* nameGetter: function(item) { return $(node).text(); },
*
* // Function that will be called when a re-order starts (optional, can be not set).
* // Useful if you need to save information about the initial state.
* // This function should have two parameters. The first will be a
* // jQuery object for the list that was reordered, the second will
* // be the jQuery object for the item moved - which will not yet have been moved.
* // Note, it is quite possible for reorderStart to be called with no
* // subsequent call to reorderDone.
* reorderStart: function($list, $item) { ... }
*
* // Function that will be called when a drag has finished, and the list
* // has been reordered. This function should have three parameters. The first will be
* // a jQuery object for the list that was reordered, the second will be the jQuery
* // object for the item moved, and the third will be the new order, which is
* // an array of ids obtained by calling idGetter on each item in the list in order.
* // This callback will only be called in the new order is actually different from the old order.
* reorderDone: function($list, $item, newOrder) { ... }
*
* // Function that is alwasy called when a re-order ends (optional, can be not set)
* // whether or not the order has changed. Useful if you need to undo changes made
* // in reorderStart, since reorderDone is only called if the new order is different
* // from the original order.
* reorderEnd: function($list, $item) { ... }
* }
*
* There is a subtlety ( === hack?) that you can use. If you have items in your list that do not
* have a drag handle, they they are considered to be placeholders in otherwise empty containers.
* See how block_userlinks does it, if this seems like it might be useful. nameGetter should return
* the container name for these items.
*
* @param config As above.
*/
return function(config) {
var dragStart = null, // Information about when and where the drag started.
originalOrder = null, // Array of ids.
itemDragging = null, // Item being moved by dragging (jQuery object).
itemMoving = null, // Item being moved using the accessible modal (jQuery object).
proxy = null; // Drag proxy (jQuery object).
var startDrag = function(event, details) {
dragStart = {
time: new Date().getTime(),
x: details.x,
y: details.y
};
itemDragging = $(event.currentTarget).closest(config.itemInPage);
if (typeof config.reorderStart !== 'undefined') {
config.reorderStart(itemDragging.closest(config.list), itemDragging);
}
originalOrder = getCurrentOrder();
proxy = $(config.proxyHtml.replace('%%ITEM_HTML%%', itemDragging.html())
.replace('%%ITEM_CLASS_NAME%%', itemDragging.attr('class')));
$(document.body).append(proxy);
proxy.css('position', 'absolute');
proxy.css(itemDragging.offset());
proxy.width(itemDragging.outerWidth());
proxy.height(itemDragging.outerHeight());
itemDragging.addClass(config.itemMovingClass);
// Start drag.
drag.start(event, proxy, dragMove, dragEnd);
};
var dragMove = function() {
var list = itemDragging.closest(config.list);
var closestItem = null;
var closestDistance = null;
list.find(config.item).each(function (index, element) {
var distance = distanceBetweenElements(element, proxy);
if (closestItem === null || distance < closestDistance) {
closestItem = $(element);
closestDistance = distance;
}
});
if (closestItem[0] === itemDragging[0]) {
return;
}
if (midY(proxy) < midY(closestItem)) {
itemDragging.insertBefore(closestItem);
} else {
itemDragging.insertAfter(closestItem);
}
};
/**
* It outer and inner are two CSS selectors, which may contain commas,
* then combine them safely. So combineSelectors('a, b', 'c, d')
* gives 'a c, a d, b c, b d'.
* @param outer
* @param inner
* @returns {string}
*/
var combineSelectors = function(outer, inner) {
var combined = [];
outer.split(',').forEach(function(firstSelector) {
inner.split(',').forEach(function(secondSelector) {
combined.push(firstSelector.trim() + ' ' + secondSelector.trim());
});
});
return combined.join(', ');
};
var dragEnd = function(x, y) {
if (typeof config.reorderEnd !== 'undefined') {
config.reorderEnd(itemDragging.closest(config.list), itemDragging);
}
var newOrder = getCurrentOrder();
if (!arrayEquals(originalOrder, newOrder)) {
// Order has changed, call the callback.
config.reorderDone(itemDragging.closest(config.list), itemDragging, newOrder);
} else if (new Date().getTime() - dragStart.time < 500 &&
Math.abs(dragStart.x - x) < 10 && Math.abs(dragStart.y - y) < 10) {
// This was really a click. Set the focus on the current item.
itemDragging[0].focus();
}
proxy.remove();
proxy = null;
itemDragging.removeClass(config.itemMovingClass);
itemDragging = null;
dragStart = null;
};
/**
* Items can be moved and placed using certain keys.
* Tab for tabbing though and choose the item to be moved
* space, arrow-right arrow-down for moving current element forewards.
* arrow-right arrow-down for moving the current element backwards.
* @param e, the event
* @param item, the current moving item
*/
var itemMovedByKeyboard = function (e, item) {
var current;
switch (e.keyCode) {
case keys.space:
case keys.arrowRight:
case keys.arrowDown:
e.preventDefault();
e.stopPropagation();
current = document.getElementById(item.attr('id'));
var next = current.nextSibling;
if (next !== null) {
next.parentNode.insertBefore(next, next.previousSibling);
}
break;
case keys.arrowLeft:
case keys.arrowUp:
e.preventDefault();
e.stopPropagation();
current = document.getElementById(item.attr('id'));
var prev = current.previousSibling;
if (prev !== null) {
prev.parentNode.insertBefore(prev, prev.nextSibling.nextSibling);
}
break;
}
};
/**
* Get the x-position of the middle of the DOM node represented by the given jQuery object.
* @param jQuery wrapping a DOM node.
* @returns Number the x-coordinate of the middle (left plus half outerWidth).
*/
var midX = function(jQuery) {
return jQuery.offset().left + jQuery.outerWidth() / 2;
};
/**
* Get the y-position of the middle of the DOM node represented by the given jQuery object.
* @param jQuery wrapping a DOM node.
* @returns Number the y-coordinate of the middle (top plus half outerHeight).
*/
var midY = function(jQuery) {
return jQuery.offset().top + jQuery.outerHeight() / 2;
};
/**
* Calculate the distance between the centres of two elements.
* @param element1 selector, element or jQuery.
* @param element2 selector, element or jQuery.
* @return number the distance in pixels.
*/
var distanceBetweenElements = function(element1, element2) {
var e1 = $(element1), e2 = $(element2);
var dx = midX(e1) - midX(e2);
var dy = midY(e1) - midY(e2);
return Math.sqrt(dx * dx + dy * dy);
};
/**
* Get the current order of the list containing itemDragging.
* @returns Array of strings, the id of each element in order.
*/
var getCurrentOrder = function() {
return (itemDragging || itemMoving).closest(config.list).find(config.item).map(
function(index, item) { return config.idGetter(item); }).get();
};
/**
* Compare two arrays, which just contain simple values like ints or strings,
* to see if they are equal.
* @param a1 first array.
* @param a2 second array.
* @return boolean true if they both contain the same elements in the same order, else false.
*/
var arrayEquals = function(a1, a2) {
return a1.length === a2.length &&
a1.every(function(v, i) { return v === a2[i]; });
};
config.itemInPage = combineSelectors(config.list, config.item);
// AJAX for section drag and click-to-move.
$(config.list).on('mousedown touchstart', config.item, function(event) {
var details = drag.prepare(event);
if (details.start) {
startDrag(event, details);
}
});
$(config.list).on('keydown', config.item, function(event) {
itemMoving = $(event.currentTarget).closest(config.itemInPage);
originalOrder = getCurrentOrder();
itemMovedByKeyboard(event, itemMoving);
var newOrder = getCurrentOrder();
if (!arrayEquals(originalOrder, newOrder)) {
// Order has changed, call the callback.
config.reorderDone(itemMoving.closest(config.list), itemMoving, newOrder);
}
});
// Make the items tabbable.
$(config.itemInPage).attr('tabindex', '0');
};
});
@@ -1,207 +0,0 @@
/*!
* jQuery UI Touch Punch Improved 0.3.1
*
*
* Copyright 2013, Chris Hutchinson <[email protected]>
* Original jquery-ui-touch-punch Copyright 2011, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
(function( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define([ "jquery", "jqueryui" ], factory );
} else {
// Browser globals
factory( window.jQuery );
}
}(function ($) {
var pointerEnabled = window.navigator.pointerEnabled
|| window.navigator.msPointerEnabled;
// Detect touch support
$.support.touch = 'ontouchend' in document || pointerEnabled;
// Ignore browsers without touch support or mouse support
if (!$.support.touch || !$.ui.mouse) {
return;
}
var mouseProto = $.ui.mouse.prototype,
_mouseInit = mouseProto._mouseInit,
touchHandled;
// see http://stackoverflow.com/a/12714084/220825
function fixTouch(touch) {
var winPageX = window.pageXOffset,
winPageY = window.pageYOffset,
x = touch.clientX,
y = touch.clientY;
if (touch.pageY === 0 && Math.floor(y) > Math.floor(touch.pageY)
|| touch.pageX === 0 && Math.floor(x) > Math.floor(touch.pageX)) {
// iOS4 clientX/clientY have the value that should have been
// in pageX/pageY. While pageX/page/ have the value 0
x = x - winPageX;
y = y - winPageY;
} else if (y < (touch.pageY - winPageY) || x < (touch.pageX - winPageX)) {
// Some Android browsers have totally bogus values for clientX/Y
// when scrolling/zooming a page. Detectable since clientX/clientY
// should never be smaller than pageX/pageY minus page scroll
x = touch.pageX - winPageX;
y = touch.pageY - winPageY;
}
return {
clientX: x,
clientY: y
};
}
/**
* Simulate a mouse event based on a corresponding touch event
* @param {Object} event A touch event
* @param {String} simulatedType The corresponding mouse event
*/
function simulateMouseEvent (event, simulatedType) {
// Ignore multi-touch events
if ((!pointerEnabled && event.originalEvent.touches.length > 1) || (pointerEnabled && !event.isPrimary)) {
return;
}
var touch = pointerEnabled ? event.originalEvent : event.originalEvent.changedTouches[0],
simulatedEvent = document.createEvent('MouseEvents'),
coord = fixTouch(touch);
// Check if element is an input or a textarea
if ($(touch.target).is("input") || $(touch.target).is("textarea")) {
event.stopPropagation();
} else {
event.preventDefault();
}
// Initialize the simulated mouse event using the touch event's coordinates
simulatedEvent.initMouseEvent(
simulatedType, // type
true, // bubbles
true, // cancelable
window, // view
1, // detail
event.screenX || touch.screenX, // screenX
event.screenY || touch.screenY, // screenY
event.clientX || coord.clientX, // clientX
event.clientY || coord.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false, // metaKey
0, // button
null // relatedTarget
);
// Dispatch the simulated event to the target element
event.target.dispatchEvent(simulatedEvent);
}
/**
* Handle the jQuery UI widget's touchstart events
* @param {Object} event The widget element's touchstart event
*/
mouseProto._touchStart = function (event) {
var self = this;
// Ignore the event if another widget is already being handled
if (touchHandled || (!pointerEnabled && !self._mouseCapture(event.originalEvent.changedTouches[0]))) {
return;
}
// Set the flag to prevent other widgets from inheriting the touch event
touchHandled = true;
// Track movement to determine if interaction was a click
self._touchMoved = false;
// Simulate the mouseover event
simulateMouseEvent(event, 'mouseover');
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
// Simulate the mousedown event
simulateMouseEvent(event, 'mousedown');
};
/**
* Handle the jQuery UI widget's touchmove events
* @param {Object} event The document's touchmove event
*/
mouseProto._touchMove = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Interaction was not a click
this._touchMoved = true;
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
};
/**
* Handle the jQuery UI widget's touchend events
* @param {Object} event The document's touchend event
*/
mouseProto._touchEnd = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Simulate the mouseup event
simulateMouseEvent(event, 'mouseup');
// Simulate the mouseout event
simulateMouseEvent(event, 'mouseout');
// If the touch interaction did not move, it should trigger a click
if (!this._touchMoved) {
// Simulate the click event
simulateMouseEvent(event, 'click');
}
// Unset the flag to allow other widgets to inherit the touch event
touchHandled = false;
};
/**
* A duck punch of the $.ui.mouse _mouseInit method to support touch events.
* This method extends the widget with bound touch event handlers that
* translate touch events to mouse events and pass them to the widget's
* original mouse event handling methods.
*/
mouseProto._mouseInit = function () {
var self = this;
self.element.on({
'touchstart': $.proxy(self, '_touchStart'),
'touchmove': $.proxy(self, '_touchMove'),
'touchend': $.proxy(self, '_touchEnd'),
'pointerDown': $.proxy(self, '_touchStart'),
'pointerMove': $.proxy(self, '_touchMove'),
'pointerUp': $.proxy(self, '_touchEnd'),
'MSPointerDown': $.proxy(self, '_touchStart'),
'MSPointerMove': $.proxy(self, '_touchMove'),
'MSPointerUp': $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse init method
_mouseInit.call(self);
};
}));
@@ -1,17 +0,0 @@
define(['jquery', 'jqueryui', 'qtype_ordering/jquery.ui.touch-punch-improved'], function($) {
return {
init: function (sortableid, responseid, ablockid, axis) {
$('#' + sortableid).sortable({
axis : axis,
containment: $('#'+sortableid),
opacity : 0.6,
update : function () {
var items = $(this).sortable('toArray').toString();
$('#' + responseid).attr('value', items);
}
});
}
};
});
+27
View File
@@ -0,0 +1,27 @@
define(['jquery', 'qtype_ordering/drag_reorder'], function($, dragReorder) {
return {
/**
* Initialise one ordering question.
*
* @param {String} sortableid id of ul for this question.
* @param {String} responseid id of hidden field for this question.
*/
init: function (sortableid, responseid) {
new dragReorder({
list: 'ul#' + sortableid,
item: 'li.sortableitem',
proxyHtml: '<div class="que ordering dragproxy">' +
'<ul class="sortablelist"><li class="%%ITEM_CLASS_NAME%% item-moving">' +
'%%ITEM_HTML%%</li></ul></div>',
itemMovingClass: "current-drop",
idGetter: function (item) { return $(item).attr('id'); },
nameGetter: function (item) { return $(item).text; },
reorderStart: function() {},
reorderEnd: function() {},
reorderDone: function(list, item, newOrder) {
$('input#' + responseid)[0].value = newOrder.join(',');
}
});
}
};
});
+2 -2
View File
@@ -92,8 +92,8 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
// Items cannot be dragged in readonly mode.
} else if (method_exists($PAGE->requires, 'js_call_amd')) {
// Moodle >= 2.9
$params = array($sortableid, $responseid, $ablockid, $axis);
$PAGE->requires->js_call_amd('qtype_ordering/ordering', 'init', $params);
$params = array($sortableid, $responseid, $axis);
$PAGE->requires->js_call_amd('qtype_ordering/reorder', 'init', $params);
} else {
// Moodle <= 2.8
$script = "\n";
+45 -34
View File
@@ -1,28 +1,20 @@
/* ensure container covers the draggable items */
.que.ordering div.answer.ordering { overflow: auto; }
/* Styles for the initial layout of the question. */
/* Ensure container covers the draggable items. */
.que.ordering div.answer.ordering {
overflow: auto;
}
.que.ordering ul.sortablelist {
border : 1px solid #ccc;
float : left;
font-family : Arial, sans-serif;
font-size : 13px;
list-style-type : none;
margin : 0px;
margin : 0;
margin-left : 5px;
padding : 4px 4px 0 4px;
width : 80%;
}
.que.ordering ul.sortablelist,
.que.ordering ul.sortablelist li {
-webkit-border-radius: 6px; /* Safari and Chrome */
-moz-border-radius: 6px; /* Firefox */
-o-border-radius: 6px; /* Opera */
border-radius: 6px; /* CSS3 */
}
.que.ordering ul.sortablelist li {
background-color : #eeeeee;
border : 1px solid #cccccc;
border-image : initial;
background-color : #ffffff;
border : 1px solid #000;
list-style-type : none;
margin : 4px;
padding : 6px 12px;
@@ -30,8 +22,10 @@
.que.ordering ul.sortablelist li.sortableitem {
position : relative;
cursor : move;
touch-action : none;
-ms-touch-action : none;
}
.que.ordering ul.sortablelist li.sortableitem:focus {
border-color: #0a0;
box-shadow: 0 0 5px 5px rgba(255, 255, 150, 1);
}
.que.ordering ul.sortablelist li.horizontal {
float : left;
@@ -39,6 +33,33 @@
.que.ordering ul.sortablelist li.vertical {
min-height : 18px;
}
/* Styles for when things are being dragged. */
.que.ordering.dragproxy {
margin: 0;
padding: 0;
border: 0 none;
}
.que.ordering.dragproxy ul.sortablelist {
margin: 0;
padding: 0;
float: none;
}
.que.ordering.dragproxy ul.sortablelist li {
margin: 0;
}
.que.ordering.dragproxy ul.sortablelist li.horizontal {
float: none;
}
.item-moving {
box-shadow: 3px 3px 4px #000;
}
.current-drop {
visibility: hidden;
}
/* Styles for feedback. */
.que.ordering ul.sortablelist li.correct {
background-color : #dff4d8; /* light green */
border-color : #99ff66; /* gentle green */
@@ -59,20 +80,8 @@
background-color : #ffdddd; /* light red */
border-color : #ff7373; /* gentle red */
}
.que.ordering ul.sortablelist li.correct,
.que.ordering ul.sortablelist li.partial66,
.que.ordering ul.sortablelist li.partial33,
.que.ordering ul.sortablelist li.partial00,
.que.ordering ul.sortablelist li.incorrect,
.que.ordering ul.sortablelist li.unscored {
border-style : solid;
border-width : 4px;
margin-bottom : 4px;
margin-top : 4px;
}
/*
force containing DIV to cover the floating LI elements
Force containing DIV to cover the floating LI elements
Note: if you add "overflow:auto; to "ol.correctorder"
then the numbers for the <LI> elements disappear !!
*/
@@ -85,11 +94,13 @@
margin-right : 24px;
}
.que.ordering div.rightanswer ol.correctorder li.horizontal:first-child {
margin-left : 0px;
margin-left : 0;
}
.que.ordering div.rightanswer ol.correctorder li.vertical {
}
/* the width restriction can be limited to editors for draggable items
by inserting "fieldset:nth-child(n+4)" before "div.feditor" */
#page-question-type-ordering form.mform fieldset:nth-child(n+4) div.feditor { max-width: 480px; }
#page-question-type-ordering form.mform fieldset:nth-child(n+4) div.feditor {
max-width: 480px;
}