2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('sortable', function(Y) {
12 * The class allows you to create a Drag & Drop reordered list.
16 * The class allows you to create a Drag & Drop reordered list.
23 var Sortable = function(o) {
24 Sortable.superclass.constructor.apply(this, arguments);
26 CURRENT_NODE = 'currentNode',
27 OPACITY_NODE = 'opacityNode',
32 PARENT_NODE = 'parentNode',
37 Y.extend(Sortable, Y.Base, {
41 * @description A reference to the DD.Delegate instance.
44 initializer: function() {
45 var id = 'sortable-' + Y.guid(), c,
47 container: this.get(CONT),
48 nodes: this.get(NODES),
50 invalid: this.get('invalid'),
56 if (this.get('handles')) {
57 delConfig.handles = this.get('handles');
59 del = new Y.DD.Delegate(delConfig);
63 del.dd.plug(Y.Plugin.DDProxy, {
71 groups: del.dd.get('groups')
72 }).on('drop:over', Y.bind(this._onDropOver, this));
75 'drag:start': Y.bind(this._onDragStart, this),
76 'drag:end': Y.bind(this._onDragEnd, this),
77 'drag:over': Y.bind(this._onDragOver, this),
78 'drag:drag': Y.bind(this._onDrag, this)
86 _onDrag: function(e) {
87 if (e.pageY < this._y) {
89 } else if (e.pageY > this._y) {
98 * @param Event e The Event Object
99 * @description Handles the DropOver event to append a drop node to an empty target
101 _onDropOver: function(e) {
102 if (!e.drop.get(NODE).test(this.get(NODES))) {
103 var nodes = e.drop.get(NODE).all(this.get(NODES));
104 if (nodes.size() === 0) {
105 e.drop.get(NODE).append(e.drag.get(NODE));
111 * @method _onDragOver
112 * @param Event e The Event Object
113 * @description Handles the DragOver event that moves the object in the list or to another list.
115 _onDragOver: function(e) {
116 if (!e.drop.get(NODE).test(this.get(NODES))) {
119 if (e.drag.get(NODE) == e.drop.get(NODE)) {
122 // is drop a child of drag?
123 if (e.drag.get(NODE).contains(e.drop.get(NODE))) {
126 var same = false, dir, oldNode, newNode, dropsort, dropNode,
127 moveType = this.get('moveType').toLowerCase();
129 if (e.drag.get(NODE).get(PARENT_NODE).contains(e.drop.get(NODE))) {
132 if (same && moveType == 'move') {
137 dir = ((this._up) ? 'before' : 'after');
138 dropNode = e.drop.get(NODE);
139 if (Y.Sortable._test(dropNode, this.get(CONT))) {
140 dropNode.append(e.drag.get(NODE));
142 dropNode.insert(e.drag.get(NODE), dir);
146 Y.DD.DDM.swapNode(e.drag, e.drop);
150 dropsort = Y.Sortable.getSortable(e.drop.get(NODE).get(PARENT_NODE));
156 Y.DD.DDM.getDrop(e.drag.get(NODE)).addToGroup(dropsort.get(ID));
160 Y.DD.DDM.swapNode(e.drag, e.drop);
162 if (this.get('moveType') == 'copy') {
164 oldNode = e.drag.get(NODE);
165 newNode = oldNode.cloneNode(true);
168 e.drag.set(NODE, newNode);
169 dropsort.delegate.createDrop(newNode, [dropsort.get(ID)]);
175 e.drop.get(NODE).insert(e.drag.get(NODE), 'before');
180 this.fire(moveType, { same: same, drag: e.drag, drop: e.drop });
181 this.fire('moved', { same: same, drag: e.drag, drop: e.drop });
185 * @method _onDragStart
186 * @param Event e The Event Object
187 * @description Handles the DragStart event and initializes some settings.
189 _onDragStart: function(e) {
190 this.delegate.get('lastNode').setStyle(ZINDEX, '');
191 this.delegate.get(this.get(OPACITY_NODE)).setStyle(OPACITY, this.get(OPACITY));
192 this.delegate.get(CURRENT_NODE).setStyle(ZINDEX, '999');
197 * @param Event e The Event Object
198 * @description Handles the DragEnd event that cleans up the settings in the drag:start event.
200 _onDragEnd: function(e) {
201 this.delegate.get(this.get(OPACITY_NODE)).setStyle(OPACITY, 1);
202 this.delegate.get(CURRENT_NODE).setStyles({
210 * @param Class cls The class to plug
211 * @param Object config The class config
212 * @description Passthrough to the DD.Delegate.ddplug method
215 plug: function(cls, config) {
216 //I don't like this.. Not at all, need to discuss with the team
217 if (cls && cls.NAME.substring(0, 4).toLowerCase() === 'sort') {
218 this.constructor.superclass.plug.call(this, cls, config);
220 this.delegate.dd.plug(cls, config);
226 * @description Passthrough to the DD.Delegate syncTargets method.
230 this.delegate.syncTargets();
233 destructor: function() {
234 this.delegate.destroy();
235 Sortable.unreg(this);
239 * @param Sortable sel The Sortable list to join with
240 * @param String type The type of join to do: full, inner, outer, none. Default: full
241 * @description Join this Sortable with another Sortable instance.
243 * <li>full: Exchange nodes with both lists.</li>
244 * <li>inner: Items can go into this list from the joined list.</li>
245 * <li>outer: Items can go out of the joined list into this list.</li>
246 * <li>none: Removes the join.</li>
250 join: function(sel, type) {
251 if (!(sel instanceof Y.Sortable)) {
252 Y.error('Sortable: join needs a Sortable Instance');
258 type = type.toLowerCase();
259 var method = '_join_' + type;
270 * @param Sortable sel The Sortable to remove the join from
271 * @description Removes the join with the passed Sortable.
273 _join_none: function(sel) {
274 this.delegate.dd.removeFromGroup(sel.get(ID));
275 sel.delegate.dd.removeFromGroup(this.get(ID));
280 * @param Sortable sel The Sortable list to join with
281 * @description Joins both of the Sortables together.
283 _join_full: function(sel) {
284 this.delegate.dd.addToGroup(sel.get(ID));
285 sel.delegate.dd.addToGroup(this.get(ID));
289 * @method _join_outer
290 * @param Sortable sel The Sortable list to join with
291 * @description Allows this Sortable to accept items from the passed Sortable.
293 _join_outer: function(sel) {
294 this.delegate.dd.addToGroup(sel.get(ID));
298 * @method _join_inner
299 * @param Sortable sel The Sortable list to join with
300 * @description Allows this Sortable to give items to the passed Sortable.
302 _join_inner: function(sel) {
303 sel.delegate.dd.addToGroup(this.get(ID));
306 * 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.
307 * @method getOrdering
308 * @param Function callback
311 getOrdering: function(callback) {
314 if (!Y.Lang.isFunction(callback)) {
315 callback = function (node) {
320 Y.one(this.get(CONT)).all(this.get(NODES)).each(function(node) {
321 ordering.push(callback(node));
330 * @description Drag handles to pass on to the internal DD.Delegate instance.
337 * @attribute container
338 * @description A selector query to get the container to listen for mousedown events on. All "nodes" should be a child of this container.
346 * @description A selector query to get the children of the "container" to make draggable elements from.
350 value: '.dd-draggable'
354 * @description The opacity to change the proxy item to when dragging.
361 * @attribute opacityNode
362 * @description The node to set opacity on when dragging (dragNode or currentNode). Default: currentNode.
370 * @description The id of this Sortable, used to get a reference to this Sortable list from another list.
377 * @attribute moveType
378 * @description How should an item move to another list: insert, swap, move, copy. Default: insert
386 * @description A selector string to test if a list item is invalid and not sortable
395 * @property _sortables
398 * @description Hash map of all Sortables on the page.
404 * @param {Node} node The node instance to test.
405 * @param {String|Node} test The node instance or selector string to test against.
406 * @description Test a Node or a selector for the container
408 _test: function(node, test) {
409 if (test instanceof Y.Node) {
410 return (test === node);
412 return node.test(test);
417 * @method getSortable
418 * @param {String|Node} node The node instance or selector string to use to find a Sortable instance.
419 * @description Get a Sortable instance back from a node reference or a selector string.
421 getSortable: function(node) {
424 Y.each(Y.Sortable._sortables, function(v) {
425 if (Y.Sortable._test(node, v.get(CONT))) {
434 * @param Sortable s A Sortable instance.
435 * @description Register a Sortable instance with the singleton to allow lookups later.
438 Y.Sortable._sortables.push(s);
443 * @param Sortable s A Sortable instance.
444 * @description Unregister a Sortable instance with the singleton.
447 Y.each(Y.Sortable._sortables, function(v, k) {
449 Y.Sortable._sortables[k] = null;
450 delete Sortable._sortables[k];
456 Y.Sortable = Sortable;
460 * @description A Sortable node was moved.
461 * @param {Event.Facade} event An Event Facade object with the following specific property added:
463 * <dt>same</dt><dd>Moved to the same list.</dd>
464 * <dt>drag</dt><dd>The Drag Object</dd>
465 * <dt>drop</dt><dd>The Drop Object</dd>
467 * @type {Event.Custom}
471 * @description A Sortable node was moved.
472 * @param {Event.Facade} event An Event Facade object with the following specific property added:
474 * <dt>same</dt><dd>Moved to the same list.</dd>
475 * <dt>drag</dt><dd>The Drag Object</dd>
476 * <dt>drop</dt><dd>The Drop Object</dd>
478 * @type {Event.Custom}
482 * @description A Sortable node was moved.
483 * @param {Event.Facade} event An Event Facade object with the following specific property added:
485 * <dt>same</dt><dd>Moved to the same list.</dd>
486 * <dt>drag</dt><dd>The Drag Object</dd>
487 * <dt>drop</dt><dd>The Drop Object</dd>
489 * @type {Event.Custom}
493 * @description A Sortable node was moved.
494 * @param {Event.Facade} event An Event Facade object with the following specific property added:
496 * <dt>same</dt><dd>Moved to the same list.</dd>
497 * <dt>drag</dt><dd>The Drag Object</dd>
498 * <dt>drop</dt><dd>The Drop Object</dd>
500 * @type {Event.Custom}
504 * @description A Sortable node was moved.
505 * @param {Event.Facade} event An Event Facade object with the following specific property added:
507 * <dt>same</dt><dd>Moved to the same list.</dd>
508 * <dt>drag</dt><dd>The Drag Object</dd>
509 * <dt>drop</dt><dd>The Drop Object</dd>
511 * @type {Event.Custom}
516 }, '3.3.0' ,{requires:['dd-delegate', 'dd-drop-plugin', 'dd-proxy']});