MDL-51803 core: integration review
authorMarina Glancy <marina@moodle.com>
Tue, 2 Oct 2018 09:23:18 +0000 (11:23 +0200)
committerMarina Glancy <marina@moodle.com>
Mon, 8 Oct 2018 09:50:47 +0000 (11:50 +0200)
lib/amd/build/autoscroll.min.js
lib/amd/build/sortable_list.min.js
lib/amd/src/autoscroll.js
lib/amd/src/sortable_list.js

index 9bd86ba..2c179ea 100644 (file)
Binary files a/lib/amd/build/autoscroll.min.js and b/lib/amd/build/autoscroll.min.js differ
index 85971fb..57297b4 100644 (file)
Binary files a/lib/amd/build/sortable_list.min.js and b/lib/amd/build/sortable_list.min.js differ
index f8a57b1..3473dc9 100644 (file)
@@ -74,7 +74,7 @@ define(['jquery'], function($) {
          * This should be called in response to mouse down or touch start.
          *
          * @public
-         * @param {Object} callback Optional callback that is called every time it scrolls
+         * @param {Function} callback Optional callback that is called every time it scrolls
          */
         start: function(callback) {
             $(window).on('mousemove', autoscroll.mouseMove);
@@ -188,5 +188,22 @@ define(['jquery'], function($) {
         }
     };
 
-    return autoscroll;
+    return {
+        /**
+         * Starts automatic scrolling if user moves near edge of window.
+         * This should be called in response to mouse down or touch start.
+         *
+         * @public
+         * @param {Function} callback Optional callback that is called every time it scrolls
+         */
+        start: autoscroll.start,
+
+        /**
+         * Stops automatic scrolling. This should be called in response to mouse up or touch end.
+         *
+         * @public
+         */
+        stop: autoscroll.stop,
+    };
+
 });
index 69e9708..78c1daa 100644 (file)
@@ -52,23 +52,11 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
     /**
      * Default parameters
      *
-     * @property {String} listSelector                CSS selector for sortable lists, must be specified during initialization.
-     * @property {String} 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} isHorizontal      Set to true if the list is horizontal
-     *                                                (can also be a callback with list as an argument)
-     * @property {Boolean} autoScroll                 Engages autoscroll module for automatic vertical scrolling of the whole page
-     * @property {Function} elementNameCallback       Should return a string or Promise. Used for move dialogue title and
-     *                                                destination name
-     * @property {Function} destinationNameCallback   Callback that returns a string or Promise with the label
-     *                                                for the move destination
-     * @property {Function} moveDialogueTitleCallback Should return a string or Promise. Used to form move dialogue title
-     *
      * @private
      * @type {Object}
      */
     var defaultParameters = {
-        listSelector: null,
+        targetListSelector: null,
         moveHandlerSelector: null,
         isHorizontal: false,
         autoScroll: true,
@@ -99,12 +87,12 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @type {Object}
      */
     var CSS = {
-        keyboardDragClass: 'dragdrop-keyboard-drag', /* Class of the list of destinations in the popup */
-        isDraggedClass: 'sortable-list-is-dragged', /* Class added to the element that is dragged. */
-        currentPositionClass: 'sortable-list-current-position', /* Class added to the current position of a dragged element. */
-        sourceListClass: 'sortable-list-source', /* Class added to the list where dragging was started from. */
-        targetListClass: 'sortable-list-target', /* Class added to all lists where item can be dropped. */
-        overElementClass: 'sortable-list-over-element' /* Class added to the list element when the dragged element is above it. */
+        keyboardDragClass: 'dragdrop-keyboard-drag',
+        isDraggedClass: 'sortable-list-is-dragged',
+        currentPositionClass: 'sortable-list-current-position',
+        sourceListClass: 'sortable-list-source',
+        targetListClass: 'sortable-list-target',
+        overElementClass: 'sortable-list-over-element'
     };
 
     /**
@@ -113,7 +101,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @private
      * @type {Object}
      */
-    var params = {};
+    var config = {};
 
     /**
      * Stores information about currently dragged item
@@ -152,14 +140,16 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @private
      */
     var resetDraggedClasses = function() {
-        var lists = $(params.listSelector);
-        lists.children()
-            .removeClass(params.isDraggedClass)
-            .removeClass(params.currentPositionClass)
-            .removeClass(params.overElementClass);
-        lists
-            .removeClass(params.targetListClass)
-            .removeClass(params.sourceListClass);
+        var classes = [
+            config.isDraggedClass,
+            config.currentPositionClass,
+            config.overElementClass,
+            config.targetListClass,
+            config.sourceListClass
+        ];
+        for (var i in classes) {
+            $('.' + classes[i]).removeClass(classes[i]);
+        }
         if (proxy) {
             proxy.remove();
             proxy = $();
@@ -213,7 +203,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @param {Event} evt
      */
     var dragStartHandler = function(evt) {
-        params = evt.data.params;
+        config = evt.data.config;
         if (info !== null) {
             if (info.type === 'click') {
                 // Ignore double click.
@@ -225,16 +215,20 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
         }
 
         if (evt.type === 'mousedown' && evt.which !== 1) {
-            // We only need left mouse click.
+            // We only need left mouse click. If this is a mousedown event with right/middle click ignore it.
             return;
         }
 
         calculatePositionOnPage(evt);
-        var movedElement = $(evt.currentTarget);
+        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.
+            return;
+        }
 
         // Check that we grabbed the element by the handle.
-        if (params.moveHandlerSelector !== null) {
-            if (!$(evt.target).closest(params.moveHandlerSelector, movedElement).length) {
+        if (config.moveHandlerSelector !== null) {
+            if (!$(evt.target).closest(config.moveHandlerSelector, movedElement).length) {
                 return;
             }
         }
@@ -258,14 +252,18 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
             startTime: new Date().getTime()
         };
 
-        $(params.listSelector).addClass(params.targetListClass);
+        $(config.targetListSelector).addClass(config.targetListClass);
 
         var offset = movedElement.offset();
-        movedElement.addClass(params.currentPositionClass);
+        movedElement.addClass(config.currentPositionClass);
         proxyDelta = {x: offset.left - evt.pageX, y: offset.top - evt.pageY};
         proxy = $();
         var thisDragCounter = dragCounter;
         setTimeout(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) {
                 return;
             }
@@ -279,8 +277,8 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
         $('body').on('keypress', dragcancelHandler);
 
         // Start autoscrolling. Every time the page is scrolled emulate the mousemove event.
-        if (params.autoScroll) {
-            autoScroll.start(function () {
+        if (config.autoScroll) {
+            autoScroll.start(function() {
                 $('body').trigger('mousemove');
             });
         }
@@ -295,8 +293,8 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
     var createProxy = function() {
         proxy = info.element.clone();
         info.sourceList.append(proxy);
-        proxy.removeAttr('id').removeClass(params.currentPositionClass)
-            .addClass(params.isDraggedClass).css({position: 'fixed'});
+        proxy.removeAttr('id').removeClass(config.currentPositionClass)
+            .addClass(config.isDraggedClass).css({position: 'fixed'});
         proxy.offset({top: proxyDelta.y + lastEvent.pageY, left: proxyDelta.x + lastEvent.pageX});
     };
 
@@ -316,13 +314,11 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
         }
         evt.preventDefault();
         evt.stopPropagation();
-        params = evt.data.params;
+        config = evt.data.config;
 
         // Find the element that this draghandle belongs to.
-        var sourceList = $(evt.currentTarget).closest(params.listSelector),
-            movedElement = sourceList.children().filter(function() {
-                return $.contains(this, evt.currentTarget);
-            });
+        var sourceList = $(evt.currentTarget).closest(config.listSelector),
+            movedElement = $(evt.target).closest(sourceList.children());
         if (!movedElement.length) {
             return;
         }
@@ -392,7 +388,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @return {Boolean}
      */
     var isListHorizontal = function(element) {
-        var isHorizontal = params.isHorizontal;
+        var isHorizontal = config.isHorizontal;
         if (isHorizontal === true || isHorizontal === false) {
             return isHorizontal;
         }
@@ -416,12 +412,12 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
         var element = $(document.elementFromPoint(evt.clientX, evt.clientY));
 
         // Find the list element and the list over the mouse position.
-        var current = element.closest('.' + params.targetListClass + ' > :not(.' + params.isDraggedClass + ')'),
-            currentList = element.closest('.' + params.targetListClass);
+        var current = element.closest('.' + config.targetListClass + ' > :not(.' + config.isDraggedClass + ')'),
+            currentList = element.closest('.' + config.targetListClass);
 
         // Add the specified class to the list element we are hovering.
-        $('.' + params.overElementClass).removeClass(params.overElementClass);
-        current.addClass(params.overElementClass);
+        $('.' + config.overElementClass).removeClass(config.overElementClass);
+        current.addClass(config.overElementClass);
 
         // Move proxy to the current position.
         proxy.offset({top: proxyDelta.y + evt.pageY, left: proxyDelta.x + evt.pageX});
@@ -436,7 +432,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
             if (coordinates) {
                 var parent = current.parent(),
                     ratio = isListHorizontal(parent) ? coordinates.xRatio : coordinates.yRatio,
-                    subList = current.find('.' + params.targetListClass),
+                    subList = current.find('.' + config.targetListClass),
                     isNotCurrent = function() {
                         return this !== info.element[0];
                     },
@@ -510,7 +506,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      */
     var finishDragging = function() {
         resetDraggedClasses();
-        if (params.autoScroll) {
+        if (config.autoScroll) {
             autoScroll.stop();
         }
         $('body').off('mousemove touchmove mouseup touchend', dragHandler);
@@ -569,7 +565,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @return {Promise}
      */
     var getElementName = function(element) {
-        return convertToPromise(params.elementNameCallback(element));
+        return convertToPromise(config.elementNameCallback(element));
     };
 
     /**
@@ -583,7 +579,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @return {Promise}
      */
     var getDestinationName = function(parentElement, afterElement) {
-        return convertToPromise(params.destinationNameCallback(parentElement, afterElement));
+        return convertToPromise(config.destinationNameCallback(parentElement, afterElement));
     };
 
     /**
@@ -594,7 +590,7 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      * @return {Promise}
      */
     var getMoveDialogueTitle = function(element) {
-        return convertToPromise(params.moveDialogueTitleCallback(element));
+        return convertToPromise(config.moveDialogueTitleCallback(element));
     };
 
     /**
@@ -606,21 +602,21 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
      */
     var getDestinationsList = function(modal) {
         var addedLists = [],
-            targets = $(params.listSelector),
-            list = $('<ul/>').addClass(params.keyboardDragClass),
+            targets = $(config.targetListSelector),
+            list = $('<ul/>').addClass(config.keyboardDragClass),
             createLink = function(parentElement, beforeElement, afterElement) {
                 if (beforeElement.is(info.element) || afterElement.is(info.element)) {
                     return;
                 }
                 var li = $('<li/>').appendTo(list);
                 var a = $('<a href="#"/>')
-                    .click(function(e) {
+                    .on('click', function(e) {
                         e.preventDefault();
                         e.stopPropagation();
                         moveElement(parentElement, beforeElement);
                         info.endTime = new Date().getTime();
                         info.dropped = true;
-                        info.element.find(params.moveHandlerSelector).focus();
+                        info.element.find(config.moveHandlerSelector).focus();
                         executeCallback('drop');
                         modal.hide();
                     })
@@ -668,24 +664,56 @@ function($, log, autoScroll, str, ModalFactory, ModalEvents, Notification) {
             modal.setLarge();
             modal.show();
             return modal;
-        });
+        }).catch(Notification.exception);
     };
 
     return {
         /**
          * Initialise sortable list.
          *
-         * @param {Object} params Parameters for the list. See defaultParameters above for examples.
+         * @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(params) {
-            if (typeof params.listSelector === 'undefined') {
-                log.error('Parameter listSelector must be specified');
-                return;
+        init: function(root, config) {
+            if (typeof config === 'undefined' || !config) {
+                config = {};
+            }
+            config = $.extend({}, defaultParameters, CSS, config);
+            config.listSelector = root;
+            if (!config.targetListSelector) {
+                config.targetListSelector = root;
+            }
+            if (typeof config.listSelector === 'object') {
+                $(config.listSelector).on('mousedown touchstart', {config: config}, dragStartHandler);
+            } else {
+                $('body').on('mousedown touchstart', config.listSelector, {config: config}, dragStartHandler);
             }
-            params = $.extend({}, defaultParameters, CSS, params);
-            $(params.listSelector).on('mousedown touchstart', '> *', {params: params}, dragStartHandler);
-            if (params.moveHandlerSelector !== null) {
-                $(params.listSelector).on('click keypress', params.moveHandlerSelector, {params: params}, clickHandler);
+            if (config.moveHandlerSelector !== null) {
+                $('body').on('click keypress', config.moveHandlerSelector, {config: config}, clickHandler);
             }
         }
     };