2012-05-11 02:00:45 +00:00
/ *
* jQuery UI Nested Sortable
2013-07-02 08:46:44 +00:00
* v 1.3 . 5 / 21 jun 2012
* http : //mjsarfatti.com/code/nestedSortable
2012-05-11 02:00:45 +00:00
*
2013-07-02 08:46:44 +00:00
* Depends on :
2012-05-11 02:00:45 +00:00
* jquery . ui . sortable . js 1.8 +
*
2013-07-02 08:46:44 +00:00
* Copyright ( c ) 2010 - 2012 Manuele J Sarfatti
* Licensed under the MIT License
* http : //www.opensource.org/licenses/mit-license.php
2012-05-11 02:00:45 +00:00
* /
( function ( $ ) {
2013-07-02 08:46:44 +00:00
$ . widget ( "mjs.nestedSortable" , $ . extend ( { } , $ . ui . sortable . prototype , {
2012-05-11 02:00:45 +00:00
options : {
tabSize : 20 ,
2013-07-02 08:46:44 +00:00
disableNesting : 'mjs-nestedSortable-no-nesting' ,
errorClass : 'mjs-nestedSortable-error' ,
doNotClear : false ,
2012-05-11 02:00:45 +00:00
listType : 'ol' ,
maxLevels : 0 ,
2013-07-02 08:46:44 +00:00
protectRoot : false ,
rootID : null ,
rtl : false ,
isAllowed : function ( item , parent ) { return true ; }
2012-05-11 02:00:45 +00:00
} ,
_create : function ( ) {
this . element . data ( 'sortable' , this . element . data ( 'nestedSortable' ) ) ;
2013-07-02 08:46:44 +00:00
if ( ! this . element . is ( this . options . listType ) )
throw new Error ( 'nestedSortable: Please check the listType option is set to your actual list type' ) ;
2012-05-11 02:00:45 +00:00
return $ . ui . sortable . prototype . _create . apply ( this , arguments ) ;
} ,
destroy : function ( ) {
this . element
. removeData ( "nestedSortable" )
. unbind ( ".nestedSortable" ) ;
return $ . ui . sortable . prototype . destroy . apply ( this , arguments ) ;
} ,
_mouseDrag : function ( event ) {
//Compute the helpers position
this . position = this . _generatePosition ( event ) ;
this . positionAbs = this . _convertPositionTo ( "absolute" ) ;
if ( ! this . lastPositionAbs ) {
this . lastPositionAbs = this . positionAbs ;
}
2013-07-02 08:46:44 +00:00
var o = this . options ;
2012-05-11 02:00:45 +00:00
//Do scrolling
if ( this . options . scroll ) {
2013-07-02 08:46:44 +00:00
var scrolled = false ;
2012-05-11 02:00:45 +00:00
if ( this . scrollParent [ 0 ] != document && this . scrollParent [ 0 ] . tagName != 'HTML' ) {
if ( ( this . overflowOffset . top + this . scrollParent [ 0 ] . offsetHeight ) - event . pageY < o . scrollSensitivity )
this . scrollParent [ 0 ] . scrollTop = scrolled = this . scrollParent [ 0 ] . scrollTop + o . scrollSpeed ;
else if ( event . pageY - this . overflowOffset . top < o . scrollSensitivity )
this . scrollParent [ 0 ] . scrollTop = scrolled = this . scrollParent [ 0 ] . scrollTop - o . scrollSpeed ;
if ( ( this . overflowOffset . left + this . scrollParent [ 0 ] . offsetWidth ) - event . pageX < o . scrollSensitivity )
this . scrollParent [ 0 ] . scrollLeft = scrolled = this . scrollParent [ 0 ] . scrollLeft + o . scrollSpeed ;
else if ( event . pageX - this . overflowOffset . left < o . scrollSensitivity )
this . scrollParent [ 0 ] . scrollLeft = scrolled = this . scrollParent [ 0 ] . scrollLeft - o . scrollSpeed ;
} else {
if ( event . pageY - $ ( document ) . scrollTop ( ) < o . scrollSensitivity )
scrolled = $ ( document ) . scrollTop ( $ ( document ) . scrollTop ( ) - o . scrollSpeed ) ;
else if ( $ ( window ) . height ( ) - ( event . pageY - $ ( document ) . scrollTop ( ) ) < o . scrollSensitivity )
scrolled = $ ( document ) . scrollTop ( $ ( document ) . scrollTop ( ) + o . scrollSpeed ) ;
if ( event . pageX - $ ( document ) . scrollLeft ( ) < o . scrollSensitivity )
scrolled = $ ( document ) . scrollLeft ( $ ( document ) . scrollLeft ( ) - o . scrollSpeed ) ;
else if ( $ ( window ) . width ( ) - ( event . pageX - $ ( document ) . scrollLeft ( ) ) < o . scrollSensitivity )
scrolled = $ ( document ) . scrollLeft ( $ ( document ) . scrollLeft ( ) + o . scrollSpeed ) ;
}
if ( scrolled !== false && $ . ui . ddmanager && ! o . dropBehaviour )
$ . ui . ddmanager . prepareOffsets ( this , event ) ;
}
//Regenerate the absolute position used for position checks
this . positionAbs = this . _convertPositionTo ( "absolute" ) ;
2013-07-02 08:46:44 +00:00
// Find the top offset before rearrangement,
var previousTopOffset = this . placeholder . offset ( ) . top ;
2012-05-11 02:00:45 +00:00
//Set the helper position
if ( ! this . options . axis || this . options . axis != "y" ) this . helper [ 0 ] . style . left = this . position . left + 'px' ;
if ( ! this . options . axis || this . options . axis != "x" ) this . helper [ 0 ] . style . top = this . position . top + 'px' ;
//Rearrange
for ( var i = this . items . length - 1 ; i >= 0 ; i -- ) {
//Cache variables and intersection, continue if no intersection
var item = this . items [ i ] , itemElement = item . item [ 0 ] , intersection = this . _intersectsWithPointer ( item ) ;
if ( ! intersection ) continue ;
if ( itemElement != this . currentItem [ 0 ] //cannot intersect with itself
&& this . placeholder [ intersection == 1 ? "next" : "prev" ] ( ) [ 0 ] != itemElement //no useless actions that have been done before
&& ! $ . contains ( this . placeholder [ 0 ] , itemElement ) //no action if the item moved is the parent of the item checked
&& ( this . options . type == 'semi-dynamic' ? ! $ . contains ( this . element [ 0 ] , itemElement ) : true )
//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
) {
$ ( itemElement ) . mouseenter ( ) ;
this . direction = intersection == 1 ? "down" : "up" ;
if ( this . options . tolerance == "pointer" || this . _intersectsWithSides ( item ) ) {
$ ( itemElement ) . mouseleave ( ) ;
this . _rearrange ( event , item ) ;
} else {
break ;
}
// Clear emtpy ul's/ol's
this . _clearEmpty ( itemElement ) ;
this . _trigger ( "change" , event , this . _uiHash ( ) ) ;
break ;
}
}
2013-07-02 08:46:44 +00:00
var parentItem = ( this . placeholder [ 0 ] . parentNode . parentNode &&
$ ( this . placeholder [ 0 ] . parentNode . parentNode ) . closest ( '.ui-sortable' ) . length )
? $ ( this . placeholder [ 0 ] . parentNode . parentNode )
: null ,
2012-05-11 02:00:45 +00:00
level = this . _getLevel ( this . placeholder ) ,
2013-07-02 08:46:44 +00:00
childLevels = this . _getChildLevels ( this . helper ) ;
2012-05-11 02:00:45 +00:00
2013-07-02 08:46:44 +00:00
// To find the previous sibling in the list, keep backtracking until we hit a valid list item.
var previousItem = this . placeholder [ 0 ] . previousSibling ? $ ( this . placeholder [ 0 ] . previousSibling ) : null ;
2012-05-11 02:00:45 +00:00
if ( previousItem != null ) {
2013-07-02 08:46:44 +00:00
while ( previousItem [ 0 ] . nodeName . toLowerCase ( ) != 'li' || previousItem [ 0 ] == this . currentItem [ 0 ] || previousItem [ 0 ] == this . helper [ 0 ] ) {
2012-05-11 02:00:45 +00:00
if ( previousItem [ 0 ] . previousSibling ) {
previousItem = $ ( previousItem [ 0 ] . previousSibling ) ;
} else {
previousItem = null ;
break ;
}
}
}
2013-07-02 08:46:44 +00:00
// To find the next sibling in the list, keep stepping forward until we hit a valid list item.
var nextItem = this . placeholder [ 0 ] . nextSibling ? $ ( this . placeholder [ 0 ] . nextSibling ) : null ;
if ( nextItem != null ) {
while ( nextItem [ 0 ] . nodeName . toLowerCase ( ) != 'li' || nextItem [ 0 ] == this . currentItem [ 0 ] || nextItem [ 0 ] == this . helper [ 0 ] ) {
if ( nextItem [ 0 ] . nextSibling ) {
nextItem = $ ( nextItem [ 0 ] . nextSibling ) ;
} else {
nextItem = null ;
break ;
}
}
}
var newList = document . createElement ( o . listType ) ;
2012-05-11 02:00:45 +00:00
this . beyondMaxLevels = 0 ;
2013-07-02 08:46:44 +00:00
// If the item is moved to the left, send it to its parent's level unless there are siblings below it.
if ( parentItem != null && nextItem == null &&
( o . rtl && ( this . positionAbs . left + this . helper . outerWidth ( ) > parentItem . offset ( ) . left + parentItem . outerWidth ( ) ) ||
! o . rtl && ( this . positionAbs . left < parentItem . offset ( ) . left ) ) ) {
2012-05-11 02:00:45 +00:00
parentItem . after ( this . placeholder [ 0 ] ) ;
this . _clearEmpty ( parentItem [ 0 ] ) ;
this . _trigger ( "change" , event , this . _uiHash ( ) ) ;
}
2013-07-02 08:46:44 +00:00
// If the item is below a sibling and is moved to the right, make it a child of that sibling.
else if ( previousItem != null &&
( o . rtl && ( this . positionAbs . left + this . helper . outerWidth ( ) < previousItem . offset ( ) . left + previousItem . outerWidth ( ) - o . tabSize ) ||
! o . rtl && ( this . positionAbs . left > previousItem . offset ( ) . left + o . tabSize ) ) ) {
this . _isAllowed ( previousItem , level , level + childLevels + 1 ) ;
2012-05-11 02:00:45 +00:00
if ( ! previousItem . children ( o . listType ) . length ) {
previousItem [ 0 ] . appendChild ( newList ) ;
}
2013-07-02 08:46:44 +00:00
// If this item is being moved from the top, add it to the top of the list.
if ( previousTopOffset && ( previousTopOffset <= previousItem . offset ( ) . top ) ) {
previousItem . children ( o . listType ) . prepend ( this . placeholder ) ;
}
// Otherwise, add it to the bottom of the list.
else {
previousItem . children ( o . listType ) [ 0 ] . appendChild ( this . placeholder [ 0 ] ) ;
}
2012-05-11 02:00:45 +00:00
this . _trigger ( "change" , event , this . _uiHash ( ) ) ;
}
else {
2013-07-02 08:46:44 +00:00
this . _isAllowed ( parentItem , level , level + childLevels ) ;
2012-05-11 02:00:45 +00:00
}
//Post events to containers
this . _contactContainers ( event ) ;
//Interconnect with droppables
if ( $ . ui . ddmanager ) $ . ui . ddmanager . drag ( this , event ) ;
//Call callbacks
this . _trigger ( 'sort' , event , this . _uiHash ( ) ) ;
this . lastPositionAbs = this . positionAbs ;
return false ;
} ,
_mouseStop : function ( event , noPropagation ) {
// If the item is in a position not allowed, send it back
if ( this . beyondMaxLevels ) {
this . placeholder . removeClass ( this . options . errorClass ) ;
2013-07-02 08:46:44 +00:00
if ( this . domPosition . prev ) {
$ ( this . domPosition . prev ) . after ( this . placeholder ) ;
2012-05-11 02:00:45 +00:00
} else {
2013-07-02 08:46:44 +00:00
$ ( this . domPosition . parent ) . prepend ( this . placeholder ) ;
2012-05-11 02:00:45 +00:00
}
2013-07-02 08:46:44 +00:00
this . _trigger ( "revert" , event , this . _uiHash ( ) ) ;
2012-05-11 02:00:45 +00:00
}
// Clean last empty ul/ol
for ( var i = this . items . length - 1 ; i >= 0 ; i -- ) {
var item = this . items [ i ] . item [ 0 ] ;
this . _clearEmpty ( item ) ;
}
$ . ui . sortable . prototype . _mouseStop . apply ( this , arguments ) ;
2013-07-02 08:46:44 +00:00
// for Orbit ----> Check the number of pages in each page
if ( $ ( '#items' ) . length ) {
var $sortable = $ ( '.sortable' ) ,
$host = $sortable . children ( '.navbar' ) . eq ( 0 ) ,
$navbar = $ ( '.sortable li' ) . children ( '.navbar' ) ,
$quantity = $sortable . find ( 'li' ) . length ;
$host . find ( '.badge' ) . text ( $quantity ) ;
$navbar . each ( function ( i ) {
if ( $navbar . eq ( i ) . next ( 'ol' ) . length > 0 ) {
var $amount = $navbar . eq ( i ) . next ( 'ol' ) . find ( 'li' ) . length ;
$navbar . eq ( i ) . find ( '.badge' ) . text ( $amount ) ;
} else {
$navbar . eq ( i ) . find ( '.badge' ) . text ( '0' ) ;
}
if ( $navbar . eq ( i ) . next ( 'ol' ) . length == 1 && $navbar . eq ( i ) . next ( 'ol' ) . children ( 'li' ) . length == 0 ) {
$navbar . eq ( i ) . next ( 'ol' ) . remove ( ) ;
}
$navbar . eq ( i ) . find ( '.badge' ) . text ( ) > 0 ? $navbar . eq ( i ) . find ( '.badge' ) . addClass ( 'badge-info' ) : $navbar . eq ( i ) . find ( '.badge' ) . removeClass ( 'badge-info' ) ;
} ) ;
}
//
2012-05-11 02:00:45 +00:00
} ,
2013-07-02 08:46:44 +00:00
serialize : function ( options ) {
2012-05-11 02:00:45 +00:00
2013-07-02 08:46:44 +00:00
var o = $ . extend ( { } , this . options , options ) ,
items = this . _getItemsAsjQuery ( o && o . connected ) ,
str = [ ] ;
2012-05-11 02:00:45 +00:00
$ ( items ) . each ( function ( ) {
var res = ( $ ( o . item || this ) . attr ( o . attribute || 'id' ) || '' )
. match ( o . expression || ( /(.+)[-=_](.+)/ ) ) ,
pid = ( $ ( o . item || this ) . parent ( o . listType )
2013-07-02 08:46:44 +00:00
. parent ( o . items )
2012-05-11 02:00:45 +00:00
. attr ( o . attribute || 'id' ) || '' )
. match ( o . expression || ( /(.+)[-=_](.+)/ ) ) ;
if ( res ) {
str . push ( ( ( o . key || res [ 1 ] ) + '[' + ( o . key && o . expression ? res [ 1 ] : res [ 2 ] ) + ']' )
+ '='
2013-07-02 08:46:44 +00:00
+ ( pid ? ( o . key && o . expression ? pid [ 1 ] : pid [ 2 ] ) : o . rootID ) ) ;
2012-05-11 02:00:45 +00:00
}
} ) ;
if ( ! str . length && o . key ) {
str . push ( o . key + '=' ) ;
}
return str . join ( '&' ) ;
} ,
2013-07-02 08:46:44 +00:00
toHierarchy : function ( options ) {
2012-05-11 02:00:45 +00:00
2013-07-02 08:46:44 +00:00
var o = $ . extend ( { } , this . options , options ) ,
sDepth = o . startDepthCount || 0 ,
2012-05-11 02:00:45 +00:00
ret = [ ] ;
2013-07-02 08:46:44 +00:00
$ ( this . element ) . children ( o . items ) . each ( function ( ) {
var level = _recursiveItems ( this ) ;
2012-05-11 02:00:45 +00:00
ret . push ( level ) ;
} ) ;
return ret ;
2013-07-02 08:46:44 +00:00
function _recursiveItems ( item ) {
var id = ( $ ( item ) . attr ( o . attribute || 'id' ) || '' ) . match ( o . expression || ( /(.+)[-=_](.+)/ ) ) ;
2012-05-11 02:00:45 +00:00
if ( id ) {
2013-07-02 08:46:44 +00:00
var currentItem = { "id" : id [ 2 ] } ;
if ( $ ( item ) . children ( o . listType ) . children ( o . items ) . length > 0 ) {
currentItem . children = [ ] ;
$ ( item ) . children ( o . listType ) . children ( o . items ) . each ( function ( ) {
var level = _recursiveItems ( this ) ;
currentItem . children . push ( level ) ;
2012-05-11 02:00:45 +00:00
} ) ;
}
2013-07-02 08:46:44 +00:00
return currentItem ;
2012-05-11 02:00:45 +00:00
}
}
} ,
2013-07-02 08:46:44 +00:00
toArray : function ( options ) {
2012-05-11 02:00:45 +00:00
2013-07-02 08:46:44 +00:00
var o = $ . extend ( { } , this . options , options ) ,
sDepth = o . startDepthCount || 0 ,
2012-05-11 02:00:45 +00:00
ret = [ ] ,
left = 2 ;
ret . push ( {
2013-07-02 08:46:44 +00:00
"item_id" : o . rootID ,
2012-05-11 02:00:45 +00:00
"parent_id" : 'none' ,
"depth" : sDepth ,
"left" : '1' ,
2013-07-02 08:46:44 +00:00
"right" : ( $ ( o . items , this . element ) . length + 1 ) * 2
2012-05-11 02:00:45 +00:00
} ) ;
2013-07-02 08:46:44 +00:00
$ ( this . element ) . children ( o . items ) . each ( function ( ) {
2012-05-11 02:00:45 +00:00
left = _recursiveArray ( this , sDepth + 1 , left ) ;
} ) ;
ret = ret . sort ( function ( a , b ) { return ( a . left - b . left ) ; } ) ;
return ret ;
function _recursiveArray ( item , depth , left ) {
var right = left + 1 ,
id ,
pid ;
2013-07-02 08:46:44 +00:00
if ( $ ( item ) . children ( o . listType ) . children ( o . items ) . length > 0 ) {
2012-05-11 02:00:45 +00:00
depth ++ ;
2013-07-02 08:46:44 +00:00
$ ( item ) . children ( o . listType ) . children ( o . items ) . each ( function ( ) {
2012-05-11 02:00:45 +00:00
right = _recursiveArray ( $ ( this ) , depth , right ) ;
} ) ;
depth -- ;
}
id = ( $ ( item ) . attr ( o . attribute || 'id' ) ) . match ( o . expression || ( /(.+)[-=_](.+)/ ) ) ;
if ( depth === sDepth + 1 ) {
2013-07-02 08:46:44 +00:00
pid = o . rootID ;
2012-05-11 02:00:45 +00:00
} else {
var parentItem = ( $ ( item ) . parent ( o . listType )
2013-07-02 08:46:44 +00:00
. parent ( o . items )
. attr ( o . attribute || 'id' ) )
. match ( o . expression || ( /(.+)[-=_](.+)/ ) ) ;
2012-05-11 02:00:45 +00:00
pid = parentItem [ 2 ] ;
}
if ( id ) {
ret . push ( { "item_id" : id [ 2 ] , "parent_id" : pid , "depth" : depth , "left" : left , "right" : right } ) ;
}
left = right + 1 ;
return left ;
}
} ,
_clearEmpty : function ( item ) {
var emptyList = $ ( item ) . children ( this . options . listType ) ;
2013-07-02 08:46:44 +00:00
if ( emptyList . length && ! emptyList . children ( ) . length && ! this . options . doNotClear ) {
2012-05-11 02:00:45 +00:00
emptyList . remove ( ) ;
}
} ,
_getLevel : function ( item ) {
var level = 1 ;
if ( this . options . listType ) {
var list = item . closest ( this . options . listType ) ;
2013-07-02 08:46:44 +00:00
while ( list && list . length > 0 &&
! list . is ( '.ui-sortable' ) ) {
2012-05-11 02:00:45 +00:00
level ++ ;
list = list . parent ( ) . closest ( this . options . listType ) ;
}
}
return level ;
} ,
_getChildLevels : function ( parent , depth ) {
var self = this ,
o = this . options ,
result = 0 ;
depth = depth || 0 ;
$ ( parent ) . children ( o . listType ) . children ( o . items ) . each ( function ( index , child ) {
result = Math . max ( self . _getChildLevels ( child , depth + 1 ) , result ) ;
} ) ;
return depth ? result + 1 : result ;
} ,
2013-07-02 08:46:44 +00:00
_isAllowed : function ( parentItem , level , levels ) {
var o = this . options ,
isRoot = $ ( this . domPosition . parent ) . hasClass ( 'ui-sortable' ) ? true : false ,
maxLevels = this . placeholder . closest ( '.ui-sortable' ) . nestedSortable ( 'option' , 'maxLevels' ) ; // this takes into account the maxLevels set to the recipient list
// Is the root protected?
// Are we trying to nest under a no-nest?
// Are we nesting too deep?
if ( ! o . isAllowed ( this . currentItem , parentItem ) ||
parentItem && parentItem . hasClass ( o . disableNesting ) ||
o . protectRoot && ( parentItem == null && ! isRoot || isRoot && level > 1 ) ) {
this . placeholder . addClass ( o . errorClass ) ;
if ( maxLevels < levels && maxLevels != 0 ) {
this . beyondMaxLevels = levels - maxLevels ;
} else {
this . beyondMaxLevels = 1 ;
}
} else {
if ( maxLevels < levels && maxLevels != 0 ) {
2012-05-11 02:00:45 +00:00
this . placeholder . addClass ( o . errorClass ) ;
2013-07-02 08:46:44 +00:00
this . beyondMaxLevels = levels - maxLevels ;
2012-05-11 02:00:45 +00:00
} else {
this . placeholder . removeClass ( o . errorClass ) ;
this . beyondMaxLevels = 0 ;
}
}
}
} ) ) ;
2013-07-02 08:46:44 +00:00
$ . mjs . nestedSortable . prototype . options = $ . extend ( { } , $ . ui . sortable . prototype . options , $ . mjs . nestedSortable . prototype . options ) ;
} ) ( jQuery ) ;