Created
June 5, 2012 20:06
-
-
Save tsavory/2877479 to your computer and use it in GitHub Desktop.
Copy of CSS3MultiColumn
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// CSS3MultiColumn - a javascript implementation of the CSS3 multi-column module | |
// v1.02 beta - Jan 08 2008 | |
// Copyright (c) 2005 Cdric Savarese <pro@4213miles.com> | |
// Copyright (c) 2012 Gabriel Saldana <gabriel@gabrielsaldana.org> | |
// This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/> | |
// For additional information, see : http://www.csscripting.com/ | |
// Supported Properties: | |
// column-count | |
// column-width | |
// column-gap | |
// column-rule | |
// Unsupported Properties: | |
// column-rule-width (use column-rule instead) | |
// column-rule-style (use column-rule instead) | |
// column-rule-color (use column-rule instead) | |
// column-span | |
// column-width-policy | |
// column-space-distribution | |
function CSS3MultiColumn() { | |
//alert('Development Version'); | |
var cssCache = new Object(); | |
var splitableTags = new Array('P','DIV', 'SPAN', 'BLOCKQUOTE','ADDRESS','PRE', 'A', 'EM', 'I', 'STRONG', 'B', 'CITE', 'OL', 'UL', 'LI' ); | |
var pseudoCSSRules = new Object(); | |
var ut = new CSS3Utility(); | |
var debug = ut.debug; | |
if(document.location.search.match('mode=debug')) var isDebug = true; | |
else var isDebug = false; | |
var bestSplitPoint = null; | |
var secondSplitPoint = null; | |
var secondSplitBottom = 0; | |
var documentReady = false; | |
// INITIALIZATION | |
ut.XBrowserAddEventHandler(window,'load',function() { documentReady = true; processElements(); } ); | |
loadStylesheets(); | |
// CSS PARSING | |
// -------------------------------------------------------------------------------------- | |
// loadStylesheets: | |
// Loop through the stylesheets collection and load the css text into the cssCache object | |
function loadStylesheets() { | |
if(document.styleSheets) { // Firefox & IE | |
// initialize cache | |
for(var i=0;i < document.styleSheets.length;i++) { | |
cssCache[document.styleSheets[i].href] = false; | |
} | |
// load css in the cache | |
for(var i=0;i < document.styleSheets.length;i++) { | |
loadCssCache(document.styleSheets[i], 'parseStylesheets'); | |
} | |
} else if (document.getElementsByTagName) { // OPERA | |
var Lt = document.getElementsByTagName('link'); | |
// initialize cache | |
for(var i= 0; i<Lt.length; i++) { | |
cssCache[Lt[i].href] = false; | |
} | |
// load css in the cache | |
for(var i= 0; i<Lt.length; i++) { | |
loadCssCache(Lt[i], 'parseStylesheets'); | |
} | |
//var St = document.getElementsByTagName('style'); | |
} | |
} | |
// loadCssCache | |
// Asynchronous function. Call the 'callback' function when done. | |
function loadCssCache(s, callback) { | |
if (s.href && s.cssText) { | |
cssCache[s.href] = s.cssText; | |
eval(callback)(); | |
} | |
if (s.href && typeof XMLHttpRequest!='undefined') { | |
var xmlhttp = new XMLHttpRequest(); | |
//if(xmlhttp.abort) xmlhttp.abort(); | |
xmlhttp.onreadystatechange = function() { | |
if(xmlhttp.readyState == 4) { | |
if(typeof xmlhttp.status == 'undefined' || xmlhttp.status == 200 || xmlhttp.status == 304 ) { | |
cssCache[s.href] = xmlhttp.responseText; | |
eval(callback)(); | |
} | |
} | |
}; | |
// IE8 uses XDoaminRequest instead of XMLHttpRequest | |
if(window.XDomainRequest) { | |
xmlhttp = new window.XDomainRequest(); | |
xmlhttp.onload = function() { | |
cssCache[s.href] = xmlhttp.responseText; | |
eval(callback)(); | |
}; | |
} | |
xmlhttp.open("GET", s.href, true); //synchrone transaction crashes Opera 8.01 | |
xmlhttp.send(); | |
} | |
} | |
// parseStylesheets: | |
// Iterates the cssCache object and send the serialized css to the mini-parser. | |
function parseStylesheets() { | |
var allDone = true; | |
for(var i in cssCache) { | |
if(cssCache[i]!=false) parseStylesheet(cssCache[i]); | |
else allDone = false; | |
} | |
if(allDone) { | |
processElements(); | |
} | |
} | |
// parseStylesheet: | |
// Loads the pseudoCSSRules object with the values for column-count, column-width, column-gap... | |
function parseStylesheet(cssText) { | |
// Retrieving column-count property | |
var cc = new ut.getPseudoCssRules('column-count',cssText); | |
for(var i=0; cc && i<cc.cssRules.length;i++) { | |
if(!pseudoCSSRules[cc.cssRules[i].selectorText]) | |
pseudoCSSRules[cc.cssRules[i].selectorText] = new Object(); | |
pseudoCSSRules[cc.cssRules[i].selectorText]['column-count'] = cc.cssRules[i].value; | |
} | |
// Retrieving column-width property | |
cc = new ut.getPseudoCssRules('column-width',cssText); | |
for(var i=0; cc && i<cc.cssRules.length;i++) { | |
if(!pseudoCSSRules[cc.cssRules[i].selectorText]) | |
pseudoCSSRules[cc.cssRules[i].selectorText] = new Object(); | |
pseudoCSSRules[cc.cssRules[i].selectorText]['column-width'] = cc.cssRules[i].value; | |
} | |
// Retrieving column-gap property | |
cc = new ut.getPseudoCssRules('column-gap',cssText); | |
for(var i=0; cc && i<cc.cssRules.length;i++) { | |
if(!pseudoCSSRules[cc.cssRules[i].selectorText]) | |
pseudoCSSRules[cc.cssRules[i].selectorText] = new Object(); | |
pseudoCSSRules[cc.cssRules[i].selectorText]['column-gap'] = cc.cssRules[i].value; | |
} | |
// Retrieving column-rule property | |
cc = new ut.getPseudoCssRules('column-rule',cssText); | |
for(var i=0; cc && i<cc.cssRules.length;i++) { | |
if(!pseudoCSSRules[cc.cssRules[i].selectorText]) | |
pseudoCSSRules[cc.cssRules[i].selectorText] = new Object(); | |
pseudoCSSRules[cc.cssRules[i].selectorText]['column-rule'] = cc.cssRules[i].value; | |
} | |
} | |
// COLUMN PROCESSING | |
function processElements() { | |
// wait for page to finish loading | |
if(!documentReady) return; | |
for(var i in pseudoCSSRules) { | |
debug(i + ' cc:' + pseudoCSSRules[i]['column-count'] + ' cw:' + pseudoCSSRules[i]['column-width'] + ' cr:' + pseudoCSSRules[i]['column-rule'] + ' cg:' + pseudoCSSRules[i]['column-gap']); | |
var affectedElements = ut.cssQuery(i); | |
for(var j=0;j<affectedElements.length;j++) { | |
//debug("affected element: " + affectedElements[j].tagName + ' [' + affectedElements[j].id + ' / ' + affectedElements[j].className + ']'); | |
processElement(affectedElements[j], pseudoCSSRules[i]['column-count'], pseudoCSSRules[i]['column-width'], pseudoCSSRules[i]['column-gap'], pseudoCSSRules[i]['column-rule']); | |
} | |
} | |
} | |
function processElement(affectedElement, column_count, column_width, column_gap, column_rule ) { | |
//affectedElement.style.visibility = 'hidden'; | |
var widthUnit; | |
var width; | |
var column_rule_width = 0; | |
// Get available width | |
// see http://www.csscripting.com/css-multi-column/dom-width-height.php | |
// offsetWidth & scrollWidth are the only consistent values across browsers. | |
// offsetWidth includes border, padding and scroll bars | |
// scrollWidth includes border and padding | |
// clientWidth when available includes padding only. | |
// see http://msdn.microsoft.com/workshop/author/om/measuring.asp | |
if(affectedElement.clientWidth && affectedElement.clientWidth != 0) { | |
var padding; | |
if(affectedElement.currentStyle) { | |
padding = parseInt(affectedElement.currentStyle.paddingLeft.replace(/[\D]*/gi,"")) + parseInt(affectedElement.currentStyle.paddingRight.replace(/[\D]*/gi,"")) | |
} else if (document.defaultView && document.defaultView.getComputedStyle) { | |
padding = parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) + parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) | |
//padding = parseInt(window.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) + parseInt(window.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) | |
} | |
if (isNaN(padding)) padding = 0; | |
width = (affectedElement.clientWidth - padding).toString() + "px"; | |
} | |
else if(affectedElement.scrollWidth) { | |
var borderWidth; | |
var padding; | |
if(affectedElement.currentStyle) { | |
padding = parseInt(affectedElement.currentStyle.paddingLeft.replace(/[\D]*/gi,"")) + parseInt(affectedElement.currentStyle.paddingRight.replace(/[\D]*/gi,"")) | |
} else if (document.defaultView && document.defaultView.getComputedStyle) { | |
padding = parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) + parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) | |
} | |
if (isNaN(padding)) padding = 0; | |
if(affectedElement.currentStyle) { | |
borderWidth = parseInt(affectedElement.currentStyle.borderLeftWidth.replace(/[\D]*/gi,"")) + parseInt(affectedElement.currentStyle.borderRightWidth.replace(/[\D]*/gi,"")) | |
} else if (document.defaultView && document.defaultView.getComputedStyle) { | |
borderWidth = parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("border-left-width").replace(/[\D]*/gi,"")) + parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("border-right-width").replace(/[\D]*/gi,"")) | |
} | |
if (isNaN(borderWidth)) borderWidth = 0; | |
width = (affectedElement.scrollWidth - padding - borderWidth).toString() + "px"; | |
} | |
else width = "99%"; // ever used? | |
var availableWidth = parseInt(width.replace(/[\D]*/gi,"")); | |
// Get width unit | |
if(!column_width || column_width == 'auto') | |
widthUnit = width.replace(/[\d]*/gi,""); | |
else | |
widthUnit = column_width.replace(/[\d]*/gi,""); | |
if(!widthUnit) | |
widthUnit = "px"; | |
if(!column_gap) { // Compute column spacing (column_gap) | |
if(widthUnit=="%") | |
column_gap = 1; //%; | |
else | |
column_gap = 15; //px; | |
} else { | |
column_gap = parseInt(column_gap.replace(/[\D]*/gi,"")); | |
} | |
if(column_rule && column_rule != 'none') { | |
column_gap = Math.floor(column_gap/2); | |
// we add half the original column_gap to the column_rule_width to fix the column_width count below. | |
column_rule_width = column_gap + parseInt(column_rule.substring(column_rule.search(/\d/),column_rule.search(/\D/))); | |
} | |
if(!column_width || column_width == 'auto') {// Compute columns' width | |
column_width = (availableWidth-((column_gap+column_rule_width)*(column_count-1))) / column_count; | |
} else { | |
column_width = parseInt(column_width.replace(/[\D]*/gi,"")) | |
if(!column_count || column_count == 'auto') {// Compute column count | |
column_count = Math.floor(availableWidth / (column_width + column_gap)); | |
} | |
} | |
column_width -= 1; | |
// Create a wrapper | |
var wrapper = document.createElement('div'); //affectedElement.tagName | |
var pn = affectedElement.parentNode; | |
wrapper = pn.insertBefore(wrapper, affectedElement); | |
var elem = pn.removeChild(affectedElement); | |
elem = wrapper.appendChild(elem); | |
//wrapper.style.border = "1px solid #F00"; | |
wrapper.className = elem.className; | |
elem.className = ""; | |
// since all columns will be left-floating we need to clear the floats after them. | |
//wrapper.style.overflow = 'auto'; | |
// Assign the content element a random Id ? | |
elem.id = ut.randomId(); | |
// Adjust content's width and float the element | |
elem.style.width = column_width.toString() + widthUnit; | |
//elem.style.padding = "0"; | |
//elem.style.margin = "0"; | |
if(typeof elem.style.styleFloat != 'undefined') | |
elem.style.styleFloat = "left"; | |
if(typeof elem.style.cssFloat != 'undefined') | |
elem.style.cssFloat = "left"; | |
// Compute Desired Height | |
var newHeight = Math.floor(elem.offsetHeight / column_count)+14; | |
if(!wrapper.id) wrapper.id = ut.randomId(); | |
// Find split points (j is the max # of attempts to find a good height with no unsplittable element on the split point. | |
var j=1; | |
for(var i=1; i < column_count && elem && j < (column_count + 5) ; i++) { | |
bestSplitPoint = null; | |
secondSplitPoint = null; | |
secondSplitBottom = 0; | |
findSplitPoint(elem, newHeight*i, wrapper); | |
if(isDebug) bestSplitPoint.style.border = "1px solid #00FF00"; | |
if(bestSplitPoint && !isElementSplitable(bestSplitPoint)) { | |
newHeight = getElementRelativeTop(bestSplitPoint, wrapper) + bestSplitPoint.offsetHeight + 10; | |
i=1; // reset the height. Try again. | |
debug('reset new Height = '+newHeight + ' relativetop=' + getElementRelativeTop(bestSplitPoint, wrapper) + ' offsetHeight= ' + bestSplitPoint.offsetHeight ); | |
} | |
else if (!bestSplitPoint) { | |
debug("No split point found with " + newHeight); | |
} | |
j++; | |
} | |
//wrapper.style.minHeight = newHeight + 'px'; | |
//if(document.all && !window.opera) | |
//wrapper.style.height = newHeight + 'px'; | |
debug('<table><tr><td>Avail. Width</td><td>'+availableWidth+'</td><td>Units</td><td>'+widthUnit+'</td></tr><tr><td>column_width</td><td>'+column_width+'</td><td>column_count</td><td>'+column_count+'</td></tr><tr><td>column_gap</td><td>'+column_gap+'</td><td>column_rule</td><td>'+column_rule+'</td></tr><tr><td>New Height</td><td>' + newHeight + '</td><td></td><td></td></tr></table>' ); | |
for(var i=1; i < column_count && elem; i++) { | |
// Find the split point (a child element, sitting on the column split point) | |
bestSplitPoint = null; | |
secondSplitPoint = null; | |
secondSplitBottom = 0; | |
findSplitPoint(elem, newHeight, wrapper); | |
if(bestSplitPoint && isElementSplitable(bestSplitPoint) && elem.id != bestSplitPoint.id) { | |
var splitE = bestSplitPoint; | |
if(isDebug) secondSplitPoint.style.border = "1px dotted #00F"; | |
} | |
else { | |
var splitE = secondSplitPoint; | |
} | |
if(!splitE) { | |
debug("<hr />No split point found for " + elem.tagName + ' ' + newHeight); | |
return; | |
} | |
// DEBUG ONLY: SHOW SPLIT ELEMENT | |
//debug("split top=" + getElementRelativeTop(splitE, wrapper)); | |
if(isDebug) splitE.style.border = "1px solid #F00"; | |
// END DEBUG ONLY: SHOW SPLIT ELEMENT | |
// Create New Column | |
var newCol = elem.cloneNode(false); | |
newCol.id = ut.randomId(); | |
// Insert new column in the document | |
elem.parentNode.insertBefore(newCol, elem.nextSibling); | |
// Add the column_gap | |
newCol.style.paddingLeft = column_gap + widthUnit; | |
// Add the column_rule | |
if(column_rule && column_rule != 'none') { | |
newCol.style.borderLeft = column_rule; | |
elem.style.paddingRight = column_gap + widthUnit; | |
} | |
if(document.all && !window.opera) | |
elem.style.height = newHeight+'px'; | |
elem.style.minHeight = newHeight+'px'; | |
// Move all elements after the element to be splitted (splitE) to the new column | |
var insertPoint = createNodeAncestors(splitE,elem, newCol, 'append'); | |
var refElement = splitE; | |
while(refElement && refElement.id != elem.id ) { | |
var littleSib = refElement.nextSibling; | |
while(littleSib) { | |
moveNode(littleSib, elem, newCol); | |
littleSib = refElement.nextSibling; | |
} | |
refElement = refElement.parentNode; | |
} | |
var strippedLine = splitElement(splitE, newHeight - getElementRelativeTop(splitE, wrapper), elem, newCol); | |
// cleaning emptied elements | |
var pn = splitE.parentNode; | |
while(pn && pn.id != elem.id) { | |
var n = pn.firstChild; | |
while(n) { | |
if((n.nodeType==1 && n.childNodes.length == 0) || | |
(n.nodeType==3 && n.nodeValue.replace(/[\u0020\u0009\u000A]*/,'') == "")) { | |
pn.removeChild(n); | |
n = pn.firstChild; | |
} else { | |
n = n.nextSibling; | |
} | |
} | |
pn = pn.parentNode; | |
} | |
// if text-align is justified, insert to force the justify | |
if(strippedLine) { | |
splitE = elem.lastChild; | |
if(splitE && (document.defaultView && document.defaultView.getComputedStyle(splitE,'').getPropertyValue('text-align')=='justify') || | |
(splitE.currentStyle && splitE.currentStyle.textAlign == 'justify')) { | |
var txtFiller = document.createTextNode(' ' + strippedLine.replace(/./g,"\u00a0")); // | |
var filler = document.createElement('span'); | |
splitE.appendChild(filler); | |
filler.style.lineHeight="1px"; | |
filler.appendChild(txtFiller); | |
} | |
} | |
// move on to split the newly added column | |
elem = newCol; | |
} | |
if(elem) {//mainly to set the column rule at the right height. | |
if(document.all && !window.opera) | |
elem.style.height = newHeight+'px'; | |
elem.style.minHeight = newHeight+'px'; | |
} | |
var clearFloatDiv = document.createElement('div'); | |
clearFloatDiv.style.clear = "left"; // < bug in Safari 1.3 ? (duplicates content) | |
clearFloatDiv.appendChild(document.createTextNode(' ')); | |
wrapper.appendChild(clearFloatDiv); | |
if(navigator.userAgent.toLowerCase().indexOf('safari') + 1) | |
wrapper.innerHTML+=' '; // forces redraw in safari and fixes bug above. | |
//wrapper.style.visibility = 'visible'; | |
} | |
// Find the deepest splitable element that sits on the split point. | |
function findSplitPoint(n, newHeight, wrapper) { | |
if (n.nodeType==1) { | |
var top = getElementRelativeTop(n, wrapper); | |
var bot = top+n.offsetHeight; | |
if(top < newHeight && bot > newHeight) { | |
bestSplitPoint = n; | |
if(isElementSplitable(n)) { | |
for(var i=0;i<n.childNodes.length;i++) { | |
findSplitPoint(n.childNodes[i], newHeight, wrapper); | |
} | |
} | |
return; | |
} | |
if(bot <= newHeight && bot >= secondSplitBottom) { | |
secondSplitBottom = bot; | |
secondSplitPoint = n; | |
} | |
} | |
return; | |
} | |
function isElementSplitable(n) { | |
if(n.tagName) { | |
var tagName = n.tagName.toUpperCase(); | |
for(var i=0;i<splitableTags.length;i++) | |
if(tagName==splitableTags[i]) return true; | |
} | |
return false; | |
} | |
function splitElement(n, targetHeight, col1, col2) { | |
var cn = n.lastChild; | |
while(cn) { | |
// if the child node is a text node | |
if(cn.nodeType==3) { | |
var strippedText = "dummmy"; | |
var allStrippedText = ""; | |
// the +2 is for tweaking.. allowing lines to fit more easily | |
while(n.offsetHeight > targetHeight+2 && strippedText!="") { | |
// remove lines of text until the splittable element reaches the targeted height or we run out of text. | |
strippedText = stripOneLine(cn); | |
allStrippedText = strippedText + allStrippedText; | |
} | |
if(allStrippedText!="") { | |
var insertPoint = createNodeAncestors(cn,col1,col2,'insertBefore'); | |
insertPoint.insertBefore(document.createTextNode(allStrippedText), insertPoint.firstChild); | |
} | |
if(cn.nodeValue=="") { | |
cn.parentNode.removeChild(cn); | |
} | |
else | |
break; | |
} | |
else { | |
// move element | |
var insertPoint = createNodeAncestors(cn,col1,col2,'insertBefore'); | |
insertPoint.insertBefore(cn.parentNode.removeChild(cn), insertPoint.firstChild); | |
} | |
cn = n.lastChild; | |
} | |
return strippedText; // returns the last line of text removed (used later for forcing the justification) | |
} | |
// stripOneLine() | |
// This function removes exactly one line to | |
// any element containing text | |
// and returns the removed text as a string. | |
function stripOneLine (n) { | |
// get the text node | |
while(n && n.nodeType != 3) | |
n = n.firstChild; | |
if(!n) return; | |
// get the height of the element | |
var e = n.parentNode; | |
var h = e.offsetHeight; | |
if(!h) { | |
//debug('no height for: ' + e.tagName); | |
return ""; | |
} | |
// get the text as a string | |
var str = n.nodeValue; | |
// remove a word from the end of the string | |
// until the height of the element changes | |
// (ie. a line has been removed) | |
var wIdx= n.nodeValue.lastIndexOf(' '); | |
while(wIdx!=-1 && e.offsetHeight == h) { | |
n.nodeValue = n.nodeValue.substr(0, wIdx); | |
wIdx = n.nodeValue.lastIndexOf(' '); | |
if(wIdx==-1) wIdx = n.nodeValue.lastIndexOf('\n'); | |
//debug(e.offsetHeight + ' ' + h + ' text=' + n.nodeValue + ' wIdx= ' + wIdx); | |
} | |
if(e.offsetHeight == h) | |
n.nodeValue = ""; | |
// returns the removed text | |
return str.substr(n.nodeValue.length); | |
} | |
// method= 'append'/'insertBefore', relative to col2 | |
function createNodeAncestors(n,col1,col2,method) { | |
var ancestors = new Array; | |
var insertNode = col2; | |
var pn = n.parentNode; | |
while(pn && pn.id != col1.id) { | |
ancestors[ancestors.length] = pn; | |
if(!pn.id) pn.id = ut.randomId(); | |
pn = pn.parentNode; | |
} | |
for (var i=ancestors.length-1; i >= 0; i--) { | |
for(var j=0; j < insertNode.childNodes.length && (insertNode.childNodes[j].nodeType==3 || !insertNode.childNodes[j].className.match(ancestors[i].id+'-css3mc')); j++); | |
if(j==insertNode.childNodes.length) { | |
// Ancestor node not found, needs to be created. | |
if(method=='append') | |
insertNode = insertNode.appendChild(document.createElement(ancestors[i].tagName)); | |
else | |
insertNode = insertNode.insertBefore(document.createElement(ancestors[i].tagName),insertNode.firstChild); | |
insertNode.className = ancestors[i].className+ ' ' + ancestors[i].id + '-css3mc'; | |
insertNode.style.marginTop = "0"; | |
insertNode.style.paddingTop = "0"; | |
if(insertNode.tagName.toUpperCase() == 'OL' && n.nodeType == 1 && n.tagName.toUpperCase() =='LI') { | |
var prevsib = n.previousSibling; | |
var count=0; | |
while(prevsib) { | |
if(prevsib.nodeType==1 && prevsib.tagName.toUpperCase() == 'LI') | |
count++; | |
prevsib = prevsib.previousSibling; | |
} | |
insertNode.setAttribute('start', count); | |
} | |
} else { | |
insertNode = insertNode.childNodes[j]; | |
if(insertNode.tagName.toUpperCase() == 'OL' && (insertNode.start==-1 || insertNode.start==1) && n.nodeType == 1 && n.tagName.toUpperCase() =='LI') { | |
// happens if the tag was created while processing a text node. | |
var prevsib = n.previousSibling; | |
var count=0; | |
while(prevsib) { | |
if(prevsib.nodeType==1 && prevsib.tagName.toUpperCase() == 'LI') | |
count++; | |
prevsib = prevsib.previousSibling; | |
} | |
insertNode.setAttribute('start', count); | |
} | |
} | |
} | |
return insertNode; | |
} | |
function moveNode(n,col1,col2) { | |
var insertNode=createNodeAncestors(n,col1,col2, 'append'); | |
var movedNode = insertNode.appendChild(n.parentNode.removeChild(n)); | |
if(insertNode.id == col2.id && movedNode.nodeType ==1 ) { | |
movedNode.style.paddingTop = "0px"; | |
movedNode.style.marginTop = "0px"; | |
} | |
return movedNode; | |
} | |
function getElementRelativeTop(obj, refObj) { | |
var cur = 0; | |
if(obj.offsetParent) { | |
while(obj.offsetParent) { | |
cur+=obj.offsetTop; | |
obj = obj.offsetParent; | |
} | |
} | |
var cur2 = 0; | |
if(refObj.offsetParent) { | |
while(refObj.offsetParent) { | |
cur2+=refObj.offsetTop; | |
refObj = refObj.offsetParent; | |
} | |
} | |
return cur-cur2; // + document.body.offsetTop; | |
} | |
} | |
// ===================================================================================== | |
// Utility Class Constructor skeleton | |
function CSS3Utility() { | |
// Event Handler utility list | |
this.handlerList = new Array(); | |
} | |
// Public Methods | |
// ============== | |
// querying of a DOM document using CSS selectors (a getElementsByTagName on steroids) | |
// see http://dean.edwards.name/my/cssQuery.js.html | |
/* | |
License: http://creativecommons.org/licenses/by/1.0/ | |
Author: Dean Edwards/2004 | |
Web: http://dean.edwards.name/ | |
*/ | |
CSS3Utility.prototype.cssQuery = function() { | |
var version = "1.0.1"; // timestamp: 2004/05/25 | |
// constants | |
var STANDARD_SELECT = /^[^>\+~\s]/; | |
var STREAM = /[\s>\+~:@#\.]|[^\s>\+~:@#\.]+/g; | |
var NAMESPACE = /\|/; | |
var IMPLIED_SELECTOR = /([\s>\+~\,]|^)([\.:#@])/g; | |
var ASTERISK ="$1*$2"; | |
var WHITESPACE = /^\s+|\s*([\+\,>\s;:])\s*|\s+$/g; | |
var TRIM = "$1"; | |
var NODE_ELEMENT = 1; | |
var NODE_TEXT = 3; | |
var NODE_DOCUMENT = 9; | |
// sniff for explorer (cos of one little bug) | |
var isMSIE = /MSIE/.test(navigator.appVersion), isXML; | |
// cache results for faster processing | |
var cssCache = {}; | |
// this is the query function | |
function cssQuery(selector, from) { | |
if (!selector) return []; | |
var useCache = arguments.callee.caching && !from; | |
from = (from) ? (from.constructor == Array) ? from : [from] : [document]; | |
isXML = false;//checkXML(from[0]); | |
// process comma separated selectors | |
var selectors = parseSelector(selector).split(","); | |
var match = []; | |
for (var i in selectors) { | |
// convert the selector to a stream | |
selector = toStream(selectors[i]); | |
// process the stream | |
var j = 0, token, filter, cacheSelector = "", filtered = from; | |
while (j < selector.length) { | |
token = selector[j++]; | |
filter = selector[j++]; | |
cacheSelector += token + filter; | |
// process a token/filter pair | |
filtered = (useCache && cssCache[cacheSelector]) ? cssCache[cacheSelector] : select(filtered, token, filter); | |
if (useCache) cssCache[cacheSelector] = filtered; | |
} | |
match = match.concat(filtered); | |
} | |
// return the filtered selection | |
return match; | |
}; | |
cssQuery.caching = false; | |
cssQuery.reset = function() { | |
cssCache = {}; | |
}; | |
cssQuery.toString = function () { | |
return "function cssQuery() {\n [version " + version + "]\n}"; | |
}; | |
var checkXML = (isMSIE) ? function(node) { | |
if (node.nodeType != NODE_DOCUMENT) node = node.document; | |
return node.mimeType == "XML Document"; | |
} : function(node) { | |
if (node.nodeType == NODE_DOCUMENT) node = node.documentElement; | |
return node.localName != "HTML"; | |
}; | |
function parseSelector(selector) { | |
return selector | |
// trim whitespace | |
.replace(WHITESPACE, TRIM) | |
// encode attribute selectors | |
.replace(attributeSelector.ALL, attributeSelector.ID) | |
// e.g. ".class1" --> "*.class1" | |
.replace(IMPLIED_SELECTOR, ASTERISK); | |
}; | |
// convert css selectors to a stream of tokens and filters | |
// it's not a real stream. it's just an array of strings. | |
function toStream(selector) { | |
if (STANDARD_SELECT.test(selector)) selector = " " + selector; | |
return selector.match(STREAM) || []; | |
}; | |
var pseudoClasses = { // static | |
// CSS1 | |
"link": function(element) { | |
for (var i = 0; i < document.links; i++) { | |
if (document.links[i] == element) return true; | |
} | |
}, | |
"visited": function(element) { | |
// can't do this without jiggery-pokery | |
}, | |
// CSS2 | |
"first-child": function(element) { | |
return !previousElement(element); | |
}, | |
// CSS3 | |
"last-child": function(element) { | |
return !nextElement(element); | |
}, | |
"root": function(element) { | |
var document = element.ownerDocument || element.document; | |
return Boolean(element == document.documentElement); | |
}, | |
"empty": function(element) { | |
for (var i = 0; i < element.childNodes.length; i++) { | |
if (isElement(element.childNodes[i]) || element.childNodes[i].nodeType == NODE_TEXT) return false; | |
} | |
return true; | |
} | |
// add your own... | |
}; | |
var QUOTED = /([\'\"])[^\1]*\1/; | |
function quote(value) {return (QUOTED.test(value)) ? value : "'" + value + "'"}; | |
function unquote(value) {return (QUOTED.test(value)) ? value.slice(1, -1) : value}; | |
var attributeSelectors = []; | |
function attributeSelector(attribute, compare, value) { | |
// properties | |
this.id = attributeSelectors.length; | |
// build the test expression | |
var test = "element."; | |
switch (attribute.toLowerCase()) { | |
case "id": | |
test += "id"; | |
break; | |
case "class": | |
test += "className"; | |
break; | |
default: | |
test += "getAttribute('" + attribute + "')"; | |
} | |
// continue building the test expression | |
switch (compare) { | |
case "=": | |
test += "==" + quote(value); | |
break; | |
case "~=": | |
test = "/(^|\\s)" + unquote(value) + "(\\s|$)/.test(" + test + ")"; | |
break; | |
case "|=": | |
test = "/(^|-)" + unquote(value) + "(-|$)/.test(" + test + ")"; | |
break; | |
} | |
push(attributeSelectors, new Function("element", "return " + test)); | |
}; | |
attributeSelector.prototype.toString = function() { | |
return attributeSelector.PREFIX + this.id; | |
}; | |
// constants | |
attributeSelector.PREFIX = "@"; | |
attributeSelector.ALL = /\[([^~|=\]]+)([~|]?=?)([^\]]+)?\]/g; | |
// class methods | |
attributeSelector.ID = function(match, attribute, compare, value) { | |
return new attributeSelector(attribute, compare, value); | |
}; | |
// select a set of matching elements. | |
// "from" is an array of elements. | |
// "token" is a character representing the type of filter | |
// e.g. ">" means child selector | |
// "filter" represents the tag name, id or class name that is being selected | |
// the function returns an array of matching elements | |
function select(from, token, filter) { | |
//alert("token="+token+",filter="+filter); | |
var namespace = ""; | |
if (NAMESPACE.test(filter)) { | |
filter = filter.split("|"); | |
namespace = filter[0]; | |
filter = filter[1]; | |
} | |
var filtered = [], i; | |
switch (token) { | |
case " ": // descendant | |
for (i in from) { | |
if(typeof from[i]=='function') continue; | |
var subset = getElementsByTagNameNS(from[i], filter, namespace); | |
for (var j = 0; j < subset.length; j++) { | |
if (isElement(subset[j]) && (!namespace || compareNamespace(subset[j], namespace))) | |
push(filtered, subset[j]); | |
} | |
} | |
break; | |
case ">": // child | |
for (i in from) { | |
var subset = from[i].childNodes; | |
for (var j = 0; j < subset.length; j++) | |
if (compareTagName(subset[j], filter, namespace)) push(filtered, subset[j]); | |
} | |
break; | |
case "+": // adjacent (direct) | |
for (i in from) { | |
var adjacent = nextElement(from[i]); | |
if (adjacent && compareTagName(adjacent, filter, namespace)) push(filtered, adjacent); | |
} | |
break; | |
case "~": // adjacent (indirect) | |
for (i in from) { | |
var adjacent = from[i]; | |
while (adjacent = nextElement(adjacent)) { | |
if (adjacent && compareTagName(adjacent, filter, namespace)) push(filtered, adjacent); | |
} | |
} | |
break; | |
case ".": // class | |
filter = new RegExp("(^|\\s)" + filter + "(\\s|$)"); | |
for (i in from) if (filter.test(from[i].className)) push(filtered, from[i]); | |
break; | |
case "#": // id | |
for (i in from) if (from[i].id == filter) push(filtered, from[i]); | |
break; | |
case "@": // attribute selector | |
filter = attributeSelectors[filter]; | |
for (i in from) if (filter(from[i])) push(filtered, from[i]); | |
break; | |
case ":": // pseudo-class (static) | |
filter = pseudoClasses[filter]; | |
for (i in from) if (filter(from[i])) push(filtered, from[i]); | |
break; | |
} | |
return filtered; | |
}; | |
var getElementsByTagNameNS = (isMSIE) ? function(from, tagName) { | |
return (tagName == "*" && from.all) ? from.all : from.getElementsByTagName(tagName); | |
} : function(from, tagName, namespace) { | |
return (namespace) ? from.getElementsByTagNameNS("*", tagName) : from.getElementsByTagName(tagName); | |
}; | |
function compareTagName(element, tagName, namespace) { | |
if (namespace && !compareNamespace(element, namespace)) return false; | |
return (tagName == "*") ? isElement(element) : (isXML) ? (element.tagName == tagName) : (element.tagName == tagName.toUpperCase()); | |
}; | |
var PREFIX = (isMSIE) ? "scopeName" : "prefix"; | |
function compareNamespace(element, namespace) { | |
return element[PREFIX] == namespace; | |
}; | |
// return the previous element to the supplied element | |
// previousSibling is not good enough as it might return a text or comment node | |
function previousElement(element) { | |
while ((element = element.previousSibling) && !isElement(element)) continue; | |
return element; | |
}; | |
// return the next element to the supplied element | |
function nextElement(element) { | |
while ((element = element.nextSibling) && !isElement(element)) continue; | |
return element; | |
}; | |
function isElement(node) { | |
return Boolean(node.nodeType == NODE_ELEMENT && node.tagName != "!"); | |
}; | |
// use a baby push function because IE5.0 doesn't support Array.push | |
function push(array, item) { | |
array[array.length] = item; | |
}; | |
// fix IE5.0 String.replace | |
if ("i".replace(/i/,function(){return""})) { | |
// preserve String.replace | |
var string_replace = String.prototype.replace; | |
// create String.replace for handling functions | |
var function_replace = function(regexp, replacement) { | |
var match, newString = "", string = this; | |
while ((match = regexp.exec(string))) { | |
// five string replacement arguments is sufficent for cssQuery | |
newString += string.slice(0, match.index) + replacement(match[0], match[1], match[2], match[3], match[4]); | |
string = string.slice(match.lastIndex); | |
} | |
return newString + string; | |
}; | |
// replace String.replace | |
String.prototype.replace = function (regexp, replacement) { | |
this.replace = (typeof replacement == "function") ? function_replace : string_replace; | |
return this.replace(regexp, replacement); | |
}; | |
} | |
return cssQuery; | |
}(); | |
// Cross-Browser event handler. | |
CSS3Utility.prototype.XBrowserAddEventHandler = function(target,eventName,handlerName) { | |
if(!target) return; | |
if (target.addEventListener) { | |
target.addEventListener(eventName, function(e){eval(handlerName)(e);}, false); | |
} else if (target.attachEvent) { | |
target.attachEvent("on" + eventName, function(e){eval(handlerName)(e);}); | |
} else { | |
// THIS CODE NOT TESTED | |
var originalHandler = target["on" + eventName]; | |
if (originalHandler) { | |
target["on" + eventName] = function(e){originalHandler(e);eval(handlerName)(e);}; | |
} else { | |
target["on" + eventName] = eval(handlerName); | |
} | |
} | |
// Keep track of added handlers. | |
var l = this.handlerList.length; | |
this.handlerList[l] = new Array(2); | |
this.handlerList[l][0] = target.id; | |
this.handlerList[l][1] = eventName; | |
// see http://weblogs.asp.net/asmith/archive/2003/10/06/30744.aspx | |
// for a complete XBrowserAddEventHandler | |
} | |
// getPseudoCssRules() | |
// Constructor for a pseudo-css rule object | |
// (an unsupported property, thus not present in the DOM rules collection) | |
// Constructor parameters | |
// ---------------------- | |
// the css property name | |
// the stylesheet (as a text stream) | |
// Object properties: | |
// ------------------ | |
// selector (string) | |
// property (string) | |
// value (string) | |
CSS3Utility.prototype.getPseudoCssRules = function(propertyName, serializedStylesheet) { | |
this.cssRules = new Array(); | |
var valuePattern = propertyName.replace("-","\-")+"[\\s]*:[\\s]*([^;}]*)[;}]"; | |
var selectorPattern = "$"; | |
var regx = new RegExp(valuePattern,"g"); | |
var regxMatch = regx.exec(serializedStylesheet); | |
var j=0; | |
while(regxMatch){ | |
var str = serializedStylesheet.substr(0,serializedStylesheet.substr(0,serializedStylesheet.indexOf(regxMatch[0])).lastIndexOf('{')); | |
var selectorText = str.substr(str.lastIndexOf('}')+1).replace(/^\s*|\s*$/g,""); | |
// ignore commented rule !! | |
this.cssRules[j] = new Object(); | |
this.cssRules[j].selectorText = selectorText; | |
this.cssRules[j].property = propertyName; | |
this.cssRules[j].value = regxMatch[1].replace(/(\r?\n)*/g,""); // suppress line breaks | |
j++; | |
regxMatch = regx.exec(serializedStylesheet); | |
} | |
} | |
// Generates a random ID | |
CSS3Utility.prototype.randomId = function () { | |
var rId = ""; | |
for (var i=0; i<6;i++) | |
rId += String.fromCharCode(97 + Math.floor((Math.random()*24))) | |
return rId; | |
} | |
CSS3Utility.prototype.debug = function(text) { | |
var debugOutput = document.getElementById('debugOutput'); // Debug Output | |
if(typeof debugOutput != "undefined" && debugOutput) { | |
//debugOutput.appendChild(document.createElement('hr')); | |
//debugOutput.appendChild(document.createTextNode(text)); | |
debugOutput.innerHTML+= text; | |
} | |
} | |
// Object Instance | |
var css3MC = new CSS3MultiColumn(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment