-
Get a monthly update on best practices for delivering successful software.
The Ajax Experience last week in Boston yielded lots of exciting developments on the Ajax history management front:
I have to say, the crowd here felt like my tribe. The guys running around with the word "JavaScript" shaved into their hair put a smile on my face. Ajax developers are often third-class citizens at other conferences. They're either jammed together with designers and user experience folks, or thrown into the midst of Java and Ruby developers. That wasn't the case here, and I dug it highly.
My least favorite aspect of the conference had nothing to do with the crowd or the content; it was the depressing lack of vegan food. One meal was 90% vegan, but most were 0%. Given the conference center's distance from civilization, it would have been nice if attendees' diverse dietary needs had been taken into consideration. On the plus side, I think I lost five pounds.
Many thanks to the folks from Tech Target for the awesome speaker support. My name, so amusingly misspelled on the monitor outside the ballroom where I spoke, had been corrected by the time I took the podium.
And special thanks to Ben, Dion and everyone at Ajaxian for throwing such a jam-packed event, letting me speak at it, and doing so much for the Ajax community over the years.
Time to come clean: I've been a terrible project lead on Really Simple History since version 0.6 launched last fall. The problem has been twofold:
The essential functionality of RSH works well in most supported browsers, but there are several special cases that have to be coded around in your actual application. Even basic usage, however, is documented mostly through example, not through tutorial-style, narrative prose. This has resulted in lots of noise in the issue tracker from folks seeking guidance on how to use the library. For all the folks whose questions and bug reports have gone unanswered, I offer a sincere and heartfelt apology. And to the more experienced users who stepped up to answer questions and help out, I offer heartfelt thanks.
The launch of Safari 3 caused some serious problems because code created to work around Safari 2's deficiencies caused things to break in Safari 3. I should have accepted suggested patches from some gallant RSH users and pushed out a new version months ago. But to be honest, I was so swamped with paid client work for Pathfinder that I couldn't find the time. I've learned my lesson about brittle, browser-specific workarounds. The next version of the library will fail far more gracefully.
Speaking of the next release: RSH 0.8 is nearing completion. I expect to publish an alpha version to coincide with my presentation October 1 at The Ajax Experience. My talk covers lots of interesting developments in Ajax history management, and I figured I should, you know, deliver the goods to my users before getting up on that stage.
Topics: Ajax, Javascript, Really Simple History, The Ajax Experience
Progress on Really Simple History is progressing nicely, though I'm taking a break this week to finish up my presentation for next week's O'Reilly Web 2.0 Expo in San Francisco (more on that tomorrow).
In taking RSH one step closer to a 1.0 release, I am basically re-writing everything from the ground up. My objectives include the following:
Topics: Ajax Bookmarking, Really Simple History
Speaking of Really Simple History....
While perusing John Resig's widely discussed analysis of IE8, I was surprised by his lack of fanfare when discussing its support of HTML 5's Ajax navigation module. This is a major development in the arena of Ajax history and bookmark management.
John's comments:
HTML 5: window.location.hash
Already supported fairly well by most browsers. Modifying window.location.hash changes the page URL and adds the page to the history (allowing for back-button simulation in Ajax applications). IE went a step further and broadcasts the hashchanged event (the first browser to do so, as far as I know).
Topics: Ajax Bookmarking, IE8, Javascript, Really Simple History
Anybody perusing the issue tracker or Google Group for Really Simple History would think I've abandoned my duties as code steward. For the past several months, that's been the unfortunate truth. My involvement in other freelance and Pathfinder projects has kept me away from my favorite pastime: Hacking away at the next iteration of RSH. Fear not, fans of Ajax history and bookmarking. I have carved out a large block of time starting next week to get back to work.
Over the next week or so, I'll be responding to threads in the Google Group and making my way through the issue tracker. Then, I'll dive into bug-fixes, enhancements and support for the new and emerging browsers, from Safari 3.1 to IE8 to Firefox 3.
Thanks, everyone, for your patience, your bug reports and your feature requests!
Topics: Really Simple History
Besides providing a useful tool to other developers, my chief interest in taking over Brad Neuberg's Really Simple History was to test the limits of my own JavaScript and browser-hacking skills. Version 0.6 was basically a maintenance release designed to acquaint the library with the browsers that had come of age since its initial 2005 release. The forthcoming 0.8 will be more of a redesign, adding new functionality while taking advantage of more modern JavaScript programming idioms. Lazy function definitions will improve performance, while closures and internal functions will lock down several methods that are currently public but need to be private.
Andrew Mattie is also hard at work modernizing his own Ajax history library: dsHistory, which just came out with a new release. I've been playing around with dsHistory, and I have to say I'm impressed. The differentiating feature of Really Simple History is its ability to maintain history states even after the user navigates away to other sites and back again. The unique thing about dsHistory, however, is its ability to bind the back button to arbitrary JavaScript functions without touching the URL fragment identifier. It's always interesting to see how other developers solve the same problems, often by slightly reframing them. Their differing outlooks make the two libraries suitable for different audiences, but there's also a lot of common ground.
Topics: Ajax Bookmarking, Really Simple History
Things have been quiet on the Really Simple History front for the last month or so. But progress is continuing behind the scenes, albeit slowly. The forthcoming 0.8 release, which I had hoped to complete by the end of December, now looks more likely for February. What can I say? A little thing called the holidays got in the way. The good news? I'm one marathon weekend session away from completing the required feature work, refactoring and cross-browser testing.
Speaking of marathon weekend sessions, I should point out that all of my development on Really Simple History has been funded by my employer, Pathfinder Development. I've sunk at least 160 person-hours into the library so far - and that's a pretty conservative estimate. I have spent many long weekdays and weekends working on RSH instead of doing Pathfinder R&D or working on paid client projects. I'm therefore happy to announce that RSH now has a page on the Pathfinder website. More importantly, Pathfinder is now offering my services as an RSH/Ajax history consultant. Paid consulting on RSH will help us fund continued development of the library. (Read the announcement here.)
Topics: Ajax Bookmarking, Really Simple History
About a month ago I reviewed the GWT history manager and lamented its less than full support for the (admittedly buggy) Safari 2. Since then I've chatted with Joel Webber of GWT and gotten more insight into his team's approach to the Safari question. According to Joel, it all comes down to iframes: The approach taken to Safari 2 by the .Net history manager, Really Simple History and other libraries falls apart when the history-enabled application makes use of iframes - which, of course, are required by rich-text editing widgets. The GWT team tried to code around this using cookies, but that approach tanked when multiple copies of the same app were open simultaneously. Rather than provide support that didn't work for some of its core widgets, GWT history fails gracefully in Safari 2. It works beautifully in Safari 3/Mac because the Webkit team fixed the bugs that required all this hackery in the first place.
It's interesting to chat directly with other history-management developers and get a better understanding of how they make their peace with inconsistent or downright wrong browser behavior. As for Really Simple History, I'm still hard at work preparing a beta of 0.8.
Topics: GWT, Really Simple History
A week after wondering whether title updates were worth the trouble in Really Simple History, I'm reconsidering my opinion. It turns out a couple of users had already tackled this problem and come up with some solutions to the worst issues. (That will teach me to post about RSH without keeping up to date on my own Google Group and issue tracker.) At this point I have Firefox, IE and Safari working. Opera is proving to be a bigger challenge.
My original problem was that when a user clicks the back button to
land on a previous history point, RSH can't update the document title
until the history change is already complete. That means the browser's
history entry for the new hash gets set to the document title that
matches the previous hash. The trick - as pointed out in the comments here
- is to simply reload the current hash after the title change is
complete each time a history callback gets triggered. That causes the
browser history stack to replace the incorrect title with the correct
one. This seems to do the trick in Safari and Firefox. The code looks
like this:
Topics: Ajax Bookmarking, Really Simple History
One of the features I want to implement for Really Simple History 0.8 - based on requests from the RSH Google Group - is automatic updates of the document title. When the hash changes, so will the title, guaranteeing that bookmarks, the history menu and the dropdowns on the back and forward buttons all give relevant information about each discrete page state. Instead of bookmarking a page with a generic title such as "My Email Program," you'll get something useful, like "My Email Program: Inbox" or "My Email Program: Drafts."
Initial implementation was easy. When you add a history point in RSH, you pass in an optional JSON hash of supplementary data that can be recovered for the duration of that session. This historyData object was the most sensible place to pass in a new document title, so I added a method to inspect the historyData of any given history point, look for a member called newTitle, and dynamically update the title. I also got a little fancy and gave the user the option to set a base title with a replacement parameter so that they didn't have to keep passing in the name of their website with each history point. The new method looked like this:
changeTitle: function(historyData) {
/*change the document title if called to do so*/
if (historyData && historyData.newTitle) {
/*Plug new title into the base title or use it raw if none is found*/
var winTitle = this.baseTitle
? this.baseTitle.replace('@@@', historyData.newTitle)
: historyData.newTitle
;/*IE history is keyed off the iframe, so we need to update its title, too*/
if (this.isIE) {
this.iframe.contentWindow.document.title = winTitle;
}
document.title = winTitle;
}
},
Then, I just needed to add code to delegate to the new changeTitle method each time a history point was added or a history event fired. The changes to my add method looked like this:
add: function(newLocation, historyData) {
if (this.isSafari) {//EXISTING CODE HERE
this.changeTitle(historyData);
} else {
var that = this;
var addImpl = function() {
//EXISTING CODE HERE
that.changeTitle(historyData);
};/*Now queue up this add request*/
window.setTimeout(addImpl, this.currentWaitTime);/*Indicate that the next request will have to wait for awhile*/
this.currentWaitTime = this.currentWaitTime + this.waitTime;
}
},
The changes to my fireHistoryEvent method looked like this:
fireHistoryEvent: function(newHash) {
var decodedHash = decodeURIComponent(newHash)
/*extract the value from our history storage for this hash*/
var historyData = historyStorage.get(decodedHash);this.changeTitle(historyData);
/*call our listener*/
this.listener.call(null, decodedHash, historyData);
},
Playing around with various test pages and RSH applications, I was happy with my work. Each time I added a history point or clicked the back button, my page title updated to exactly what I wanted it to. Then things got tricky. To put my new method through its paces, I started navigating back and forward through the history stack and inspecting the back and forward dropdowns each time. Things quickly got weird. Page titles that had previously appeared correct in these dropdowns were getting updated with the wrong values. You'd go to a history point with a title such as "Inbox" and instead you'd see the "Drafts" page. It took some playing around, but I finally realized why.
Each time the back or forward button gets clicked, the document title for the page that is coming off of the history stack to become the current document gets reset to the current document title. (This only happens when the history changes involve new hash values on the same base URL.) The document title doesn't get set when a document moves out of the current document position, but rather when it moves into the current document position.
That's a real problem for RSH and other history managers. Because RSH is essentially reactive - waiting for history changes to occur via back or forward and then working its magic - there's no way to change the document title before the history change occurs. So as a page that's already in the history stack becomes the current page, it gets assigned the the wrong title. This behavior manifests itself a little differently in various browsers - especially IE, whose history-stack page titles get set by a hidden iframe - but it's an issue in all of them. Setting a title when a history point gets added: no problem. Setting a title after a history change has occurred: too late.
The new feature does get a couple of things right: The title in the browser title bar always reflects the current state of the application, and bookmarks always reflect the correct title. (By the time a user is able to bookmark a page state, the title has already been updated to reflect that state.) Two out of three ain't bad - right?
I'm a little torn as to where to go from here. Is this really a feature rather than a bug if it improves some aspects of the browsing experience while degrading others? At least when the history stack shows the same document title 10 times in a row, the only sin is a lack of information. With this new feature enabled, the history stack reports wrong information. The user who tries to find a specific page state in the history stack will end up with the wrong place and quickly grow annoyed.
I've given some thought about how this state of affairs could be improved, but any potential solutions would vastly complicate the fireHistoryEvent method. You'd have to intercept each history change, change the document title to the correct value, change the hash back to the previous hash so that the correct title would get set in the history stack, then change the hash again. I'll probably implement this functionality just to see whether it's possible and how weird the flicker looks in the address bar. But it seems like a lot of extra code - and extra testing - for something that's not essential to history management.
If any RSH users want to play around with the code and offer solutions, I'm all ears. The provisional changes are part of the latest code in the Google Code SVN repo.
Topics: Ajax Bookmarking, Really Simple History
I took GWT's history manager for a spin last week courtesy of the Kitchen Sink Example Project. Overall I was impressed; it worked flawlessly in a range of up-to-date browsers and operating systems. Then I decided to give it a try in the two browsers that caused me the most pain with Really Simple History: Safari 2.x and Opera 9.2x. The history implementation in each browser is deeply buggy; I resorted to lots of user-agent-specific hackery to work around it. GWT apparently doesn't try.
In Safari 2.03 and 2.04, both of which RSH supports, GWT offers no back-button support. Hit a number of different history points in the kitchen sink app, then hit "back" and you'll end up on whatever site you were on before you got there. Things in Opera 9.2x fail less gracefully. History points get created, but the back button doesn't trigger GWT's history listener. So if you visit five different history points within a GWT application, you'll still have to hit your back button five times to get back to whatever site you were on before. Yet your application state doesn't change as the result of any of those back-button clicks.
Having profited from the hard work of other history-manager authors and spent countless hours on my own implementation, I know exactly why both of these problems are occurring. When you change the hash value of a URL in Safari 2.x, the only part of its history object that gets updated is history.length. If you don't key all of your history events and callbacks off the value of history.length, your history manager simply won't work. As for Opera 9.2x, hitting the back button completely blows away all JavaScript timers. You have to get creative in order to re-initialize your history-change polling. Luckily, Safari 2.x and Opera 9.2x are on their way out. GWT's history manager works as expected in Safari 3/Mac and Opera 9.5. (Safari 3/Windows is just as broken for GWT as it is for RSH; it's just too buggy to hack around.)
The question of browser support in Ajax libraries is an interesting one. To what extent should a library author feel obligated to fight the good fight against buggy browsers - especially niche ones? RSH 0.6 would be a good 20% smaller - and have taken about 60% less of my time to develop - if I had opted to support only the latest and greatest betas of Opera and Safari/Mac. I even have a half-implemented feature to return an isSupported boolean upon initialization of my library. I haven't gone further with that particular enhancement because once you go down the road of monitoring minor version numbers and changing your library to react to them, you've doomed yourself to constant maintenance. You've got to draw a line in the sand somewhere; GWT and RSH simply draw the line at different places.
Topics: GWT, Really Simple History
Really Simple History 0.6 rolled over this morning from release candidate into final production code. All previous versions are now considered deprecated. The production version of 0.6 is exactly the same as RC1, but it includes more complete release notes and a minified version of rsh.js. (Much respect to Dojo ShrinkSafe!)
I'd like to extend thanks to the many tireless beta-testers who have put RSH through its paces over the last couple of months. Thanks also to the many developers whose work on other Ajax history frameworks has informed my own. Most of all, thanks to Brad Neuberg for initiating the project and allowing me to get my grubby little paw prints all over it.
As always, you can download RSH from its home at Google Code and participate in the RSH community at its Google Group.
The roadmap from 0.6 to 1.0 is taking shape:
0.8 will become available for beta testing by the end of the year. It will include the following enhancements and changes:
1.0 is scheduled for 2008 Q1. It will include full jsUnit and Selenium test coverage.
Topics: Ajax Bookmarking, Really Simple History
I committed several weeks ago to roll Really Simple History 0.6 RC1 over into a final release on Dec. 1. That's still the plan, but I'm having trouble resisting the urge to tinker. I'm starting to see how a project like this could remain in indefinite limbo if I don't just draw a line in the sand and release a final "hard" version that people can rely on for production code. The current SVN trunk includes a number of backwards-compatible enhancements. I'm working hard to test them all as comprehensively as possible, but I'm going to avoid adding risk to 0.6. Instead, I'll save them for 0.7, which will come out in beta before Christmas.
The enhancements targeted for 0.7, some of them already complete, include the following:
The roadmap for 1.0 includes the following:
In the meantime, downloads for 0.6 RC1 have crept to just under 350 almost 700. Look for me to "flip the switch" on Saturday, promote 0.6 to the stable production version, and deprecate 0.4 once and for all.
There's never been a better time to be a JavaScript developer. JS hit the big time with the advent of Ajax, and overnight client-side programmers went from being the redheaded step-children of the web-development world to front-and-center participants in the RIA revolution. Even if you're working on a team of one, there's a thriving global community of JavaScript gurus constanly pushing the language forward, working out browser kinks, demonstrating how to import concepts from other languages and computing paradigms, and otherwise inspire you. It's a big change from 10 years ago, when you had Webmonkey and the David Flanagan book and a few cookbook-style compendiums of tips and tricks. This is seriously the golden age.
Working for the last couple of months on Really Simple History, though, I've become interested in the way specific techniques become the flavor of the month and quickly redefine how we conceptualize "good" JavaScript coding practices. Take, for instance, DOMContentLoaded. After the first version came onto the scene, within a matter of months we had many competing implementations of the basic concept that your scripts shouldn't have to wait for window.onload to fire before getting down to business. Now, DOMContentLoaded is the de facto start point for many, many JavaScript applications. Heck, it's even the basis of the entire jQuery event model. There's nothing wrong with that, but widespread use of Ajax toolkits can conceal the fact that DOMContentLoaded is just a collection of hacks. It works, as long as our toolkits keep iterating one step ahead of the browser vendors. But do we really need it?
At Orbitz Worldwide, my previous employer, we implemented a first-rate unobtrusive-JavaScript architecture. (See it in action at Ebookers UK.) Everything was progressively enhanced from dumb markup. That meant our application worked just about everywhere. But it also meant lots of DOM parsing and initialization that couldn't begin until window.onload. As our application grew, this caused enough of a UI flicker - form controls turning into widgets, links morphing into Ajax calls - that we needed to jump the gun on window.onload if we wanted a good experience for the majority of users ... the ones with modern, JS-capable browsers. DOMContentLoaded made sense, and architect Nik Krimm pounced on it.
But for a large subset of websites, there really isn't THAT much difference between window.onload and DOMContentLoaded. If you're merely adding an unobtrusive behavior layer to a traditional, content-driven website, chances are that good old window.onload will perform just fine. There's usually little reason NOT to use DOMContentLoaded. But unless you are developing a desktop-style webapp or implementing progressive enhancement on a foundational level, it's not really necessary. And in certain cases, it just flat-out won't work.
An enthusiastic beta-tester discovered such a case when trying to initialize RSH's dhtmlHistory object from DOMContentLoaded. It broke, and I immediately knew why: RSH relies on the ability of modern browsers to auto-save form data for the life of a session. RSH uses a hidden textarea to cache serialized Ajax application state and render the back button useful again. Internet Explorer doesn't repopulate the cached value of that textarea until an instant after window.onload. If you try to access that cache during DOMContentLoaded, it simply isn't there. This is true of both IE6 and IE7. Therefore, you need to wait for window.onload.
I'm not really sure there's any big conclusion to draw from this example. As I said, it's just interesting to me that in the space of a year or two, DOMContentLoaded has become a de facto standard. As we pile browser hacks on top of one another to push the web forward, sometimes they're going to conflict. Luckily, we can always peek past the curtains and figure out what's going on behind the scenes.
Topics: Ajax Bookmarking, Ajax Frameworks, Browsers, IE, IE6, IE7, Javascript, Javascript Libraries, jQuery, Really Simple History
After much tinkering, I am happy to announce the release of Really Simple History 0.6 Release Candidate 1. This version offers a number of improvements and bug-fixes over the 0.6 beta released on Oct. 23. I encourage the 600+ people who have downloaded the beta to upgrade immediately to this release candidate. As for the 3,200+ people who have downloaded the stable 0.4 version in the last two months, it's your turn, too. Beta-testers helped me put RSH 0.6 through its paces in a wide range of browsers and platforms, alongside a wide range of Ajax frameworks, and the result is solid. I fully expect this version of the code to become the new, stable 0.6 release on December 1, barring any show-stopping bug reports.
We'll provide a minified version of the stable release for download. We'll also provide links to sample apps. That's it.
If you use Really Simple History 0.6 in your production code, I'd love to take a look and possibly highlight your app in a future post. Please email me, comment here, or send a note to our Google Group.