Geek alert!
If you’re not a Javascript/Web developer, skip quickly to the next post — that will minimize the pain. If you are a Javascript/Web developer, you might be interested in this.
I’m working on an application now that has several different needs (and almost certainly more to come) that all fit the same pattern: quite large tables (100s of thousands of entries) that need to be viewed, scanned, filtered, and sorted. The entries in these tables represent real-world electronic trading events; it’s vital for the information to be up-to-date and accurate.
My first stab at a solution for this took advantage of the fact that modern web browsers are fully capable of displaying truly gigantic HTML tables in an IFRAME element, making a nicely scrollable (horizontally and vertically) UI element. However, there were quite a few shortcomings with this approach. First, the HTML was generated at the server and delivered to the client in a GET; this means that every change in the view required a complete download of that portion of the table that was being viewed — definitely not a zippy operation, and quite a server load as well. Second, a little thing but very annoying: the table headers scrolled right off the screen unless you were at the very top of the table. Third, and probably worst of all: when the table had more than about 8,000 rows, it was completely unreasonably slow — and the client’s CPU utilization went to 100% (always a bad thing!).
Some investigation on the third problem led me to an interesting discovery: the browser (Firefox 1.5 in my case) uses a fairly inefficient algorithm to insert a row into a table. It doesn’t matter whether the row is being parsed from an HTML document or is being inserted by client-side Javascript code operating directly on the DOM — it’s still slow. From the behavior, I think it’s an n^2 algorithm; I speculate that they are copying the entire table on every insert operation.
After a little thought I realized that the real root of my problem was that I was trying to render thousands of table rows just to see 30 or 35 of them. There had to be a better way!
The approach I chose relies heavily on Ajax techniques. First, I wrote some stuff to fetch the underlying data and store it in Javascript arrays. I used JSON as the encoding on the messaging layer, as this is very lightweight on the client side (as it’s natively supported by Javascript). This code queries the server on a configurable interval to get the data; the end result is that the array in client memory is up-to-date within that interval. This stuff was easy to write (prototype.js is my friend), it is very fast, and it imposes almost no measurable load (other than a few 10s of megabytes of RAM consumed).
Then came the really tricky Javascript part — tricky for me, anyway, as I am no Javascript guru. I created an object (I call it ViewManager, and it has some associated objects for filtering, sorting, and so on), whose purpose in life is to maintain a small (e.g., one screenful) table that is a view onto the much larger underlying array of data. Using HTML and CSS, I created a vertical scroll bar that is entirely operated by my Javascript, rather than by the browser. The HTML table seen by the browser has only 30 or 40 rows in it (the size can be changed by the user by dragging a corner) — this renders blindingly fast, with practically no CPU consumption. When the user scrolls around in the table, what’s happening under the covers is that the ViewManager object gets told to move around on the large array of data, and it simply re-renders the entire HTML table as needed to simulate this.
Because my code is managing the table’s presentation, it’s a snap for me to keep the headers on top — that works fabulously well. I handle filtering and sorting by keeping a secondary array whose elements represent the items actually being viewed, in the order that they should be viewed. Each element of this array contains an integer index to an element in the main data array. This arrangement allows me to sort and filter without actually modifying the data table — and this means I can very conveniently always return to a normal unsorted, unfiltered view. With a data array containing 500,000 items, a sort and/or filter operation takes well under one second — awesomely zippy compared to the initial method. And best of all, the server is completely uninvolved in this!
I was pleasantly surprised at how easy all this code was to implement. It’s very reminiscent of low-level GUI implementations, especially in the event-driven nature of it. The trickiest bits turned out to be accurately figuring out where an object happened to be rendered on the screen — in a browser like Firefox, the user can do things like change the font size, or resize the window, and that will re-render the screen, sometimes in unexpected ways. I’ve not found good documentation on stuff like this, so I ended up doing quite a bit of reverse-engineering and experimentation — but that’s ok, I learn a lot every time I do something like that…
The bottom line on this experience: a modern web browser, using Ajax techniques, can do just about anything that an old-fashioned WIN32 GUI can do, with just as much interactivity and zippiness. The only exception I am currently aware of is for graphical elements that you’d really like to create on the client — Javascript provides no tools for manipulating or creating images. But the vast majority of the business applications I’ve worked on had no such requirements. Without such a requirement, for me choosing a web client over a thick-client GUI application is a no brainer. With such a requirement, I’d be looking very hard at other approaches, either server-side or with client side Java applets. I’d also be looking for someone’s add-on to Javascript for image manipulation…
No comments:
Post a Comment