var MooColumns = new Class({ Implements: Options, options: { selector:'.multiColumn', className:'multiColumn', numOfColumns:2, defaultNumOfColumns:2, gutterWidth:5,//% by default.. can set to a string with px at the end to set width in px gutterClassName:'gutter', columnClassName:'column', tweak:{x:0,y:0,width:0}, splittableElements:['p','div','span','ul'], morePrecise:true,//setting to false will make script run faster tolerance:10,//how far below the target bottom non-splittable elements can hang before they are moved to the next column colBreaksTrump:true, debug:false }, sizerElWrapper:null, columnParents:[], mooColumnsAreasArr:[], initialize: function(options){ this.setOptions(options); if($type(parseFloat(this.options.numOfColumns)) != 'number'){ this.options.numOfColumns = this.options.defaultNumOfColumns; } //create wrapper for sizer elements this.sizerElWrapper = new Element('div', {'id': 'sizerElWrapper' }).inject(document.body,'inside').setStyles({ 'visibility':'hidden','position':'absolute','display':'block','padding':0,'margin':0,'top':0,'left':0,'width':0,'height':0,'overflow':'hidden' }); this.columnParents = $(document.body).getElements(this.options.selector); this.columnParents.each(function(item,index){ var id = this.getOrSetId($(item)); var aMooColumnsArea = new MooColumnsArea(this.options,item); this.mooColumnsAreasArr[id] = aMooColumnsArea; item.className +='-screen'; }.bind(this)); }, getOrSetId: function(obj){ if(!obj.id){ var d = new Date(); var milli = d.getMilliseconds().toString(); var randomNumber = Math.floor(Math.random()*1000); if(obj.nodeName){ obj.id = obj.nodeName.toString() + '-' + milli + randomNumber.toString(); }else{ obj.id = "element" + milli + randomNumber.toString(); } } return obj.id; } }); var MooColumnsArea = new Class({ Implements: Options, options: { parentEl:null, printEl:null, debug:null, colBreakDepth:5 }, columnElsArr:[], gutterElsArr:[], colWidth:null, targetHeight:null, tempContentHolder:null, sizerEl:null, tallest:0, unsplittableTags:['td','tr','table','tbody'], hasColBreaks:false, initialize: function(options,parentEl){ this.setOptions(options); this.options.parentEl = parentEl; //create sizer element this.sizerEl = new Element('div').inject($('sizerElWrapper'),'inside').setStyles({ 'visibility':'hidden','position':'absolute','display':'block','padding':0,'margin':0,'top':0,'left':0 }); //backup original content into a div for printing and refreshing this.options.printEl = new Element('div', {'class':this.options.className +'-print' }).inject(this.options.parentEl,'after').set('html',this.options.parentEl.innerHTML); this.options.parentEl.empty(); var innerWrapper = new Element('div').setStyles({'display':'block','position':'relative','padding':0,'margin':0}).inject(this.options.parentEl,'top'); this.options.parentEl = parentEl.getFirst(); //apply width & x tweaks var targetWidth = innerWrapper.getStyle('width').toInt()+this.options.tweak.width.toInt(); var targetWidthPercent = (100 * targetWidth)/innerWrapper.getStyle('width').toInt(); innerWrapper.setStyles({'width':targetWidthPercent+'%',left:this.options.tweak.x}); this.go(); window.addEvent('resize', function() { $clear(refreshTimeout); var refreshTimeout = (function(){ this.setHeights(); }.bind(this)).delay(100); }.bind(this)); }, go:function(){ var numOfGutters = this.options.numOfColumns -1; if (this.options.gutterWidth.toString().contains('px')) { //convert gutterWidth from px to % this.options.gutterWidth = Math.round((100*parseFloat(this.options.gutterWidth))/this.options.parentEl.getCoordinates().width); } //how much width% to subtract from each column to make room for the gutters var gutterWidthAdjustment = Math.round(numOfGutters*parseFloat(this.options.gutterWidth)/ this.options.numOfColumns); //determine column width this.colWidth = Math.round(100/this.options.numOfColumns)-gutterWidthAdjustment; //determine sizerElSize in px var sizerWidth = Math.round((this.options.parentEl.getCoordinates().width * this.colWidth)/100); //determine & set target height this.sizerEl.setStyle('width', sizerWidth).set('html', this.options.printEl.innerHTML ); this.targetHeight = Math.round(this.sizerEl.getCoordinates().height / this.options.numOfColumns ); //create empty columns and gutters this.makeWireFrame(); //strip scripts and html comments this.options.printEl.set('html',this.options.printEl.get('html').stripScripts().split(/)]*-->/).join("")); //wraps naked text nodes within a

tag this.wrapTextNodes(this.options.printEl); //convert 2 consecutive horizontal rules to a span with a class of 'colBreak' this.convertColBreaks(this.options.printEl); if($(this.options.printEl).getElements('.colBreak').length > 0){ this.hasColBreaks = true; } //if the column breaks are inside of something, split it while(this.options.colBreakDepth > 0){ this.splitColBreakParents(this.options.printEl); this.options.colBreakDepth--; } if(this.options.colBreaksTrump === true && this.hasColBreaks){ this.options.morePrecise = false;} //divide the content var columnsContentArr; if(this.hasColBreaks && this.options.colBreaksTrump){ //just divide on colBreaks and don't auto split content columnsContentArr = this.divideContent2(); }else{ columnsContentArr = this.divideContent(); } //fill the columns for(i=0;i this.tallest) { this.tallest = this.columnElsArr[i].getCoordinates().height; } } if(this.options.parentEl.getScrollSize().y >0){ this.tallest = this.options.parentEl.getScrollSize().y; } for (i = 0; i < this.columnElsArr.length; i++) { this.columnElsArr[i].setStyles({'top':0,'bottom':0}); this.options.parentEl.setStyle('height', this.tallest); } if(Browser.Engine.trident4){ this.options.parentEl.getParent().setStyle('height',this.tallest); this.gutterElsArr.each(function(item,index){ item.setStyle('height','100%'); }); } } }, makeWireFrame:function(){ this.options.parentEl.empty(); for(i=0;i < this.options.numOfColumns;i++){ //create column colLeft = (i*(parseFloat(this.colWidth)+parseFloat(this.options.gutterWidth))); this.columnElsArr[i] = new Element('div', {'class':'column'}).inject(this.options.parentEl,'inside').setStyles({'display':'block','position':'absolute','left':colLeft+'%','top':this.options.tweak.y, 'width':this.colWidth+'%'}); //create gutter if(i < this.options.numOfColumns-1){ this.gutterElsArr[i] = new Element('div', {'class':'gutter'}).inject(this.options.parentEl,'inside').setStyles({'display':'block','position':'absolute','left':(colLeft + parseFloat(this.colWidth)+'%'),'top':this.options.tweak.y,'width':parseFloat(this.options.gutterWidth)+'%','bottom':0}); if(this.options.debug){ this.gutterElsArr[i].setStyle('background','yellow');} } if(this.options.debug){ this.columnElsArr[i].setStyle('background','#eee'); } } if(this.options.debug){this.options.parentEl.setStyle('background','#ccc');} }, convertColBreaks:function(sourceEl){ var horizontalRules = $(sourceEl).getElements('hr'); for (var i=0; i


var case1 = item.getNext() && item.getNext().get('tag') === 'hr'; //


var case2 = item.getNext() && item.getNext().get('tag') === 'br' && item.getNext().getNext() && item.getNext().getNext().get('tag') === 'hr'; //



//since having an hr inside of a paragraph tag is invalid the browser will rearrange //things to try to fix it. case3 targets the end result html for FireFox and case4 is for IE7 var case3 = item.getNext() && item.getNext().get('tag') === 'p' && !item.getNext().get('html') && item.getNext().getNext() && item.getNext().getNext().get('tag') === 'hr'; var case4 = item.getNext() && item.getNext().getNext() && item.getNext().getNext().getNext() && item.getNext().get('tag') === 'p' && item.getNext().getNext().get('tag') === 'p' && item.getNext().getNext().getNext().get('tag') === 'hr' && !item.getNext().get('html') && !item.getNext().getNext().get('html'); if (case4 || case3 || case2 || case1) { var colBreak = new Element('span', {'class': 'colBreak' }).inject(item,'before'); this.hasColBreaks = true; } if(case4){ item.getNext().getNext().getNext().dispose(); } if(case4 || case3 || case2){ item.getNext().getNext().dispose(); } if( case4 || case3 || case2 || case1){ item.getNext().dispose(); item.dispose(); } }; }, splitColBreakParents:function(sourceEl){ $(sourceEl).getElements('.colBreak').each(function(item,index){ if(!item.getParent()){return;} if( !item.getParent().hasClass('wrapper-print') && !this.unsplittableTags.contains(item.getParent().get('tag')) ){ var original = item.getParent(); var firstHalf = original.clone(false);//the false means the clone will be empty if(!firstHalf || firstHalf.hasClass('colBreak')){return;} var nodeArr1 = $(original).childNodes; var currentNode = nodeArr1[0]; var currentNodeNumber = 1; //copy everything before the col break into firstHalf while ( currentNode && !currentNode.className || currentNode && currentNode.className != 'colBreak') { if(currentNode.parentNode.className && currentNode.parentNode.className.contains('multi')){ return; } if($type(currentNode) === 'textnode' || $type(currentNode) === 'whitespace'){ if(firstHalf && firstHalf.innerHTML && currentNode.nodeValue){ //firstHalf.innerHTML +=currentNode.nodeValue; firstHalf.set('html',firstHalf.get('html')+currentNode.nodeValue); currentNode.parentNode.removeChild(currentNode); }else{ break; } }else{ currentNode.inject(firstHalf,'bottom'); } currentNodeNumber++; currentNode = nodeArr1[currentNodeNumber]; } //all that is left in original is the second half of the contents, //so duplicate original and replace the first original with firstHalf var secondHalf = original.clone(true,true).inject(original,'after'); $(firstHalf).replaces($(original)); //now move the col break from the inside of secondHalf to inbetween firstHalf & secondHalf if(secondHalf.getElements('.colBreak').length >=1){ $(secondHalf.getElements('.colBreak')[0]).inject(firstHalf,'after'); } } }.bind(this)); }, wrapTextNodes:function(sourceEl){ var nodeArr = $(sourceEl).childNodes; for(i=0; i< nodeArr.length;i++){ if($type(nodeArr[i]) === 'textnode'){ var clothes = new Element('p').inject(nodeArr[i],'after').set('html', nodeArr[i].nodeValue ); nodeArr[i].parentNode.removeChild(nodeArr[i]); } } }, divideContent:function(){ this.tempContentHolder = new Element('div', {'id':'tempContentHolder'}).set('html',this.options.printEl.innerHTML).inject(document.body,'inside').setStyles({'display':'none','position':'absolute'}); this.sizerEl.empty(); var columnsContent = []; var currentColNum = 0; var limit = 1300; while(this.sizerEl.getCoordinates().height <= this.targetHeight && $(this.tempContentHolder).getFirst() && limit > 0){ limit--; if(!columnsContent[currentColNum]){ columnsContent[currentColNum] = new Element('div'); } $(this.tempContentHolder).getFirst().inject(this.sizerEl,'inside'); if( this.sizerEl.getCoordinates().height >= this.targetHeight || this.sizerEl.getLast().hasClass('colBreak') ){ $(columnsContent[currentColNum]).set('html',this.sizerEl.innerHTML); this.sizerEl.empty(); currentColNum++; if(currentColNum >= this.options.numOfColumns){currentColNum = this.options.numOfColumns -1;} //catch any leftover text nodes and put it at the top of the next column if(!columnsContent[currentColNum]){ columnsContent[currentColNum] = new Element('div'); } if(!this.tempContentHolder.getFirst()){ $(columnsContent[currentColNum]).set('html',this.tempContentHolder.innerHTML + $(columnsContent[currentColNum]).innerHTML);} } } $(columnsContent[currentColNum]).set('html',$(columnsContent[currentColNum]).innerHTML + this.sizerEl.innerHTML); this.sizerEl.empty(); return columnsContent; }, divideContent2:function(){ this.tempContentHolder = new Element('div', {'id':'tempContentHolder'}).set('html',this.options.printEl.innerHTML).inject(document.body,'inside').setStyles({'display':'none','position':'absolute'}); this.sizerEl.empty(); var columnsContent = []; var currentColNum = 0; var limit = 1300; while(currentColNum < this.options.numOfColumns && limit > 0){ limit--; if(!columnsContent[currentColNum]){ columnsContent[currentColNum] = new Element('div'); } if (this.tempContentHolder.getFirst()) { $(this.tempContentHolder).getFirst().inject(this.sizerEl,'inside'); } if( this.sizerEl.getLast().hasClass('colBreak') ){ $(columnsContent[currentColNum]).set('html',this.sizerEl.innerHTML); this.sizerEl.empty(); currentColNum++; if(currentColNum >= this.options.numOfColumns){currentColNum = this.options.numOfColumns -1;} //catch any leftover text nodes and put it at the top of the next column if(!columnsContent[currentColNum]){ columnsContent[currentColNum] = new Element('div'); } if(!this.tempContentHolder.getFirst()){ $(columnsContent[currentColNum]).set('html',this.tempContentHolder.innerHTML + $(columnsContent[currentColNum]).innerHTML); } } } $(columnsContent[currentColNum]).set('html',$(columnsContent[currentColNum]).innerHTML + this.sizerEl.innerHTML); this.sizerEl.empty(); return columnsContent; }, shaveColumns:function(){ for (i = 0; i < this.columnElsArr.length-1; i++) { var overlap = this.columnElsArr[i].getCoordinates().height - this.targetHeight; var ElementIsSplittable = this.columnElsArr[i].getLast().get('tag') && this.options.splittableElements.contains(this.columnElsArr[i].getLast().get('tag')); var original = this.columnElsArr[i].getLast(); if(overlap > 0 && ElementIsSplittable ){ var elementTargetHeight = original.getCoordinates().height - overlap + this.options.tolerance; var clone = original.clone().inject($('sizerElWrapper'),'inside'); clone.empty(); var limit = 50; while(limit >0 && original.childNodes.length && original.getCoordinates().height > elementTargetHeight){ limit--; if($type(original.childNodes[original.childNodes.length-1]) === 'textnode' ||$type(original.childNodes[original.childNodes.length-1]) === 'whitespace'){ //split and move textnodes var limit2 = 50; var textArr = original.childNodes[original.childNodes.length-1].nodeValue.split(" "); while ( limit2 > 0 && original.childNodes[original.childNodes.length-1].nodeValue.length >= 0 && original.getCoordinates().height > elementTargetHeight) { limit2--; //copy last word to clone if($defined(textArr.getLast())){ clone.innerHTML = textArr.getLast().toString()+ " " + clone.innerHTML; } //remove the last word from the array textArr = textArr.filter(function(item, index){ return index < (textArr.length-1) && $defined(item) ; }); //set original textnode value to flattened array original.childNodes[original.childNodes.length-1].nodeValue = textArr.join(" "); } if(!$defined(textArr.getLast())){ //delete last childNode in original original.removeChild(original.childNodes[original.childNodes.length-1]); } }else if($(original.childNodes[original.childNodes.length-1])){ if($(original.childNodes[original.childNodes.length-1]).hasClass('colBreak') ){ limit = 0; }else{ $(original.childNodes[original.childNodes.length-1]).inject(clone,'top'); //delete last childNode in original //original.removeChild(original.childNodes[original.childNodes.length-1]); } } } clone.inject(this.columnElsArr[i+1],'top'); }else if (overlap > this.options.tolerance && !ElementIsSplittable){ original.inject(this.columnElsArr[i+1],'top'); } } } });