{"id":14415,"date":"2011-09-26T18:55:26","date_gmt":"2011-09-27T01:55:26","guid":{"rendered":"http:\/\/css-tricks.com\/?p=14415"},"modified":"2017-11-29T19:33:16","modified_gmt":"2017-11-30T02:33:16","slug":"better-linkable-tabs","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/better-linkable-tabs\/","title":{"rendered":"Better Linkable Tabs"},"content":{"rendered":"

I find it’s a common desire with “tab” design patterns that there is a way to link to specific tabs. As in, you could give someone a URL and that URL would lead them to that page with the specific desired tab active and displaying the correct content.<\/p>\n

If each tab is a completely different URL, it’s easy. In the HTML that is served, the appropriate class name is applied to the tab to make it look active, and the appropriate content below is served. <\/p>\n

\"\"
Wufoo uses a tab design pattern inside the app. Each tab is a entirely different page and URL.<\/figcaption><\/figure>\n

If these tabs are “same page” tabs that hide\/show different panels of content instantly (or via Ajax), it’s a bit harder. A common (and I’d argue semantic) approach to this has been with hash links, e.g.:<\/p>\n

http:\/\/example.com\/#tab-three<\/code><\/pre>\n
\"\"
Tabs like these are likely “same page” tabs that don’t have unique URL’s to themselves<\/figcaption><\/figure>\n

With CSS3’s :target<\/code> pseudo class selector, there are ways to make functional tabbed areas with CSS<\/a> alone (no JavaScript). However, if pure CSS tabs are the goal, this is better<\/a>.<\/p>\n

As much as I’ve experimented with CSS techniques, I think functional tabbed areas are best accommodated by JavaScript. This is functionality territory, and if we adhere to the traditional model of separation of concerns, it should be handled by JavaScript.<\/p>\n

Hash links have a few other problems:<\/p>\n

    \n
  1. When the page loads with a hash link or<\/em> the hash links changes, the browser will scroll down<\/strong> so that the element with the ID of that hash is at the top of the page. There is a good chance this is not desirable. The latter is easy to fight with preventDefault()<\/code>. The former is nearly impossible to deal with cleanly.<\/li>\n
  2. Changing the hash tag of a page adds an entry to the browser history, so pressing the back button<\/strong> will go back through previous hashes. There is also a good chance this is not desirable.<\/li>\n<\/ol>\n

    Let’s solve both problems while accommodating the desire to have a way to link to a specific tab. This is what we are going to do:<\/p>\n

      \n
    1. Not use hash links but use URL parameters instead (no jump downs).<\/li>\n
    2. Use the ultra-hip history.replaceState()<\/code> so we can change the URL without affecting the back button.<\/li>\n<\/ol>\n

      Our URL’s will be like:<\/p>\n

      http:\/\/example.com\/?tab=tab-three<\/code><\/pre>\n

      Rather than re-write JavaScript based tabs from scratch, let’s use my existing Organic Tabs<\/a> demo. We need to adjust very little<\/em> of the plugin to make this work. We’ll add a param parameter so people can choose whatever they want there.<\/p>\n

      $.organicTabs.defaultOptions = {\r\n    \"speed\": 300,\r\n    \"param\": \"tab\"\r\n};<\/code><\/pre>\n

      Then in the part with all the tab-changing functionality, we’ll add this one line:<\/p>\n

      \/\/ Change window location to add URL params\r\nif (window.history && history.pushState) {\r\n  \/\/ NOTE: doesn't take into account existing params\r\n\thistory.replaceState(\"\", \"\", \"?\" + base.options.param + \"=\" + listID);\r\n}<\/code><\/pre>\n

      The tricky part is that we need to pull in the URL parameter when the page loads and do JavaScript stuff with it. That means we’ll need to use a server-side language, and intermingle some of it with our JavaScript. Here I’ll use PHP:<\/del><\/p>\n

      Update: We can access the query string entirely through JavaScript, and parse out the parts easily<\/a>. So instead of the PHP that used to be here (not ideal for many reasons) we’ll do this instead:<\/p>\n

      var queryString = {};\r\nwindow.location.href.replace(\r\n    new RegExp(\"([^?=&]+)(=([^&]*))?\", \"g\"),\r\n    function($0, $1, $2, $3) { queryString[$1] = $3; }\r\n);\r\n\r\nif (queryString[base.options.param]) {\r\n\r\n\tvar tab = $(\"a[href='#\" + queryString[base.options.param] + \"']\");\r\n\r\n\ttab\r\n\t\t.closest(\".nav\")\r\n\t\t.find(\"a\")\r\n\t\t.removeClass(\"current\")\r\n\t\t.end()\r\n\t\t.next(\".list-wrap\")\r\n\t\t.find(\"ul\")\r\n\t\t.hide();\r\n\ttab.addClass(\"current\");\r\n\t$(\"#\" + queryString[base.options.param]).show();\r\n      \r\n};<\/code><\/pre>\n

      This code grabs that URL param and make sure the current tab is highlighted and the correct content is shown.<\/p>\n

      Functional demo:<\/p>\n

      View Demo<\/a>   Download Files<\/a><\/p>\n

      Video:<\/p>\n