PageSourceSearch

https://jardibotanic.org/js2/jquery.columnizer.js

js jardibotanic.org collected 2026-10-02 06:22:35 UTC 21,468 bytes, 609 lines download raw bytes

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