1// version 1.6.0 2// http://welcome.totheinter.net/columnizer-jquery-plugin/ 3// created by: Adam Wulf @adamwulf, [email protected] 4 5(function($){ 6 7 $.fn.columnize = function(options) { 8 9 10 var defaults = { 11 // default width of columns 12 width: 400, 13 // optional # of columns instead of width 14 columns : false, 15 // true to build columns once regardless of window resize 16 // false to rebuild when content box changes bounds 17 buildOnce : false, 18 // an object with options if the text should overflow 19 // it's container if it can't fit within a specified height 20 overflow : false, 21 // this function is called after content is columnized 22 doneFunc : function(){}, 23 // if the content should be columnized into a 24 // container node other than it's own node 25 target : false, 26 // re-columnizing when images reload might make things 27 // run slow. so flip this to true if it's causing delays 28 ignoreImageLoading : true, 29 // should columns float left or right 30 columnFloat : "left", 31 // ensure the last column is never the tallest column 32 lastNeverTallest : false, 33 // (int) the minimum number of characters to jump when splitting 34 // text nodes. smaller numbers will result in higher accuracy 35 // column widths, but will take slightly longer 36 accuracy : false, 37 // don't automatically layout columns, only use manual columnbreak 38 manualBreaks : false, 39 // previx for all the CSS classes used by this plugin 40 // default to empty string for backwards compatibility 41 cssClassPrefix : "" 42 }; 43 var options = $.extend(defaults, options); 44 45 if(typeof(options.width) == "string"){ 46 options.width = parseInt(options.width); 47 if(isNaN(options.width)){ 48 options.width = defaults.width; 49 } 50 } 51 52 return this.each(function() { 53 var $inBox = options.target ? $(options.target) : $(this); 54 var maxHeight = $(this).height(); 55 var $cache = $('<div></div>'); // this is where we'll put the real content 56 var lastWidth = 0; 57 var columnizing = false; 58 var manualBreaks = options.manualBreaks; 59 var cssClassPrefix = defaults.cssClassPrefix; 60 if(typeof(options.cssClassPrefix) == "string"){ 61 cssClassPrefix = options.cssClassPrefix; 62 } 63 64 65 var adjustment = 0; 66 67 $cache.append($(this).contents().clone(true)); 68 69 // images loading after dom load 70 // can screw up the column heights, 71 // so recolumnize after images load 72 if(!options.ignoreImageLoading && !options.target){ 73 if(!$inBox.data("imageLoaded")){ 74 $inBox.data("imageLoaded", true); 75 if($(this).find("img").length > 0){ 76 // only bother if there are 77 // actually images... 78 var func = function($inBox,$cache){ return function(){ 79 if(!$inBox.data("firstImageLoaded")){ 80 $inBox.data("firstImageLoaded", "true"); 81 $inBox.empty().append($cache.children().clone(true)); 82 $inBox.columnize(options); 83 } 84 }}($(this), $cache); 85 $(this).find("img").one("load", func); 86 $(this).find("img").one("abort", func); 87 return; 88 } 89 } 90 } 91 92 $inBox.empty(); 93 94 columnizeIt(); 95 96 if(!options.buildOnce){ 97 $(window).resize(function() { 98 if(!options.buildOnce && $.browser.msie){ 99 if($inBox.data("timeout")){ 100 clearTimeout($inBox.data("timeout")); 101 } 102 $inBox.data("timeout", setTimeout(columnizeIt, 200)); 103 }else if(!options.buildOnce){ 104 columnizeIt(); 105 }else{ 106 // don't rebuild 107 } 108 }); 109 } 110 111 function prefixTheClassName(className, withDot){ 112 var dot = withDot ? "." : ""; 113 if(cssClassPrefix.length){ 114 return dot + cssClassPrefix + "-" + className; 115 } 116 return dot + className; 117 } 118 119 120 /** 121 * this fuction builds as much of a column as it can without 122 * splitting nodes in half. If the last node in the new column 123 * is a text node, then it will try to split that text node. otherwise 124 * it will leave the node in $pullOutHere and return with a height 125 * smaller than targetHeight. 126 * 127 * Returns a boolean on whether we did some splitting successfully at a text point 128 * (so we know we don't need to split a real element). return false if the caller should 129 * split a node if possible to end this column. 130 * 131 * @param putInHere, the jquery node to put elements into for the current column 132 * @param $pullOutHere, the jquery node to pull elements out of (uncolumnized html) 133 * @param $parentColumn, the jquery node for the currently column that's being added to 134 * @param targetHeight, the ideal height for the column, get as close as we can to this height 135 */ 136 function columnize($putInHere, $pullOutHere, $parentColumn, targetHeight){ 137 // 138 // add as many nodes to the column as we can, 139 // but stop once our height is too tall 140 while((manualBreaks || $parentColumn.height() < targetHeight) && 141 $pullOutHere[0].childNodes.length){ 142 var node = $pullOutHere[0].childNodes[0] 143 // 144 // Because we're not cloning, jquery will actually move the element" 145 // http://welcome.totheinter.net/2009/03/19/the-undocumented-life-of-jquerys-append/ 146 if($(node).find(prefixTheClassName("columnbreak", true)).length){ 147 // 148 // our column is on a column break, so just end here 149 return; 150 } 151 if($(node).hasClass(prefixTheClassName("columnbreak"))){ 152 // 153 // our column is on a column break, so just end here 154 return; 155 } 156 $putInHere.append(node); 157 } 158 if($putInHere[0].childNodes.length == 0) return; 159 160 // now we're too tall, so undo the last one 161 var kids = $putInHere[0].childNodes; 162 var lastKid = kids[kids.length-1]; 163 $putInHere[0].removeChild(lastKid); 164 var $item = $(lastKid); 165 166 //
167 // now lets try to split that last node 168 // to fit as much of it as we can into this column 169 if($item[0].nodeType == 3){ 170 // it's a text node, split it up 171 var oText = $item[0].nodeValue; 172 var counter2 = options.width / 18; 173 if(options.accuracy) 174 counter2 = options.accuracy; 175 var columnText; 176 var latestTextNode = null; 177 while($parentColumn.height() < targetHeight && oText.length){ 178 var indexOfSpace = oText.indexOf(' ', counter2); 179 if (indexOfSpace != -1) { 180 columnText = oText.substring(0, oText.indexOf(' ', counter2)); 181 } else { 182 columnText = oText; 183 } 184 latestTextNode = document.createTextNode(columnText); 185 $putInHere.append(latestTextNode); 186 187 if(oText.length > counter2 && indexOfSpace != -1){ 188 oText = oText.substring(indexOfSpace); 189 }else{ 190 oText = ""; 191 } 192 } 193 if($parentColumn.height() >= targetHeight && latestTextNode != null){ 194 // too tall :( 195 $putInHere[0].removeChild(latestTextNode); 196 oText = latestTextNode.nodeValue + oText; 197 } 198 if(oText.length){ 199 $item[0].nodeValue = oText; 200 }else{ 201 return false; // we ate the whole text node, move on to the next node 202 } 203 } 204 205 if($pullOutHere.contents().length){ 206 $pullOutHere.prepend($item); 207 }else{ 208 $pullOutHere.append($item); 209 } 210 211 return $item[0].nodeType == 3; 212 } 213 214 /** 215 * Split up an element, which is more complex than splitting text. We need to create 216 * two copies of the element with it's contents divided between each 217 */ 218 function split($putInHere, $pullOutHere, $parentColumn, targetHeight){ 219 if($putInHere.contents(":last").find(prefixTheClassName("columnbreak", true)).length){ 220 // 221 // our column is on a column break, so just end here 222 return; 223 } 224 if($putInHere.contents(":last").hasClass(prefixTheClassName("columnbreak"))){ 225 // 226 // our column is on a column break, so just end here 227 return; 228 } 229 if($pullOutHere.contents().length){ 230 var $cloneMe = $pullOutHere.contents(":first"); 231 // 232 // make sure we're splitting an element 233 if($cloneMe.get(0).nodeType != 1) return; 234 235 // 236 // clone the node with all data and events 237 var $clone = $cloneMe.clone(true); 238 // 239 // need to support both .prop and .attr if .prop doesn't exist. 240 // this is for backwards compatibility with older versions of jquery. 241 if($cloneMe.hasClass(prefixTheClassName("columnbreak"))){ 242 // 243 // ok, we have a columnbreak, so add it into 244 // the column and exit 245 $putInHere.append($clone); 246 $cloneMe.remove(); 247 }else if (manualBreaks){ 248 // keep adding until we hit a manual break 249 $putInHere.append($clone); 250 $cloneMe.remove(); 251 }else if($clone.get(0).nodeType == 1 && !$clone.hasClass(prefixTheClassName("dontend"))){ 252 $putInHere.append($clone); 253 if($clone.is("img") && $parentColumn.height() < targetHeight + 20){ 254 // 255 // we can't split an img in half, so just add it 256 // to the column and remove it from the pullOutHere section 257 $cloneMe.remove(); 258 }else if(!$cloneMe.hasClass(prefixTheClassName("dontsplit")) && $parentColumn.height() < targetHeight + 20){ 259 // 260 // pretty close fit, and we're not allowed to split it, so just 261 // add it to the column, remove from pullOutHere, and be done 262 $cloneMe.remove(); 263 }else if($clone.is("img") || $cloneMe.hasClass(prefixTheClassName("dontsplit"))){ 264 // 265 // it's either an image that's too tall, or an unsplittable node 266 // that's too tall. leave it in the pullOutHere and we'll add it to the 267 // next column 268 $clone.remove(); 269 }else{ 270 // 271 // ok, we're allowed to split the node in half, so empty out 272 // the node in the column we're building, and start splitting 273 // it in half, leaving some of it in pullOutHere 274 $clone.empty(); 275 if(!columnize($clone, $cloneMe, $parentColumn, targetHeight)){ 276 // this node still has non-text nodes to split 277 // add the split class and then recur 278 $cloneMe.addClass(prefixTheClassName("split")); 279 if($cloneMe.children().length){ 280 split($clone, $cloneMe, $parentColumn, targetHeight); 281 } 282 }else{ 283 // this node only has text node children left, add the 284 // split class and move on. 285 $cloneMe.addClass(prefixTheClassName("split")); 286 } 287 if($clone.get(0).childNodes.length == 0){ 288 // it was split, but nothing is in it :( 289 $clone.remove(); 290 } 291 } 292 } 293 } 294 } 295 296 297 function singleColumnizeIt() {
vendor: 11,083 bytes, lines 298-600
298 if ($inBox.data("columnized") && $inBox.children().length == 1) { 299 return; 300 } 301 $inBox.data("columnized", true); 302 $inBox.data("columnizing", true); 303 304 $inBox.empty(); 305 $inBox.append($("<div class='" 306 + prefixTheClassName("first") + " " 307 + prefixTheClassName("last") + " " 308 + prefixTheClassName("column") + " " 309 + "' style='width:100%; float: " + options.columnFloat + ";'></div>")); //" 310 $col = $inBox.children().eq($inBox.children().length-1); 311 $destroyable = $cache.clone(true); 312 if(options.overflow){ 313 targetHeight = options.overflow.height; 314 columnize($col, $destroyable, $col, targetHeight); 315 // make sure that the last item in the column isn't a "dontend" 316 if(!$destroyable.contents().find(":first-child").hasClass(prefixTheClassName("dontend"))){ 317 split($col, $destroyable, $col, targetHeight); 318 } 319 320 while($col.contents(":last").length && checkDontEndColumn($col.contents(":last").get(0))){ 321 var $lastKid = $col.contents(":last"); 322 $lastKid.remove(); 323 $destroyable.prepend($lastKid); 324 } 325 326 var html = ""; 327 var div = document.createElement('DIV'); 328 while($destroyable[0].childNodes.length > 0){ 329 var kid = $destroyable[0].childNodes[0]; 330 if(kid.attributes){ 331 for(var i=0;i<kid.attributes.length;i++){ 332 if(kid.attributes[i].nodeName.indexOf("jQuery") == 0){ 333 kid.removeAttribute(kid.attributes[i].nodeName); 334 } 335 } 336 } 337 div.innerHTML = ""; 338 div.appendChild($destroyable[0].childNodes[0]); 339 html += div.innerHTML; 340 } 341 var overflow = $(options.overflow.id)[0]; 342 overflow.innerHTML = html; 343 344 }else{ 345 $col.append($destroyable); 346 } 347 $inBox.data("columnizing", false); 348 349 if(options.overflow && options.overflow.doneFunc){ 350 options.overflow.doneFunc(); 351 } 352 353 } 354 355 /** 356 * returns true if the input dom node 357 * should not end a column. 358 * returns false otherwise 359 */ 360 function checkDontEndColumn(dom){ 361 if(dom.nodeType == 3){ 362 // text node. ensure that the text 363 // is not 100% whitespace 364 if(/^\s+$/.test(dom.nodeValue)){ 365 // 366 // ok, it's 100% whitespace, 367 // so we should return checkDontEndColumn 368 // of the inputs previousSibling 369 if(!dom.previousSibling) return false; 370 return checkDontEndColumn(dom.previousSibling); 371 } 372 return false; 373 } 374 if(dom.nodeType != 1) return false; 375 if($(dom).hasClass(prefixTheClassName("dontend"))) return true; 376 if(dom.childNodes.length == 0) return false; 377 return checkDontEndColumn(dom.childNodes[dom.childNodes.length-1]); 378 } 379 380 381 382 function columnizeIt() { 383 //reset adjustment var 384 adjustment = 0; 385 if(lastWidth == $inBox.width()) return; 386 lastWidth = $inBox.width(); 387 388 var numCols = Math.round($inBox.width() / options.width); 389 var optionWidth = options.width; 390 var optionHeight = options.height; 391 if(options.columns) numCols = options.columns; 392 if(manualBreaks){ 393 numCols = $cache.find(prefixTheClassName("columnbreak", true)).length + 1; 394 optionWidth = false; 395 } 396 397// if ($inBox.data("columnized") && numCols == $inBox.children().length) { 398// return; 399// } 400 if(numCols <= 1){ 401 return singleColumnizeIt(); 402 } 403 if($inBox.data("columnizing")) return; 404 $inBox.data("columnized", true); 405 $inBox.data("columnizing", true); 406 407 $inBox.empty(); 408 $inBox.append($("<div style='width:" + (Math.floor(100 / numCols))+ "%; float: " + options.columnFloat + ";'></div>")); //" 409 $col = $inBox.children(":last"); 410 $col.append($cache.clone()); 411 maxHeight = $col.height(); 412 $inBox.empty(); 413 414 var targetHeight = maxHeight / numCols; 415 var firstTime = true; 416 var maxLoops = 3; 417 var scrollHorizontally = false; 418 if(options.overflow){ 419 maxLoops = 1; 420 targetHeight = options.overflow.height; 421 }else if(optionHeight && optionWidth){ 422 maxLoops = 1; 423 targetHeight = optionHeight; 424 scrollHorizontally = true; 425 } 426 427 // 428 // We loop as we try and workout a good height to use. We know it initially as an average 429 // but if the last column is higher than the first ones (which can happen, depending on split 430 // points) we need to raise 'adjustment'. We try this over a few iterations until we're 'solid'. 431 // 432 // also, lets hard code the max loops to 20. that's /a lot/ of loops for columnizer, 433 // and should keep run aways in check. if somehow someone has content combined with 434 // options that would cause an infinite loop, then this'll definitely stop it. 435 for(var loopCount=0;loopCount<maxLoops && maxLoops < 20;loopCount++){ 436 $inBox.empty(); 437 var $destroyable; 438 try{ 439 $destroyable = $cache.clone(true); 440 }catch(e){ 441 // jquery in ie6 can't clone with true 442 $destroyable = $cache.clone(); 443 } 444 $destroyable.css("visibility", "hidden"); 445 // create the columns 446 for (var i = 0; i < numCols; i++) { 447 /* create column */ 448 var className = (i == 0) ? prefixTheClassName("first") : ""; 449 className += " " + prefixTheClassName("column"); 450 var className = (i == numCols - 1) ? (prefixTheClassName("last") + " " + className) : className; 451 $inBox.append($("<div class='" + className + "' style='width:" + (Math.floor(100 / numCols))+ "%; float: " + options.columnFloat + ";'></div>")); //" 452 } 453 454 // fill all but the last column (unless overflowing) 455 var i = 0; 456 while(i < numCols - (options.overflow ? 0 : 1) || scrollHorizontally && $destroyable.contents().length){ 457 if($inBox.children().length <= i){ 458 // we ran out of columns, make another 459 $inBox.append($("<div class='" + className + "' style='width:" + (Math.floor(100 / numCols))+ "%; float: " + options.columnFloat + ";'></div>")); //" 460 } 461 var $col = $inBox.children().eq(i); 462 columnize($col, $destroyable, $col, targetHeight); 463 // make sure that the last item in the column isn't a "dontend" 464 split($col, $destroyable, $col, targetHeight); 465 466 while($col.contents(":last").length && checkDontEndColumn($col.contents(":last").get(0))){ 467 var $lastKid = $col.contents(":last"); 468 $lastKid.remove(); 469 $destroyable.prepend($lastKid); 470 } 471 i++; 472 473 // 474 // https://github.com/adamwulf/Columnizer-jQuery-Plugin/issues/47 475 // 476 // check for infinite loop. 477 // 478 // this could happen when a dontsplit or dontend item is taller than the column 479 // we're trying to build, and its never actually added to a column. 480 // 481 // this results in empty columns being added with the dontsplit item 482 // perpetually waiting to get put into a column. lets force the issue here 483 if($col.contents().length == 0 && $destroyable.contents().length){ 484 // 485 // ok, we're building zero content columns. this'll happen forever 486 // since nothing can ever get taken out of destroyable. 487 // 488 // to fix, lets put 1 item from destroyable into the empty column 489 // before we iterate 490 $col.append($destroyable.contents(":first")); 491 }else if(i == numCols - (options.overflow ? 0 : 1) && !options.overflow){ 492 // 493 // ok, we're about to exit the while loop because we're done with all 494 // columns except the last column. 495 // 496 // if $destroyable still has columnbreak nodes in it, then we need to keep 497 // looping and creating more columns. 498 if($destroyable.find(prefixTheClassName("columnbreak", true)).length){ 499 numCols ++; 500 } 501 } 502 503 } 504 if(options.overflow && !scrollHorizontally){ 505 var IE6 = false /*@cc_on || @_jscript_version < 5.7 @*/; 506 var IE7 = (document.all) && (navigator.appVersion.indexOf("MSIE 7.") != -1); 507 if(IE6 || IE7){ 508 var html = ""; 509 var div = document.createElement('DIV'); 510 while($destroyable[0].childNodes.length > 0){ 511 var kid = $destroyable[0].childNodes[0]; 512 for(var i=0;i<kid.attributes.length;i++){ 513 if(kid.attributes[i].nodeName.indexOf("jQuery") == 0){ 514 kid.removeAttribute(kid.attributes[i].nodeName); 515 } 516 } 517 div.innerHTML = ""; 518 div.appendChild($destroyable[0].childNodes[0]); 519 html += div.innerHTML; 520 } 521 var overflow = $(options.overflow.id)[0]; 522 overflow.innerHTML = html; 523 }else{ 524 $(options.overflow.id).empty().append($destroyable.contents().clone(true)); 525 } 526 }else if(!scrollHorizontally){ 527 // the last column in the series 528 $col = $inBox.children().eq($inBox.children().length-1); 529 while($destroyable.contents().length) $col.append($destroyable.contents(":first")); 530 var afterH = $col.height(); 531 var diff = afterH - targetHeight; 532 var totalH = 0; 533 var min = 10000000; 534 var max = 0; 535 var lastIsMax = false; 536 var numberOfColumnsThatDontEndInAColumnBreak = 0; 537 $inBox.children().each(function($inBox){ return function($item){ 538 var $col = $inBox.children().eq($item); 539 var endsInBreak = $col.children(":last").find(prefixTheClassName("columnbreak", true)).length; 540 if(!endsInBreak){ 541 var h = $col.height(); 542 lastIsMax = false; 543 totalH += h; 544 if(h > max) { 545 max = h; 546 lastIsMax = true; 547 } 548 if(h < min) min = h; 549 numberOfColumnsThatDontEndInAColumnBreak++; 550 } 551 }}($inBox)); 552 553 var avgH = totalH / numberOfColumnsThatDontEndInAColumnBreak; 554 if(totalH == 0){ 555 // 556 // all columns end in a column break, 557 // so we're done here 558 loopCount = maxLoops; 559 }else if(options.lastNeverTallest && lastIsMax){ 560 // the last column is the tallest 561 // so allow columns to be taller 562 // and retry 563 // 564 // hopefully this'll mean more content fits into 565 // earlier columns, so that the last column 566 // can be shorter than the rest 567 adjustment += 30; 568 569 targetHeight = targetHeight + 30; 570 if(loopCount == maxLoops-1) maxLoops++; 571 }else if(max - min > 30){ 572 // too much variation, try again 573 targetHeight = avgH + 30; 574 }else if(Math.abs(avgH-targetHeight) > 20){ 575 // too much variation, try again 576 targetHeight = avgH; 577 }else { 578 // solid, we're done 579 loopCount = maxLoops; 580 } 581 }else{ 582 // it's scrolling horizontally, fix the width/classes of the columns 583 $inBox.children().each(function(i){ 584 $col = $inBox.children().eq(i); 585 $col.width(optionWidth + "px"); 586 if(i==0){ 587 $col.addClass(prefixTheClassName("first")); 588 }else if(i==$inBox.children().length-1){ 589 $col.addClass(prefixTheClassName("last")); 590 }else{ 591 $col.removeClass(prefixTheClassName("first")); 592 $col.removeClass(prefixTheClassName("last")); 593 } 594 }); 595 $inBox.width($inBox.children().length * optionWidth + "px"); 596 } 597 $inBox.append($("<br style='clear:both;'>")); 598 } 599 $inBox.find(prefixTheClassName("column", true)).find(":first" + prefixTheClassName("removeiffirst", true)).remove(); 600 $inBox.find(prefixTheClassName("column", true)).find(':last' + prefixTheClassName("removeiflast", true)).remove();
601 $inBox.data("columnizing", false); 602 603 if(options.overflow){ 604 options.overflow.doneFunc(); 605 } 606 options.doneFunc(); 607 } 608 }); 609 }; 610})(jQuery);
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.