Introduction
Rich clients in web applications seem to be the �trendy� way of constructing user interfaces. The pursuit of seamless user experience without the start-stop nature of the traditional webapps imposes various �restrictions� on how to do things.
I had to construct a UI for a web application and it had to be fast, at least that was what I wanted to achieve. It was until the very-end that I realized that the �UI responsiveness� effect that I was trying to achieve with the various JavaScript tricks was undermined by the long times of loading data from the server. It would be much faster to use paging in the large datasets, but it�s obvious that there is no desktop application with paging in its tables.
I thought it would be beautiful to have a scrollbar and load each time only a fraction of the total recordset. This would give to the user the experience I wanted, as he could scroll the table at any speed without having to wait for several seconds at the beginning �
Solution
But there is no scrollbar widget you can use in an HTML page and there was only one way to display a scrollbar in an HTML page, and this was through the overflow
CSS property. In order to show a scroll bar, we need an HTML code like the one below:
<div id="scrollBar" style="overflow:scroll;height:100px;width:17px">
<div id=�valueBar� style=�height=200px�></div>
</div>
Then an event handler can be attached on the onscroll
event of scrollBar
and we can calculate the percentage of the scrolling. I created a JavaScript to do that:
VerticalScrollBar.prototype.attach = function (oId) {
if (document.getElementById(oId)!=null) {
elem = document.getElementById(oId);
if (elem.tagName!="DIV") {
alert("VerticalScrollBar Init : Please attach me to a div");
return;
}
elem.onscroll = this._onScroll;
elem.object = this;
this.scrollBar = elem;
vbar = document.createElement('div');
vbar.style.height = elem.style.pixelHeight*this.maxValue;
elem.appendChild(vbar);
}
};
oId
is the parent div
ID. The parent div
should have its style set to �overflow:scroll;width:17px;height:someValue�
.
Then I create a second div
with size parentSize*maxValue
, and I append it inside the parent div
and attach an event handler on the onscroll
event of the parent.
VerticalScrollBar.prototype._onScroll = function () {
st = event.srcElement.scrollTop ;
sh = event.srcElement.scrollHeight ;
sp = st * (100/sh);
s = (event.srcElement.object.maxValue)*(sp/100);
event.srcElement.object.value=Math.round(s);
if (event.srcElement.object.onScroll!=null)
event.srcElement.object.onScroll.call();
};
The only point of interest here is the following snippet:
sp = st * (100/sh);
s = (event.srcElement.object.maxValue)*(sp/100);
where the percentage of scrolling is calculated and multiplied with the maximum value of the scrollbar. I must notice here that even when the scrollbar is fully scrolled, the scrollHeight
is more than the scrollTop
(I was expecting to be the same). I don�t know why this is happening and would really like to now.
So after the scrollbar is created, we can use it to get the scroll position and request the appropriate part of the recordset.
The webservice
The webservice that returns the record set should be something like this:
public struct QueryResult
{
public int offset;
public int count;
public int totalCount;
public string[] result;
}
[WebMethod(EnableSession=true)]
public QueryResult GetRecords(int s,int count)
{
QueryResult qres = new QueryResult();
string[] ar = GetRecordset();
qres.totalCount = ar.Length;
string[] res = new string[count];
for (int i=0;i<count;i++)
{
if ((i+s)<ar.Length)
res[i]=ar[i+s];
else
{
qres.count = i;
qres.offset = s;
qres.result = res;
return qres;
}
}
qres.count = count;
qres.offset = s;
qres.result = res;
return qres;
}
The full recordset is stored in the session, and depending on the supplied parameters, we fetch only a portion of it. A struct
is used in order to avoid the creation of a second method that returns the total count of the records.
The client side code
I am used to the webservice behavior so I am going to use it to create the client side code that interacts with the webservice. The method call to GetRecords
will be synchronous as we do not want to mess with asynchronous calls' handling. So we create a JavaScript function _getRecords
that calls synchronously the webservice and returns the result set.
function _getRecords(s,count)
{
var callObj = new Object();
callObj.funcName = "GetRecords";
callObj.async = false;
callObj.timeout = 5;
callObj.SOAPHeader = ""; callObj.SOAPHeader += "";
callObj.SOAPHeader += 5;
callObj.SOAPHeader += ""; callObj.SOAPHeader += "";
result = service.Service.callService(callObj, s,count);
if (!result.error) {
return result.value;
} else {
alert("Error:"+result.errorDetail.string);
}
}
On the body onload
event, we assign an event handler that initializes the webservice behavior and the scrollbar. When the webservice is available, the setup()
function is executed that initializes the scrollbar.
function setup(){
res1 = _getRecords(0,0);
totalRecords =res1.totalCount;
vb = new VerticalScrollBar(totalRecords);
vb.onScroll = vbscroll; vb.attach('sbar');
};
After that the event handler for the scrolling is trivial.
function vbscroll() {
getRecords(vb.value);
};
The only problem here that I couldn�t figure out how to solve, is how to determine automatically the number of the requested rows depending on the table�s size. That's why the number of the requested rows is manually set to a number that does not expand the table a lot. From a few tries, I found that 16 is okay for the table height I�ve specified.
Conclusion
Due to the nature of the medium, a lot of times we have to give up to workarounds like this to achieve some specific effect.
So, I doubt whether this is an elegant way to create the desired effect but it�s pretty catchy especially from the user�s point of view, as it provides the responsiveness the user demands from a rich client.
That�s all.