jQuery BBQ: Back Button & Query Library

| 65 | No TrackBacks

jQuery BBQ leverages the hashchange event to allow simple, yet powerful bookmarkable #hash history. In addition, jQuery BBQ provides a full .deparam() method, along with both hash state management, and fragment / query string parse and merge utility methods.

This plugin and the jQuery urlInternal plugin supersede the URL Utils plugin.

Note: If you’re using jQuery 1.3.2 and need BBQ to merge query string or fragment params containing [], you’ll want to include the jQuery 1.4 .param method in your code.

What jQuery BBQ allows you to do:

While this brief overview will give you the broad strokes, for specifics you should look at the the basic examples below, read the documentation, and check out the full examples listed above.

  • Deserialize any params string, the document query string or fragment into an object, including the new jQuery.param format (coming in jQuery 1.4). (example)
  • Merge any URL plus query string or fragment params—in an object, params string or second URL (including the current document location)—into a new URL.
  • Update the “URL attribute” (ie. a[href], img[src], form[action], etc) in multiple elements, merging any URL plus query string or fragment params—in an object, params string or second URL (including the current document location)—into a new URL, which is then set into that attribute.
  • Push (and retrieve) bookmarkable, history-enabling “state” objects or strings onto the document fragment, allowing cross-browser back- and next-button functionality for dynamic web applications (example 1, example 2, example 3)
  • Bind event handlers to a normalized, cross-browser hashchange event (example 1, example 2, example 3)

Why BBQ? AKA: Why “history” and “deparam” together?

Imagine three scenarios. Now, imagine a star wipe

The single widget

In the first scenario, you’ve got a single widget on the page. Maybe the page is the widget, whatever. Either way, things are so simple that every history plugin can do this (including jQuery BBQ):

<Widget> Yo, hash, update the state with this string.
<Hash> No prob, dude, done. Sure, your state takes up the whole hash, but what do you care, you’re the only widget on the page!
<Widget> But I’m so lonely.
<Hash> Tough luck, kid. There’s only room in this hash for one state.

Multiple widgets, rough-and-tumble

In the next scenario, you’ve got multiple widgets on the page. And unfortunately, because the history plugin developer didn’t provide an easy way to manage multiple, separate, individual states simultaneously, your widgets need to be somewhat aware of each other’s existence, so they don’t accidentally erase each other’s state in the hash:

<Widget> Yo, hash.. I need to update my state parameters. Whatcha got in there?
<Hash> A string representation of your state, plus maybe some others, I’m not sure. Whatever. Here ya go!
<Widget> Wow, that sure contains a lot of stuff I don’t care about.. But a job’s a job, right?
<Widget> So, first let me figure out where my parameters are. Ok, right.. Wait! I think some other widget’s parameters are in here too! Ok. Let’s see, add this in there, carry the one.. great, that seems to be working now.
<Widget> Well, hash, here’s the whole new state. I inserted my parameters in there next to all the other parameters that were there. Or maybe i didn’t. I dunno, it probably works.
<Widget2> Are you telling me I’ve got to do all that too? 404 dudes, I quit.

Multiple widgets, with some tasty BBQ sauce

In this final scenario, while there are multiple widgets on the same page, each one can get and set its own state very easily because the history plugin can deparameterize any fragment-based params string into its component parts easily, then merge replacement params in, and update:

<Widget> Yo, hash, update my state parameters.
<Hash> No prob, dude, done. And you didn’t even have to know about that other widget’s parameter, I just merged them in there for you.
<Widget> There’s another widget?
<Widget2> Huh? Did someone say my name?

In case you hadn’t guessed, jQuery BBQ helps you do all this the easy way.

Examples: jQuery.deparam

In the following examples, jQuery.deparam is used to deserialize params strings generated with the built-in jQuery.param method. Check out the jQuery.deparam example page and documentation for more information.

// Serializing a params string using the built-in jQuery.param method.
// myStr is set to "a=1&b=2&c=true&d=hello+world"
var myStr = $.param({ a:1, b:2, c:true, d:"hello world" });

// Deserialize the params string into an object.
// myObj is set to { a:"1", b:"2", c:"true", d:"hello world" }
var myObj = $.deparam( myStr );

// Deserialize the params string into an object, coercing values.
// myObj is set to { a:1, b:2, c:true, d:"hello world" }
var myObj = $.deparam( myStr, true );

// Deserialize jQuery 1.4-style params strings.
// myObj is set to { a:[1,2], b:{ c:[3], d:4 } }
var myObj = $.deparam( "a[]=1&a[]=2&b[c][]=3&b[d]=4", true );

Examples: jQuery.deparam with query string and fragment

The jQuery.deparam.querystring and jQuery.deparam.fragment methods can be used to parse params strings out of any URL, including the current document. Complete usage information is available in the documentation.

// Deserialize current document query string into an object.
var myObj = $.deparam.querystring();

// Deserialize current document fragment into an object.
var myObj = $.deparam.fragment();

// Parse URL, deserializing query string into an object.
// myObj is set to { a:"1", b:"2", c:"hello" }
var myObj = $.deparam.querystring( "/foo.php?a=1&b=2&c=hello#test" );

// Parse URL, deserializing fragment into an object.
// myObj is set to { a:"3", b:"4", c:"world" }
var myObj = $.deparam.fragment( "/foo.php?test#a=3&b=4&c=world" );

Examples: Parsing the query string or fragment from a URL

The jQuery.param.querystring and jQuery.param.fragment methods can be used to return a normalized query string or fragment from the current document or a specified URL. Complete usage information is available in the documentation.

// Return the document query string (similar to location.search, but with
// any leading ? stripped out).
var qs = $.param.querystring();

// Return the document fragment (similar to location.hash, but with any
// leading # stripped out. The result is *not* urldecoded).
var hash = $.param.fragment();

// Parse URL, returning the query string, stripping out the leading ?.
// qs is set to "a=1&b=2&c=3"
var qs = $.param.querystring( "/index.php?a=1&b=2&c=3#hello-world" );

// Parse URL, returning the fragment, stripping out the leading #.
// hash is set to "hello-world"
var hash = $.param.fragment( "/index.php?a=1&b=2&c=3#hello-world" );

Examples: URL building, using query string and fragment

The jQuery.param.querystring and jQuery.param.fragment methods can also be used to merge a params string or object into an existing URL. Complete usage information and merge options are available in the documentation.

var url = "http://example.com/file.php?a=1&b=2#c=3&d=4",
  paramsStr = "a=5&c=6",
  paramsObj = { a:7, c:8 };

// Build URL, merging params_str into url query string.
// newUrl is set to "http://example.com/file.php?a=5&b=2&c=6#c=3&d=4"
var newUrl = $.param.querystring( url, paramsStr );

// Build URL, merging params_obj into url query string.
// newUrl is set to "http://example.com/file.php?a=7&b=2&c=8#c=3&d=4"
var newUrl = $.param.querystring( url, paramsObj );

// Build URL, merging params_str into url fragment.
// newUrl is set to "http://example.com/file.php?a=1&b=2#a=5&c=6&d=4"
var newUrl = $.param.fragment( url, paramsStr );

// Build URL, merging params_obj into url fragment.
// newUrl is set to "http://example.com/file.php?a=1&b=2#a=7&c=8&d=4"
var newUrl = $.param.fragment( url, paramsObj );

// Build URL, overwriting url fragment with new fragment string.
// newUrl is set to "index.php#/path/to/file.php"
var newUrl = $.param.fragment( "index.php", "/path/to/file.php", 2 );

Examples: URL building in elements with “URL attributes”

The jQuery.fn.querystring and jQuery.fn.fragment methods are used to merge a params string or object into an existing URL, in the appropriate selected elements’ “URL attribute” (ie. a[href], img[src], form[action], etc). Complete usage information and merge options, as well as a list of all elements’ default “URL attributes” are available in the documentation.

// Merge a=1 and b=2 into the `href` attribute's URL's query string,
// for every `a` element.
$("a").querystring({ a:1, b:2 });

// Completely replace the `href` attribute's URL's query string with
// "a=1&b=2", for every `a` element.
$("a").querystring( "a=1&b=2", 2 );

// Completely replace the `href` attribute's URL's fragment with
// "new-fragment", for every `a` element.
$("a").fragment( "new-fragment", 2 );

// Merge the current document's query string params into every
// `a[href]` and `form[action]` attribute, but don't
// propagate the "foo" parameter.
var qsObj = $.deparam.querystring();
delete qsObj.foo;
$("a, form").querystring( qsObj );

Examples: History & bookmarking via hashchange event

jQuery BBQ leverages the hashchange event plugin to create a normalized, cross-browser window.onhashchange event that enables very powerful but easy to use location.hash state / history and bookmarking. Check out the basic hashchange, advanced hashchange and jQuery UI Tabs history & bookmarking examples, as well as the documentation for more information.

// Be sure to bind to the "hashchange" event on document.ready, not
// before, or else it may fail in IE6/7. This limitation may be
// removed in a future revision.
$(function(){

  // Override the default behavior of all `a` elements so that, when
  // clicked, their `href` value is pushed onto the history hash
  // instead of being navigated to directly.
  $("a").click(function(){
    var href = $(this).attr( "href" );

    // Push this URL "state" onto the history hash.
    $.bbq.pushState({ url: href });

    // Prevent the default click behavior.
    return false;
  });

  // Bind a callback that executes when document.location.hash changes.
  $(window).bind( "hashchange", function(e) {
    // In jQuery 1.4, use e.getState( "url" );
    var url = $.bbq.getState( "url" );

    // In this example, whenever the event is triggered, iterate over
    // all `a` elements, setting the class to "current" if the
    // href matches (and removing it otherwise).
    $("a").each(function(){
      var href = $(this).attr( "href" );

      if ( href === url ) {
        $(this).addClass( "current" );
      } else {
        $(this).removeClass( "current" );
      }
    });

    // You probably want to actually do something useful here..
  });

  // Since the event is only triggered when the hash changes, we need
  // to trigger the event now, to handle the hash the page may have
  // loaded with.
  $(window).trigger( "hashchange" );
});

Projects or Websites using jQuery BBQ

Have you used jQuery BBQ in a project or website? Let me know, and I’ll mention it here!

If you have any non-bug-related feedback or suggestions, please let me know below in the comments, and if you have any bug reports, please report them in the issues tracker, thanks!

I want to thank Paul Irish and Yehuda Katz for all their help refining the jQuery BBQ API, as well as Brandon Aaron for explaining parts of the jQuery.event.special API for me and providing me the example code on which I based the hashchange event plugin. I also want to thank everyone in the #jquery IRC channel on irc.freenode.net for all their suggestions and enthusiasm!

No TrackBacks

TrackBack URL: http://benalman.com/mt/mt-tb.cgi/224

65 Comments

  • Hi Ben. First of all this is fantastic stuff, you’ve built, and I’ve been using similar plugin called jQuery Address, so now I’m curious - expcept for nice URL parsing features, could you tell me what’s the difference between yours and Asual’s approach?

  • criography, I’ve never used jQuery Address, but it seems like jQuery BBQ has a somewhat simpler API and smaller footprint, not to mention jQuery.deparam and all the fragment / query string parsing and merging methods.

    Also, while jQuery BBQ can be used with deep-linking style “basic state” fragments, it excels at param-style “multiple state” fragments, where separate individual widgets’ state can be set and retrieved very easily, independently of the state of any other widget. Check out the jQuery UI Tabs example to see this in action.

    Finally, wherever possible, my examples use photos of delicious grilled food, which makes jQuery BBQ very, very tasty.

  • Hey Ben - Very well formed and thorough code, not to mention the usefulness. I am having trouble in your demos getting the IE7 support. I do not see the iframe in the DOM which I realize is needed to poll for changes in the hash. Let me know if I am doing something wrong. I would love to use some of your ideas and integrate them into a pagination/sorting requests plugin I have developed which now needs AJAX browser history/bookmarking support. Thanks for your time.

  • Chad, the iframe isn’t created until you actually bind a handler to the “hashchange” event. Are you sure it’s not being created? Is it being created in the project examples?

  • Ben - Yeah I am testing in IE8 (in which everything is great!) and switching to IE7 with the IE developer’s toolbar. I click to change the loaded remote html file and nothing happens, and I search the html/DOM for an iframe and do not find one. Thank you so much for replying. Feel free to email me and discuss!

  • Chad, it appears that IE 8 in both “IE7” and “IE8 Compatibility View” modes erroneously reports that it supports the native window.onhashchange event. I will update jQuery BBQ as soon as I can to work around this issue. Thanks for the heads’ up!

  • Hi Ben,

    Just a small issue: IE6 throws up a “This page contains both secure and nonsecure items” error when using the library on a site which runs over HTTPS. See the following link:

    http://support.microsoft.com/default.aspx?scid=kb;en-us;261188

    The fix is simple: just add a dummy “src” attribute to the iframe with the value “javascript: false”.

    On line 807 change:

    iframe = $('<iframe/>').hide().appendTo( 'body' )[0].contentWindow;

    to

    iframe = $('<iframe/>').attr( "src", "javascript: false" ).hide().appendTo( 'body' )[0].contentWindow;

    Thanks for a brilliant library.

    Jamie

  • Jamie, great catch, thanks! I’ve fixed this issue in v1.0.2, which is now available.

  • I just had a problem with IE due to trying to bind the event before document was ready. I would make an assumption that the plugin is broken if it didn’t work in Your demos ;)

    You might consider preventing people from that by checking if document.ready is done and if it’s not - save the passed function for later and do all the iframe building in document.ready.

    Really great job with the iframe trick.

  • Great catch, Zbyszek. For now, just be sure to bind the event handler on document.ready and I’ll look into deferring the IFRAME creation internally in an upcoming revision.

  • Hi Ben, sorry for my late response and thank you for your detailed explanation. I’m going to use your plugin for my current project and will share my experiences when it’s done! As strange as it may sound, I’m actually quite excited about it.

  • Hi Ben,

    This is a fantastic plugin, but I have a problem with IE7. When I working with IE8 there is not a problem. When I switch to IE7 mode or compatibility view with IE developer’s toolbar the back button does not work correctly.

    I mean, if I scroll down the page and click on some “BBQ” content (e.g. #ribs.html), then I click on the back button (or press backspace) I get the right content (from the javascript cache), but the browser jump to the top of the page, it does not stay where I scrolled before. I don’t know why that is.

    Thanks for in advance.

    Gábor

    //sorry for my english

  • Gabor, this is a known issue with IE6/7. When navigating back to the same page, but with no hash, the hash mush actually be set to something to avoid a page reload, so it’s set to #.

    Of course, when that happens, the browser will often scroll to the top of the page. But nobody uses either of those browsers anymore, right? (right, in our dreams)

    One solution to this problem would be to use jQuery.scrollTop to set the page’s vertical scroll manually, as necessary, in the window.onhashchange event callback.

  • Hey Ben, I’m having an issue merging an existing url and parameters from my form, i.e.

    $.param.querystring( $.param.querystring(), $('select').serialize() )

    I reported an issue here: http://plugins.jquery.com/node/1117

    Otherwise this plugin is just what I’ve been looking for.

  • Jacob, since the inner $.param.querystring() returns just the params string with no leading “?”, the outer $.param.querystring() will get confused, because it doesn’t know the url is a query string (it thinks it’s just a path).

    Try this:

    $.param.querystring( '?' + $.param.querystring(), $('select').serialize() )

  • Hey there,

    Nice library! I am currently trying to implement it, but wih my heavily dynamic AJAX “link” call model I am in a tricky situation. My links do their own AJAX calls. They update the hash string via pushState(). Now, the problem is, I also need the hashChange-Event, obviously. It manages activation of the browser’s back button. hashChange triggers a regular site load just like the other links do. This results in a (normally infinite) loop which is stopped after the second hash update (pushState()) as the hash doesn’t change: User clicks link -> pushState() being called to update hash -> hash update triggers hashChange -> hashChange results in the same like clicking a regular link.

    So, my question is: Can you please implement a method like pushStateSilent() or add a parameter to pushState() so you can prevent hashChange from being triggered? I’d do it myself, but I’m not really looking through your code.

    Thanks a lot

    Eric

  • Eric, you seem to be going about this backwards. You need to decouple the link click and the action, instead using the link to simply change the hash state. Then the callback bound to the window.onhashchange event can perform the action. You could abstract that decoupling in many different ways, but it needs to be done. Once you do that, you won’t need anything like a pushStateSilent() method, and you’ll have the added benefit that the back/next buttons and bookmarked links will then trigger all the click actions (AJAX calls, or whatever).

  • I just wanted to drop a line and say this works pretty well. I do have one issue that i haven’t been able to solve, when this is running on a ASPX page i get a double redirect sound and the history becomes very odd. For example, if i click ITEM1 then ITEM2 and hit the back button it goes to item 1 (making the weird double redirect sound again) and then when i hit back again it goes to ITEM2 (which it never should, its interesting to note this doesn’t make the dual redirect sound). After hitting back one more time it goes to the correct hash before anything was clicked.

    Any ideas on this issue, i tried copy and pasting the code to an html file and the results work perfectly like your example, soon as its on a blank aspx page i get the weird double post sound with a messed up history.

    -Chris

  • Chris, I’m really not sure what to suggest. Is it possible that the page you’re using has some JavaScript that conflicts with jQuery BBQ? Maybe you can systematically try to remove code until you find out what’s causing the conflict.

  • Its kind of funny, i was trying to test this quickly and lazily so I named my divs the same as the #hash (minus the # of course) which gives very bizarre results. I’m guessing the # handler code was inserting the history item and then the browser was also trying to do its own logic to quickly navigate to that div which was modifying the history a second time.

    Adding something as simple as _div to the names of all my divs fixed the problem, all though i still experience the “double navigate” sound which i haven’t pin pointed yet but moved pass.

    -Chris

  • Chris, the “double click” sound is going to occur when the hash is changed in IE6/7 because of the way history is maintained in those browsers. Not only is the page hash changed, but there’s also a hidden IFRAME that gets its hash changed, which accounts for the second click sound.

  • Good work Ben.

    I just want to share a small change I did. I was having problem with IE 6/7. The page was being always scrolled to the bottom after page load.

    I changed the way the iframe gets created. Instead of adding to the body I inserted as the first child. It did the trick.

    // Create hidden IFRAME at the end of the body.
    //iframe = $('<iframe src="javascript:0"/>').hide().appendTo('body')[0].contentWindow;
    iframe = $('<iframe src="javascript:0"/>').hide().insertAfter('body')[0].contentWindow;
    

    Hope it helps.

  • Discovered a small bug that affects only IE6.

    Location: the get_history method

    Symptoms - It seems that for a url like this one:
    http://www.example.com/#sometext?p1=value1

    IE6
    document.location.hash == ‘#sometext’
    document.location.search == ‘?p1=value1’

    IE7 and up
    document.location.hash == ‘#sometext?p1=value1’
    document.location.search == ”

    FIX - replace method with something like this

    get_history = function() {
        var ie6HashFix= iframe.document.location[ str_hash ] + iframe.document.location.search;
        return ie6HashFix.replace( /^#/, '' );
    };

    Thank you for a great piece of software!

  • Bogdan, great find! I’ve addressed this issue in the 1.0.3 release, which is available now. Please test it and let me know if the issues persist, thanks!

  • It’s all good. Thank you!

  • This is working really well for my new AJAX faceted search page. One thing I’d like to be able to do, and haven’t been able to figure it, is to remove something from the hash state.

    Say I add a state of #City=Atlanta. Then, if the user deselects Atlanta, I’d like to just go back to nothing in the state instead of #City=-Atlanta or something.

    Is there any way to do a $.bbq.removeState(‘City’)?

  • Jason, this is actually on my list of features to add into jQuery BBQ, I just haven’t been able to yet. Use this in the meantime: http://gist.github.com/248507

  • I am experiencing a bug in Firefox 3.5.5. The back button ignores all history and jumps back to the previous page (not AJAX loaded, but previous static page). It’s working fine in other browsers. Any ideas?

    Thank you

  • mrbrdo, the only hash history bug I’m aware of in Firefox is the remote XHR location.href hash update bug, so please check that link out and see if it applies to what you’re doing.

    Otherwise, since all the jQuery BBQ unit tests and examples appear to work perfectly in Firefox 3.5.5, I have no idea what the problem might be. Perhaps you can pare your code down a bit to find out what exactly is causing the problem.

  • Great job! To bad that I don’t find accordion handling like UI tabs :-)

  • I also ran into the problem in IE7 the binding to hash change causes the page to scroll to the bottom.

  • Hey, I’m testing your plugin and I have a little problem. I’ve set up a test page with two links, one which loads another html page (with jquery .load()) and the second should be a “home” link, which clears every hash. I have use this code:

    $(window).bind("hashchange", function(e){ 
      var url = e.getState("url");
      $("#load").load(url);
    });
    

    Now the problem is, if I click at first on the home link it loads the page a second time (if I click on the test link at first and then at the home link I just come back to the home site)

    I have two possible solutions for this problem, but I’m not really satisfied with them. The fist would be to load the url differently with the “home” link: onclick="window.location.href='history_test.html'; return false".
    And the second would be to check the url with an if.

    I can live with the first solution, but as I said before I’m not really satisfied with it, so I just wanted to know if there’s a better way to handle this.

  • Is there anyway to make this plugin store a JS object assosiated to a certain hash? This way I could not only use it for ajax, but for animations and stuff too

  • Hey there Ben, I think I’ll detail my question a little bit more :)

    I’d like to make ajax calls between pages passing plenty of parameters from one page to another but only displaying in the address bar a small portion of information, maybe a more stylized hash for example

    I’d like to send this params “a=1&b=2&c=3&d=4” to a specific page, such as “script1.php” and I’d like my hash to look like “mysite.com/#a/1/b/2” whereas I’m not displaying c or d parameter either I’m showing that the page I’m calling.

    basically I’m trying to store more info that it is to a specific hash :P

  • Markus, calling $.bbq.pushState(); with no arguments will clear the current hash, leaving just a “#” at the end of the URL. In some browsers, if you remove the “#”, even with an empty hash, the page will reload.

  • PERR0_HUNTER, you can do that easily, Just maintain a data store in your page that uses the hash as a key.

    The example below uses a simple object, but you could just as easily use cookies, localStorage, or window.name to make your cache a bit more permanent. What I’ve described here is essentially the way the HTML 5 history interface works (or will work).

    Note that since the whole state isn’t represented in the hash, it won’t necessarily be bookmarkable the same way.

    The example:

    var cache = {};
    
    $(window).bind( 'hashchange', function(e) {
      var key = $.param.fragment(),
        obj = cache[key] || {};
    
      // Do something with obj!
    });
    
    function set_hash( key, obj ) {
      cache[key] = obj;
      $.bbq.pushState( '#' + key );
    };
    
    set_hash( 'some_id', { my: 'large', data: 'object' } );
    
  • Great plugin. I got it working perfectly with the jquery ui tabs example, but I can’t figure out if there is a way to make it work for links inside of the tabs as well? So that once I click on a tab, I can then navigate to a new page within that tab, and have the tab and the page bookmark-able.

    Does that make any sense? If not let me know and I can post an example.

    Thanks!

  • This plugin is simply awesome, congrats Ben!! ;)

    I have only one question: is there a way to force the reload of a page when clicking on the same link twice? What I mean is: in my website I have a user menu in a separate DIV and when I click on a link in the menu (like a <href=”photos.php”>Photo</a>) the photo page shows up (in another div). Now if I click again on the same link to refresh the page nothing happen… I must first change page and then comeback (or use the refresh button of the browser). How I can make the page refresh everytime I click on a link ignoring if I’m already in that page? Thank you!

  • Marco, since jQuery BBQ utilizes the window.onhashchange event, the event callback will only ever trigger when the hash changes. Which means, by definition, if you click the same link twice, nothing will happen the second time.

    Of course, while the URL doesn’t change, you could use add a “cache busting” parameter to each link’s fragment which would change on every click, regardless.. for example, the code below sets up a clicks counter, then on every link click adds a c=clicks param into the fragment of that link. Because the counter is incremented every time, no two clicks will ever produce the same fragment, which will guarantee the event triggers!

    var clicks = 0;
    
    $('a').click(function(){
      $(this).fragment({ c: clicks++ });
    });
    
  • Ben— thank you so much for the bbq plugin. I heard you on the jQuery podcast and was able to use it on a project where I had the UI tab problem w/ the back button. It took just a few minutes to get it up and running. Great work, bro!

    Rob

  • Hi Ben! Thanks for this awesome plugin. I am facing the following problem: I have requirement where I have multiple fragments i.e. #workspace=’this’&module=’that’&node=’those’ which are hierarchical in nature (workspace->module->node).

    How can I delete unrelated fragments from the URL and do pushState of the modified URL?

    for e.g. You are in this state, #workspace=’this’&module=’that’&node=’those’ if you now select a workspace called “them”. It should drop module and node, leaving only #workspace=’them’

    Choesang

  • Choesang, you’re basically serializing a flat data structure right now. ie, { workspace: 'x', module: 'y', node: 'z' } which results in 3 separate “top level” properties being set in the hash. Because all three properties are at the same level (effectively siblings), none of them are implicitly dependent on any of the others. If you delete “workspace” it doesn’t remove “module” or “node” because they are totally separate.

    What if, instead of using that flat data structure, you used a deeper data structure? for example, serlalize something like { workspace: { name: 'x', module: 'y', node: 'z' } }. That way, there’s only one “top level” property, and when it changes, everything under it is replaced with the new value (keep in mind that serializing deep structures requires jQuery 1.4, or jQuery 1.3.2 with the jQuery 1.4 .param method).

    // "flat" way - given an empty location.hash:
    $.bbq.pushState({ a: 1, b: 2, c: 3 });
    // hash is now #a=1&b=2&c=3
    $.bbq.pushState({ a: 4 });
    // hash is now #a=4&b=2&c=3
    
    // "deep" way - given an empty location.hash:
    $.bbq.pushState({ a: { b: 1, c: 2, d: 3 } });
    // hash is now #a[b]=1&a[c]=2&a[d]=3
    $.bbq.pushState({ a: 4 });
    // hash is now #a=4
    
    // or even using JSON - given an empty location.hash:
    $.bbq.pushState({ a: JSON.stringify({ b: 1, c: 2, d: 3 }) } );
    // hash is now #a={"b":1,"c":2,"d":3}
    $.bbq.pushState({ a: 4 });
    // hash is now #a=4
    

    Of course, you could either alternately use $.bbq.pushState( obj, 2 ); (“merge_mode” of 2) to completely erase any existing hash when changing your “workspace” parameter, or use $.bbq.removeState([ 'module', 'node' ]) whenever setting the “workspace” parameter.

    Either way, there are definitely a few options!

  • HOI. Ben,

    Thanks a lot for sharing your code with us (me). A donation wil come soon, because I appreciate your work. It works for me. It is easy to understand and your documentation is sufficient.

    At my company (Webfabriek Rotterdam) we already implemented BBQ in two projects. If you want to have a look at a those you can find the implementation of your code here:

    I’m sure that I’ll use your code more often. Hopefully you’ll go on developing jQuery BBQ.

    Cheers, Christine Fürst (aka @stinie)

  • Hi Ben, really nice work! However i can not get it to work properly using jquery-ui tabs. After some investigation it seems that also your example for jquery-ui tabs is not working as supposed to to.

    The problem is that the override of the “click”-Event is not working completely. Even if you return “false” in the click-event, the tab will still happily switch, leaving the url fragment out of sync.

    You can see this easily - just place a breakpoint in your example in the onHashChange eventhandler. Firebug will stop there, but the tab is already changed before your code gets to call $(this).tabs( 'select', idx );.

    I understand that this is not a problem of your code at all and also read your long mail to the jquery-ui mailinglist regarding this topic. I think the jquery-ui guys should try to follow your suggestion about decoupling of event and action, but it seems it will still take some time for that to happen (if at all…). Do you have any idea in the meantime how to get it to work properly?

  • Could you make jQuery.fn.querystring and jQuery.fn.fragment a getter as well? It is needed in one of my project, thanks.

  • Helianthus, while that’s a great idea, it would actually change the API, breaking some existing functionality. Until I can find a good way to add this functionality in, you can just do something like the following:

    $('a').each(function(){
      var url = $(this).attr( 'href' ),
        qs = $.param.querystring( url );
      // Do something with `qs` query string
    });
    
  • I found a simple workaround to the problem: Just use an event to trigger tab change that will never happen in reality, e.g. like this:

        var tabs = $("#myTabs").tabs({
            event: 'change'     // effectively this prevents the tab from changing ever on its own. I want to use bbq for that...
        });
        // use bbq to handle clicks on tabs.
        $('#myTabs ul.ui-tabs-nav a').click(function(){
            // ...
        });
    

    I used the ‘change’ event which will never be triggered by an anchor. To be on the total safe side i think you can also use a custom event like ‘mySpecialTabEvent’, but i did not bother to try it yet :-)

    Although this is a real ugly workaround it is working reliable and requires only a very small code change.

  • Michael, thanks for your observation and comments. I had no idea that the jQuery UI Tabs example was broken in that way. I remember testing it when I wrote it, but I must not have been thorough enough.

    The good news, however, is that I’ve fixed the problem. And the fix is actually very simple. Just take a look at the example page now and see if that helps you out!

  • Please help: I absolutely love BBQ. I have implemented it on a site that is launching before the weekend. The site seems to work in all browsers except IE. The errors seem to be stemming from BBQ. Is there any way you could take a look and help me debug? http://stage.unicornmedia.com/customers/tennis/

  • Jason, I looked at your code and reworked it (I didn’t test it, though). Take a look at this before-and-after gist to see if it helps.

    A few important things to note:

    • At the moment, $(window).bind( 'hashchange', fn ) needs to happen after DOM ready, so you must define it in a $(document).ready( fn ) event callback.
    • There’s no need to explicitly code all the logic in both the hashchange event callback and the DOM ready callback, just add it to the hashchange event callback and then .trigger the hashchange event!
    • Instead of calling $.bbq.getState( 'key' ) many times, which can make the code look a bit unwieldy, set var state = $.bbq.getState() and then reference state.key as needed.

    PS. I see you’re using a PHP JSON proxy. I have one of those, too!

  • Hi there. I’ve got a web app that has a mix of hrefs, onclicks, tabs, etc that I’m hoping to retrofit with BBQ. I’m not really sure where to begin (I guess that’s problem number one) but I’m especially stumped with what to do with the onclicks. I know this is kind of an open-ended question, but any advice or initial direction would be hugely appreciated.

    Thanks very much!

  • I’m noticing a curious behavior (bug?) in your examples, in IE6 & 7:

    http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/

    Steps to reproduce:

    1. Vist that page in IE6 or 7, and click on a few of the navigation links (Burgers, Chicken, Kebabs, etc.) Now, go back and forth through the history a bit with the back & forward buttons. Works fine.
    2. Now, click a few of those links just like in step 1), but then click the documentation link at the top of the page. Then, after the documentation page loads, hit the back button. You are presented with your last navigation selection, as you would expect.

    However, if you hit the back button again, trying to go back to a previous navigation selection (just like in step 1), you are now either kicked back to the page preceding your example page, or your back button is grayed out. So, leaving the page, and returning, you get your last history state, but no others.

    As far as I can tell, this only occurs in IE6/7. IE8 behaves as expected (as does Firefox).

    Any insight into why that might be happening?

  • Chris, I was just explaining this to someone just the other day. Because neither IE6 nor IE7 add history entries when the hash changes, a hidden Iframe is used to add artificial history entries that enable the back/forward navigation.

    Unfortunately, because the Iframe is drawn into the page dynamically, when you leave the page and come back again, all those history entries are lost, leaving only the last-visited hash.

    At the moment, I’m not sure of a good workaround for this issue, other than the most logical (yet least practical) one: tell people to stop using IE6/7.

  • Thanks, Ben!

    Yeah, this is what I’m finding. Whether it’s hand-rolled history code, the jquery history plugin, or yours, the same behavior occurs. I haven’t yet found a history implementation where the IE6/7 behavior doesn’t occur. Doesn’t mean it doesn’t exist, but I haven’t found it. If only IE6 & 7 would disappear. :-/

    Thanks again for your help!

  • Hmm, you might be able to fix the IE6/7 history issue by also storing your history values in a hidden form field and rebuilding your history stack from it when the page loads. I believe form field values are retained across all browsers when clicking the forward/back buttons and I think that YUI uses a similar technique.

    http://developer.yahoo.com/yui/history/

  • That was my first take at how to do it, and it worked in Firefox, but not IE. :-/ And this was with using a hidden form field that pre-existed on the page, not one that was dynamically created or anything. I’m curious to know how Yahoo solves it…

  • Hi Ben.

    First, thank you for this wonderful plugin.

    I have just completed a plugin that depends on yours BBQ: it allows for anchor-based-navigation that is either good for javascript-enabled or disabled clients (thus allowing Search Engines to index your pages properly). Demo page is available at http://vbolshov.org.ru/anchornav/

  • Hi Ben, awesome plugin!! I just converted over from your UrlUtil plugin to BBQ and I have a quick question. If I want to update the documents url do I just use window.location.href or is there a function in BBQ to help with this? Here’s a small example of what I am doing:

    this.ddlActiveFilter_Changed = function(sender) {
      var value = sender.options[sender.selectedIndex].value;
      window.location.href = $.param.fragment(window.location.href, { active: value });
    };
    
  • Chris, look at $.bbq.pushState(), it does exactly what you’re asking for!

  • Ben, one other quick thing that might be outside of the scope of this plugin, or it might be just another feature that I missed in the documentation.

    I have a page that has a selectbox that fires a pushState and modifies the hash state when an option is selected. All works well until you click a link to navigate away from the page and then click the browser’s back button. The hashchange event nor the document.ready event is fired and the browser (Firefox 3.6) doesn’t maintain the selected state of the selectbox.

    Is there any way to leverage your plugin to fire hashchange to correct the selectbox’s selected state? Is this completely outside the scope of this plugin? Thanks!

  • Chris, this shouldn’t be a problem. Just take a look at any of the examples, interact with them to change the hash, and then click to a new page (perhaps via the “Ben Alman” link in the top left).

    At this point, when you click the back button, the page should be restored to the last hash state!

  • FYI, the download links point to the master branch, not the tag.

  • Ben,

    I’ve been pondering this issue for about 5 hours now, I can’t seem to get bbq to work with livequery (hashchange function) ….

    the issue is this:

     $(window).bind( "hashchange", function(e) {
       //get url here... 
    }
    

    Everything is working fine on the first click (front page), it loads some ajax content, and an ajax menu with the same links as on the front page, i click a menu link one more time which works (#view=contact), then the second time i click a different link in the same menu… it travels to the real URL…. I have checked everything and read your docs inside and out, but can’t seem to find a solution to this issue… (nor does Google have an answer)

    I was able to add livequery function to everything but the hashchange events, as i seem to be getting errors when trying that… any ideas would be appreciated.

    Please note that I have my short links in the anchor name attr instead of the href attr for example:

    <a name=”15.9283” href=’/15.9283/ajax/2010/folder/’>LINK</a>

    this was to make the URL shorter #view=15.9283 and because encoded ‘/’ don’t look so great

    Jason

  • Jason, the livequery plugin monitors content added to or removed from the DOM via a list of predefined jQuery methods, and binds events to or unbinds events from these elements as necessary. It sounds like whatever livequery events you’re specifying are not being bound on the AJAX-loaded links for some reason.

    Now, I’ve never used livequery, but since the core jQuery.fn.live method handles events through delegation, I’d recommend using that for handling events on elements in containers that have their content updated via AJAX, like in my AJAX + BBQ example. It’ll be much more performant, too!

  • I have a question, when I use $.bbq.pushState with a “/” in the hash string then it shows up as “%2F” in the url, it works just fine but is there a way to show a slash instead of %2F?

Leave a comment

  • Markdown formatting is preferred, but you may use any of these HTML tags for style:
    a, b, i, br, p, strong, em, ul, ol, li, blockquote, pre, code.
  • Multi-line JavaScript code should be wrapped in <pre class="brush:js"></pre>
    (supported syntax highlighting brushes: js, css, php, plain, bash, ruby, html, xml)
  • Use &lt; instead of < and &gt; instead of > in the examples themselves.
  • Please preview your comment before submitting!