MDL-51803 core: sortable list singleton

This commit is contained in:
Marina Glancy
2018-10-09 13:28:33 +02:00
parent dbe5689ef1
commit e134ad7b38
2 changed files with 220 additions and 282 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+219 -281
View File
@@ -18,15 +18,20 @@
*
* Example of usage:
*
* define(['jquery', 'core/sortable_list'], function($, sortableList) {
* sortableList.init({
* listSelector: 'ul.my-awesome-list', // mandatory, CSS selector for the list (usually <ul> or <tbody>)
* moveHandlerSelector: '.draghandle' // CSS selector of the crossarrow handle. Make sure that this
* element can handle keypress and mouse click events for displaying accessible move popup.
* });
* $('ul.my-awesome-list > *').on('sortablelist-drop', function(evt, info) {
* define(['jquery', 'core/sortable_list'], function($, SortableList) {
* var list = new SortableList('ul.my-awesome-list', // source list (usually <ul> or <tbody>) - selector or element
* {
* moveHandlerSelector: '.draghandle' // CSS selector of the crossarrow handle. Make sure that this
* // element can handle keypress and mouse click events for displaying accessible move popup.
* });
* $('ul.my-awesome-list > *').on(SortableList.EVENTS.DROP, function(evt, info) {
* console.log(info);
* });
*
* // Advanced usage. Overwrite methods getElementName, getDestinationName, moveDialogueTitle, for example:
* list.getElementName = function(element) {
* return $.Deferred().resolve(element.attr('data-name'));
* }
* }
*
* More details: https://docs.moodle.org/dev/Sortable_list
@@ -34,10 +39,10 @@
* For the full list of possible parameters see var defaultParameters below.
*
* The following jQuery events are fired:
* - sortablelist-dragstart : when user started dragging a list element
* - sortablelist-drag : when user dragged a list element to a new position
* - sortablelist-drop : when user dropped a list element
* - sortablelist-dragend : when user finished dragging - either fired right after dropping or
* - SortableList.EVENTS.DRAGSTART : when user started dragging a list element
* - SortableList.EVENTS.DRAG : when user dragged a list element to a new position
* - SortableList.EVENTS.DROP : when user dropped a list element
* - SortableList.EVENTS.DROPEND : when user finished dragging - either fired right after dropping or
* if "Esc" was pressed during dragging
*
* @module core/sortable_list
@@ -59,25 +64,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
targetListSelector: null,
moveHandlerSelector: null,
isHorizontal: false,
autoScroll: true,
elementNameCallback: function(element) {
return element.text();
},
destinationNameCallback: function(parentElement, afterElement) {
if (!afterElement.length) {
return str.get_string('movecontenttothetop', 'moodle');
} else {
return getElementName(afterElement)
.then(function(name) {
return str.get_string('movecontentafter', 'moodle', name);
});
}
},
moveDialogueTitleCallback: function(element) {
return getElementName(element).then(function(name) {
return str.get_string('movecontent', 'moodle', name);
});
}
autoScroll: true
};
/**
@@ -96,72 +83,78 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
};
/**
* Stores parameters of the currently dragged item
* Initialise sortable list.
*
* @private
* @param {(String|jQuery|Element)} root JQuery/DOM element representing sortable list (i.e. <ul>, <tbody>) or CSS selector
* @param {Object} config Parameters for the list. See defaultParameters above for examples.
* @property {(String|jQuery|Element)} config.targetListSelector target lists, by default same as root
* @property {String} config.moveHandlerSelector CSS selector for a drag handle. By default the whole item is a handle.
* Without drag handle sorting is not accessible!
* @property {String} config.targetListSelector CSS selector for target lists. By default the same as root
* @property {(Boolean|Function)} config.isHorizontal Set to true if the list is horizontal
* (can also be a callback with list as an argument)
* @property {Boolean} config.autoScroll Engages autoscroll module for automatic vertical scrolling of the
* whole page, by default true
*/
var SortableList = function(root, config) {
this.info = null;
this.proxy = null;
this.proxyDelta = null;
this.dragCounter = 0;
this.lastEvent = null;
this.config = $.extend({}, defaultParameters, config || {});
this.config.listSelector = root;
if (!this.config.targetListSelector) {
this.config.targetListSelector = root;
}
if (typeof this.config.listSelector === 'object') {
// The root is an element on the page. Register a listener for this element.
$(this.config.listSelector).on('mousedown touchstart', $.proxy(this.dragStartHandler, this));
} else {
// The root is a CSS selector. Register a listener that picks up the element dynamically.
$('body').on('mousedown touchstart', this.config.listSelector, $.proxy(this.dragStartHandler, this));
}
if (this.config.moveHandlerSelector !== null) {
$('body').on('click keypress', this.config.moveHandlerSelector, $.proxy(this.clickHandler, this));
}
};
/**
* Events fired by this entity
*
* @public
* @type {Object}
*/
var config = {};
/**
* Stores information about currently dragged item
*
* @private
* @type {Object}
*/
var info = null;
/**
* Stores the proxy object
*
* @private
* @type {jQuery}
*/
var proxy;
/**
* Stores initial position of the proxy
*
* @private
* @type {Object}
*/
var proxyDelta;
/**
* Counter of drag events
*
* @private
* @type {Number}
*/
var dragCounter = 0;
SortableList.EVENTS = {
DRAGSTART: 'sortablelist-dragstart',
DRAG: 'sortablelist-drag',
DROP: 'sortablelist-drop',
DRAGEND: 'sortablelist-dragend'
};
/**
* Resets the temporary classes assigned during dragging
* @private
*/
var resetDraggedClasses = function() {
SortableList.prototype.resetDraggedClasses = function() {
var classes = [
config.isDraggedClass,
config.currentPositionClass,
config.overElementClass,
config.targetListClass,
config.sourceListClass
CSS.isDraggedClass,
CSS.currentPositionClass,
CSS.overElementClass,
CSS.targetListClass,
];
for (var i in classes) {
$('.' + classes[i]).removeClass(classes[i]);
}
if (proxy) {
proxy.remove();
proxy = $();
if (this.proxy) {
this.proxy.remove();
this.proxy = $();
}
};
/**
* {Event} stores the last event that had pageX and pageY defined
* @private
*/
var lastEvent;
/**
* Calculates evt.pageX, evt.pageY, evt.clientX and evt.clientY
*
@@ -171,7 +164,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @private
* @param {Event} evt
*/
var calculatePositionOnPage = function(evt) {
SortableList.prototype.calculatePositionOnPage = function(evt) {
if (evt.originalEvent && evt.originalEvent.touches && evt.originalEvent.touches[0] !== undefined) {
// This is a touchmove or touchstart event, get position from the first touch position.
@@ -183,10 +176,10 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
if (evt.pageX === undefined) {
// Information is not present in case of touchend or when event was emulated by autoScroll.
// Take the absolute mouse position from the last event.
evt.pageX = lastEvent.pageX;
evt.pageY = lastEvent.pageY;
evt.pageX = this.lastEvent.pageX;
evt.pageY = this.lastEvent.pageY;
} else {
lastEvent = evt;
this.lastEvent = evt;
}
if (evt.clientX === undefined) {
@@ -202,16 +195,15 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @private
* @param {Event} evt
*/
var dragStartHandler = function(evt) {
config = evt.data.config;
if (info !== null) {
if (info.type === 'click') {
SortableList.prototype.dragStartHandler = function(evt) {
if (this.info !== null) {
if (this.info.type === 'click') {
// Ignore double click.
return;
}
// Mouse down or touch while already dragging, cancel previous dragging.
moveElement(info.sourceList, info.sourceNextElement);
finishDragging();
this.moveElement(this.info.sourceList, this.info.sourceNextElement);
this.finishDragging();
}
if (evt.type === 'mousedown' && evt.which !== 1) {
@@ -219,7 +211,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
return;
}
calculatePositionOnPage(evt);
this.calculatePositionOnPage(evt);
var movedElement = $(evt.target).closest($(evt.currentTarget).children());
if (!movedElement.length) {
// Can't find the element user wants to drag. They clicked on the list but outside of any element of the list.
@@ -227,8 +219,8 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
}
// Check that we grabbed the element by the handle.
if (config.moveHandlerSelector !== null) {
if (!$(evt.target).closest(config.moveHandlerSelector, movedElement).length) {
if (this.config.moveHandlerSelector !== null) {
if (!$(evt.target).closest(this.config.moveHandlerSelector, movedElement).length) {
return;
}
}
@@ -238,8 +230,8 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
// Information about moved element with original location.
// This object is passed to event observers.
dragCounter++;
info = {
this.dragCounter++;
this.info = {
element: movedElement,
sourceNextElement: movedElement.next(),
sourceList: movedElement.parent(),
@@ -252,50 +244,51 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
startTime: new Date().getTime()
};
$(config.targetListSelector).addClass(config.targetListClass);
$(this.config.targetListSelector).addClass(CSS.targetListClass);
var offset = movedElement.offset();
movedElement.addClass(config.currentPositionClass);
proxyDelta = {x: offset.left - evt.pageX, y: offset.top - evt.pageY};
proxy = $();
var thisDragCounter = dragCounter;
setTimeout(function() {
movedElement.addClass(CSS.currentPositionClass);
this.proxyDelta = {x: offset.left - evt.pageX, y: offset.top - evt.pageY};
this.proxy = $();
var thisDragCounter = this.dragCounter;
setTimeout($.proxy(function() {
// This mousedown event may in fact be a beginning of a 'click' event. Use timeout before showing the
// dragged object so we can catch click event. When timeout finishes make sure that click event
// has not happened during this half a second.
// Verify dragcounter to make sure the user did not manage to do two very fast drag actions one after another.
if (info === null || info.type === 'click' || info.type === 'keypress' || dragCounter !== thisDragCounter) {
if (this.info === null || this.info.type === 'click' || this.info.type === 'keypress'
|| this.dragCounter !== thisDragCounter) {
return;
}
// Create a proxy - the copy of the dragged element that moves together with a mouse.
createProxy();
}, 500);
this.createProxy();
}, this), 500);
// Start drag.
$('body').on('mousemove touchmove mouseup touchend', dragHandler);
$('body').on('keypress', dragcancelHandler);
$(window).on('mousemove touchmove mouseup touchend', $.proxy(this.dragHandler, this));
$(window).on('keypress', $.proxy(this.dragcancelHandler, this));
// Start autoscrolling. Every time the page is scrolled emulate the mousemove event.
if (config.autoScroll) {
if (this.config.autoScroll) {
autoScroll.start(function() {
$('body').trigger('mousemove');
$(window).trigger('mousemove');
});
}
executeCallback('dragstart');
this.executeCallback(SortableList.EVENTS.DRAGSTART);
};
/**
* Creates a "proxy" object - a copy of the element that is being moved that always follows the mouse
* @private
*/
var createProxy = function() {
proxy = info.element.clone();
info.sourceList.append(proxy);
proxy.removeAttr('id').removeClass(config.currentPositionClass)
.addClass(config.isDraggedClass).css({position: 'fixed'});
proxy.offset({top: proxyDelta.y + lastEvent.pageY, left: proxyDelta.x + lastEvent.pageX});
SortableList.prototype.createProxy = function() {
this.proxy = this.info.element.clone();
this.info.sourceList.append(this.proxy);
this.proxy.removeAttr('id').removeClass(CSS.currentPositionClass)
.addClass(CSS.isDraggedClass).css({position: 'fixed'});
this.proxy.offset({top: this.proxyDelta.y + this.lastEvent.pageY, left: this.proxyDelta.x + this.lastEvent.pageX});
};
/**
@@ -304,29 +297,28 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @private
* @param {Event} evt
*/
var clickHandler = function(evt) {
SortableList.prototype.clickHandler = function(evt) {
if (evt.type === 'keypress' && evt.originalEvent.keyCode !== 13 && evt.originalEvent.keyCode !== 32) {
return;
}
if (info !== null && info.type === 'click') {
if (this.info !== null && this.info.type === 'click') {
// Ignore double click.
return;
}
evt.preventDefault();
evt.stopPropagation();
config = evt.data.config;
// Find the element that this draghandle belongs to.
var clickedElement = $(evt.currentTarget),
sourceList = clickedElement.closest(config.listSelector),
sourceList = clickedElement.closest(this.config.listSelector),
movedElement = clickedElement.closest(sourceList.children());
if (!movedElement.length) {
return;
}
// Store information about moved element with original location.
dragCounter++;
info = {
this.dragCounter++;
this.info = {
element: movedElement,
sourceNextElement: movedElement.next(),
sourceList: sourceList,
@@ -337,8 +329,8 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
startTime: new Date().getTime()
};
executeCallback('dragstart');
displayMoveDialogue(clickedElement);
this.executeCallback(SortableList.EVENTS.DRAGSTART);
this.displayMoveDialogue(clickedElement);
};
/**
@@ -352,7 +344,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @param {jQuery} element
* @returns {(Object|null)}
*/
var getPositionInNode = function(pageX, pageY, element) {
SortableList.prototype.getPositionInNode = function(pageX, pageY, element) {
if (!element.length) {
return null;
}
@@ -372,24 +364,14 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
return null;
};
/**
* Callback for filter that checks that current element is not proxy
*
* @private
* @return {boolean}
*/
var isNotProxy = function() {
return !proxy || !proxy.length || this !== proxy[0];
};
/**
* Check if list is horizontal
*
* @param {jQuery} element
* @return {Boolean}
*/
var isListHorizontal = function(element) {
var isHorizontal = config.isHorizontal;
SortableList.prototype.isListHorizontal = function(element) {
var isHorizontal = this.config.isHorizontal;
if (isHorizontal === true || isHorizontal === false) {
return isHorizontal;
}
@@ -402,64 +384,69 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @private
* @param {Event} evt
*/
var dragHandler = function(evt) {
SortableList.prototype.dragHandler = function(evt) {
calculatePositionOnPage(evt);
this.calculatePositionOnPage(evt);
// We can not use evt.target here because it will most likely be our proxy.
// Move the proxy out of the way so we can find the element at the current mouse position.
proxy.offset({top: -1000, left: -1000});
this.proxy.offset({top: -1000, left: -1000});
// Find the element at the current mouse position.
var element = $(document.elementFromPoint(evt.clientX, evt.clientY));
// Find the list element and the list over the mouse position.
var current = element.closest('.' + config.targetListClass + ' > :not(.' + config.isDraggedClass + ')'),
currentList = element.closest('.' + config.targetListClass);
var current = element.closest('.' + CSS.targetListClass + ' > :not(.' + CSS.isDraggedClass + ')'),
currentList = element.closest('.' + CSS.targetListClass),
proxy = this.proxy,
isNotProxy = function() {
return !proxy || !proxy.length || this !== proxy[0];
};
// Add the specified class to the list element we are hovering.
$('.' + config.overElementClass).removeClass(config.overElementClass);
current.addClass(config.overElementClass);
$('.' + CSS.overElementClass).removeClass(CSS.overElementClass);
current.addClass(CSS.overElementClass);
// Move proxy to the current position.
proxy.offset({top: proxyDelta.y + evt.pageY, left: proxyDelta.x + evt.pageX});
this.proxy.offset({top: this.proxyDelta.y + evt.pageY, left: this.proxyDelta.x + evt.pageX});
if (currentList.length && !currentList.children().filter(isNotProxy).length) {
// Mouse is over an empty list.
moveElement(currentList, $());
} else if (current.length === 1 && !info.element.find(current[0]).length) {
this.moveElement(currentList, $());
} else if (current.length === 1 && !this.info.element.find(current[0]).length) {
// Mouse is over an element in a list - find whether we should move the current position
// above or below this element.
var coordinates = getPositionInNode(evt.pageX, evt.pageY, current);
var coordinates = this.getPositionInNode(evt.pageX, evt.pageY, current);
if (coordinates) {
var parent = current.parent(),
ratio = isListHorizontal(parent) ? coordinates.xRatio : coordinates.yRatio,
subList = current.find('.' + config.targetListClass),
ratio = this.isListHorizontal(parent) ? coordinates.xRatio : coordinates.yRatio,
subList = current.find('.' + CSS.targetListClass),
currentElement = this.info.element[0],
isNotCurrent = function() {
return this !== info.element[0];
return this !== currentElement;
},
subListEmpty = !subList.children().filter(isNotProxy).filter(isNotCurrent).length;
if (subList.length && subListEmpty && ratio > 0.2 && ratio < 0.8) {
// This is an element that is a parent of an empty list and we are around the middle of this element.
// Treat it as if we are over this empty list.
moveElement(subList, $());
this.moveElement(subList, $());
} else if (ratio > 0.5) {
// Insert after this element.
moveElement(parent, current.next().filter(isNotProxy));
this.moveElement(parent, current.next().filter(isNotProxy));
} else {
// Insert before this element.
moveElement(parent, current);
this.moveElement(parent, current);
}
}
}
if (evt.type === 'mouseup' || evt.type === 'touchend') {
// Drop the moved element.
info.endX = evt.pageX;
info.endY = evt.pageY;
info.endTime = new Date().getTime();
info.dropped = true;
executeCallback('drop');
finishDragging();
this.info.endX = evt.pageX;
this.info.endY = evt.pageY;
this.info.endTime = new Date().getTime();
this.info.dropped = true;
this.executeCallback(SortableList.EVENTS.DROP);
this.finishDragging();
}
};
@@ -470,15 +457,15 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @param {jQuery} parentElement
* @param {jQuery} beforeElement
*/
var moveElement = function(parentElement, beforeElement) {
var dragEl = info.element;
SortableList.prototype.moveElement = function(parentElement, beforeElement) {
var dragEl = this.info.element;
if (beforeElement.length && beforeElement[0] === dragEl[0]) {
// Insert before the current position of the dragged element - nothing to do.
return;
}
if (parentElement[0] === info.targetList[0] &&
beforeElement.length === info.targetNextElement.length &&
beforeElement[0] === info.targetNextElement[0]) {
if (parentElement[0] === this.info.targetList[0] &&
beforeElement.length === this.info.targetNextElement.length &&
beforeElement[0] === this.info.targetNextElement[0]) {
// Insert in the same location as the current position - nothing to do.
return;
}
@@ -486,34 +473,34 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
if (beforeElement.length) {
// Move the dragged element before the specified element.
parentElement[0].insertBefore(dragEl[0], beforeElement[0]);
} else if (proxy && proxy.parent().length && proxy.parent()[0] === parentElement[0]) {
} else if (this.proxy && this.proxy.parent().length && this.proxy.parent()[0] === parentElement[0]) {
// We need to move to the end of the list but the last element in this list is a proxy.
// Always leave the proxy in the end of the list.
parentElement[0].insertBefore(dragEl[0], proxy[0]);
parentElement[0].insertBefore(dragEl[0], this.proxy[0]);
} else {
// Insert in the end of a list (when proxy is in another list).
parentElement[0].appendChild(dragEl[0]);
}
// Save the current position of the dragged element in the list.
info.targetList = parentElement;
info.targetNextElement = beforeElement;
executeCallback('drag');
this.info.targetList = parentElement;
this.info.targetNextElement = beforeElement;
this.executeCallback(SortableList.EVENTS.DRAG);
};
/**
* Finish dragging (when dropped or cancelled).
* @private
*/
var finishDragging = function() {
resetDraggedClasses();
if (config.autoScroll) {
SortableList.prototype.finishDragging = function() {
this.resetDraggedClasses();
if (this.config.autoScroll) {
autoScroll.stop();
}
$('body').off('mousemove touchmove mouseup touchend', dragHandler);
$('body').off('keypress', dragcancelHandler);
executeCallback('dragend');
info = null;
$(window).off('mousemove touchmove mouseup touchend', $.proxy(this.dragHandler, this));
$(window).off('keypress', $.proxy(this.dragcancelHandler, this));
this.executeCallback(SortableList.EVENTS.DRAGEND);
this.info = null;
};
/**
@@ -522,8 +509,8 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @private
* @param {String} eventName
*/
var executeCallback = function(eventName) {
info.element.trigger('sortablelist-' + eventName, info);
SortableList.prototype.executeCallback = function(eventName) {
this.info.element.trigger(eventName, this.info);
};
/**
@@ -532,41 +519,25 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @private
* @param {Event} evt
*/
var dragcancelHandler = function(evt) {
SortableList.prototype.dragcancelHandler = function(evt) {
if (evt.type !== 'keypress' || evt.originalEvent.keyCode !== 27) {
// Only cancel dragging when Esc was pressed.
return;
}
// Dragging was cancelled. Return item to the original position.
moveElement(info.sourceList, info.sourceNextElement);
finishDragging();
};
/**
* Helper method to convert a string to a promise
*
* @private
* @param {(String|Promise)} value
* @return {Promise}
*/
var convertToPromise = function(value) {
var p = value;
if (typeof value !== 'object' || !value.hasOwnProperty('then')) {
p = $.Deferred();
p.resolve(value);
}
return p;
this.moveElement(this.info.sourceList, this.info.sourceNextElement);
this.finishDragging();
};
/**
* Returns the name of the current element to be used in the move dialogue
*
* @private
* @public
* @param {jQuery} element
* @return {Promise}
*/
var getElementName = function(element) {
return convertToPromise(config.elementNameCallback(element));
SortableList.prototype.getElementName = function(element) {
return $.Deferred().resolve(element.text());
};
/**
@@ -574,24 +545,33 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
*
* Note that we use "after" in the label for better UX
*
* @private
* @public
* @param {jQuery} parentElement
* @param {jQuery} afterElement
* @return {Promise}
*/
var getDestinationName = function(parentElement, afterElement) {
return convertToPromise(config.destinationNameCallback(parentElement, afterElement));
SortableList.prototype.getDestinationName = function(parentElement, afterElement) {
if (!afterElement.length) {
return str.get_string('movecontenttothetop', 'moodle');
} else {
return this.getElementName(afterElement)
.then(function(name) {
return str.get_string('movecontentafter', 'moodle', name);
});
}
};
/**
* Returns the title for the move dialogue ("Move elementY")
*
* @private
* @public
* @param {jQuery} element
* @return {Promise}
*/
var getMoveDialogueTitle = function(element) {
return convertToPromise(config.moveDialogueTitleCallback(element));
SortableList.prototype.getMoveDialogueTitle = function(element) {
return this.getElementName(element).then(function(name) {
return str.get_string('movecontent', 'moodle', name);
});
};
/**
@@ -600,28 +580,33 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @private
* @return {Promise}
*/
var getDestinationsList = function() {
SortableList.prototype.getDestinationsList = function() {
var addedLists = [],
targets = $(config.targetListSelector),
destinations = $('<ul/>').addClass(config.keyboardDragClass),
targets = $(this.config.targetListSelector),
destinations = $('<ul/>').addClass(CSS.keyboardDragClass),
result = $.when().then(function() {
return destinations;
}),
createLink = function(parentElement, beforeElement, afterElement) {
if (beforeElement.is(info.element) || afterElement.is(info.element)) {
createLink = $.proxy(function(parentElement, beforeElement, afterElement) {
if (beforeElement.is(this.info.element) || afterElement.is(this.info.element)) {
// Can not move before or after itself.
return;
}
if ($.contains(this.info.element[0], parentElement[0])) {
// Can not move to its own child.
return;
}
result = result
.then(function() {
return getDestinationName(parentElement, afterElement);
})
.then($.proxy(function() {
return this.getDestinationName(parentElement, afterElement);
}, this))
.then(function(txt) {
var li = $('<li/>').appendTo(destinations);
var a = $('<a href="#"/>').attr('data-sortable-quickmove', 1).appendTo(li);
var a = $('<a href="#"/>').attr('data-core_sortable_list-quickmove', 1).appendTo(li);
a.data('parent-element', parentElement).data('before-element', beforeElement).text(txt);
return destinations;
});
},
}, this),
addList = function() {
// Destination lists may be nested. We want to add all move destinations in the same
// order they appear on the screen for the user.
@@ -648,82 +633,35 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
* @param {jQuery} clickedElement element to return focus to after the modal is closed
* @private
*/
var displayMoveDialogue = function(clickedElement) {
SortableList.prototype.displayMoveDialogue = function(clickedElement) {
ModalFactory.create({
type: ModalFactory.types.CANCEL,
title: getMoveDialogueTitle(info.element),
body: getDestinationsList()
}).then(function(modal) {
var quickMoveHandler = function(e) {
title: this.getMoveDialogueTitle(this.info.element),
body: this.getDestinationsList()
}).then($.proxy(function(modal) {
var quickMoveHandler = $.proxy(function(e) {
e.preventDefault();
e.stopPropagation();
moveElement($(e.currentTarget).data('parent-element'), $(e.currentTarget).data('before-element'));
info.endTime = new Date().getTime();
info.dropped = true;
this.moveElement($(e.currentTarget).data('parent-element'), $(e.currentTarget).data('before-element'));
this.info.endTime = new Date().getTime();
this.info.dropped = true;
clickedElement.focus();
executeCallback('drop');
this.executeCallback(SortableList.EVENTS.DROP);
modal.hide();
};
modal.getRoot().on('click', '[data-sortable-quickmove]', quickMoveHandler);
modal.getRoot().on(ModalEvents.hidden, function() {
}, this);
modal.getRoot().on('click', '[data-core_sortable_list-quickmove]', quickMoveHandler);
modal.getRoot().on(ModalEvents.hidden, $.proxy(function() {
// Always destroy when hidden, it is generated dynamically each time.
modal.getRoot().off('click', '[data-sortable-quickmove]', quickMoveHandler);
modal.getRoot().off('click', '[data-core_sortable_list-quickmove]', quickMoveHandler);
modal.destroy();
finishDragging();
});
this.finishDragging();
}, this));
modal.setLarge();
modal.show();
return modal;
}).catch(Notification.exception);
}, this)).catch(Notification.exception);
};
return {
/**
* Initialise sortable list.
*
* @param {(String|jQuery|Element)} root JQuery/DOM element representing sortable list (i.e. <ul>, <tbody>) or CSS selector
* @param {Object} config Parameters for the list. See defaultParameters above for examples.
* @property {(String|jQuery|Element)} config.targetListSelector target lists, by default same as root
* @property {String} config.moveHandlerSelector CSS selector for a drag handle. By default the whole item is a handle.
* Without drag handle sorting is not accessible!
* @property {(Boolean|Function)} config.isHorizontal Set to true if the list is horizontal
* (can also be a callback with list as an argument)
* @property {Boolean} config.autoScroll Engages autoscroll module for automatic vertical scrolling of the
* whole page, by default true
* @property {Function} config.elementNameCallback Should return a string or Promise. Used for move dialogue title and
* destination name
* @property {Function} config.destinationNameCallback Callback that returns a string or Promise with the label
* for the move destination
* @property {Function} config.moveDialogueTitleCallback Should return a string or Promise. Used to form move dialogue title
* @property {String} config.keyboardDragClass Class of the list of destinations in the popup
* (default 'dragdrop-keyboard-drag')
* @property {String} config.isDraggedClass Class added to the element that is dragged
* (default 'sortable-list-is-dragged')
* @property {String} config.currentPositionClass Class added to the current position of a dragged element
* (default 'sortable-list-current-position')
* @property {String} config.sourceListClass Class added to the list where dragging was started from
* (default 'sortable-list-source')
* @property {String} config.targetListClass Class added to all lists where item can be dropped
* (default 'sortable-list-target')
* @property {String} config.overElementClass Class added to the list element when the dragged element is above it
* (default 'sortable-list-over-element')
*/
init: function(root, config) {
config = $.extend({}, defaultParameters, CSS, config || {});
config.listSelector = root;
if (!config.targetListSelector) {
config.targetListSelector = root;
}
if (typeof config.listSelector === 'object') {
// The root is an element on the page. Register a listener for this element.
$(config.listSelector).on('mousedown touchstart', {config: config}, dragStartHandler);
} else {
// The root is a CSS selector. Register a listener that picks up the element dynamically.
$('body').on('mousedown touchstart', config.listSelector, {config: config}, dragStartHandler);
}
if (config.moveHandlerSelector !== null) {
$('body').on('click keypress', config.moveHandlerSelector, {config: config}, clickHandler);
}
}
};
return SortableList;
});