Section 17.6. Unique URLs


17.6. Unique URLs

Address, Bookmarks, Cut-And-Paste, Distinct, Favorites, Hyperlink, Location, Link, Mail, Share, Unique, URL

Figure 17-18. Unique URLs


17.6.1. Goal Story

Sasha is exploring a recent news event by browsing a map of the area. There's a Unique URL for each location, so she's able to link to it in her blog and add it to her social bookmarking account.

17.6.2. Problem

How can you assign distinct URLs to different parts of your application?

17.6.3. Forces

  • Expressive URLs are vital. The ability to link from one site to another is arguably what made the Web successful. Furthermore, web surfers like to bookmark pages for later on. And these bookmarks are now a social phenomenon, with the advent of "linkblogs" and social bookmarking software like del.icio.us (http://del.icio.us).

  • Browsers retain URLs in their histories, and the Back button will only work if URLs vary. The Back button is a big habit for many users. It can mean "Go back to where I was a minute ago." For many users, it can also mean "Undo my last action," and that mental model must be respected even though some experts deem it to be technically incorrect.

  • A single address for your whole site is not enough; you need to support deep linking for links to be effective. That is, each distinct component of a web site should have its own URL.

  • In conventional web sites, the URL changes when the page is refreshede.g., when the user submits a form or follows a link. But in Ajax Apps, the page never refreshes, so the browser won't change the URL.

17.6.4. Solution

Provide Unique URLs for significant application states. Each time a significant state change occurse.g., the user opens a new product in an e-commerce systemthe application's URL changes accordingly. The objective is straightforward, but as discussed later in this section, the implementation requires a few unusual tricks. This Solution is fairly complex, because it delves into the details of building Unique URLs. Increasingly, though, reusable libraries will save you the effort, so be sure to check out what's available before directly applying any of the techniques here. The Real-World Examples identifies a couple of resources, and there will doubtless be others by the time you read this.

In Ajax, most server communication occurs with XMLHttpRequest. Since XMLHttpRequest Calls don't affect the page URL, the URL is, by default, permanently stuck on the same URL that was used to launch the Ajax App, no matter how many transfers occur. The only option you have to manipulate the URL is to use JavaScript. And, in fact, it's very easy to do that with the window.location.href property:

   window.location.href = newURL; // Caution - Read on before using this. 

Very easy, but there's one big problem: under normal circumstances, the browser will automatically clear the page and load the new URL, which sort of defeats the purpose of using Ajax in the first place. Fortunately, there's a cunning workaround you can use. It was originally conceived in the Flash world and has more recently been translated to an Ajax context. Mike Stenhouse's demo and article (http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps) provides a good summary of the approach, and the Solution here is based heavily on his work.

The cunning workaround relies on fragment identifiers those optional components of URLs that follow the hash character (#). In http://ajaxpatterns.org/Main_Page#Background, for example, the fragment identifier is Background. The point of fragment identifiers has traditionally been to get browsers scrolling to different points on a page, which is why they are sometimes called "in-page links" or "named links." If you're looking at the Table of Contents on AjaxPatterns.org (http://ajaxpatterns.org#toc) and you click on the Background linkhttp://ajaxpatterns.org/#Backgroundthe address bar will update and the browser will scroll to the Background section. Here's the point: no reload actually occurs. And yet, the browser behaves as if you clicked on a standard link: the URL in the address bar changes and the Back button sends you back from whence you came (at least for some browsers; more on portability later).

You can probably see where this is going. We want Ajax Apps to change the page URL without forcing a reload, and it turns out that changes to fragment identifiers do exactly that. The solution, then, is for our script to change only the fragment component. We could do this by munging the window.location.href property, but there's actually a more direct route: window.location.hash. So when a state change is significant enough, just adjust the URL property:

   window.location.hash = summary; 

Throughout this pattern, summary refers to a string representation of the current browser stateat least that portion of the state that's worth keeping. For example, imagine that you have an Ajax interface containing several tabs: Books, Songs, and Movies. You want to assign a Unique URL to each tab, so the summary will be the name of any of these tabs. If there were other significant variables, they would be integrated into the summary string. To keep the fragment synchronized with the current state, you need to identify when state changes occur and update the fragment accordingly. In the following tab example, you can do this by making the onclick handler set the hash property in addition to changing the actual content:

   songTab.onclick = function( ) {     window.location.hash = "songs".     // ... now open up the tab   } 

So now the URL changes from http://ajax.shop/ to http://ajax.shop/#songs. That was easy enough, but what happens when you mail the URL to a friend? That friend's browser will open up http://ajax.shop and assume songs is an innocent, old-fashioned, in-page link. It will try to scroll to a songs element and won't find one, but it won't complain about it either; it will simply show the main page as before. We've made the URL change after a state change, but so far we haven't built in any logic to make the state change after a URL change.

State and URL need to be in sync. If one changes, the other must change as well. Effectively, there's a mini-protocol specific to your application that defines how state and URL map to each other. In the preceding example, the protocol says "The fragment always represents the currently open tab." Setting the fragment is one side of the protocol; the other side is reading it. After your friend opens http://ajax.shop/#songs, and after his browser unsuccessfully tries to scroll to the nonexistent songs element, we need to read the fragment and update the interface accordingly:

   window.onload = function( ) {     initializeStateFromURL( );   }   initializeStateFromURL( ) {     var initialTab = window.location.hash;     openTab(initialTab);   } 

Good. Now you can mail the link, bookmark it, and link to it from another page. In some browsers at least, it will also go into your history, which means the Back button will work as expected. For a simple implementation, that's good enough.

However, the solution still isn't ideal, because what will happen if the application is already loaded? The most likely situations occur when clicking the Back button to revert to a previous state and when retrieving a bookmark while already on that application. In both cases, the URL will change in the address bar, but the application will do absolutely nothing!

Remember, we're making the application manipulate fragments precisely because the browser doesn't reload the page when they change. When it comes to Back buttons and bookmarks and the like, we're talking about changes that are initiated by the user, yet the browser treats these with exactly the same inaction as when our script initiates them. So our onload won't be called and the application won't be affected in anyway.

To make Unique URLs work properly, we have to resort to something of a cheap trick: continuously polling the fragment property. Whenever the fragment happens to change, the browser script will notice a short time later and update state accordingly. In this example, we could schedule a check every second using setInterval( ):

   window.onload = function( ) {     initializeStateFromURL( );     setInterval(initializeStateFromURL, 1000);   } 

In fact, we should only perform an action if the hash has recently changed, so we perform a check in initializeStateFromURL( ) to see if the hash has recently changed:

   var recentHash = "";   function pollHash( ) {     if (window.location.hash==recentHash) {       return; // Nothing has changed since last polled.     }     recentHash = window.location.hash;     // URL has changed. Update the UI accordingly.     openTab(initialTab);   } 

The polling adds to performance overhead, but we now have a URL setup that feels like the real thing.

One last thing on this technique. I mentioned earlier that you can set window.location.hash manually after state changes. Now that we've considered the polling technique, you can see that it's also possible to do the reverse: change the URL in response to user events, then let the polling mechanism pick it up and change state accordingly. That adds a delay, but one big benefit is that you can easily add a hyperlink on the page to affect the system state.

Here's a quick summary of the technique just described, which I'll call the "page-URL" technique:

  1. Ensure that window.location.hash always reflects application state.

  2. Create a function, e.g., initializeStateFromURL( ), to set application state from window.location.hash.

  3. Run initializeStateFromURL on startup and at periodic intervals.

But wait, there's more! I mentioned earlier that each URL goes into history, but only "for some browsers." IE is one of the outliers here, so the above technique will fail IE's history mechanism, and the Back button won't work as expected. The good news is there's a workaround for it. The bad news is that it's a different technique and won't work properly on Firefox. So if you can live with everything working but IE history, go with the hash-rewrite technique. Otherwise, read on . . . .

The IE-specific solution relies on IFrames and on their source URLs, so I'll call it the "IFrame-URL" technique. While IE won't consider a fragment change historically significant, it will recognize changes to the source URL of an embedded IFrame. Each time you change the source URL of an IFrame, the entire page will be added to the history. Think of each page in the history as not just the main URL but the combination of main URL and URLs of any IFrames. Its the combination that must vary in order to create a new history entry.

So to get IE history working, we can embed a dummy IFrame and keep changing its source URL to reflect the current state. When the user clicks Back, the source will change to its previous state. In theory, the browser could keep polling the IFrame's source URL to find out when it's updated, but, for some reason, reverted IFrames don't seem to provide the correct source URL. So a workaround is to make the actual IFrame body contain the summary of browser state, i.e., the string which was in the fragment identifier for the previous algorithm. Then, the polling mechanism looks at the IFrame body rather than its source URL.

For example, if the user clicks on the Songs tab, the IFrame source will change to dummy.phtml?summary=Songs. The body of dummy.phtml will be made (by the server) to contain this argument verbatim, i.e., the body of the IFrame will be "Songs." When the user later clicks somewhere else and then clicks the Back button, the browser will reset the IFrame's URL to dummy.phtml?summary=Songs and the body will once again be "Songs." The polling mechanism will soon detect the change and open the Songs tab.

Now we have history, but we haven't done anything about the URLs. So when the user clicks on the Songs tab, we not only change the IFrame source but also the main document URL using a fragment identifier like before. We also have to make sure that the polling mechanism looks at the URL as well as the IFrame source. If either has changed since last poll, the application state must be updated. In addition, the one that hasn't changed must also be updated, i.e., if the IFrame source has changed, the URL must be updated, and vice versa.

Here's a quick summary of the IFrame-URL technique:

  • Embed an IFrame in your document.

  • On each significant state change, update the IFrame's source URL using a query string (dummy.phtml?summary=Songs), and update the main page URL using a fragment identifier (http://ajax.shop/#Songs).

  • Continuously poll the IFrame source URL and the main page URL. If either has changed, update the other one and update the application state according to the new value.

Note that this won't work on Firefox, because Firefox will include URL changes as well as IFrame changes in the history. Thus, each state change will actually add two entries to Firefox's history. As mentioned earlier, you'll probably need to implement both algorithms in order to fully support both browsers. A modified version of the second technique will do it as well. Also, be careful when using both approaches because there's a risk your polling mechanism will revert states too eagerly. If the user has begun changing a value and the polling mechanism kicks in, it's possible that the value will change back. If you separate input and output carefully enough, that can be avoided. A workaround is to suspend the polling mechanism at certain times. None of this is ideal, but a Unique URL mechanism is worth fighting for.

As a variant on changing the IFrame's source, Brad Neubergbased on a hack by Danny Goodmanhas pointed out that browsers will retain form field data (http://codinginparadise.org/weblog/2005/08/ajax-tutorial-saving-session-across.html). Instead of changing the IFrame source, you set the value of a hidden text field in the IFrame. That will solve the history problem, but you'll still need to do something about the main page URL to give the page a Unique URL.

To summarize all of this, here are all the things we'd want from Unique URLs and an explanation of how each can be achieved in Ajax.


Bookmarkable, linkable, and type-in-able

window.location.hash makes the URL unique, so a user can bookmark it, link to it, and type it directly into her browser. window.onload ensures that the bookmark is used to set state. Polling or IFrame ensures that this will work even when the base URL (before the hash) is the same.


History and Back button

window.location.hash will change, and polling ithis property will let you detect changes to the URL in most browers. A hidden IFrame is also required for portability; it can be polled too.


Back to Ajax App

After leaving an Ajax App, the user should be able to click the Back button and return to the final state just prior to signing off. The techniques in Heartbeat will support this behavior.

URL handling is one of the greatest complaints about Ajax, with myths abounding about how "Ajax breaks the Back button" and how "You can't bookmark Ajax Apps." That an Ajax App can include full page refreshes is enough to debunk these myths. But this pattern shows that even without page refreshes, Unique URLs are still do-able, even if somewhat complicated. This pattern identifies some of the current thinking on Unique URLs, but the problem has not yet been solved in a satisfactory manner. The best advice is to watch for new ideas and delegate to a good library where applicable.

17.6.5. Decisions

17.6.5.1. How will you support search engine indexing?

Search engines point "robot" scripts to a web site and have them accumulate a collection of pages. The robot works by scooting through the web site, finding standard links to standard URLs, and following them. It won't click on buttons or type in values as a user would, and it probably won't distinguish among fragment identifiers, either. So if it sees links to http://ajax.shop/#Songs and http://ajax.shop/#Movies, it will follow one or the other, but not both. That's a big problem, because it means an entire Ajax App will only have one search link associated with it, and you'll miss out on a lot of potential visits.

The simplest approach is to live with a single page and do whatever you can with the initial HTML. Ensure that it contains all of the info required for indexing, focusing on meta tags, headings, and initial content.

A more sophisticated technique is to provide a Site Map page that is linked from the main page and that links to all URLs you want indexed with the link text containing suitable descriptions. There is one catch here: you can't link to URLs with fragment identifiers, so you'll need to come up with a way to present search engines with standard URLs even though your application would normally present these using fragment identifiers. For example, have the Site Map link to http://ajax.shop/Movies and configure your server to redirect to http://ajax.shop/#Movies. It's probably reasonable to explicitly check if a robot is performing the search, and if it is, to preserve the URLi.e., when the robot requests http://ajax.shop/Movies, simply output the same contents as the user would see on http://ajax.shop/#Movies. Thus, the search engine will index http://ajax.shop/Movies with the correct content, and when the user clicks on a search result, the server will know (because the client is not a robot) to redirect to http://ajax.shop/#Movies.

Search engine strategies for Ajax Apps has been discussed in a http://www.backbase.com/#dev/tech/001_designing_rias_for_sea.xml: detailed paper by Jeremy Hartlet of Backbase. See that paper for more details, though note that some advice is Backbase-specific.

17.6.5.2. What will be the polling interval?

The solutions here involve polling either the main page URL, the IFrame URL, or both. What sort of polling interval is appropriate? Anything more than a few hundred milliseconds will cause a noticeable delay (though that's not a showstopper because users are used to delays in loading pages). From a responsiveness perspective, the delay should ideally be as short as possible, but there are two forces at work that will lengthen it:


Performance overhead

Continuous polling will have an impact on overall application performance, so consider 10 milliseconds the bare minimum. Somewhere between 10 and 100 milliseconds is probably the best trade-off.


Overeager synchronization

This is only a problem for the IFrame-URL approach and not the Page-URL approach. The IFrame-URL approach, as described in the Solution above, keeps Page URL, IFrame URL, and application state synchronized. Thus, there's the possibility that you will begin to change one of those things and it will "snap back" during a synchronization checkpoint. For example, you might begin changing application state and suddenly see it "snap back" because it's different than URL state. There are various pragmatic ways to avoid this sort of thing, but as long as you have to rely on polling, the problem might still arise for certain combinations of events. While far from ideal, a longer polling period will at least diminish the problem.

17.6.5.3. What state differences warrant Unique URLs?

In this pattern, I discuss "state changes" abstractly. When I say a "state change" should result in a new URL and a new entry in the browser's history, what kind of state change am I discussing? In an Ajax App, some changes will be significant enough to warrant a new URL, and some won't. Here are some guidelines:

  • User preferences shouldn't change the URLe.g., if a user opts for a yellow background, it shouldn't appear in the URL.

  • When there's a particular thing the user is looking atfor example, a product in an e-commerce catalogue, or a general categorythat thing is a logical candidate for a Unique URL.

  • When it's possible to open several things at once, you'll probably need to capture the entire collection.

17.6.5.4. What will the URLs look like?

Unique URLs requires you to make like an information architect and do some URL design work. Possibly, you'll be controlling only the fragment identifier rather than the entire URL, but even the fragment identifier has usability implications. Here are some guidelines:

  • Users sometimes hack URLs, which suggests that, in some cases, you might want to make the variable explicit. So instead of /#Movies, consider #category=Movies if you want to make the URLs more hack-friendly. Also, this format is more like a CGI-style URL, which may be more familiar to users. The downside is that you'll need a little more coding to parse such URLs.

  • As mentioned earlier, search engine indexing is also a consideration, and the fragment identifier will sometimes be mapped to a search engine-friendly URL. Therefore, ensure that the fragment identifier is as descriptive as possible in order to attract searchers. For example, favor a product name over a product ID, since the name is what users are likely to search for.

17.6.6. Real-World Examples

17.6.6.1. PairStairs

Nat Pryce's PairStairs (http://nat.truemesh.com/stairmaster.html) is an Extreme Programming tool that accepts a list of programmer initials and outputs a corresponding matrix (Figure 17-19). The URL stays synchronized with the initials that are being enterede.g., if you enter "ap ek kc mm," the URL will update so that it ends with #ap_ek_kc_mm.

Figure 17-19. PairStairs


17.6.6.2. Dojo binding library

Dojo Toolkit's dojo.io.bind (http://dojotoolkit.org/docs/intro_to_dojo_io.html) lets you specify (or autogenerate) a fragment-based URL as part of a web remoting call. After the response arrives, the application's URL will change accordingly.

17.6.6.3. Really Simple History library

Really Simple History (http://codinginparadise.org/weblog/2005_09_20_archive.html) is a framework that lets you explicitly manage browser history (Figure 17-20). A call to dhtmlHistory.add(fragment, state) will set the URL's fragment identifier to fragment and set a state variable to state. A callback function can be registered to determine when the history has changed (e.g., the Back button was pressed), and it will receive the corresponding location and state. The library has recently been incorporated into the Dojo project.

Figure 17-20. Really simple history demo


17.6.7. Code Refactoring: AjaxPatterns Unique URL Sum

This example refactors the Basic Sum Demo (http://ajaxify.com/run/sum) using both the Page-URL and IFrame-URL techniques. There are actually four implementations here:

  • Unique URL Sum Demo (http://ajaxify.com/run/sum/uniqueURL) uses the Page-URL technique so that when state changes, the URL will change.

  • Polling URL Sum Demo (http://ajaxify.com/run/sum/uniqueURL/pollURL) enhances the Unique URL Sum Demo by adding a polling mechanism, so that when the URL changes, the state will also change. Thus, the URL-state synchronization occurs in both directions.

  • IFrame URL Sum Demo (http://ajaxify.com/run/sum/uniqueURL/iFrame) alters the Unique URL Sum Demo to use the IFrame-URL technique. In this version, the history will work correctly because the IFrame source is changed, but the main page URL is not actually updated.

  • Full IFrame URL Sum Demo (http://ajaxify.com/run/sum/uniqueURL/iFrame/updateURL/) completes the IFrame-URL implementation by making the main page URL unique.

17.6.7.1. Unique URL Sum Demo

This first refactoring changes the fragment identifier (hash) when a sum is submitted. The fragment identifier always contains the main page state, which, in this case, is a comma-separated list of the three sum figures (e.g., #3,5,10):

   function submitSum( ) {     definedFigures = {       figure1: $("figure1").value,       figure2: $("figure2").value,       figure3: $("figure3").value     }     hash =     "#" + definedFigures.figure1 + "," + definedFigures.figure2               + "," +definedFigures.figure3;     window.location.hash = hash;     ajaxCaller.get("sum.phtml", definedFigures, onSumResponse, false, null);   } 

This is only useful if the startup routine actually takes the fragment identifier into account. Thus, a new function, setInitialFigures( ), is called on initialization. It inspects the fragment identifier, sets the parameters, and calls submitSum( ) to update the sum result:

   function setInitialFigures( ) {     figuresRE = /#([-]*[0-9]*),([-]*[0-9]*),([-]*[0-9]*)/;     figuresSpec = window.location.hash;     if (!figuresRE.test(figuresSpec)) {       return; // ignore url if invalid     }     $("figure1").value = figuresSpec.replace(figuresRE, "$1");     $("figure2").value = figuresSpec.replace(figuresRE, "$2");     $("figure3").value = figuresSpec.replace(figuresRE, "$3");     submitSum( );   } 

17.6.7.2. Polling URL Sum Demo

The previous version will update the URL upon state change, but not vice versa. This demo rectifies the problem by polling the URL and updating state if the URL changes. The functionality for updating state from URL is already present in the setInitialFigures( ) function of the previous version. We simply need to keep running it instead of just calling it on startup. So, setInitialFigures( ) has been renamed to pollHash( ) and is run periodically:

   window.onload = function( ) {     ...     setInterval(pollHash, 1000);   } 

The function is the same as before, but with just one change: a recentHash variable is maintained to ensure that we only change state if the hash has actually changed. We don't want to change state more times than are necessary:

   var recentHash = "";   function pollHash( ) {     if (window.location.hash==recentHash) {       return; // Nothing has changed since last polled.     }     recentHash = window.location.hash;     // ... Same code as in previous setInitialFigures( ) function ...   } 

17.6.7.3. IFrame Sum Demo

The previous versions won't work on IE because a fragment identifier change is not significant enough to get into IE's history. So here, we'll introduce an IFrame and change its source URL whenever the document changes.

The initial HTML includes an IFrame. This is the IFrame whose source URL we'll manipulate in order to add entries to browser history. It's backed by a PHP file that simply mimics the summary argument; the body of the IFrame always matches the source URL, so we can find the summary by inspecting the body. In fact, we should be able to just inspect the source URL directly, but there seems to be some bug in IE that fails to update the source property when you go back to a previous IFrame (even though it actually fetches the content according to that URL). Incidentally, the IFrame would normally be hidden via CSS but is kept visible for demonstration purposes.

   <iframe id='iFrame' name='iFrame' src='/books/2/755/1/html/2/summary.phtml?summary='></iframe> 

Each time a sum is submitted, the IFrame's source is updated:

   function submitSum( ) {     ...     $("iFrame").src = "summary.phtml?summary=" + summary;   } 

Instead of polling the URL, we're now polling the IFrame's body, which contains the state summary. Remember that in this version, the main page URL is fixedwe're only making IE history work, and not yet making the application bookmarkable. Whenever we notice that the IFrame source has been changed, we assume that it's because the Back button has been clicked. Thus, we pull out the summary from the IFrame body and adjust the application state accordingly:

   window.onload = function( ) {     ...     setInterval(pollSummary, 1000);   }   function pollSummary( ) {     summary = window["iFrame"].document.body.innerHTML;     if (summary==recentSummary) {       return; // Nothing has changed since last polled.     }     recentSummary = summary;     // Set figures according to summary string and call submitSum( )     // to set the result ...   } 

The Back button will now work as expected on both Firefox and IE.

17.6.7.4. Full IFrame Sum Demo

This final version builds on the previous version by including Unique URLs for the main page so that you can bookmark a particular combination of figures. Note that this is an "IFrame URL" demo and won't work properly on Firefox for reasons explained in the preceding Solution.

The page contains an IFrame as before, and since we're now synchronizing the URL as well, there are actually three things to keep in sync:

  • The application state, i.e., the three sum figures

  • The page URL

  • The IFrame URL

The algorithm is this: when one of these three changes, change the other two and update the sum result. To facilitate this, some convenience functions exist to read and write these properties:

   function getURLSummary( ) {     var url = window.location.href;     return URL_RE.test(url) ? url.replace(/.*#(.*)/, "$1") : "";   }   function getIFrameSummary( ) {     return util.trim(window["iFrame"].document.body.innerHTML);   }   function getInputsSummary( ) {     return $("figure1").value +","+ $("figure2").value +","+ $("figure3").value;   }   function setURL(summaryString) {     window.location.hash = "#" + summaryString;     document.title = "Unique URL Sum: " + summaryString;   }   function setIFrame(summaryString) {     $("iFrame").src = "summary.phtml?summary=" + summaryString;   } 

The updateSumResult( ) is also straightforward:

 function updateSumResult( ) {   definedFigures = {     figure1: $("figure1").value,     figure2: $("figure2").value,     figure3: $("figure3").value   }   ajaxCaller.get("sum.phtml", definedFigures, onSumResponse, false, null); } function onSumResponse(text, headers, callingContext) {   self.$("sum").innerHTML = text; } 

Now for the actual synchronization logic. The first thing that might change is the application state itself. This occurs when the Submit button is clicked and will cause the IFrame and page URL to update:

   window.onload = function( ) {     self.$("addButton").onclick = function( ) {       onSubmitted( );     }     ...   }   function onSubmitted( ) {     var inputsSummary = getInputsSummary( );     setIFrame(inputsSummary);     setURL(inputsSummary);     updateSumResult( );   } 

The other two things that can change are the IFrame source (e.g., if the Back button is clicked) or the URL (e.g., if a bookmark is selected). There's no way to notice these directly, so, as before, we poll for them. A single polling function suffices for both:

   window.onload = function( ) {     ...     pollTimer = setInterval(pollSummary, POLL_INTERVAL);   }   function pollSummary( ) {     var iframeSummary = getIFrameSummary( );     var urlSummary = getURLSummary( );     var inputsSummary = getInputsSummary( );     if (urlSummary != inputsSummary) { // URL changed, e.g., bookmark       setInputs(urlSummary);       setIFrame(urlSummary);       updateSumResult( );     } else if (iframeSummary != inputsSummary) { //IFrame changed,e.g., Back button       setInputs(iframeSummary);       setURL(iframeSummary);       updateSumResult( );     }   } 

One final point: The timer is suspended while the user edits a field in order to prevent the URL or IFrame source from reverting the application state:[*]

[*] It's probably feasible to do something more sophisticated by tracking the history of URL, IFrame, and application state independently.

   window.onload = function( ) {     ...     // Prevent iframe from triggering URL change during editing. The URL     // change would in turn trigger changing of the values currently under edit,     // which is why it needs to be stopped.     for (var i=1; i<=3; i++) {       $("figure"+i).onfocus = function( ) {         clearInterval(pollTimer);       }       $("figure"+i).onblur  = function( ) {         pollTimer = setInterval(pollSummary, POLL_INTERVAL);       }     }     ...   } 

The application is now bookmarkable and has a working history. It's still not ideal, because if you unfocus an edit field, the figures will revert to a previous state. Also, there seems to be a browser issue (in both IE and Firefox), which means that the document URL isn't updated after you manually edit it. Thus, you can change the URL using a bookmark, but if you change it manually, it will not change again in response to an update of application state.

17.6.8. Alternatives

17.6.8.1. Occasional refresh

Some Ajax Apps perform page refreshes when a major state change occurs. For example, A9 (http://a9.com) uses a lot of Display Morphing (Chapter 5) and Web Remoting (Chapter 6) but nevertheless performs a standard submission for searches, leading to clean URLs such as http://a9.com/ajax. Usually, that's enough granularity, so no special URL manipulation needs to occur.

17.6.8.2. "Here" link

Some early Ajax offerings, like Google Maps and MSN Earth, keep the same URL throughout but offer a dynamic link within the page in case the user needs it. In some cases, there's a standard link to "the current page"; in other cases, the link is generated on demand. Various text is used, such as "Bookmark this Page" or "Link to this page." It's very easy to implement this but, unfortunately, it breaks the URL model that users are familiar with; thus users might not be able to use it. Time will tell whether users will adapt to this new style or whether it will fade away as Ajax developers learn how to deal with Unique URLs.

17.6.9. Want to Know More?

  • "Uniform Resource Identifier (URI) Generic Syntax," by Tim Berners-Lee, Roy Fielding, and Larry Masinter for the Internet Engineering Task Force (http://www.gbiv.com/protocols/uri/rfc/rfc3986.html)

  • "Designing RIAs For Search Engine Accessibility," by Jeremy Hartley of Backbase (http://www.backbase.com/#dev/tech/001_designing_rias_for_sea.xml)

  • "Bookmarks and the Back Button in AJAX Applications," by Laurens Holst of Backbase (http://www.backbase.com/#dev/tech/002_bookmarks.xml)

  • "Ajax History Libraries," by Brad Neuberg (http://codinginparadise.org/weblog/2005_09_20_archive.html)

  • "Fixing the Back Button and Enabling Bookmarking for Ajax Apps," by Mike Stenhouse (http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps)

17.6.10. Acknowledgments

The Solution is based heavily on Mike Stenhouse's article (http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps).




Ajax Design Patterns
Ajax Design Patterns
ISBN: 0596101805
EAN: 2147483647
Year: 2007
Pages: 169

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net