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:
@@ -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 +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)}})}}});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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(',');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user