Tuesday, April 26, 2011

JavaScript: Maintain scroll position of page along with individual elements within

Prior to ASP .Net 2.0, page scroll position could be saved using a popular Javascript technique by storing the scroll position of the page to a cookie, and later re-scrolling the page after postback by reading the cookie. ASP .Net 2.0 reduced this extra work needed by developers by adding a new page directive called MaintainScrollPositionOnPostback. By setting this to "true", relatively 'simpler' web pages would automatically scroll to the position that they were in before the page posted back. However this technique does not work well in MasterPage-ContentPage scenarios where there are multiple updatepanels hosting multiple AJAX controls and so on. Moreover, this does not solve a common problem where one might want to save scroll position of individual scrollable controls within the page itself (textboxes etc.).

So this is my 'cadillac' version of scroll saver Javascript.

Note: The main script itself does NOT use jQuery, but in order to wire this up like mentioned below, you would need to reference jQuery.js.

Step 1. Call saveScroll() method from body onunload of Master page:

body onunload="return saveScroll();"

Step 2. Call loadScroll() from individual Content pages (the reason I use window.setTimeout is to make sure all AJAX controls are done with their required to-dos before the scrolling can start):

$(document).ready(function () {
window.setTimeout("loadScroll()", 0);
});

Step 3. Finally this is the actual script that you can put in your own javascript file or just embed within the head of master page.

Note: If you want to skip steps 1 and 2 above and do not mind storing scroll positions for ALL pages and have no other window.onunload's or window.setTimeOut's anywhere else, just add the following snippet at the top of the main script:

$(document).ready(function () {
window.setTimeout("loadScroll()", 0);
window.onunload = function () { return saveScroll() };
});

And here's the main script:

//Standard delimiters in the cookie, change to your taste :-)
var d1 = '=';
var d2 = '|';
var d3 = '^';

function loadScroll(scrollElements) {
var cookieList = document.cookie.split(';');
for (var i = 0; i < cookieList.length; i++) {
var cookieParts = cookieList[i].split(d1);
if (cookieParts[0] == 'scrollPosition') {
var values = unescape(cookieParts[1]).split(d3);
for (var j = 0; j < values.length; j++) {
var currentValue = values[j].split(d2);
try {
if (currentValue[0] == 'window' && currentValue[1] != getPageName())
return;

if (currentValue[0] == 'window') {
window.scrollTo(currentValue[2], currentValue[3]);
}

if (currentValue[0] == 'element') {
var elm = document.getElementById(currentValue[1]);
elm.scrollLeft = currentValue[2];
elm.scrollTop = currentValue[3];
}
} catch (ex) { }
}
return;
}
}
}

function saveScroll() {
var s = 'scrollPosition' + d1;
var wl, wt;
if (window.pageXOffset !== undefined) {
wl = window.pageXOffset;
wt = window.pageYOffset;
} else if (document.documentElement && document.documentElement.scrollLeft !== undefined) {
wl = document.documentElement.scrollLeft;
wt = document.documentElement.scrollTop;
} else {
wl = document.body.scrollLeft;
wt = document.body.scrollTop;
}
if (wl != undefined || wt != undefined) {
s += 'window' + d2 + getPageName() + d2 + wl + d2 + wt;
}

//Save absolute scroll position of individual page elements if you would like
//WARNING: If page has lot of elements (grids etc.), this may cause some delay, so you might want to comment the line below
s = saveIndividualScrolls(s);

document.cookie = s + ';';
}

function getPageName() {
var pathName = window.location.pathname;
return pathName.substring(pathName.lastIndexOf('/') + 1);
}

function saveIndividualScrolls(s) {
var elements = (document.all) ? document.all : document.getElementsByTagName('*');
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
if (e.id && (e.scrollLeft || e.scrollTop)) {
s += d3 + 'element' + d2 + e.id + d2 + e.scrollLeft + d2 + e.scrollTop;
}
}
return s;
}