15.9. Virtual WorkspaceCamera, Desktop, Illusion, Infinite, InfiniteScrollbar, Lens, Move, Pan, Portal, Scroll, Solipsism, Viewport, Virtual, Visible, Window, Zoom Figure 15-23. Virtual Workspace15.9.1. Goal StoryBill is using a meta-search engine to receive insurance quotes. The browser can't retrieve all 500 results at once, but Bill sees a table containing what looks like all the results. He can scroll up and down as if it were a regular table, and the newly appearing rows are populated on demand. 15.9.2. ProblemHow can the user navigate through a large workspace? 15.9.3. Forces
15.9.4. SolutionProvide 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, but 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. He can pan across, jump to a different region, and zoom in and out. Each of these actions requires a view change, so the browser transparently fetches the data for the new portion of the workspace and renders it accordingly. Here are some examples of navigable workspaces:
Users move through the space in different ways. Often they use a combination of the tools described next.
There are also different zooming techniques:
Depending on the mechanisms you're supporting, you'll need to add different types of event handlers. So, for keyboard shortcuts, watch for events like keydown; for dragging and selecting a region, watch for mousedown, mousemove, and mouseup. In some cases, you'll need a control separate from 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 that there is a new desired region or zoom level, if that's applicable. At that point, a Web Remoting (Chapter 6) call must occur, passing the server the details of the new region. Upon reply, the old data is either replaced or shifted along and the new data is rendered. This pattern is often very bandwidth-intensive, considering that the entire workspace can be massive and the interaction complex. For that reason, there are several important performance optimizationsfor more details, see "Related Patterns" later in this chapter. 15.9.5. Decisions15.9.5.1. How will you handle panning?Panning, as opposed to jumping straight from one position to another, provides some unique design challenges. 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 (Chapter 13) is very important if you wish to achieve smooth scrolling. With this, you need only to load the new portion of the workspace instead of the whole thing. This leads to a few more specific questions:
15.9.5.2. How will the view appear initially?There has to be a default view position and zoom level within the overall workspace. A typical choice for the view is at the logical start or center. Zoom level should usually be quite high to let the user quickly drill down from the starting point. 15.9.5.3. What do you display while a region is being repopulated?Most of the time, users are navigating to areas that are partly or completely unpopulated, requiring a result from the server to render them. What do you show in the interim? Here are a few options:
15.9.5.4. How will you handle changes to the existing view?Sometimes the workspace changes while the user is watching it. For example, a user might introduce a filter to a result set. A change to the workspace means the view must change too. The easiest approach is to abandon the previous view location and revert to the default. However, there is often a more logical solution. Following are some examples:
15.9.6. Real-World Examples15.9.6.1. map.search.chhttp://map.search.ch is a Swiss Ajax map. Like Google Maps (http://maps.google.com) (which it predates) and similar products, the map constitutes a huge Virtual Workspace, and the user only views a tiny portion of it at any point in time. 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 horizontal imagemap, clicking on Zoom In and Zoom Out buttons, or pressing Page Up and Page Down. 15.9.6.2. OpenRico Search DemoThe OpenRico Search Demo (http://openrico.org/rico/yahooSearch.page) is a Cross-Domain Proxy (Chapter 10) providing an Ajax interface to Yahoo! Search (Figure 15-24). Its philosophy was best summed up by OpenRico developer Bill Scott as "Death to Paging!" (http://looksgoodworkswell.blogspot.com/2005/06/death-to-paging-rico-livegrid-released.html). 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. Figure 15-24. OpenRico Search Demo15.9.6.3. Giant-Ass Image Viewer (GSV) libraryMichael Magurski's GSV (http://mike.teczno.com/giant/pan/) is a library that lets web developers show an image of any size and allow the user to pan and zoom within it. The homepage contains a working demo. 15.9.6.4. Dunstan Orchard's blogDunstan Orchard's blog (http://www.1976design.com/blog/colophon) presents a slick panoramic, cartoonish view from his home. When the mouse rolls anywhere on the image, a couple of transparent Popups (see earlier in this chapter) 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 different form of Virtual Workspace, since the whole workspace is local. 15.9.7. Code Refactoring: AjaxPatterns OpenRico SearchThe OpenRico Search Demo is based on the OpenRico's LiveGrid API. In Data Grid (Chapter 14), 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 (http://prototype.conio.net/) to allow for a more object-oriented coding style. 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 include thousands of virtual rows, but the table itself 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 behavior 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 fifth 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 into a Browser-Side Cache known as Buffer. 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]); } ... }, 15.9.8. Alternatives15.9.8.1. Virtual Magnifying GlassInstead of showing a partial view, you could present the entire thing in low detail and offer a "Virtual Magnifying Glass" or "Virtual Fish-Eye Lens" to zoom in on the detail. This might sound like it could only be applied to an image, but it is also applicable to tables and other interfaces. 15.9.9. Related Patterns15.9.9.1. Browser-Side CacheBrowser-Side Cache (Chapter 13) is very important with respect to the Virtual Workspace if the user moves 1 percent down, you don't want to download the entire view again. 15.9.9.2. Predictive FetchIt's useful to perform a Predictive Fetch (Chapter 13) on regions of the Virtual Workspace that the user is likely to navigate to next. Panning is a common task, so it's worthwhile caching the regions neighboring the current view. You might also cache at the next and previous zoom levels. 15.9.9.3. GuesstimateSometimes you might be able to quickly satisfy a navigation action with a Guesstimate (Chapter 13) while waiting for the real data to return. 15.9.9.4. Multi-Stage DownloadIf the response is large, break it into more than one part; download the most important information first, then follow up with more refined content. 15.9.9.5. Drag-And-DropDrag-And-Drop (see earlier) is often used to let the user pan by enabling her to drag the entire workspace across the view. 15.9.9.6. SliderThe zoom control is often a Slider (Chapter 14). 15.9.9.7. Unique URLsViews need to be associated with Unique URLs (Chapter 17) so that the user can highlight a particular part of the workspace rather than the entire thing. That way, the user can easily bookmark the view or mail it to a friend. 15.9.10. MetaphorDid you see The Truman Show (http://www.imdb.com/title/tt0120382/)? 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. As he walks around town, props are adjusted and the "townspeople" are directed a few seconds ahead of his arrival. There's a similar type of perception management going in Virtual Workspace; the illusion is that there's a whole world of content beyond the view, but the reality is that it's all constructed on demand. 15.9.11. Want to Know More?Death to PagingBill Scott's LiveGrid Announcement (http://looksgoodworkswell.blogspot.com/2005/06/death-to-paging-rico-livegrid-released.html). 15.9.12. AcknowledgmentsThanks to Bill Scott for pointing out this example and keeping me updated on the progress of OpenRico and the LiveGrid functionality. Bill and fellow OpenRico developer Richard Cowin helped explain some of the implementation. |