tim laqua dot com Thoughts and Code from Tim Laqua

12Mar/090

Scrolling to and Highlighting Anchor Target via JavaScript

I implemented a SSAS cube action to link to a SharePoint page (URL Action) with the name of the measure contained in the cell the user fired the action from. The theory here is to have a page that contains a list of definitions for all measures (both real and calculated) in the cube. As we were flushing out this implementation, it was suggested that the page should scroll to the specified measure and highlight it in some way. The implementation of this using the CSS :target pseudo class is pretty straightforward - however we're a Microsoft shop and we absolutely have to support Internet Explorer 7 and Internet Explorer 8, so that's out.

Originally, the plan was to use Anchor Targets, but since the implementation ended up being pure JavaScript, there really was no reason to use anchors - we used something like this to define measures:

<div id="dataDictionary">
    <div id="termNotFound">Specified definition could not be found.</div>
 
    <div>
        <h1>Term goes here</h1>
        <p>Definition goes here</p>
        <h2>Additional Comments</h2>
        <ul>
            <li>comment</li>
        </ul>
    </div>
    <div>
        <h1>Other Term goes here</h1>
        <p>Other Definition goes here</p>
        <h2>Additional Comments</h2>
        <ul>
            <li>comment</li>
        </ul>
    </div>
</div>

And the following JavaScript is used to locate the appropriate heading, modify it's parent DIV (to add the selected className so it gets different styling), and scroll down to it:

ParseURI function from http://blog.stevenlevithan.com/archives/parseuri

function parseUri (str) {
	var	o   = parseUri.options,
		m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
		uri = {},
		i   = 14;
 
	while (i--) uri[o.key[i]] = m[i] || "";
 
	uri[o.q.name] = {};
	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
		if ($1) uri[o.q.name][$1] = $2;
	});
 
	return uri;
};
 
parseUri.options = {
	strictMode: false,
	key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
	q:   {
		name:   "queryKey",
		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
	},
	parser: {
		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
		loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
	}
};

ScrollToElement function from http://radio.javaranch.com/pascarello/2005/01/09/1105293729000.html

function ScrollToElement(theElement){
 
  var selectedPosX = 0;
  var selectedPosY = 0;
 
  while(theElement != null){
    selectedPosX += theElement.offsetLeft;
    selectedPosY += theElement.offsetTop;
    theElement = theElement.offsetParent;
  }
 
 window.scrollTo(selectedPosX,selectedPosY);
 
}

Some quick string manipulation functions form http://www.webtoolkit.info/javascript-trim.html

function trim(str, chars) {
	return ltrim(rtrim(str, chars), chars);
}
 
function ltrim(str, chars) {
	chars = chars || "\\s";
	return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}
 
function rtrim(str, chars) {
	chars = chars || "\\s";
	return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

And finally, some original code to make it all work:

function hotlink() {
	var colP = document.getElementsByTagName('p');
	if ( colP.length > 0 ) {
		for(var i=0; i<colP.length; i++) {
			colP[i].innerHTML = colP[i].innerHTML.replace(new RegExp("(" + ddTerms + "^$)","g"),"<a href=\"javascript:findTerm('$1')\"'>$1</a>");
		}
	}
}
 
function findTerm(term) {
	var colDIV = document.getElementsByTagName('div');
	if ( colDIV.length > 0 ) {
		for(var i=0; i<colDIV.length; i++) {
			if (i % 2 == 0) {
				colDIV[i].className = 'alt';
			}
			else {
				className = '';
			}
		}
	}
 
	var colH1 = document.getElementsByTagName('h1');
	if ( colH1.length > 0 ) {
		var found = false;
		for(var i=0; i<colH1.length; i++) {
			var ddTerm = trim(colH1[i].innerHTML.toLowerCase());
			if (ddTerm == term.toLowerCase()) {
				found = true;
				colH1[i].parentNode.className = 'selected';
				ScrollToElement(colH1[i]);
			}
			ddTerms += "\\b" + ddTerm.replace(new RegExp("(\\(|\\))", "g"), "\\$1") + "\\b|";
		}
 
		if (!found && term.length > 0) {
			var tnf = document.getElementById('termNotFound');
			tnf.style.display = 'block';
			ScrollToElement(tnf);
		}
	}
}
 
var ddTerms = '';
 
window.onload = function () {
	findTerm(unescape(parseUri(window.location.href).anchor));
	hotlink();
}

Note, there's also a hotlink() function in there - since this is a page full of terms and definitions, we might as well link up the page so all terms are clickable within the document.

Finally, make it looke somewhat presentable with either an inline style block or just add these to your external style sheet:

	#dataDictionary a { font-style: italic; text-decoration: underline; }
	#dataDictionary 
	{
		font-size: 110%;
	}
 
	#dataDictionary div 
	{ 
		background-color: #ccccff; 
		margin-bottom: 4pt;
	}
 
	#dataDictionary div p
	{ 
		margin: 2pt;
	}
 
	#dataDictionary div ul
	{ 
		margin-top: 2pt;
	}
 
	#termNotFound 
	{ 
		display: none; 
		color: red; 
		font-weight: bold; 
		font-style: italic; 
		border: 2px dotted red;
		background-color: #ffff77 !important;
		padding: 2pt;
	}
 
	#dataDictionary div h1 
	{ 
		font-size: 130%; 
		border-bottom: 1px solid white; 
		background-color: #ccbbff;
		margin: 0pt, 0pt, 2pt, 0pt;
	}
	#dataDictionary div h2 { 
		font-size: 115%; 
		margin: 3pt, 0pt, 2pt, 0pt;
	}
 
	#dataDictionary div ul 
	{
		margin-bottom: 2pt;
	}
 
	#dataDictionary div.alt  h1
	{ 
		background-color: #88ff88; 
	}
	#dataDictionary div.alt 
	{ 
		background-color: #bbffaa; 
	}	
 
	#dataDictionary div.selected h1 
	{ 
		background-color: #ff7755; 
	}
	#dataDictionary div.selected 
	{ 
		background-color: #ff9988; 
		font-size: 125%;
	}
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.