Archive for the ‘work’ Category

Our Back Button Story

Wednesday, May 9th, 2007

Ah, the back button. That thing users want and don’t understand why it isn’t so simple anymore. Our project has a user story for the back button. It described a specific set of scenarios and what the users expectations were for when the back button was clicked. Suprisingly enough, it was easily one of the biggest stories for us in terms of difficulty and time to implement. I just wanted to talk through our path to making this story work. The back button is a fun, difficult challenge to be sure ;)

For the impatient, I’ll lay it out for you right up front. Put simply, the approach we were able to take to tackle the back button was as follows:

  • Have a client-side model that represents the state of the app (the user’s search criteria in our case)
  • Serialize that model (JSON) and post it to an iframe in response to user actions (actually, ours had a 300ms dirty-check interval that would post to the iframe).
  • Have the content that is loaded in the iframe notify (call a function) on the parent window passing it the result and the model used to get that result (basically just echo back the serialized model alongside the results).

And the longer-winded walk-through:

The site is for a real estate company. It allows users to find listings based on the typical (and some custom) criteria they specify via some widgets on the page. As they make their selections, the results are ajax-erifically loaded for them in the results area. They can page through the results and click on a listing to see further detail. The detail page is a full page load with a restful url.

From what I’ve described so far, the back button should behave as follows:

  • If the user is viewing page 3 of the results, then goes to page 4, the back button should take them back to page 3 of the results.
  • If the user is on a detail page, the back button should take them back to the results (and it should be on the correct page if the user was paging).

Sounds simple enough right? In Web1.0, this is no big deal. The user changes search criteria and clicks ’submit’, server answers with the results and pre-populates the page with the users’ criteria. One full-page load later, the page has everything it needs wired right into its very source code. User adds more criteria, clicks submit and voila, another page load and another full page that has all the data it needs. Click the back button and that first page returns…still wired up with the results AND the criteria for that page…as though the last page load never even happened.

Web2.0(-ish, there really isn’t a social aspect to this app, but its more lively that the typical sites) we want to stay on the same page…just load the results portion. This makes the user experience more fluid as the page reloads tend to be more jarring. It should also be more performant as there is less to render (fewer database queries) and ultimately less data to transfer.

The first task to handle is actually getting some results via ajax, and this is fairly simple. The user makes their selections and an ajax call is made to get the matching results. The results are then plopped into a div on the page when they return. The server code is pretty straight forward, simpler than 1.0 even, because it just has to answer the specific question and render only the results. No need to re-populate the user’s criteria. Now, how to handle the back button?

As described, the back button will do what it was designed to do: take the user to the previously viewed page. When the user is searching in our app, they are only ever on one page, so nothing they are doing is added to the browser history. The actions that they expect to be able to be ‘backed out’ of are not. For the back button to EVER work, we needed to have our ajax code add to the browser history.

One approach (described http://dojotoolkit.org/node/100) is to simply add to the fragment identifier portion part of the url (everything after the #). The browser treats these as links to elements within the current document and will attempt to scroll to the element whose id matches the hash value. So it will add to the history, but it won’t reload the page. In our case, we’d do this for every criteria change or pagination change (switch from results page 3 to results page 4).

At this point our app would be adding to the ‘history’ and the back button would be changing the url…but the results won’t be changing just yet because we don’t have anything to respond to/reset the results when the back button was clicked. Essentially, we need to store some state (the users criteria and the results) with the value we put on the url, so that when the back button is clicked, that state can be restored. That is exactly what the dojo undo and the RSH and others do for us…but we have to have clean urls so adding #’s to the current url won’t do.

The other approach is pretty old-school. Load the results via a hidden iframe. As the user changes criteria we modify the src of an iframe to get the results instead of via an XmlHttpRequest. The iframe src changes are added to the browser’s history as well. We’d need a way to know when the iframe was ready with the results so that the page could pick them up and display them. We knew that some ajax frameworks would degrade to using an iframe approach when a proper XHR is not available…but the simplest thing we could get to work at the time was to have an onload in the result content that the iframe loaded that would tell the parent window what the results were by calling a javascript function on the parent. Basically the results would announce themselves to the containing window. (In hind-site our final approach may be more re-usable if we switch to monitoring the readystate of the iframe via and onreadystatechanged handler).

So at this point, we have results loading in the iframe, then the iframe telling the parent that results were loaded and the parent placing the results on the page. When the back button is clicked, the iframe loads the previous page of results (because iframe src changes are added to the browser history) and the iframe again tells the parent that results were loaded and the parent displays those results. And we still have our pretty urls ;)

However…we don’t have the correct criteria. The iframe only loads the results. So if the user changes the criteria a few times, then clicks the back button, the results will ‘go back’ but the criteria will not change, so they still appear to be the last criteria used. Not only that, but if we load a detail page (those are full page loads) and hit the back button, the correct results page will load, but there will be NO criteria present (because the criteria change on the page dynamically and hence are not part of the source that is restored by the back button…and the iframe that IS restored only has the results). If only the criteria were part of the results…

That’s pretty much what we did. We decided that we needed to have the results page also pass along the search criteria. When loaded, the results AND the criteria used to get those results would be passed along to the parent. Then the parent would render the results and restore the criteria.

The real key in simplifying the back button problem was to organize our javascript into true objects and have a model that could be used to represent the state of the page. That model could be passed around via the iframe and restored with ease in response to the back button. The road to refactoring the javascript to get to this point is another interesting topic for another day.

Thinking out loud, it would be interesting to revisit the RSH and dojo approaches and see if there could be a hybrid to what we needed to do here (or maybe they could already support what we need and we just didn’t see it ;)). I’m thinking like an api that would make an XHR call, then when the results come back, adds a # to the src of an iframe, then puts the result text as well as the model into an input in the iframe. There would be a readystatechanged listener on the iframe that would attempt to restore the state of the parent page from the ‘results’ and model stored in the iframe.

Sorry, no code for this one…just our story.

Automated Tests with Junit + Selenium + JsUnit

Tuesday, March 21st, 2006

I plan to have a more cohesive post on the process I went through and some of the specific tweaks I had to make…but right now excitement has the best of me and I’m going to let the keys fall where they may.

Recently, the project I’m on REALLY needed some automated testing. We had (have) a rich web-app (ajax/dwr) and were beginning to feel very uncomfortable without tests…a bit like going “without a net”. We had been researching Selenium for the integration/ajax testing and jsunit for some (although admittedly very few) of the javascript objects/classes/widgets we had written, but nothing automated. It was time…so I dove in.

To make a long story short, what I have today is Selenium integrated jUnit tests. The jUnit Tests look like this:

public class SomeScreenTest_UT extends BaseSeleniumTestCase{
public void testCanSearchOrders(){
open(”/products/search”);
assertNotVisible(”searchResults”);
type(”searchField”, “amber bottles”);
click(”searchButton”);
waitForValue(”searchStatus”, “complete”);
assertText(”resultsMessage”, “Found*”);
}
}

Upon execution, a browser will open to the SeleneseRunner.html and will begin running the tests. It is really pretty sweet. The base test case we’re using will open the login page and login first on setup. Then the tests do their thing and if a selenium assert or command fails, then the junit test fails.

I managed to get this working with Selenium 0.6.0.  I wound up implementing a SocketBasedCommandProcessor (can you guess what it does?) as well as a client to go with it. Extended DefaultSelenium to have SeleniumDriver (I needed to have more control over the url the browser would open…and I didn’t like the name). Also implemented a SeleniumDriverServlet to go along with it (the browser talked to the servlet…which talked to the processor via the client). I was pretty proud of it…even followed (fairly) strict TDD.

After implementing all this, I got to REALLY pouring through the Selenium code (last night) and noticed a few interesting classes…namely CommandBridge and CommandBridgeClient. In short, they do (or appear to do) what my implementation does, just without the sockets. It would have been REALLY nice to know about them first. I honestly felt I was tricked :) The docs are aparenly out of date and some of the examples refered to code that has been moved. When I couldn’t find them, I though they were just making suggestions ;) So one strike against the docs (btw not even the wiki has any mention of them). Even when reading the package structure, it wasn’t at all clear to me that the ‘outbedded’ package and the CommandBridge (which IS a servlet) were what I needed to get a servlet that talked to an external tomcat process…especially when nothing from the actual servlets package seemed to fit. Go Figure.

But after reading the forums, I’m not so sure the CommandBridge code even works. Aparently the “driven” stuff is undergoing a huge overhaul and is now going to be called “selenium remote control”. Which is good really because these guys have some good stuff under the hood and it just needs to be a bit easier to use. It could use some better names, and packaging…and definitely better docs, but it is still some great work.

Also, just today got Selenium to drive the jsunit tests for the app.  That was cool too…we needed jsunit to run in the build as well and I figured we already had this selenium setup, why not have IT drive the jsunit tests too.  It took some tweaking because jsunit expects to be the top level frame, but it worked.   I should mention that I do prefer to have jsunit run without need of the server running…and we still have that ability, this was just an easy-ish way to get it working with something we already had, just for the continuous integration.  I also know that jsunit has a server bit that ant can drive, but we wanted to be able to make it work just by running the junit test, this gives us that…no need for ant to kick anything extra off, just make sure the local server is running, which it usually is).  The test suite that selenium ‘drives’ is actually a jsp (so there IS one benefit to having the server running) that will scan the js folder for all _UT.html files, and adds the path to them to the test suite.