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');
}
}
}
});