1/** 2 * 3 * Nested Sortable Plugin for jQuery/Interface. 4 * 5 * Version 1.0.1 6 * 7 *Change Log: 8 * 1.0 9 * Initial Release 10 * 1.0.1 11 * Added noNestingClass option to prevent nesting in some elements. 12 * 13 * 14 * Copyright (c) 2007 Bernardo de Padua dos Santos 15 * Dual licensed under the MIT (MIT-LICENSE.txt) 16 * and GPL (GPL-LICENSE.txt) licenses. 17 * 18 * http://code.google.com/p/nestedsortables/ 19 * 20 */ 21jQuery.iNestedSortable = { 22 /* 23 * Called when an item is being dragged. Puts the sort helper in the proper place. 24 * The "e" argument passed in is the Sortable element. 25 */ 26 checkHover : function(e,o) { 27 if(e.isNestedSortable){ 28 //A brand new NestedSortable hovering check 29 jQuery.iNestedSortable.scroll(e); 30 return jQuery.iNestedSortable.newCheckHover(e); 31 } else { 32 //The legacy hovering check performed by plain Sortables 33 //I don't think "o" is even used, but it is in the function declaration 34 return jQuery.iNestedSortable.oldCheckHover(e,o); 35 } 36 }, 37 oldCheckHover : jQuery.iSort.checkhover, 38 newCheckHover : function (e) { 39 if (!jQuery.iDrag.dragged) { 40 return; 41 } 42 if ( !(e.dropCfg.el.size() > 0) ) { 43 return; 44 } 45 46 //we need to recalculate the position of the sortables, 47 //or there will be some mismatches 48 if(!e.nestedSortCfg.remeasured) { 49 jQuery.iSort.measure(e); 50 e.nestedSortCfg.remeasured = true; 51 } 52 53 //finds item that is on top of the one being dragged, 54 //and in a compatible nesting level 55 var precItem = jQuery.iNestedSortable.findPrecedingItem(e); 56 57 var shouldNest = jQuery.iNestedSortable.shouldNestItem(e, precItem); 58 var touchingFirst = (!precItem) ? jQuery.iNestedSortable.isTouchingFirstItem(e) : false; 59 var quit = false; 60 61 //avoids doing things more than once 62 if(precItem) { 63 if(e.nestedSortCfg.lastPrecedingItem === precItem && e.nestedSortCfg.lastShouldNest === shouldNest) { 64 quit = true; 65 } 66 } 67 else if(e.nestedSortCfg.lastPrecedingItem === precItem && e.nestedSortCfg.lastTouchingFirst === touchingFirst) { 68 quit = true; 69 } 70 e.nestedSortCfg.lastPrecedingItem = precItem; 71 e.nestedSortCfg.lastShouldNest = shouldNest; 72 e.nestedSortCfg.lastTouchingFirst = touchingFirst; 73 if(quit) {return;} 74 75 if (precItem !== null) { 76 //there is an element on top of this one 77 //on the same nesting, or smaller 78 if (shouldNest) { 79 jQuery.iNestedSortable.nestItem(e, precItem); 80 } else { 81 jQuery.iNestedSortable.appendItem(e, precItem); 82 } 83 } else if (touchingFirst) { 84 //no element on top, but touches the first item on the list 85 jQuery.iNestedSortable.insertOnTop(e); 86 } 87 88 }, 89 /* 90 * Auto scrolls the page when we are dragging an element. 91 */ 92 scroll: function(e) { 93 94 if(!e.nestedSortCfg.autoScroll) { 95 return false; 96 } 97 var sensitivity = e.nestedSortCfg.scrollSensitivity; 98 var speed = e.nestedSortCfg.scrollSpeed; 99 var pointer = jQuery.iDrag.dragged.dragCfg.currentPointer; 100 var docDim = jQuery.iUtil.getScroll(); 101 if((pointer.y - docDim.ih) - docDim.t > -sensitivity) { 102 window.scrollBy(0,speed); 103 } 104 if(pointer.y - docDim.t < sensitivity) { 105 window.scrollBy(0,-speed); 106 } 107 //The two lines bellow are for horizontal scrolling. It is not needed. 108 //if((pointer.x - docDim.iw) - docDim.l > -sensitivity) {window.scrollBy(speed,0);} 109 //if(pointer.x - docDim.l < sensitivity) {window.scrollBy(-speed,0);} 110 111 }, 112 /* 113 * Called when the item is released after a drag. 114 * The argument passed in is the dragged element released. 115 */ 116 check : function(dragged) { 117 //A brand new NestedSortable release check 118 jQuery.iNestedSortable.newCheck(dragged); 119 120 //The legacy release check performed by plain Sortables 121 return jQuery.iNestedSortable.oldCheck(dragged); 122 }, 123 oldCheck : jQuery.iSort.check, 124 newCheck : function(dragged) { 125 //removes nesting styling 126 if (jQuery.iNestedSortable.latestNestingClass && 127 jQuery.iNestedSortable.currentNesting) 128 { 129 jQuery.iNestedSortable.currentNesting 130 .removeClass(jQuery.iNestedSortable.latestNestingClass); 131 jQuery.iNestedSortable.currentNesting = null; 132 jQuery.iNestedSortable.latestNestingClass = ""; 133 } 134
135 if(jQuery.iDrop.overzone.isNestedSortable) { 136 jQuery.iDrop.overzone.nestedSortCfg.remeasured = false; 137 } 138 139 }, 140 /* 141 * Called when there is a need to serialize the 142 * NestedSortable, to send back to the server. The 143 * parameter is a string with the id of the NestedSortable 144 * element, or an array of ids. If no parameter is passed, 145 * all nested sortables in the page will be serialized. The 146 * return value is an object with a 'hash' parameter, that 147 * contains a string for use in GET or POST, and a 'o' parameter, 148 * with contains a JavaScript object representation 149 * of the item order. 150 */ 151 serialize: function(s) { 152 if(jQuery('#' + s).get(0).isNestedSortable){ 153 //A brand new NestedSortable serialization 154 return jQuery.iNestedSortable.newSerialize(s); 155 } else { 156 //The legacy serialization 157 return jQuery.iNestedSortable.oldSerialize(s); 158 } 159 }, 160 oldSerialize: jQuery.iSort.serialize, 161 newSerialize: function (s) { 162 var i; 163 var h = ''; //url get string that represents the element order 164 var currentPath = ''; //used so the recursive function can build h 165 var o = {}; //json object that represents the element order 166 var e; //NestedSortable element being worked on 167 /* 168 * This recursive function will build the get string (h) 169 * and return the object (o) for a given NestedSortable. 170 */ 171 var buildHierarchySer = function(context) { 172 var retVal = []; 173 thisChildren = jQuery(context).children('.' + jQuery.iSort.collected[s]); 174 thisChildren.each( function(i) { 175 176 //Extracts part of the HTML element ID that 177 //will be shown in the serialization, using a RegExp. 178 var serId = jQuery.attr(this,'id'); 179 if(serId && serId.match) { 180 serId = serId.match(e.nestedSortCfg.serializeRegExp)[0]; 181 } 182 183 184 if (h.length > 0) { 185 h += '&'; 186 } 187 h += s + currentPath + '['+i+'][id]=' + serId; 188 retVal[i] = {id: serId}; 189 var newContext = jQuery(this).children(e.nestedSortCfg.nestingTag + "." + e.nestedSortCfg.nestingTagClass.split(" ").join(".")).get(0); 190 var oldPath = currentPath; 191 currentPath += '['+i+'][children]'; 192 var thisChildren = buildHierarchySer(newContext); 193 if (thisChildren.length > 0) { 194 retVal[i].children = thisChildren; 195 } 196 currentPath = oldPath; 197 }); 198 return retVal; 199 }; 200 201 //analises the parameter passed in 202 if (s) { 203 if (jQuery.iSort.collected[s] ) { 204 //when only one NestedSortable id was passed in 205 e = jQuery('#' + s).get(0); 206 o[s] = buildHierarchySer(e); 207 } else { 208 for ( a in s) { 209 //when an array of NestedSortables ids was passed in 210 if (jQuery.iSort.collected[s[a]] ) { 211 e = jQuery('#' + s[a]).get(0); 212 o[s[a]] = buildHierarchySer(e); 213 } 214 } 215 } 216 } else { 217 //when nothing was passed in (we will serialize all) 218 for ( i in jQuery.iSort.collected){ 219 e = jQuery('#' + i).get(0); 220 o[i] = buildHierarchySer(e); 221 } 222 } 223 return {hash:h, o:o}; 224 }, 225 226 /* 227 * Finds the sortable item that is on top of the one being dragged, 228 * and in a compatible nesting level. Returns null if none was found. 229 */ 230 findPrecedingItem : function (e) { 231 var largestY = 0; 232 var preceding = jQuery.grep(e.dropCfg.el, function(i) { 233 //needs to be on top of the one being dragged 234 var isOnTop = (i.pos.y < jQuery.iDrag.dragged.dragCfg.ny) && (i.pos.y > largestY); 235 if(!isOnTop) { 236 return false; 237 } 238 239 //needs to be on the same nesting level or a "child" level 240 var isSameLevel; 241 if(e.nestedSortCfg.rightToLeft) { 242 isSameLevel = (i.pos.x + i.pos.wb + e.nestedSortCfg.snapTolerance > jQuery.iDrag.dragged.dragCfg.nx + jQuery.iDrag.dragged.dragCfg.oC.wb); 243 } else { 244 isSameLevel = (i.pos.x - e.nestedSortCfg.snapTolerance < jQuery.iDrag.dragged.dragCfg.nx); 245 } 246 if(!isSameLevel) { 247 return false; 248 } 249 250 //can't be an element that is being dragged 251 var isBeingDragged = jQuery.iNestedSortable.isBeingDragged(e, i); 252 if(isBeingDragged) { 253 return false; 254 } 255 256 //got here because it is a match 257 largestY = i.pos.y; 258 return true; 259 }); 260 261 if ( preceding.length > 0 ) { 262 //the last one should be it 263 return preceding[(preceding.length-1)]; 264 } else { 265 //nothing above it 266 return null; 267 } 268 }, 269 /* 270 * Checks if the item being dragged is on top and touching the first item on the list. 271 * Returns true or false. 272 */ 273 isTouchingFirstItem : function (e) { 274 var lowestY; 275 var firstItem = jQuery.grep(e.dropCfg.el, function(i) { 276 //needs to be on top of all elements already looked up 277 var isBefore = (lowestY === undefined || i.pos.y < lowestY); 278 if (!isBefore) { 279 return false;
280 } 281 282 //can't be an element that is being dragged 283 var isBeingDragged = jQuery.iNestedSortable.isBeingDragged(e, i); 284 if(isBeingDragged) { 285 return false; 286 } 287 288 //got here because it is a match 289 lowestY = i.pos.y; 290 return true; 291 }); 292 if ( firstItem.length > 0 ) { 293 //the last one should be it 294 firstItem = firstItem[(firstItem.length-1)]; 295 return firstItem.pos.y < jQuery.iDrag.dragged.dragCfg.ny + jQuery.iDrag.dragged.dragCfg.oC.hb && 296 firstItem.pos.y > jQuery.iDrag.dragged.dragCfg.ny; 297 } else { 298 return false; 299 } 300 }, 301 /* 302 * Checks to see if an element is being dragged. 303 * You may de dragging an element by itself or by one of his ancestors. 304 * Returns true or false. 305 */ 306 isBeingDragged : function (e, elem) { 307 var dragged = jQuery.iDrag.dragged; 308 309 //nothing being dragged 310 if(!dragged) { 311 return false; 312 } 313 314 //trivial case 315 if(elem == dragged) { 316 return true; 317 } 318 319 //looks for its ancestors 320 if ( 321 jQuery(elem) 322 .parents("." + e.sortCfg.accept.split(" ").join(".")) 323 .filter(function() {return this == dragged;}).length !== 0 324 ) { 325 return true; 326 } else { 327 return false; 328 } 329 }, 330 shouldNestItem : function(e, precedingItem ) { 331 //there should be a preceding item to be able to nest 332 if(!precedingItem) {return false;} 333 if(e.nestedSortCfg.noNestingClass && 334 jQuery(precedingItem).filter("." + e.nestedSortCfg.noNestingClass).get(0) === precedingItem) 335 {return false;} 336 //This code is to limit the levels of nesting that can be achieved 337 //Development of this is currently halted. 338 /* 339 if( e.nestedSortCfg.nestingLimit !== false) { 340 //nesting level of the preceding item 341 var nLevelPrec = jQuery(precedingItem) 342 .parents("." + e.sortCfg.accept.split(" ").join(".")) 343 .length; 344 345 //don't allow nesting 346 if (nLevelPrec >= e.nestedSortCfg.nestingLimit) return false; 347 348 //will hold the maximum level of nesting in the element being dragged 349 var nLevelDrag = 0; 350 351 //for each leaf element inside the dragged element (that have no elements nested to it) 352 jQuery(e.nestedSortCfg.nestingTag + "." + e.nestedSortCfg.nestingTagClass.split(" ").join(".") + " > ." + e.sortCfg.accept.split(" ").join(".") + ":first-child", jQuery.iDrag.dragged) 353 .not("*["+e.nestedSortCfg.nestingTag + "." + e.nestedSortCfg.nestingTagClass.split(" ").join(".")+"]") 354 .each( function(i) { 355 var draggedIndex = 0; 356 //finds out 357 var parentLength = jQuery(this).parents("." + e.sortCfg.accept.split(" ").join(".")).each( 358 function(i) { 359 draggedIndex = i; 360 return this == jQuery.iDrag.dragged; 361 } 362 ).length; 363 364 //here dradraggedIndex holds the index 365 //of the dragged element in the ancestors array of the leaf element 366 var thisLevel = parentLength - draggedIndex; 367 if (thisLevel > nLevelDrag ) 368 nLevelDrag = thisLevel; 369 } 370 ); 371 372 //don't allow nesting 373 if (nLevelPrec + nLevelDrag > e.nestedSortCfg.nestingLimit) return false; 374 }*/ 375 376 if (e.nestedSortCfg.rightToLeft) { 377 return precedingItem.pos.x + precedingItem.pos.wb - (e.nestedSortCfg.nestingPxSpace - e.nestedSortCfg.snapTolerance) > jQuery.iDrag.dragged.dragCfg.nx + jQuery.iDrag.dragged.dragCfg.oC.wb; 378 } else { 379 return precedingItem.pos.x + (e.nestedSortCfg.nestingPxSpace - e.nestedSortCfg.snapTolerance) < jQuery.iDrag.dragged.dragCfg.nx; 380 } 381 }, 382 nestItem: function(e, parent) { 383 //selects the nesting tag inside the parent 384 var parentNesting = jQuery(parent).children(e.nestedSortCfg.nestingTag + "." + e.nestedSortCfg.nestingTagClass.split(" ").join(".") ); 385 var helper = jQuery.iSort.helper; 386 styleHelper = helper.get(0).style; 387 styleHelper.width = 'auto'; //makes sure helper gets resized properly 388 389 //if there is none, creates it 390 if ( !parentNesting.size()) { 391 var newUl = "<" + e.nestedSortCfg.nestingTag + " class='"+e.nestedSortCfg.nestingTagClass+"'></" + e.nestedSortCfg.nestingTag+">"; 392 // Place new nesting tag and adds style 393 parentNesting = jQuery(parent).append(newUl).children(e.nestedSortCfg.nestingTag).css(e.nestedSortCfg.styleToAttach); 394 } 395 396 //styles the nesting 397 jQuery.iNestedSortable.updateCurrentNestingClass(e, parentNesting ); 398 399 //does stuff before the helper is removed 400 jQuery.iNestedSortable.beforeHelperRemove(e); 401 402 //puts the helper in the proper place. 403 parentNesting.prepend(helper.get(0)); 404 405 //does stuff after the helper is inserted 406 jQuery.iNestedSortable.afterHelperInsert(e); 407 }, 408 appendItem: function(e, itemBefore) { 409 jQuery.iNestedSortable.updateCurrentNestingClass(e, jQuery(itemBefore).parent() ); 410 jQuery.iNestedSortable.beforeHelperRemove(e); 411 jQuery(itemBefore).after(jQuery.iSort.helper.get(0)); 412 jQuery.iNestedSortable.afterHelperInsert(e); 413 }, 414 insertOnTop: function (e) { 415 jQuery.iNestedSortable.updateCurrentNestingClass(e, e); 416 jQuery.iNestedSortable.beforeHelperRemove(e); 417 jQuery(e).prepend(jQuery.iSort.helper.get(0)); 418 jQuery.iNestedSortable.afterHelperInsert(e); 419 }, 420 beforeHelperRemove : function (e) { 421 //hides the nesting when it becomes empty 422 var parent = jQuery.iSort.helper.parent(e.nestedSortCfg.nestingTag + "." + e.nestedSortCfg.nestingTagClass.split(" ").join(".")); 423 var numSiblings = parent 424 .children("." + e.sortCfg.accept.split(" ").join(".") + ":visible") 425 .size(); 426 if(numSiblings === 0 && parent.get(0) !== e) { 427 parent.hide(); 428 } 429 }, 430 afterHelperInsert : function (e) { 431 //displays the nesting after something is inserted 432 var parent = jQuery.iSort.helper.parent(); 433 if(parent.get(0) !== e) { 434 parent.show(); 435 } 436 e.nestedSortCfg.remeasured = false;
437 }, 438 updateCurrentNestingClass : function(e, nestingElem) { 439 440 var nesting = jQuery(nestingElem); 441 442 if ((e.nestedSortCfg.currentNestingClass) && //makes sure a special class is desired 443 (!jQuery.iNestedSortable.currentNesting || nesting.get(0) != jQuery.iNestedSortable.currentNesting.get(0)) ){ //avoids doing it on the same elem 444 445 //removes from last nesting 446 if(jQuery.iNestedSortable.currentNesting) { 447 jQuery.iNestedSortable.currentNesting 448 .removeClass(e.nestedSortCfg.currentNestingClass); 449 } 450 451 if(nesting.get(0) != e) { //not root nesting 452 jQuery.iNestedSortable.currentNesting = nesting; 453 454 //adds to this one 455 nesting.addClass(e.nestedSortCfg.currentNestingClass); 456 457 //this is need to remove the styling on "check" 458 jQuery.iNestedSortable.latestNestingClass = e.nestedSortCfg.currentNestingClass; 459 } else { 460 //don't style the "root nesting" 461 462 //cleans up 463 jQuery.iNestedSortable.currentNesting = null; 464 jQuery.iNestedSortable.latestNestingClass = ""; 465 } 466 } 467 }, 468 destroy: function () { 469 return this.each( 470 function () { 471 if(this.isNestedSortable) { 472 this.nestedSortCfg = null; 473 this.isNestedSortable = null; 474 jQuery(this).SortableDestroy(); 475 } 476 } 477 ); 478 }, 479 build: function(conf) { 480 if (conf.accept && jQuery.iUtil && jQuery.iDrag && jQuery.iDrop && jQuery.iSort) 481 { 482 this.each( 483 function() { 484 this.isNestedSortable = true; 485 this.nestedSortCfg = { 486 noNestingClass : conf.noNestingClass ? conf.noNestingClass : false, 487 rightToLeft : conf.rightToLeft ? true : false , 488 nestingPxSpace : parseInt(conf.nestingPxSpace, 10) || 30 , 489 currentNestingClass : conf.currentNestingClass ? conf.currentNestingClass : "", 490 nestingLimit : conf.nestingLimit ? conf.nestingLimit : false, //not implemented yet 491 autoScroll : conf.autoScroll !== undefined ? conf.autoScroll == true : true, 492 scrollSensitivity: conf.scrollSensitivity ? conf.scrollSensitivity : 20, 493 scrollSpeed: conf.scrollSpeed ? conf.scrollSpeed : 20, 494 serializeRegExp : conf.serializeRegExp ? conf.serializeRegExp : /[^\-]*$/ 495 }; 496 497 //a "do nothing" tolerance when nesting/un-nesting, to stop things from jumping up too quickly 498 this.nestedSortCfg.snapTolerance = parseInt(this.nestedSortCfg.nestingPxSpace * 0.4, 10); 499 500 //the tag that will be used to nest items to parent items 501 this.nestedSortCfg.nestingTag = this.tagName; 502 this.nestedSortCfg.nestingTagClass = this.className; 503 504 //style that makes nested elements be padded to the right or to the left 505 this.nestedSortCfg.styleToAttach = (this.nestedSortCfg.rightToLeft) ? 506 {"padding-left":0, "padding-right": this.nestedSortCfg.nestingPxSpace + 'px' } 507 :{"padding-left": this.nestedSortCfg.nestingPxSpace + 'px', "padding-right": 0 }; 508 jQuery(this.nestedSortCfg.nestingTag, this) 509 .css(this.nestedSortCfg.styleToAttach); 510 511 } 512 ); 513 514 //overides checkhover, check and serialize, without losing backwards compatilibity (eg. plain Sortables can stil be used) 515 jQuery.iSort.checkhover = jQuery.iNestedSortable.checkHover;
516 jQuery.iSort.check = jQuery.iNestedSortable.check; 517 jQuery.iSort.serialize = jQuery.iNestedSortable.serialize; 518 } 519 520 return this.Sortable(conf); 521 } 522}; 523 524//Extends jQuery to add the plugin. 525jQuery.fn.extend( 526 { 527 NestedSortable : jQuery.iNestedSortable.build, 528 NestedSortableDestroy: jQuery.iNestedSortable.destroy 529 } 530); 531 532 533//Monkey patches interface with some corrections, which are not applied to 534//the 1.2 version yet. getScroll is needed by the autoScroll option. 535jQuery.iUtil.getScroll = function (e) 536 { 537 var t, l, w, h, iw, ih; 538 if (e && e.nodeName.toLowerCase() != 'body') { 539 t = e.scrollTop; 540 l = e.scrollLeft; 541 w = e.scrollWidth; 542 h = e.scrollHeight; 543 iw = 0; 544 ih = 0; 545 } else { 546 if (document.documentElement && document.documentElement.scrollTop) { 547 t = document.documentElement.scrollTop; 548 l = document.documentElement.scrollLeft; 549 w = document.documentElement.scrollWidth; 550 h = document.documentElement.scrollHeight; 551 } else if (document.body) { 552 t = document.body.scrollTop; 553 l = document.body.scrollLeft; 554 w = document.body.scrollWidth; 555 h = document.body.scrollHeight; 556 } 557 iw = self.innerWidth||document.documentElement.clientWidth||document.body.clientWidth||0; 558 ih = self.innerHeight||document.documentElement.clientHeight||document.body.clientHeight||0; 559 } 560 return { t: t, l: l, w: w, h: h, iw: iw, ih: ih }; 561 };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.