Gebruikersergernis: verspringende scrollbars
Gebruikersergernissen zitten vaak in een klein hoekje. Eén daarvan is dat de positie van de schuifbalk verloren gaat wanneer je een webpagina ververst, bijvoorbeeld bij het aanvinken van een selectiebox of een keuzeveld. In deze blogpost laat ik zie hoe je met behulp van JavaScript hiervoor eenvoudig een oplossing kunt realiseren.
Waarom dit probleem?
Soms kun je het verversen van (delen van) webpagina’s niet vermijden. Ontwikkelaars houden hier meestal rekening mee en proberen requests bij dit soort gebruikersacties te vermijden of zoveel mogelijk te beperken. Soms kan het echter niet anders. Er kunnen bijvoorbeeld afhankelijkheden zitten tussen de verschillende invoervelden en andere formulierelementen. Zie onderstaand voorbeeld:
In het voorbeeld zijn twee containers te zien met een scrollbar. Om functionele redenen worden bij iedere selectie van een item de gegevens verstuurd waarmee iets gedaan moet worden. Voorbeelden daarvan zijn het tonen van informatie over de huidige selectie of het bijwerken van de waarden van een ander afhankelijk formulierelement. Microsoft ASP.NET verstuurt deze data met ‘Postbacks’. Andere implementaties, in bijvoorbeeld PHP, versturen direct een page request terwijl AJAX de inhoud van de formulierelementen vervangt. Ongeacht de gebruikte technologie zal bij het vervangen van de inhoud de scrollbar naar boven verspringen, naar de beginpositie. Dit is niet gebruiksvriendelijk.
Oplossingsrichting
Dit verspringen van de scrollbar is eenvoudig te verhelpen. Functioneel werkt het als volgt. Sla de positie van de balk op zodra deze wijzigt. Wanneer je de inhoud ververst kun je de positie weer terugzetten. De technische werking van deze oplossingsrichting bevat tevens een aantal ontwerpbeslissingen. Wanneer de pagina laadt zul je het scroll event moeten koppelen aan de logica die de positie van de scrollbar opslaat. Het opslaan van de posities moet op een manier gebeuren die tussen pagina requests niet verloren gaat. Dit kan door middel van hidden input values die met het formulier meegestuurd worden. Nog beter is om cookies te gebruiken als opslagmechanisme. Ook als elementen met een scrollbar zich buiten een formulier bevinden, wordt zo de waarde opgeslagen. Om de functionaliteit herbruikbaar te maken voor gebruik in de toekomst, kun je specificeren van welke elementen de scrollbarpositie opgeslagen moet worden. Dit los je op door de class ‘RememberScrollPosition’ aan de betreffende HTML DOM-elementen toe te kennen.
Technische oplossing
In dit voorbeeld kiezen we voor een client side oplossing in JavaScript en gaan we uit van een ASP.NET oplossing die requests/postbacks uitvoert bij iedere selectie van een item. De HTML-code en ASPX-code ziet er als volgt uit:
<div id="scrolbaarElement1" class="RememberScrollPosition"
style="width: 100px; height: 150px; overflow: auto; border: 1px solid #000;">
<asp:CheckBoxList ID="CheckBoxList1" runat="server" AutoPostBack="True">
<asp:ListItem>Item 1</asp:ListItem>
<asp:ListItem>Item 2</asp:ListItem>
<asp:ListItem>Item n</asp:ListItem>
…
</asp:CheckBoxList>
</div>
Allereerst is er logica nodig. Met de class ‘RememberScrollPosition’ kun je die op het scroll event van DOM-elementen uitvoeren. Wanneer jQuery beschikbaar is, kun je dit heel eenvoudig oplossen:
$(document).ready(function () {
scrollpositionRestoreAll();
$(".RememberScrollPosition").each(function () {
$(this).scroll(function () {
scrollpositionSave(this);
});
});
});
Bovenstaande code komt in de <head> sectie van de pagina en wordt uitgevoerd bij het laden van de pagina. Eerst roep je scrollpositionRestoreAll() aan om eerder opgeslagen scrollbalkposities te herstellen. Vervolgens selecteer je met behulp van jQuery alle DOM-elementen met de class ‘RememberScrollPosition’. Op het scroll event van ieder element roep je vervolgens scrollpositionSave() aan. Vanzelfsprekend bevat dit de logica om de positie op te slaan in een cookie. Dat gaat als volgt:
function scrollpositionSave(element) {
if (elementCanBeIdentified(element) && elementHasScrolled(element)) {
setCookie(element.id, element.scrollTop);
}
}
function elementCanBeIdentified(element) {
return document.getElementById(element.id) && element.id.length > 0;
}
function elementHasScrolled(element) {
return element.scrollTop > 0;
}
De DOM-elementen waarvan je de scrollpositie opslaat, moeten een ID-attribuut hebben zodat het element uniek is en herkend kan worden. Anders is niet duidelijk om welk element op de pagina het gaat. De eigenschap ‘scrollTop’ kun je in JavaScript gebruiken om de positie van de schuifbalk op te halen. De positie wordt alleen opgeslagen wanneer deze niet in de beginpositie staat. Zo beperk je de opslag van de posities tot wat strikt noodzakelijk is.
Nu duidelijk is hoe het opslagmechanisme werkt, kun je de posities van de schuifbalk implementeren. Dit gebeurt door middel van het uitlezen van het cookie. Voor iedere waarde die in het cookie staat, wordt het element en de positie uitgelezen én toegepast wanneer het element op de pagina is gevonden.
function scrollpositionRestoreAll() {
var cookies = getCookiesAsArray();
for (var cookieKey in cookies) {
if (elementExists(cookieKey)) {
var elementId = cookieKey;
var elementScrollPosition = cookies[elementId];
elementSetScrollPosition(elementId, elementScrollPosition);
}
}
}
In bovenstaande code worden hulpfuncties gebruikt. Die zijn in aparte functies gezet om ze in andere delen van de programmatuur opnieuw te kunnen gebruiken.
function getCookiesAsArray() {
var result = { };
var cookieContent = document.cookie;
if (cookieContent.length > 0) {
var cookies = cookieContent.split(";");
for (var i = 0; i < cookies.length; i++) {
var keyValuePair = cookies[i].split("=");
try {
var key = keyValuePair[0].replace(" ", "");
var value = keyValuePair[1];
result[key] = value;
}
catch (e) {
}
}
}
return result;
}
function elementExists(elementId) {
return document.getElementById(elementId);
}
function elementSetScrollPosition(elementId, position) {
document.getElementById(elementId).scrollTop = position;
}
In sommige situaties is het nodig om de eerder opgeslagen schuifbalkposities te verwijderen. Dit gaat op dezelfde manier als scrollpositionsRestoreAll(). De actie echter is niet het herstellen van de schuifbalkpositie, maar het verlopen van de cookie. De browser verwijdert in dat geval de waarde.
function scrollpositionsClearAll() {
var cookies = getCookiesAsArray();
for (var cookieKey in cookies) {
if (elementExists(cookieKey)) {
clearCookie(cookieKey);
}
}
}
Bovenstaande JavaScript-methodes kun je opslaan in een apart .js bestand. De functionaliteit is daarmee herbruikbaar op andere pagina’s.
Eventueel kun je scrollpositionsClearAll() gebruiken wanneer de pagina voor het eerst wordt aangeroepen. De gebruiker start dan iedere keer met een herkenbare beginsituatie. Dit kan in de codebehind van de ASPX-1pagina:
C#:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(), "Body_Load_ClearScrollPositionsScript",
"scrollpositionsClearAll()", true);
}
}
VB:
Protected Overrides Sub OnLoad(ByVal e As EventArgs)
MyBase.OnLoad(e)
If Not Me.IsPostBack() Then
Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), "Body_Load_ClearScrollPositionsScript", "scrollpositionsClearAll()", True)
End If
End Sub
Conclusies
Met enkele eenvoudige JavaScript-methodes heb je zo een oplossing gerealiseerd waarbij de scrollposities van elementen worden onthouden tussen de postbacks en het verversen van de pagina. De functionaliteit kan hergebruikt worden. Het zou echter handig zijn als we dit in een jQuery plugin kunnen wrappen. Dat zorgt er niet alleen voor dat het eenvoudiger wordt om deze functionaliteit opnieuw te gebruiken, maar onderhoudt ook de programmatuur beter. In een volgende blogpost zal ik dit verder uitwerken.
» Download hier het demo project (Visual Studio 2010)
- Visual Studio 2010
- ASP.NET 4.0 Web Application
- jQuery 1.4.1 (standaard beschikbaar in ASP.NET 4.0 Web Application)

