# Yahoo! UI Library

### sortable 3.3.0

Yahoo! UI Library > sortable > sortable.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

        /**
         * The class allows you to create a Drag & Drop reordered list.
         * @module sortable
         */
        /**
         * The class allows you to create a Drag & Drop reordered list.
         * @class Sortable
         * @extends Base
         * @constructor
         */

        var Sortable = function(o) {
            Sortable.superclass.constructor.apply(this, arguments);
        },
        CURRENT_NODE = 'currentNode',
        OPACITY_NODE = 'opacityNode',
        CONT = 'container',
        ID = 'id',
        ZINDEX = 'zIndex',
        OPACITY = 'opacity',
        PARENT_NODE = 'parentNode',
        NODES = 'nodes',
        NODE = 'node';

        Y.extend(Sortable, Y.Base, {
            /**
            * @property delegate
            * @type DD.Delegate
            * @description A reference to the DD.Delegate instance.
            */
            delegate: null,
            initializer: function() {
                var id = 'sortable-' + Y.guid(), c,
                    delConfig = {
                        container: this.get(CONT),
                        nodes: this.get(NODES),
                        target: true,
                        invalid: this.get('invalid'),
                        dragConfig: {
                            groups: [ id ]
                        }
                    }, del;

                if (this.get('handles')) {
                    delConfig.handles = this.get('handles');
                }
                del = new Y.DD.Delegate(delConfig);

                this.set(ID, id);

                del.dd.plug(Y.Plugin.DDProxy, {
                    moveOnEnd: false,
                    cloneNode: true
                });

                c = new Y.DD.Drop({
                    node: this.get(CONT),
                    bubbleTarget: del,
                    groups: del.dd.get('groups')
                }).on('drop:over', Y.bind(this._onDropOver, this));

                del.on({
                    'drag:start': Y.bind(this._onDragStart, this),
                    'drag:end': Y.bind(this._onDragEnd, this),
                    'drag:over': Y.bind(this._onDragOver, this),
                    'drag:drag': Y.bind(this._onDrag, this)
                });

                this.delegate = del;
                Sortable.reg(this);
            },
            _up: null,
            _y: null,
            _onDrag: function(e) {
                if (e.pageY < this._y) {
                    this._up = true;
                } else if (e.pageY > this._y) {
                    this._up = false;
                }

                this._y = e.pageY;
            },
            /**
            * @private
            * @method _onDropOver
            * @param Event e The Event Object
            * @description Handles the DropOver event to append a drop node to an empty target
            */
            _onDropOver: function(e) {
                if (!e.drop.get(NODE).test(this.get(NODES))) {
                    var nodes = e.drop.get(NODE).all(this.get(NODES));
                    if (nodes.size() === 0) {
                        e.drop.get(NODE).append(e.drag.get(NODE));
                    }
                }
            },
            /**
            * @private
            * @method _onDragOver
            * @param Event e The Event Object
            * @description Handles the DragOver event that moves the object in the list or to another list.
            */
            _onDragOver: function(e) {
                if (!e.drop.get(NODE).test(this.get(NODES))) {
                    return;
                }
                if (e.drag.get(NODE) == e.drop.get(NODE)) {
                    return;
                }
                // is drop a child of drag?
                if (e.drag.get(NODE).contains(e.drop.get(NODE))) {
                    return;
                }
                var same = false, dir, oldNode, newNode, dropsort, dropNode,
                    moveType = this.get('moveType').toLowerCase();

                if (e.drag.get(NODE).get(PARENT_NODE).contains(e.drop.get(NODE))) {
                    same = true;
                }
                if (same && moveType == 'move') {
                    moveType = 'insert';
                }
                switch (moveType) {
                    case 'insert':
                        dir = ((this._up) ? 'before' : 'after');
                        dropNode = e.drop.get(NODE);
                        if (Y.Sortable._test(dropNode, this.get(CONT))) {
                            dropNode.append(e.drag.get(NODE));
                        } else {
                            dropNode.insert(e.drag.get(NODE), dir);
                        }
                        break;
                    case 'swap':
                        Y.DD.DDM.swapNode(e.drag, e.drop);
                        break;
                    case 'move':
                    case 'copy':
                        dropsort = Y.Sortable.getSortable(e.drop.get(NODE).get(PARENT_NODE));

                        if (!dropsort) {
                            Y.log('No delegate parent found', 'error', 'sortable');
                            return;
                        }

                        Y.DD.DDM.getDrop(e.drag.get(NODE)).addToGroup(dropsort.get(ID));

                        //Same List
                        if (same) {
                            Y.DD.DDM.swapNode(e.drag, e.drop);
                        } else {
                            if (this.get('moveType') == 'copy') {
                                //New List
                                oldNode = e.drag.get(NODE);
                                newNode = oldNode.cloneNode(true);

                                newNode.set(ID, '');
                                e.drag.set(NODE, newNode);
                                dropsort.delegate.createDrop(newNode, [dropsort.get(ID)]);
                                oldNode.setStyles({
                                    top: '',
                                    left: ''
                                });
                            }
                            e.drop.get(NODE).insert(e.drag.get(NODE), 'before');
                        }
                        break;
                }

                this.fire(moveType, { same: same, drag: e.drag, drop: e.drop });
                this.fire('moved', { same: same, drag: e.drag, drop: e.drop });
            },
            /**
            * @private
            * @method _onDragStart
            * @param Event e The Event Object
            * @description Handles the DragStart event and initializes some settings.
            */
            _onDragStart: function(e) {
                this.delegate.get('lastNode').setStyle(ZINDEX, '');
                this.delegate.get(this.get(OPACITY_NODE)).setStyle(OPACITY, this.get(OPACITY));
                this.delegate.get(CURRENT_NODE).setStyle(ZINDEX, '999');
            },
            /**
            * @private
            * @method _onDragEnd
            * @param Event e The Event Object
            * @description Handles the DragEnd event that cleans up the settings in the drag:start event.
            */
            _onDragEnd: function(e) {
                this.delegate.get(this.get(OPACITY_NODE)).setStyle(OPACITY, 1);
                this.delegate.get(CURRENT_NODE).setStyles({
                    top: '',
                    left: ''
                });
                this.sync();
            },
            /**
            * @method plug
            * @param Class cls The class to plug
            * @param Object config The class config
            * @description Passthrough to the DD.Delegate.ddplug method
            * @chainable
            */
            plug: function(cls, config) {
                //I don't like this.. Not at all, need to discuss with the team
                if (cls && cls.NAME.substring(0, 4).toLowerCase() === 'sort') {
                    this.constructor.superclass.plug.call(this, cls, config);
                } else {
                    this.delegate.dd.plug(cls, config);
                }
                return this;
            },
            /**
            * @method plug
            * @description Passthrough to the DD.Delegate syncTargets method.
            * @chainable
            */
            sync: function() {
                this.delegate.syncTargets();
                return this;
            },
            destructor: function() {
                this.delegate.destroy();
                Sortable.unreg(this);
            },
            /**
            * @method join
            * @param Sortable sel The Sortable list to join with
            * @param String type The type of join to do: full, inner, outer, none. Default: full
            * @description Join this Sortable with another Sortable instance.
            * <ul>
            *   <li>full: Exchange nodes with both lists.</li>
            *   <li>inner: Items can go into this list from the joined list.</li>
            *   <li>outer: Items can go out of the joined list into this list.</li>
            *   <li>none: Removes the join.</li>
            * </ul>
            * @chainable
            */
            join: function(sel, type) {
                if (!(sel instanceof Y.Sortable)) {
                    Y.error('Sortable: join needs a Sortable Instance');
                    return this;
                }
                if (!type) {
                    type = 'full';
                }
                type = type.toLowerCase();
                var method = '_join_' + type;

                if (this[method]) {
                    this[method](sel);
                }

                return this;
            },
            /**
            * @private
            * @method _join_none
            * @param Sortable sel The Sortable to remove the join from
            * @description Removes the join with the passed Sortable.
            */
            _join_none: function(sel) {
                this.delegate.dd.removeFromGroup(sel.get(ID));
                sel.delegate.dd.removeFromGroup(this.get(ID));
            },
            /**
            * @private
            * @method _join_full
            * @param Sortable sel The Sortable list to join with
            * @description Joins both of the Sortables together.
            */
            _join_full: function(sel) {
                this.delegate.dd.addToGroup(sel.get(ID));
                sel.delegate.dd.addToGroup(this.get(ID));
            },
            /**
            * @private
            * @method _join_outer
            * @param Sortable sel The Sortable list to join with
            * @description Allows this Sortable to accept items from the passed Sortable.
            */
            _join_outer: function(sel) {
                this.delegate.dd.addToGroup(sel.get(ID));
            },
            /**
            * @private
            * @method _join_inner
            * @param Sortable sel The Sortable list to join with
            * @description Allows this Sortable to give items to the passed Sortable.
            */
            _join_inner: function(sel) {
                sel.delegate.dd.addToGroup(this.get(ID));
            },
            /**
            * A custom callback to allow a user to extract some sort of id or any other data from the node to use in the "ordering list" and then that data should be returned from the callback.
            * @method getOrdering
            * @param Function callback
            * @returns Array
            */
            getOrdering: function(callback) {
                var ordering = [];

                if (!Y.Lang.isFunction(callback)) {
                    callback = function (node) {
                        return node;
                    };
                }

                Y.one(this.get(CONT)).all(this.get(NODES)).each(function(node) {
                    ordering.push(callback(node));
                });
                return ordering;
           }
        }, {
            NAME: 'sortable',
            ATTRS: {
                /**
                * @attribute handles
                * @description Drag handles to pass on to the internal DD.Delegate instance.
                * @type Array
                */
                handles: {
                    value: false
                },
                /**
                * @attribute container
                * @description A selector query to get the container to listen for mousedown events on. All "nodes" should be a child of this container.
                * @type String
                */
                container: {
                    value: 'body'
                },
                /**
                * @attribute nodes
                * @description A selector query to get the children of the "container" to make draggable elements from.
                * @type String
                */
                nodes: {
                    value: '.dd-draggable'
                },
                /**
                * @attribute opacity
                * @description The opacity to change the proxy item to when dragging.
                * @type String
                */
                opacity: {
                    value: '.75'
                },
                /**
                * @attribute opacityNode
                * @description The node to set opacity on when dragging (dragNode or currentNode). Default: currentNode.
                * @type String
                */
                opacityNode: {
                    value: 'currentNode'
                },
                /**
                * @attribute id
                * @description The id of this Sortable, used to get a reference to this Sortable list from another list.
                * @type String
                */
                id: {
                    value: null
                },
                /**
                * @attribute moveType
                * @description How should an item move to another list: insert, swap, move, copy. Default: insert
                * @type String
                */
                moveType: {
                    value: 'insert'
                },
                /**
                * @attribute invalid
                * @description A selector string to test if a list item is invalid and not sortable
                * @type String
                */
                invalid: {
                    value: ''
                }
            },
            /**
            * @static
            * @property _sortables
            * @private
            * @type Array
            * @description Hash map of all Sortables on the page.
            */
            _sortables: [],
            /**
            * @static
            * @method _test
            * @param {Node} node The node instance to test.
            * @param {String|Node} test The node instance or selector string to test against.
            * @description Test a Node or a selector for the container
            */
            _test: function(node, test) {
                if (test instanceof Y.Node) {
                    return (test === node);
                } else {
                    return node.test(test);
                }
            },
            /**
            * @static
            * @method getSortable
            * @param {String|Node} node The node instance or selector string to use to find a Sortable instance.
            * @description Get a Sortable instance back from a node reference or a selector string.
            */
            getSortable: function(node) {
                var s = null;
                node = Y.one(node);
                Y.each(Y.Sortable._sortables, function(v) {
                    if (Y.Sortable._test(node, v.get(CONT))) {
                        s = v;
                    }
                });
                return s;
            },
            /**
            * @static
            * @method reg
            * @param Sortable s A Sortable instance.
            * @description Register a Sortable instance with the singleton to allow lookups later.
            */
            reg: function(s) {
                Y.Sortable._sortables.push(s);
            },
            /**
            * @static
            * @method unreg
            * @param Sortable s A Sortable instance.
            * @description Unregister a Sortable instance with the singleton.
            */
            unreg: function(s) {
                Y.each(Y.Sortable._sortables, function(v, k) {
                    if (v === s) {
                        Y.Sortable._sortables[k] = null;
                        delete Sortable._sortables[k];
                    }
                });
            }
        });

        Y.Sortable = Sortable;

        /**
        * @event copy
        * @description A Sortable node was moved.
        * @param {Event.Facade} event An Event Facade object with the following specific property added:
        * <dl>
        * <dt>same</dt><dd>Moved to the same list.</dd>
        * <dt>drag</dt><dd>The Drag Object</dd>
        * <dt>drop</dt><dd>The Drop Object</dd>
        * </dl>
        * @type {Event.Custom}
        *
        *
        * @event move
        * @description A Sortable node was moved.
        * @param {Event.Facade} event An Event Facade object with the following specific property added:
        * <dl>
        * <dt>same</dt><dd>Moved to the same list.</dd>
        * <dt>drag</dt><dd>The Drag Object</dd>
        * <dt>drop</dt><dd>The Drop Object</dd>
        * </dl>
        * @type {Event.Custom}
        *
        *
        * @event insert
        * @description A Sortable node was moved.
        * @param {Event.Facade} event An Event Facade object with the following specific property added:
        * <dl>
        * <dt>same</dt><dd>Moved to the same list.</dd>
        * <dt>drag</dt><dd>The Drag Object</dd>
        * <dt>drop</dt><dd>The Drop Object</dd>
        * </dl>
        * @type {Event.Custom}
        *
        *
        * @event swap
        * @description A Sortable node was moved.
        * @param {Event.Facade} event An Event Facade object with the following specific property added:
        * <dl>
        * <dt>same</dt><dd>Moved to the same list.</dd>
        * <dt>drag</dt><dd>The Drag Object</dd>
        * <dt>drop</dt><dd>The Drop Object</dd>
        * </dl>
        * @type {Event.Custom}
        *
        *
        * @event moved
        * @description A Sortable node was moved.
        * @param {Event.Facade} event An Event Facade object with the following specific property added:
        * <dl>
        * <dt>same</dt><dd>Moved to the same list.</dd>
        * <dt>drag</dt><dd>The Drag Object</dd>
        * <dt>drop</dt><dd>The Drop Object</dd>
        * </dl>
        * @type {Event.Custom}
        */

Copyright © 2011 Yahoo! Inc. All rights reserved.