/** * 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() { 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, /** * @property drop * @type DD.Drop * @description A reference to the DD.Drop instance */ drop: null, initializer: function() { var id = 'sortable-' + Y.guid(), 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 }); this.drop = new Y.DD.Drop({ node: this.get(CONT), bubbleTarget: del, groups: del.dd.get('groups') }); this.drop.on('drop:enter', Y.bind(this._onDropEnter, 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, id); }, _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 _onDropEnter * @param Event e The Event Object * @description Handles the DropEnter event to append a new node to a target. */ _onDropEnter: function(e) { var dropNode = e.drop.get(NODE), dragNode = e.drag.get(NODE); if (!dropNode.test(this.get(NODES)) && !dragNode.get(PARENT_NODE).compareTo(dropNode)) { dropNode.append(dragNode); } }, /** * @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() { var del = this.delegate, lastNode = del.get('lastNode'); if (lastNode && lastNode.getDOMNode()) { lastNode.setStyle(ZINDEX, ''); } del.get(this.get(OPACITY_NODE)).setStyle(OPACITY, this.get(OPACITY)); del.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() { this.delegate.get(this.get(OPACITY_NODE)).setStyle(OPACITY, 1); this.delegate.get(CURRENT_NODE).setStyle(ZINDEX, ''); 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 sync * @description Passthrough to the DD.Delegate syncTargets method. * @chainable */ sync: function() { this.delegate.syncTargets(); return this; }, destructor: function() { this.drop.destroy(); this.delegate.destroy(); Sortable.unreg(this, this.get(ID)); }, /** * @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 * @return 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 Object * @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) { var ret; if (test instanceof Y.Node) { ret = (test === node); } else { ret = node.test(test); } return ret; }, /** * @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, id = null; node = Y.one(node); id = node.get(ID); if(id && Y.Sortable._sortables[id]) { return Y.Sortable._sortables[id]; } Y.Object.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. * @param String id (optional) The id of the sortable instance. * @description Register a Sortable instance with the singleton to allow lookups later. */ reg: function(s, id) { if (!id) { id = s.get(ID); } Y.Sortable._sortables[id] = s; }, /** * @static * @method unreg * @param Sortable s A Sortable instance. * @param String id (optional) The id of the sortable instance. * @description Unregister a Sortable instance with the singleton. */ unreg: function(s, id) { if (!id) { id = s.get(ID); } if (id && Y.Sortable._sortables[id]) { delete Y.Sortable._sortables[id]; return; } Y.Object.each(Y.Sortable._sortables, function(v, k) { if (v === s) { delete Sortable._sortables[k]; } }); } }); Y.Sortable = Sortable; /** * @event copy * @description A Sortable node was moved with a copy. * @param {EventFacade} event An Event Facade object * @param {Boolean} event.same Moved to the same list. * @param {DD.Drag} event.drag The drag instance. * @param {DD.Drop} event.drop The drop instance. */ /** * @event move * @description A Sortable node was moved with a move. * @param {EventFacade} event An Event Facade object with the following specific property added: * @param {Boolean} event.same Moved to the same list. * @param {DD.Drag} event.drag The drag instance. * @param {DD.Drop} event.drop The drop instance. */ /** * @event insert * @description A Sortable node was moved with an insert. * @param {EventFacade} event An Event Facade object with the following specific property added: * @param {Boolean} event.same Moved to the same list. * @param {DD.Drag} event.drag The drag instance. * @param {DD.Drop} event.drop The drop instance. */ /** * @event swap * @description A Sortable node was moved with a swap. * @param {EventFacade} event An Event Facade object with the following specific property added: * @param {Boolean} event.same Moved to the same list. * @param {DD.Drag} event.drag The drag instance. * @param {DD.Drop} event.drop The drop instance. */ /** * @event moved * @description A Sortable node was moved. * @param {EventFacade} event An Event Facade object with the following specific property added: * @param {Boolean} event.same Moved to the same list. * @param {DD.Drag} event.drag The drag instance. * @param {DD.Drop} event.drop The drop instance. */