MDL-38774 JavaScript: Migrate moodle-core-dragdrop to Shifter
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 4 Dec 2013 06:13:41 +0000 (14:13 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 9 Dec 2013 03:27:44 +0000 (11:27 +0800)
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js [new file with mode: 0644]
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js [new file with mode: 0644]
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js [new file with mode: 0644]
lib/yui/dragdrop/dragdrop.js [deleted file]
lib/yui/src/dragdrop/build.json [new file with mode: 0644]
lib/yui/src/dragdrop/js/dragdrop.js [new file with mode: 0644]
lib/yui/src/dragdrop/meta/dragdrop.json [new file with mode: 0644]

diff --git a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js
new file mode 100644 (file)
index 0000000..5014285
Binary files /dev/null and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js differ
diff --git a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js
new file mode 100644 (file)
index 0000000..7cd70d5
Binary files /dev/null and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js differ
diff --git a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js
new file mode 100644 (file)
index 0000000..5014285
Binary files /dev/null and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js differ
diff --git a/lib/yui/dragdrop/dragdrop.js b/lib/yui/dragdrop/dragdrop.js
deleted file mode 100644 (file)
index 773373c..0000000
+++ /dev/null
@@ -1,513 +0,0 @@
-YUI.add('moodle-core-dragdrop', function(Y) {
-    var MOVEICON = {
-        pix: "i/move_2d",
-        largepix: "i/dragdrop",
-        component: 'moodle',
-        cssclass: 'moodle-core-dragdrop-draghandle'
-    };
-
-   /*
-    * General DRAGDROP class, this should not be used directly,
-    * it is supposed to be extended by your class
-    */
-    var DRAGDROP = function() {
-        DRAGDROP.superclass.constructor.apply(this, arguments);
-    };
-
-    Y.extend(DRAGDROP, Y.Base, {
-        goingup : null,
-        absgoingup : null,
-        samenodeclass : null,
-        parentnodeclass : null,
-        groups : [],
-        lastdroptarget : null,
-        initializer : function() {
-            // Listen for all drag:start events
-            Y.DD.DDM.on('drag:start', this.global_drag_start, this);
-            // Listen for all drag:end events
-            Y.DD.DDM.on('drag:end', this.global_drag_end, this);
-            // Listen for all drag:drag events
-            Y.DD.DDM.on('drag:drag', this.global_drag_drag, this);
-            // Listen for all drop:over events
-            Y.DD.DDM.on('drop:over', this.global_drop_over, this);
-            // Listen for all drop:hit events
-            Y.DD.DDM.on('drop:hit', this.global_drop_hit, this);
-            // Listen for all drop:miss events
-            Y.DD.DDM.on('drag:dropmiss', this.global_drag_dropmiss, this);
-
-            Y.one(Y.config.doc.body).delegate('key', this.global_keydown, 'down:32, enter, esc', '.' + MOVEICON.cssclass, this);
-            Y.one(Y.config.doc.body).delegate('click', this.global_keydown, '.' + MOVEICON.cssclass , this);
-        },
-
-        get_drag_handle: function(title, classname, iconclass, large) {
-            var iconname = MOVEICON.pix;
-            if (large) {
-                iconname = MOVEICON.largepix;
-            }
-            var dragicon = Y.Node.create('<img />')
-                .setStyle('cursor', 'move')
-                .setAttrs({
-                    'src' : M.util.image_url(iconname, MOVEICON.component),
-                    'alt' : title
-                });
-            if (iconclass) {
-                dragicon.addClass(iconclass);
-            }
-
-            var dragelement = Y.Node.create('<span></span>')
-                .addClass(classname)
-                .setAttribute('title', title)
-                .setAttribute('tabIndex', 0)
-                .setAttribute('data-draggroups', this.groups)
-                .setAttribute('aria-grabbed', 'false')
-                .setAttribute('role', 'button');
-            dragelement.appendChild(dragicon);
-            dragelement.addClass(MOVEICON.cssclass);
-
-            return dragelement;
-        },
-
-        lock_drag_handle: function(drag, classname) {
-            drag.removeHandle('.'+classname);
-        },
-
-        unlock_drag_handle: function(drag, classname) {
-            drag.addHandle('.'+classname);
-        },
-
-        ajax_failure: function(response) {
-            var e = {
-                name : response.status+' '+response.statusText,
-                message : response.responseText
-            };
-            return new M.core.exception(e);
-        },
-
-        in_group: function(target) {
-            var ret = false;
-            Y.each(this.groups, function(v, k) {
-                if (target._groups[v]) {
-                    ret = true;
-                }
-            }, this);
-            return ret;
-        },
-        /*
-         * Drag-dropping related functions
-         */
-        global_drag_start : function(e) {
-            // Get our drag object
-            var drag = e.target;
-            // Check that drag object belongs to correct group
-            if (!this.in_group(drag)) {
-                return;
-            }
-            // Set some general styles here
-            drag.get('node').setStyle('opacity', '.25');
-            drag.get('dragNode').setStyles({
-                opacity: '.75',
-                borderColor: drag.get('node').getStyle('borderColor'),
-                backgroundColor: drag.get('node').getStyle('backgroundColor')
-            });
-            drag.get('dragNode').empty();
-            this.drag_start(e);
-        },
-
-        global_drag_end : function(e) {
-            var drag = e.target;
-            // Check that drag object belongs to correct group
-            if (!this.in_group(drag)) {
-                return;
-            }
-            //Put our general styles back
-            drag.get('node').setStyles({
-                visibility: '',
-                opacity: '1'
-            });
-            this.drag_end(e);
-        },
-
-        global_drag_drag : function(e) {
-            var drag = e.target,
-                info = e.info;
-
-            // Check that drag object belongs to correct group
-            if (!this.in_group(drag)) {
-                return;
-            }
-
-            // Note, we test both < and > situations here. We don't want to
-            // effect a change in direction if the user is only moving side
-            // to side with no Y position change.
-
-            // Detect changes in the position relative to the start point.
-            if (info.start[1] < info.xy[1]) {
-                // We are going up if our final position is higher than our start position.
-                this.absgoingup = true;
-
-            } else if (info.start[1] > info.xy[1]) {
-                // Otherwise we're going down.
-                this.absgoingup = false;
-            }
-
-            // Detect changes in the position relative to the last movement.
-            if (info.delta[1] < 0) {
-                // We are going up if our final position is higher than our start position.
-                this.goingup = true;
-
-            } else if (info.delta[1] > 0) {
-                // Otherwise we're going down.
-                this.goingup = false;
-            }
-
-            this.drag_drag(e);
-        },
-
-        global_drop_over : function(e) {
-            // Check that drop object belong to correct group.
-            if (!e.drop || !e.drop.inGroup(this.groups)) {
-                return;
-            }
-
-            // Get a reference to our drag and drop nodes.
-            var drag = e.drag.get('node'),
-                drop = e.drop.get('node');
-
-            // Save last drop target for the case of missed target processing.
-            this.lastdroptarget = e.drop;
-
-            // Are we dropping within the same parent node?
-            if (drop.hasClass(this.samenodeclass)) {
-                var where;
-
-                if (this.goingup) {
-                    where = "before";
-                } else {
-                    where = "after";
-                }
-
-                // Add the node contents so that it's moved, otherwise only the drag handle is moved.
-                drop.insert(drag, where);
-            } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
-                // We are dropping on parent node and it is empty
-                if (this.goingup) {
-                    drop.append(drag);
-                } else {
-                    drop.prepend(drag);
-                }
-            }
-            this.drop_over(e);
-        },
-
-        global_drag_dropmiss : function(e) {
-            // drag:dropmiss does not have e.drag and e.drop properties
-            // we substitute them for the ease of use. For e.drop we use,
-            // this.lastdroptarget (ghost node we use for indicating where to drop)
-            e.drag = e.target;
-            e.drop = this.lastdroptarget;
-            // Check that drag object belongs to correct group
-            if (!this.in_group(e.drag)) {
-                return;
-            }
-            // Check that drop object belong to correct group
-            if (!e.drop || !e.drop.inGroup(this.groups)) {
-                return;
-            }
-            this.drag_dropmiss(e);
-        },
-
-        global_drop_hit : function(e) {
-            // Check that drop object belong to correct group
-            if (!e.drop || !e.drop.inGroup(this.groups)) {
-                return;
-            }
-            this.drop_hit(e);
-        },
-
-        /**
-         * This is used to build the text for the heading of the keyboard
-         * drag drop menu and the text for the nodes in the list.
-         * @method find_element_text
-         * @param {Node} n The node to start searching for a valid text node.
-         * @returns {string} The text of the first text-like child node of n.
-         */
-        find_element_text : function(n) {
-            // The valid node types to get text from.
-            var nodes = n.all('h2, h3, h4, h5, span, p, div.no-overflow, div.dimmed_text');
-            var text = '';
-
-            nodes.each(function () {
-                if (text == '') {
-                    if (Y.Lang.trim(this.get('text')) != '') {
-                        text = this.get('text');
-                    }
-                }
-            });
-
-            if (text != '') {
-                return text;
-            }
-            return M.util.get_string('emptydragdropregion', 'moodle');
-        },
-
-        /**
-         * This is used to initiate a keyboard version of a drag and drop.
-         * A dialog will open listing all the valid drop targets that can be selected
-         * using tab, tab, tab, enter.
-         * @method global_start_keyboard_drag
-         * @param {Event} e The keydown / click event on the grab handle.
-         * @param {Node} dragcontainer The resolved draggable node (an ancestor of the drag handle).
-         * @param {Node} draghandle The node that triggered this action.
-         */
-        global_start_keyboard_drag : function(e, draghandle, dragcontainer) {
-            M.core.dragdrop.keydragcontainer = dragcontainer;
-            M.core.dragdrop.keydraghandle = draghandle;
-
-            // Indicate to a screenreader the node that is selected for drag and drop.
-            dragcontainer.setAttribute('aria-grabbed', 'true');
-            // Get the name of the thing to move.
-            var nodetitle = this.find_element_text(dragcontainer);
-            var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
-
-            // Build the list of drop targets.
-            var droplist = Y.Node.create('<ul></ul>');
-            droplist.addClass('dragdrop-keyboard-drag');
-            var listitem;
-            var listitemtext;
-
-            // Search for possible drop targets.
-            var droptargets = Y.all('.' + this.samenodeclass + ', .' + this.parentnodeclass);
-
-            droptargets.each(function (node) {
-                var validdrop = false, labelroot = node;
-                if (node.drop && node.drop.inGroup(this.groups) && node.drop.get('node') != dragcontainer) {
-                    // This is a drag and drop target with the same class as the grabbed node.
-                    validdrop = true;
-                } else {
-                    var elementgroups = node.getAttribute('data-draggroups').split(' ');
-                    var i, j;
-                    for (i = 0; i < elementgroups.length; i++) {
-                        for (j = 0; j < this.groups.length; j++) {
-                            if (elementgroups[i] == this.groups[j]) {
-                                // This is a parent node of the grabbed node (used for dropping in empty sections).
-                                validdrop = true;
-                                // This node will have no text - so we get the first valid text from the parent.
-                                labelroot = node.get('parentNode');
-                                break;
-                            }
-                        }
-                        if (validdrop) {
-                            break;
-                        }
-                    }
-                }
-
-                if (validdrop) {
-                    // It is a valid drop target - create a list item for it.
-                    listitem = Y.Node.create('<li></li>');
-                    listlink = Y.Node.create('<a></a>');
-                    nodetitle = this.find_element_text(labelroot);
-
-                    listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
-                    listlink.setContent(listitemtext);
-
-                    // Add a data attribute so we can get the real drop target.
-                    listlink.setAttribute('data-drop-target', node.get('id'));
-                    // Notify the screen reader this is a valid drop target.
-                    listlink.setAttribute('aria-dropeffect', 'move');
-                    // Allow tabbing to the link.
-                    listlink.setAttribute('tabindex', '0');
-
-                    // Set the event listeners for enter, space or click.
-                    listlink.on('click', this.global_keyboard_drop, this);
-                    listlink.on('key', this.global_keyboard_drop, 'down:enter,32', this);
-
-                    // Add to the list or drop targets.
-                    listitem.append(listlink);
-                    droplist.append(listitem);
-                }
-            }, this);
-
-            // Create the dialog for the interaction.
-            M.core.dragdrop.dropui = new M.core.dialogue({
-                headerContent : dialogtitle,
-                bodyContent : droplist,
-                draggable : true,
-                visible : true,
-                centered : true
-            });
-
-            // Focus the first drop target.
-            if (droplist.one('a')) {
-                droplist.one('a').focus();
-            }
-        },
-
-        /**
-         * This is used as a simulated drag/drop event in order to prevent any
-         * subtle bugs from creating a real instance of a drag drop event. This means
-         * there are no state changes in the Y.DD.DDM and any undefined functions
-         * will trigger an obvious and fatal error.
-         * The end result is that we call all our drag/drop handlers but do not bubble the
-         * event to anyone else.
-         *
-         * The functions/properties implemented in the wrapper are:
-         * e.target
-         * e.drag
-         * e.drop
-         * e.drag.get('node')
-         * e.drop.get('node')
-         * e.drag.addHandle()
-         * e.drag.removeHandle()
-         *
-         * @class simulated_drag_drop_event
-         * @param {Node} dragnode The drag container node
-         * @param {Node} dropnode The node to initiate the drop on
-         */
-        simulated_drag_drop_event : function(dragnode, dropnode) {
-
-            // Subclass for wrapping both drag and drop.
-            var dragdropwrapper = function(node) {
-                this.node = node;
-            }
-
-            // Method e.drag.get() - get the node.
-            dragdropwrapper.prototype.get = function(param) {
-                if (param == 'node' || param == 'dragNode' || param == 'dropNode') {
-                    return this.node;
-                }
-                return null;
-            };
-
-            // Method e.drag.inGroup() - we have already run the group checks before triggering the event.
-            dragdropwrapper.prototype.inGroup = function() {
-                return true;
-            };
-
-            // Method e.drag.addHandle() - we don't want to run this.
-            dragdropwrapper.prototype.addHandle = function() {};
-            // Method e.drag.removeHandle() - we don't want to run this.
-            dragdropwrapper.prototype.removeHandle = function() {};
-
-            // Create instances of the dragdropwrapper.
-            this.drop = new dragdropwrapper(dropnode);
-            this.drag = new dragdropwrapper(dragnode);
-            this.target = this.drop;
-        },
-
-        /**
-         * This is used to complete a keyboard version of a drag and drop.
-         * A drop event will be simulated based on the drag and drop nodes.
-         * @method global_keyboard_drop
-         * @param {Event} e The keydown / click event on the proxy drop node.
-         */
-        global_keyboard_drop : function(e) {
-            // The drag node was saved.
-            var dragcontainer = M.core.dragdrop.keydragcontainer;
-            dragcontainer.setAttribute('aria-grabbed', 'false');
-            // The real drop node is stored in an attribute of the proxy.
-            var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
-
-            // Close the dialog.
-            M.core.dragdrop.dropui.hide();
-            // Cancel the event.
-            e.preventDefault();
-            // Convert to drag drop events.
-            var dragevent = new this.simulated_drag_drop_event(dragcontainer, dragcontainer);
-            var dropevent = new this.simulated_drag_drop_event(dragcontainer, droptarget);
-            // Simulate the full sequence.
-            this.drag_start(dragevent);
-            this.global_drop_over(dropevent);
-            this.global_drop_hit(dropevent);
-            M.core.dragdrop.keydraghandle.focus();
-        },
-
-        /**
-         * This is used to cancel a keyboard version of a drag and drop.
-         *
-         * @method global_cancel_keyboard_drag
-         */
-        global_cancel_keyboard_drag : function() {
-            if (M.core.dragdrop.keydragcontainer) {
-                M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
-                M.core.dragdrop.keydraghandle.focus();
-                M.core.dragdrop.keydragcontainer = null;
-            }
-        },
-
-        /**
-         * Process key events on the drag handles.
-         * @method global_keydown
-         * @param {Event} e The keydown / click event on the drag handle.
-         */
-        global_keydown : function(e) {
-            var draghandle = e.target.ancestor('.' + MOVEICON.cssclass, true),
-                dragcontainer,
-                draggroups;
-
-            if (draghandle === null) {
-                // The element clicked did not have a a draghandle in it's lineage.
-                return;
-            }
-
-            if (e.keyCode === 27 ) {
-                // Escape to cancel from anywhere.
-                this.global_cancel_keyboard_drag();
-                e.preventDefault();
-                return;
-            }
-
-            // Only process events on a drag handle.
-            if (!draghandle.hasClass(MOVEICON.cssclass)) {
-                return;
-            }
-
-            // Do nothing if not space or enter.
-            if (e.keyCode !== 13 && e.keyCode !== 32 && e.type !== 'click') {
-                return;
-            }
-
-            // Check the drag groups to see if we are the handler for this node.
-            draggroups = draghandle.getAttribute('data-draggroups').split(' ');
-            var i, j, validgroup = false;
-
-            for (i = 0; i < draggroups.length; i++) {
-                for (j = 0; j < this.groups.length; j++) {
-                    if (draggroups[i] === this.groups[j]) {
-                        validgroup = true;
-                        break;
-                    }
-                }
-                if (validgroup) {
-                    break;
-                }
-            }
-            if (!validgroup) {
-                return;
-            }
-
-            // Valid event - start the keyboard drag.
-            dragcontainer = draghandle.ancestor('.yui3-dd-drop');
-            this.global_start_keyboard_drag(e, draghandle, dragcontainer);
-
-            e.preventDefault();
-        },
-
-        /*
-         * Abstract functions definitions
-         */
-        drag_start : function(e) {},
-        drag_end : function(e) {},
-        drag_drag : function(e) {},
-        drag_dropmiss : function(e) {},
-        drop_over : function(e) {},
-        drop_hit : function(e) {}
-    }, {
-        NAME : 'dragdrop',
-        ATTRS : {}
-    });
-
-M.core = M.core || {};
-M.core.dragdrop = DRAGDROP;
-
-}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'event-key', 'event-focus', 'moodle-core-notification']});
diff --git a/lib/yui/src/dragdrop/build.json b/lib/yui/src/dragdrop/build.json
new file mode 100644 (file)
index 0000000..e21619a
--- /dev/null
@@ -0,0 +1,10 @@
+{
+    "name": "moodle-core-dragdrop",
+    "builds": {
+        "moodle-core-dragdrop": {
+            "jsfiles": [
+                "dragdrop.js"
+            ]
+        }
+    }
+}
diff --git a/lib/yui/src/dragdrop/js/dragdrop.js b/lib/yui/src/dragdrop/js/dragdrop.js
new file mode 100644 (file)
index 0000000..87bfd03
--- /dev/null
@@ -0,0 +1,640 @@
+/**
+ * The core drag and drop module for Moodle which extends the YUI drag and
+ * drop functionality with additional features.
+ *
+ * @module moodle-core-dragdrop
+ */
+var MOVEICON = {
+    pix: "i/move_2d",
+    largepix: "i/dragdrop",
+    component: 'moodle',
+    cssclass: 'moodle-core-dragdrop-draghandle'
+};
+
+/**
+ * General DRAGDROP class, this should not be used directly,
+ * it is supposed to be extended by your class
+ *
+ * @class M.core.dragdrop
+ * @constructor
+ * @extends Base
+ */
+var DRAGDROP = function() {
+    DRAGDROP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(DRAGDROP, Y.Base, {
+    /**
+     * Whether the item is being moved upwards compared with the last
+     * location.
+     *
+     * @property goingup
+     * @type Boolean
+     * @default null
+     */
+    goingup: null,
+
+    /**
+     * Whether the item is being moved upwards compared with the start
+     * point.
+     *
+     * @property absgoingup
+     * @type Boolean
+     * @default null
+     */
+    absgoingup: null,
+
+    /**
+     * The class for the object.
+     *
+     * @property samenodeclass
+     * @type String
+     * @default null
+     */
+    samenodeclass: null,
+
+    /**
+     * The class on the parent of the item being moved.
+     *
+     * @property parentnodeclass
+     * @type String
+     * @default
+     */
+    parentnodeclass: null,
+
+    /**
+     * The groups for this instance.
+     *
+     * @property groups
+     * @type Array
+     * @default []
+     */
+    groups: [],
+
+    /**
+     * The previous drop location.
+     *
+     * @property lastdroptarget
+     * @type Node
+     * @default null
+     */
+    lastdroptarget: null,
+
+    /**
+     * The initializer which sets up the move action.
+     *
+     * @method initializer
+     * @protected
+     */
+    initializer: function() {
+        // Listen for all drag:start events.
+        Y.DD.DDM.on('drag:start', this.global_drag_start, this);
+
+        // Listen for all drag:end events.
+        Y.DD.DDM.on('drag:end', this.global_drag_end, this);
+
+        // Listen for all drag:drag events.
+        Y.DD.DDM.on('drag:drag', this.global_drag_drag, this);
+
+        // Listen for all drop:over events.
+        Y.DD.DDM.on('drop:over', this.global_drop_over, this);
+
+        // Listen for all drop:hit events.
+        Y.DD.DDM.on('drop:hit', this.global_drop_hit, this);
+
+        // Listen for all drop:miss events.
+        Y.DD.DDM.on('drag:dropmiss', this.global_drag_dropmiss, this);
+
+        // Add keybaord listeners for accessible drag/drop
+        Y.one(Y.config.doc.body).delegate('key', this.global_keydown,
+                'down:32, enter, esc', '.' + MOVEICON.cssclass, this);
+
+        // Make the accessible drag/drop respond to a single click.
+        Y.one(Y.config.doc.body).delegate('click', this.global_keydown,
+                '.' + MOVEICON.cssclass , this);
+    },
+
+    /**
+     * Build a new drag handle Node.
+     *
+     * @method get_drag_handle
+     * @param {String} title The title on the drag handle
+     * @param {String} classname The name of the class to add to the node
+     * wrapping the drag icon
+     * @param {String} [iconclass] The class to add to the icon
+     * @param {Boolean} [large=false] whether to use the larger version of
+     * the drag icon
+     * @return Node The built drag handle.
+     */
+    get_drag_handle: function(title, classname, iconclass, large) {
+        var iconname = MOVEICON.pix;
+        if (large) {
+            iconname = MOVEICON.largepix;
+        }
+        var dragicon = Y.Node.create('<img />')
+            .setStyle('cursor', 'move')
+            .setAttrs({
+                'src': M.util.image_url(iconname, MOVEICON.component),
+                'alt': title
+            });
+        if (iconclass) {
+            dragicon.addClass(iconclass);
+        }
+
+        var dragelement = Y.Node.create('<span></span>')
+            .addClass(classname)
+            .setAttribute('title', title)
+            .setAttribute('tabIndex', 0)
+            .setAttribute('data-draggroups', this.groups)
+            .setAttribute('role', 'button')
+            .setAttribute('aria-grabbed', 'false');
+        dragelement.appendChild(dragicon);
+        dragelement.addClass(MOVEICON.cssclass);
+
+        return dragelement;
+    },
+
+    lock_drag_handle: function(drag, classname) {
+        drag.removeHandle('.'+classname);
+    },
+
+    unlock_drag_handle: function(drag, classname) {
+        drag.addHandle('.'+classname);
+    },
+
+    ajax_failure: function(response) {
+        var e = {
+            name: response.status+' '+response.statusText,
+            message: response.responseText
+        };
+        return new M.core.exception(e);
+    },
+
+    in_group: function(target) {
+        var ret = false;
+        Y.each(this.groups, function(v) {
+            if (target._groups[v]) {
+                ret = true;
+            }
+        }, this);
+        return ret;
+    },
+    /*
+        * Drag-dropping related functions
+        */
+    global_drag_start: function(e) {
+        // Get our drag object
+        var drag = e.target;
+        // Check that drag object belongs to correct group
+        if (!this.in_group(drag)) {
+            return;
+        }
+        // Set some general styles here
+        drag.get('node').setStyle('opacity', '.25');
+        drag.get('dragNode').setStyles({
+            opacity: '.75',
+            borderColor: drag.get('node').getStyle('borderColor'),
+            backgroundColor: drag.get('node').getStyle('backgroundColor')
+        });
+        drag.get('dragNode').empty();
+        this.drag_start(e);
+    },
+
+    global_drag_end: function(e) {
+        var drag = e.target;
+        // Check that drag object belongs to correct group
+        if (!this.in_group(drag)) {
+            return;
+        }
+        //Put our general styles back
+        drag.get('node').setStyles({
+            visibility: '',
+            opacity: '1'
+        });
+        this.drag_end(e);
+    },
+
+    global_drag_drag: function(e) {
+        var drag = e.target,
+            info = e.info;
+
+        // Check that drag object belongs to correct group
+        if (!this.in_group(drag)) {
+            return;
+        }
+
+        // Note, we test both < and > situations here. We don't want to
+        // effect a change in direction if the user is only moving side
+        // to side with no Y position change.
+
+        // Detect changes in the position relative to the start point.
+        if (info.start[1] < info.xy[1]) {
+            // We are going up if our final position is higher than our start position.
+            this.absgoingup = true;
+
+        } else if (info.start[1] > info.xy[1]) {
+            // Otherwise we're going down.
+            this.absgoingup = false;
+        }
+
+        // Detect changes in the position relative to the last movement.
+        if (info.delta[1] < 0) {
+            // We are going up if our final position is higher than our start position.
+            this.goingup = true;
+
+        } else if (info.delta[1] > 0) {
+            // Otherwise we're going down.
+            this.goingup = false;
+        }
+
+        this.drag_drag(e);
+    },
+
+    global_drop_over: function(e) {
+        // Check that drop object belong to correct group.
+        if (!e.drop || !e.drop.inGroup(this.groups)) {
+            return;
+        }
+
+        // Get a reference to our drag and drop nodes.
+        var drag = e.drag.get('node'),
+            drop = e.drop.get('node');
+
+        // Save last drop target for the case of missed target processing.
+        this.lastdroptarget = e.drop;
+
+        // Are we dropping within the same parent node?
+        if (drop.hasClass(this.samenodeclass)) {
+            var where;
+
+            if (this.goingup) {
+                where = "before";
+            } else {
+                where = "after";
+            }
+
+            // Add the node contents so that it's moved, otherwise only the drag handle is moved.
+            drop.insert(drag, where);
+        } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
+            // We are dropping on parent node and it is empty
+            if (this.goingup) {
+                drop.append(drag);
+            } else {
+                drop.prepend(drag);
+            }
+        }
+        this.drop_over(e);
+    },
+
+    global_drag_dropmiss: function(e) {
+        // drag:dropmiss does not have e.drag and e.drop properties
+        // we substitute them for the ease of use. For e.drop we use,
+        // this.lastdroptarget (ghost node we use for indicating where to drop)
+        e.drag = e.target;
+        e.drop = this.lastdroptarget;
+        // Check that drag object belongs to correct group
+        if (!this.in_group(e.drag)) {
+            return;
+        }
+        // Check that drop object belong to correct group
+        if (!e.drop || !e.drop.inGroup(this.groups)) {
+            return;
+        }
+        this.drag_dropmiss(e);
+    },
+
+    global_drop_hit: function(e) {
+        // Check that drop object belong to correct group
+        if (!e.drop || !e.drop.inGroup(this.groups)) {
+            return;
+        }
+        this.drop_hit(e);
+    },
+
+    /**
+     * This is used to build the text for the heading of the keyboard
+     * drag drop menu and the text for the nodes in the list.
+     * @method find_element_text
+     * @param {Node} n The node to start searching for a valid text node.
+     * @return {string} The text of the first text-like child node of n.
+     */
+    find_element_text: function(n) {
+        // The valid node types to get text from.
+        var nodes = n.all('h2, h3, h4, h5, span, p, div.no-overflow, div.dimmed_text');
+        var text = '';
+
+        nodes.each(function () {
+            if (text === '') {
+                if (Y.Lang.trim(this.get('text')) !== '') {
+                    text = this.get('text');
+                }
+            }
+        });
+
+        if (text !== '') {
+            return text;
+        }
+        return M.util.get_string('emptydragdropregion', 'moodle');
+    },
+
+    /**
+     * This is used to initiate a keyboard version of a drag and drop.
+     * A dialog will open listing all the valid drop targets that can be selected
+     * using tab, tab, tab, enter.
+     * @method global_start_keyboard_drag
+     * @param {Event} e The keydown / click event on the grab handle.
+     * @param {Node} dragcontainer The resolved draggable node (an ancestor of the drag handle).
+     * @param {Node} draghandle The node that triggered this action.
+     */
+    global_start_keyboard_drag: function(e, draghandle, dragcontainer) {
+        M.core.dragdrop.keydragcontainer = dragcontainer;
+        M.core.dragdrop.keydraghandle = draghandle;
+
+        // Indicate to a screenreader the node that is selected for drag and drop.
+        dragcontainer.setAttribute('aria-grabbed', 'true');
+        // Get the name of the thing to move.
+        var nodetitle = this.find_element_text(dragcontainer);
+        var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
+
+        // Build the list of drop targets.
+        var droplist = Y.Node.create('<ul></ul>');
+        droplist.addClass('dragdrop-keyboard-drag');
+        var listitem;
+        var listitemtext;
+
+        // Search for possible drop targets.
+        var droptargets = Y.all('.' + this.samenodeclass + ', .' + this.parentnodeclass);
+
+        droptargets.each(function (node) {
+            var validdrop = false, labelroot = node;
+            if (node.drop && node.drop.inGroup(this.groups) && node.drop.get('node') !== dragcontainer) {
+                // This is a drag and drop target with the same class as the grabbed node.
+                validdrop = true;
+            } else {
+                var elementgroups = node.getAttribute('data-draggroups').split(' ');
+                var i, j;
+                for (i = 0; i < elementgroups.length; i++) {
+                    for (j = 0; j < this.groups.length; j++) {
+                        if (elementgroups[i] === this.groups[j]) {
+                            // This is a parent node of the grabbed node (used for dropping in empty sections).
+                            validdrop = true;
+                            // This node will have no text - so we get the first valid text from the parent.
+                            labelroot = node.get('parentNode');
+                            break;
+                        }
+                    }
+                    if (validdrop) {
+                        break;
+                    }
+                }
+            }
+
+            if (validdrop) {
+                // It is a valid drop target - create a list item for it.
+                listitem = Y.Node.create('<li></li>');
+                listlink = Y.Node.create('<a></a>');
+                nodetitle = this.find_element_text(labelroot);
+
+                listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
+                listlink.setContent(listitemtext);
+
+                // Add a data attribute so we can get the real drop target.
+                listlink.setAttribute('data-drop-target', node.get('id'));
+                // Notify the screen reader this is a valid drop target.
+                listlink.setAttribute('aria-dropeffect', 'move');
+                // Allow tabbing to the link.
+                listlink.setAttribute('tabindex', '0');
+
+                // Set the event listeners for enter, space or click.
+                listlink.on('click', this.global_keyboard_drop, this);
+                listlink.on('key', this.global_keyboard_drop, 'down:enter,32', this);
+
+                // Add to the list or drop targets.
+                listitem.append(listlink);
+                droplist.append(listitem);
+            }
+        }, this);
+
+        // Create the dialog for the interaction.
+        M.core.dragdrop.dropui = new M.core.dialogue({
+            headerContent: dialogtitle,
+            bodyContent: droplist,
+            draggable: true,
+            visible: true,
+            centered: true
+        });
+
+        // Focus the first drop target.
+        if (droplist.one('a')) {
+            droplist.one('a').focus();
+        }
+    },
+
+    /**
+     * This is used as a simulated drag/drop event in order to prevent any
+     * subtle bugs from creating a real instance of a drag drop event. This means
+     * there are no state changes in the Y.DD.DDM and any undefined functions
+     * will trigger an obvious and fatal error.
+     * The end result is that we call all our drag/drop handlers but do not bubble the
+     * event to anyone else.
+     *
+     * The functions/properties implemented in the wrapper are:
+     * e.target
+     * e.drag
+     * e.drop
+     * e.drag.get('node')
+     * e.drop.get('node')
+     * e.drag.addHandle()
+     * e.drag.removeHandle()
+     *
+     * @method simulated_drag_drop_event
+     * @param {Node} dragnode The drag container node
+     * @param {Node} dropnode The node to initiate the drop on
+     */
+    simulated_drag_drop_event: function(dragnode, dropnode) {
+
+        // Subclass for wrapping both drag and drop.
+        var DragDropWrapper = function(node) {
+            this.node = node;
+        };
+
+        // Method e.drag.get() - get the node.
+        DragDropWrapper.prototype.get = function(param) {
+            if (param === 'node' || param === 'dragNode' || param === 'dropNode') {
+                return this.node;
+            }
+            return null;
+        };
+
+        // Method e.drag.inGroup() - we have already run the group checks before triggering the event.
+        DragDropWrapper.prototype.inGroup = function() {
+            return true;
+        };
+
+        // Method e.drag.addHandle() - we don't want to run this.
+        DragDropWrapper.prototype.addHandle = function() {};
+        // Method e.drag.removeHandle() - we don't want to run this.
+        DragDropWrapper.prototype.removeHandle = function() {};
+
+        // Create instances of the DragDropWrapper.
+        this.drop = new DragDropWrapper(dropnode);
+        this.drag = new DragDropWrapper(dragnode);
+        this.target = this.drop;
+    },
+
+    /**
+     * This is used to complete a keyboard version of a drag and drop.
+     * A drop event will be simulated based on the drag and drop nodes.
+     * @method global_keyboard_drop
+     * @param {Event} e The keydown / click event on the proxy drop node.
+     */
+    global_keyboard_drop: function(e) {
+        // The drag node was saved.
+        var dragcontainer = M.core.dragdrop.keydragcontainer;
+        dragcontainer.setAttribute('aria-grabbed', 'false');
+        // The real drop node is stored in an attribute of the proxy.
+        var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
+
+        // Close the dialog.
+        M.core.dragdrop.dropui.hide();
+        // Cancel the event.
+        e.preventDefault();
+        // Convert to drag drop events.
+        var dragevent = new this.simulated_drag_drop_event(dragcontainer, dragcontainer);
+        var dropevent = new this.simulated_drag_drop_event(dragcontainer, droptarget);
+        // Simulate the full sequence.
+        this.drag_start(dragevent);
+        this.global_drop_over(dropevent);
+        this.global_drop_hit(dropevent);
+        M.core.dragdrop.keydraghandle.focus();
+    },
+
+    /**
+     * This is used to cancel a keyboard version of a drag and drop.
+     *
+     * @method global_cancel_keyboard_drag
+     */
+    global_cancel_keyboard_drag: function() {
+        if (M.core.dragdrop.keydragcontainer) {
+            M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
+            M.core.dragdrop.keydraghandle.focus();
+            M.core.dragdrop.keydragcontainer = null;
+        }
+    },
+
+    /**
+     * Process key events on the drag handles.
+     *
+     * @method global_keydown
+     * @param {EventFacade} e The keydown / click event on the drag handle.
+     */
+    global_keydown: function(e) {
+        var draghandle = e.target.ancestor('.' + MOVEICON.cssclass, true),
+            dragcontainer,
+            draggroups;
+
+        if (draghandle === null) {
+            // The element clicked did not have a a draghandle in it's lineage.
+            return;
+        }
+
+        if (e.keyCode === 27 ) {
+            // Escape to cancel from anywhere.
+            this.global_cancel_keyboard_drag();
+            e.preventDefault();
+            return;
+        }
+
+        // Only process events on a drag handle.
+        if (!draghandle.hasClass(MOVEICON.cssclass)) {
+            return;
+        }
+
+        // Do nothing if not space or enter.
+        if (e.keyCode !== 13 && e.keyCode !== 32 && e.type !== 'click') {
+            return;
+        }
+
+        // Check the drag groups to see if we are the handler for this node.
+        draggroups = draghandle.getAttribute('data-draggroups').split(' ');
+        var i, j, validgroup = false;
+
+        for (i = 0; i < draggroups.length; i++) {
+            for (j = 0; j < this.groups.length; j++) {
+                if (draggroups[i] === this.groups[j]) {
+                    validgroup = true;
+                    break;
+                }
+            }
+            if (validgroup) {
+                break;
+            }
+        }
+        if (!validgroup) {
+            return;
+        }
+
+        // Valid event - start the keyboard drag.
+        dragcontainer = draghandle.ancestor('.yui3-dd-drop');
+        this.global_start_keyboard_drag(e, draghandle, dragcontainer);
+
+        e.preventDefault();
+    },
+
+
+    // Abstract functions definitions.
+
+    /**
+     * Callback to use when dragging starts.
+     *
+     * @method drag_start
+     * @param {EventFacade} e
+     */
+    drag_start: function() {},
+
+    /**
+     * Callback to use when dragging ends.
+     *
+     * @method drag_end
+     * @param {EventFacade} e
+     */
+    drag_end: function() {},
+
+    /**
+     * Callback to use during dragging.
+     *
+     * @method drag_drag
+     * @param {EventFacade} e
+     */
+    drag_drag: function() {},
+
+    /**
+     * Callback to use when dragging ends and is not over a drop target.
+     *
+     * @method drag_dropmiss
+     * @param {EventFacade} e
+     */
+    drag_dropmiss: function() {},
+
+    /**
+     * Callback to use when a drop over event occurs.
+     *
+     * @method drop_over
+     * @param {EventFacade} e
+     */
+    drop_over: function() {},
+
+    /**
+     * Callback to use on drop:hit.
+     *
+     * @method drop_hit
+     * @param {EventFacade} e
+     */
+    drop_hit: function() {}
+}, {
+    NAME: 'dragdrop',
+    ATTRS: {}
+});
+
+M.core = M.core || {};
+M.core.dragdrop = DRAGDROP;
diff --git a/lib/yui/src/dragdrop/meta/dragdrop.json b/lib/yui/src/dragdrop/meta/dragdrop.json
new file mode 100644 (file)
index 0000000..4e4f51c
--- /dev/null
@@ -0,0 +1,14 @@
+{
+    "moodle-core-dragdrop": {
+        "requires": [
+            "base",
+            "node",
+            "io",
+            "dom",
+            "dd",
+            "event-key",
+            "event-focus",
+            "moodle-core-notification"
+        ]
+    }
+}