Virtual Workspace
From Ajax Patterns
There's an archived version of this pattern available, taken from the Ajax Pattern book draft, showing roughly how it appeared before the page became publicly editable.
Evidence: 2/3
Tags: Camera Desktop Infinite Move Pan Portal Scroll Virtual Visible Window Zoom
Contents |
Ajax/Javascript Programming and Usability in "Ajax Design Patterns" Book |
In A Blink
Diagram an online map
Goal Story
Bill is using a meta-search engine to receive insurance quotes. The browser can't retrieve all 500 results at once, but what Bill sees is a table that looks like all the results are in it. He can scroll up and down as if it were a regular table, and the newly-appearing rows are populated on demand.
Problem
How can the user navigate through a large workspace?
Forces
- Ajaxian applications often require data from the server.
- On the server-side, data storage is virtually unlimited - search engines, for example, store billions of web pages. Even on corporate intranets, query results are often huge too - try searching for every retail transaction in a single day.
- To be responsive, the server needs to respond to data requests as quickly as possible.
- Bandwidth constraints mean its not feasible for an interactive application to download the entire result. Yet, users often should be free to explore the whole thing.
Solution
Provide a browser-side view into a server-side workspace, allowing users to navigate the entire workspace as if it were held locally. The illusion is that the entire workspace is already in the browser, while the reality is that the server actually provides content on demand. At any time, the user is looking at an "opening" or "portal" into the entire workspace. They can pan across, jump to a different region, and zoom in and out. When that happens, the current view needs to change, so the browser fetches the data for the new portion of the workspace and renders accordingly.
Examples of navigable workspaces:
- Lists. e.g. search results, user preferences.
- Tables.e.g. Data Grids, spreadsheets.
- Physical images. e.g. diagrams, photos, maps.
- Documents. e.g. web content.
- 3D Models. e.g. computer-rendered landscapes, visualisations of chemical molecule.
- Time-dependent artifacts Any artifact that changes with time - world population, for instance, - can be "navigated" by moving across time.
Users move through the space in different ways, often a combination of these:
- Scrollbars: You can make the content scroll incrementally or jump to a completely different region with scrollbars.
- Dragging tool: A dragging metaphor lets the user drag the entire document while keeping the view fixed. Dragging rightward makes the document pan right, hence exposes content on the left.
- Keyboard shortcuts: Directional keys are often used to scroll incrementally. Page-Up and Page-Down are often used to completely replenish the current view with an adjacent region. Home and End are often used to jump to the start and end.
- Direct command: The user uses a separate control to specify the exact region. For example:
- The user can type in a row number for a table.
- For an image, the user can drag around a box on a thumbnail view of the entire document. The box represents the current view.
There are also different zooming techniques:
- Zoom slider: An adjustable slider represents the current zoom level.
- Keyboard shortcuts: Keyboard shortcuts for zooming in and out. A common choice is - and + (or =, since + is usually shift-=).
- Selecting a region: Selecting a region within the current view to zoom further into.
Depending on the mechanisms you're supporting, you'll need to add different types of event handler. So, for keyboard shortcuts, watch for events like keydown; or for dragging and selecting a region, mousedown, mousemove, mouseup. In some cases, you'll need a control separate to the view itself, as is the case with a zoom slider or a thumbnail sketch of the entire workspace.
Whatever the event mechanism, the upshot is that the browser script will sometimes receive notifications of a new desired region, and zoom level too if that's applicable. At that point, a Web Remoting call must occur, passing the server the details of the new region. Upon reply, the old data is replaced or shifted along, and the new data rendered.
This pattern is often very bandwidth-intensive, considering that the entire workspace can be massive and the interaction. For that reason, there are several important performance optimisations - see Related Patterns below for more details.
Decisions
How will you handle panning?
Panning provides some unique design challenges relative to jumping straight from one position to another. Unlike a complete jump, the user will expect a smooth transition from one position to another. Fortunately though, you already have most of the workspace loaded. Thus, a Browser-Side Cache is very important if you wish to achieve smooth scrolling. This way, you only need load the new portion of the workspace instead of the whole thing. This leads to a few more specific issues to consider:
- How much of the workspace will you cache? A standard caching trade-off between memory and speed.
- How will you track what's changed? Again, a good cache can abstract this away. At one level, the script will ask for the whole workspace, but the cache will decide which portion of that actually requires server content.
How will the view appear initially?
There has to be a default view position and zoom level within the overall workspace. Typical choices would be at the logical start or centre. Zoom level should usually be quite high, to let the user quickly drill down from the starting point.
How will you handle unpopulated regions?
Most of the time, users are navigating to areas which are partly or completely unpopulated, requiring a result from the server to render them. What do you show in place of this detail? A few options:
- Nothing.
- A Progress Indicator
- Some Embedded Text - perhaps just a single word - indicating what content will be placed there.
- A Guesstimate of the content, perhaps based on extrapolating neighbouring data held in the cache.
How will you handle changes to the existing view?
Sometimes, the workspace will change while the user is watching it. For example, a user might introduce a filter to a result set. A change to the workspace means means the view must change too. The easiest approach is to abandon the previous view location and revert to the default. However, if there if often a more logical solution. For example:
- You could keep the proportion the same as before - if the user was looking at rows one-third of the way down a table, then show the new rows that appear one-third of the way down the reduced table.
- You could fix on certain content - keep the top row in the same spot, if it still exists in the table, and show the remaining rows that are now directly below it. If the top row no longer exists, continue working downwards until one of the rows does exist.
Real-World Examples
Dart PowerWEB Zoom for ASP.NET
PowerWEB Zoom for ASP.NET is a unique server control for ASP.NET that provides panning and zooming for high-resolution images. Without utilizing plugins or ActiveX controls, PowerWEB Zoom allows a developer to simply drop the control on a page, assign an image and get cross-browser image scaling, rendering and on-demand delivery of content using Ajax. Other features include onscreen 'widgets' for in-place magnification, thumbnail view, zoom level selection and panning. Both client-side and server-side APIs are provided for advanced application integration. On 2006-08-18, Dart released a Free Version of PowerWEB Zoom for ASP.NET. This version includes most of the same features, but at no cost.
map.search.ch
map.search.ch is a Swiss Ajax map. Like Google Maps (which it pre-dates) and similar products, the map constitutes a huge Virtual Workspace and the user at any time is only viewing a tiny portion of it. The map can be panned by dragging the workspace, clicking on arrow icons just outside its boundaries, and pressing the arrow keys. Zooming is controlled by clicking on a a horizontal imagemap, clicking on Zoom In and Zoom Out buttons, or pressing Page Up and Page Down.
OpenRico Search Demo
The OpenRico Search Demo is a Cross-Domain Proxy, providing an Ajaxian interface to Yahoo! Search. It's philosophy was best summed up as "Death to Paging!". Instead of wading through a sequence of pages, you're presented with a single table containing all results. The results are a Virtual Workspace and the table is a view into that space. Each time you navigate within the table , new results are pulled down from the server.
Giant-Ass Image Viewer (GSV)
Michael Magurski's GSV is a framework that lets web developers show an arbitrary image, and allow the user to pan and zoom it. The homepage contains a working demo.
Dunstan Orchard's Blog
Dunstan Orchard's blog presents a slick panoramic, cartoonish, view from his home. When the mouse rolls anywhere on the image, a couple of transparent Popups appear on each side of the panorama. Rolling onto either of those, and keeping the mouse there, causes the banner to pan in that direction. Note that this is a weaker form of Virtual Workspace, since there's no use of XMLHttpRequest Call during scrolling.
Code Examples
OpenRico Search Demo
The openRico Search Demo is based on the OpenRico's LiveGrid API. In Query-Report Application, the code example shows how to use the API. This example covers some of the API internals, specifically regarding the inclusion of a Virtual Workspace. Note that openRico uses Prototype to allow for a more object-oriented coding style. Many of the details, as well as functionality such as changing sort order, aren't covered here.
In OpenRico, the user's view is a GridViewPort object,`which has a fixed row height and also tracks the view's starting position, i.e. the index that the top row of the GridViewPort corresponds to.
Rico.GridViewPort.prototype = {
initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
...
this.rowHeight = rowHeight;
this.div.style.height = this.rowHeight * visibleRows;
...
this.startPos = 0;
},
The results might contain thousands of virtual rows, but the table is only about 20 rows (as determined by visibleRows). How then, is the scrollbar created to make it appear as if there were thousands of rows? OpenRico creates a custom scrollbar. The trick is to create a 1-pixel wide div, whose height matches the height of the virtual workspace. The virtual height can be calculated, since the scrollbar has access to the visible table height (visibleHeight, the number of virtual rows (metaData.getTotalRows()), and the number of rows in the view (metaData.getPageSize()). The scrollbar div's height is set to this virtual height, so the browser will render a scrollbar that appears to scroll across the entire virtual table.
createScrollBar: function() {
var visibleHeight = this.liveGrid.viewPort.visibleHeight();
// create the outer div...
this.scrollerDiv = document.createElement("div");
...
// create the inner div...
this.heightDiv = document.createElement("div");
this.heightDiv.style.width = "1px";
this.heightDiv.style.height = parseInt(visibleHeight *
this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
this.scrollerDiv.appendChild(this.heightDiv);
this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
var table = this.liveGrid.table;
table.parentNode.parentNode.insertBefore(
this.scrollerDiv, table.parentNode.nextSibling);
},
Events on the scrollbar are dispatched to handleScroll. After any scrolling behaviour has occurred, this function calculates the portion of virtual space being viewed. The algorithm determines the new virtual row that should appear on top of the viewport. For instance, if the row height is 10 pixels and the user has scrolled to 50 pixels from the top, then the virtual row is 5; the viewport will need to be refreshed such that the new top row is the 5th virtual row.
handleScroll: function() {
...
var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
this.liveGrid.requestContentRefresh(contentOffset);
this.viewPort.scrollTo(this.scrollerDiv.scrollTop);
...
},
requestContentRefresh fetches the required content "buffer". The buffer is a form of Browser-Side Cache, retaining previous server queries and issuing new XMLHttpRequest Calls where the data isn't stored locally. With the buffer in place, you can smoothly scroll back to previously seen results without any call required.
fetchBuffer: function(offset) {
...
var bufferStartPos = this.buffer.getFetchOffset(offset);
this.processingRequest = new Rico.LiveGridRequest(offset);
this.processingRequest.bufferOffset = bufferStartPos;
...
callParms.push('id=' + this.tableId);
callParms.push('page_size=' + fetchSize);
callParms.push('offset=' + bufferStartPos);
...
ajaxEngine.sendRequest.apply( ajaxEngine, callParms );
...
},
Eventually, the viewport table is populated with the new set of rows.
refreshContents: function(startPos) {
...
for (var i=0; i < rows.length; i++) {//initialize what we have
this.populateRow(this.table.rows[i + contentOffset], rows[i]);
}
...
},
Alternatives
Related Patterns
Browser-Side Caching
Browser-side caching is very important here - if the user moves 1% down, you don't want to be downloading the entire view again.
Predictive Fetch
It's useful to [Predictive Fetch|predictively fetch] regions of the Virtual Workspace which the user is likely to navigate to next. Panning is a common task, so it's worthwhile caching the regions neighbouring the current view. You might also cache at the next and previous zoom levels.
Guesstimate
Sometimes, you might be able to quickly satisfy a navigation action with a Guesstimate, while waiting for the real data to return.
Multistage Download
If the response is large, break it into more than one part - download the most important information first, then follow up with more refined content. [map.search.ch map.
Drag-And-Drop
Drag-And-Drop is often used to let the user pan, by dragging the entire workspace across the view.
Slider
The zoom control is often a Slider.
Unique URLs
View should often have a Unique URLs associated with them. That way, a user can easily bookmark the view or mail it to a friend, so as to highlight a particular part of the workspace rather than the entire thing.
Visual Metaphor
Did you see The Truman Show? It's the story of a man who has no idea his whole world is fake, completely architected for the purposes of a reality TV show. When he walks around town, props are adjusted in anticipation and all the actors directed a few seconds ahead of his arrival. Thus, his impression at any point of time is a normal reality, while everything outside of his direct perception is essentially a product of his own imagination. The movie is an exploration of the philosophical notion of solipsism, itself a nice metaphor for this pattern.
Want to Know More?
Death to Paging Bill Scott's LiveGrid Announcement.
Time your website with
WebWait - from the creator of AjaxPatterns.org
