What’s this? I know you thought this was supposed to be a .NET blog, yet so far all I’ve posted is jQuery and iPhone stuff. Well the idea of this blog is that I will post stuff I’m learning on the job, and at the moment I’m learning a lot about jQuery, so here we go.
This continues on from the Knowledge Base project I used in a previous post. In this example, I start out on the “Help” page. I navigate to the Browse page, and then within the Browse page I we have a page which shows/hides different sections when the mouse is clicked.
In this example, I start on the Help page and navigate to a FAQ using cascading drop-downs. I choose a value from my “level1” dropdown, Animal Control. The “level2” drop down is populated with Animal Control-related topics, and I choose Dogs. The results pane is loaded with Dog-related FAQs, and I expand the first result, about micro-chipping dogs. I click a link to view the Dogs topic, and then click the browser back button. The browser takes me back to the Help page, but my previous state (viewing the “Do the dogs need to be microchipped” FAQ) isn’t displayed, instead the Help page is in its initial state.
We want to make it so that when the back button is clicked, we’ll be returned to the FAQ we were viewing on the Help page.
Existing Code
$(document).ready(function() { $('.dropdown').change(onSelectChange); loadServices(); // loads all the Services into the level1 dropdown } function onSelectChange() { var selected = $("#" + this.id + " option:selected").val(); // the new selected value if (this.id === 'level1') { // 'this' is the dropdown that was changed loadTopicsForService(selected); // loads topics into the level2 dropdown with AJAX } else if (this.id === 'level2') { showAnswers(selected); // shows FAQs for the selected topic with AJAX } }
Plugins
To get the back button working, we need to kind of trick the browser into thinking that we’re navigating up and down within the same page by adding # tags to the address. There are a number of jQuery plugins for doing this and I tried out a few – jquery history plugin, history, and jQuery BBQ.
I originally got it working with jQuery history, but it wouldn’t work properly for IE7. So I tried again with jQuery BBQ and it worked fine across all our target browsers – IE6, IE7, IE8 and FireFox.
Code changes
First, I changed the dropdown change event. Instead of populating the drop downs by calling loadTopicsForService() or showing FAQs by calling showAnswers, I’ll call bbq.pushState:
$(document).ready(function() { $(window).bind("hashchange", historyCallback); // needed for bbq plugin to work $('.dropdown').change(function() { var dropdown = $(this); var id = this.id; var selected = dropdown.find("option:selected").val(); if (selected != 0) { if (dropdown === "level1") { $.bbq.pushState({ level1: selected }, 2); // merge_mode=2 means wipe out other params (e.g. level2) } else if (dropdown === "level2") { $.bbq.pushState({ level2: selected }); } }); loadServices(); // loads all the Services into the level1 dropdown historyCallback(); // manually call the bqq history callback, to process the # tags if any were previously // added. This is called when we load the page by pressing back, fwd, or refresh. }
Now when I select “Animal Control” from the level 1 (aka Service) drop-down, jQuery.BBQ will 1. add “#level1=1” to the current page location, i.e. the address bar in the browser, and 2. call my historyCallback function which will do the work that used to be defined in the dropdown change event. #1 tricks the web browser into thinking that I’m now on a different page, which adds a new entry into the browser’s history so that clicking Back in the browser will take me to the previous page.
Now we need to define our callbacks which actually do the work of populating the cascading drop downs and displaying FAQs. Remember, historyCallback() will now do the work that used to be defined in onSelectChange().
function historyCallback(e) { levelOneCallback(); levelTwoCallback(); } function levelOneCallback() { var L1 = $.bbq.getState("level1"); if (L1 != undefined && L1 != 0) { $('#level1').val(L1); // set dropdown value - needed for when fwd is clicked loadTopicsForService(L1); // loads topics into the level2 dropdown with AJAX } else { // level1 not specified so reset the page $('#level1').val(0); $('#row2').hide(); } }
levelOneCallback() asks jQuery.BBQ if the level1 has been selected, and if it has been it loads up the level2 drop down. If level1 isn’t specified then we should explicitly reset the level1 dropdown.
The rest of the code is here:
function levelTwoCallback() { var L2 = $.bbq.getState("level2"); if (L2 != undefined && L2 != 0) { $('#level2').val(L2); // set dropdown - needed when fwd is clicked showAnswers($("#level1").val(), selected); } else { // level2 not in querystring, so reset level2 $('#level2').val(0); } }
So, when I choose a value from the first dropdown, #level1=3 is added to the URL of the page. When I choose a value from the second dropdown, #level2=7 is added. For brevity I have omitted the code which adds #question=55 to the URL when a question is clicked.
Here’s a video of the final result:
Once again I navigate through the drop downs, as before, but this time when I click back, we’re brought back to the previous state which is viewing the question about microchipping dogs. As I keep clicking back, each previous state is loaded. And then when I click Fwd, my previous actions with the dropdowns are repeated!
I’d like to thank “Cowboy” Ben Alman for the jQuery BBQ library and also for suggesting some improvements to my code.
Hope that helps someone.