Yesterday I set out with the goal of creating some Frequently Asked Questions (FAQs), with the idea of using jQuery to provide some added functionality. That's when I stumbled across the Bit Repository. They provided a very nice way of accomplishing what I wanted, so I decided to follow their tutorial.
It gave me great inspiration and while implementing the idea, I decided to go ahead and improve upon what they had done. My solution had to be developed so it could easily be reused without having to touch any of the script's again. The following steps outline the approach I took and the accompanying results. Enjoy!
Step 1: Download the following jQuery and jQuery UI libraries. And while you're at it, pick up the jQuery scrollTo plug-in.
Step 2: Now that we have downloaded the necessary scripts, we have to structure our frequently asked questions. Using an unordered list, we'll add an ID to each list item (question) that corresponds to that questions number and wrap that question with an anchor tag.
<ul id="questions"> <li id="q1"><a href="#a1">Question 1?</a></li> <li id="q2"><a href="#a2">Question 2?</li> <li id="q3"><a href="#a3">Question 3?</li> <li id="q4"><a href="#a4">Question 4?</a></li> <li id="q5"><a href="#a4">Question 5?</a></li> </ul>
Now the only real surprise here might be the anchor tag linking to "#a1" or "#a2", etc. These href's will correspond with our answer's ID's. That way if for some reason JavaScript isn't enabled, the FAQ will still work and take your visitor to the appropriate answer. This is one aspect that wasn't accounted for in Bit Repository's tutorial that I needed to have.
Step 3: Followed by our question HTML will be the HTML for our answers. Each answer will be encompassed in a div that has an id which corresponds to it's above questions href. (i.e. <a href="#a1"> links to <div id="a1">)
<div id="a1"> <h2>Question 1?</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent ornare feugiat diam sit amet semper. </p> </div> <div id="a2"> <h2>Question 2?</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In interdum enim sem, in lacinia turpis. Ut dignissim posuere lectus et fermentum.</p> </div> <div id="a3"> <h2>Question 3?</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce porta, nunc a adipiscing scelerisque, leo ante iaculis nisl, eget molestie mi ipsum eget justo.</p> </div> <div id="a4"> <h2>Question 4?</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi vitae elit eros, a luctus nisl. </p> </div> <div id="a5"> <h2>Question 5?</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nec magna tellus, nec tempor metus. </p> </div>
Step 4: With our questions and answers structured, we need to begin adding functionality to them. For starters we'll need to reference our scripts, that we downloaded earlier, within the <head> section of our page.
<script src="/your-path/jquery.js"></script> <script src="/your-path/jquery-ui.js"></script> <script src="/your-path/jquery-scrollTo.js"></script>
NOTE: You do not have to host the jQuery or jQuery scripts on your server. Check out the Google AJAX Libraries for more info.
Step 5: Next we need to setup our script that will make all of this work. We'll start with some jQuery that will be used to let us know when the DOM is ready. This too, should go within your <head> section as well, if possible (after the above script declarations), or better yet, in a separate .js file altogether:
<script type="text/javascript"> $(function() { // code here }); </script>
This syntax is shorthand for doing it the old $(document).ready(function(){ // code here }); way.
NOTE: If you use multiple libraries (Prototype, MooTools, etc.) it is usually a good idea to make sure jQuery will not interfere with them. If this is the case, consider using the following script instead of what was just provided. This will allow you to use the rest of the code snippets below, as-is, without issue.
<script type="text/javascript"> jQuery.noConflict(); jQuery(function($) { // code here }); </script>
Step 6: The DOM is ready and now it's time to make the magic happen. We'll start by adding a click event to all of our questions.
<script type="text/javascript"> $(function() { // step 6 $("#questions > li").click(function() { // click events }); }); </script>
Step 7: This step is an important one. I want to create a single function() that will serve ALL questions, instead of creating a function for each question, like in the Bit Repository tutorial.
<script type="text/javascript"> $(function() { $("#questions > li").click(function() { // step 7 var qu = $(this).attr("id"); var an = "#" + qu.replace("q","a"); }); }); </script>
The variable "qu" gets the ID of the question that was clicked. For example, if I click on question 2, "qu" would equal "q2". With that ID, we can create another variable, "an", that will match our answer's id b/c we have replaced the "q" with an "a".
Step 8: Setting up our scrollTo() method is quite easy at this point. All we have to do is apply the method to our answer ID which is what the "an" variable is.
<script type="text/javascript"> $(function() { $("#questions > li").click(function() { var qu = $(this).attr("id"); var an = "#" + qu.replace("q","a"); // step 8 $.scrollTo(an, {duration: 800, axis:"y"}); }); }); </script>
The first thing we specified in the $.scrollTo(...) method is the answer ID we want the our clicked question to scroll to. Really that's all you need. However, I decided to set an allotted duration for how long I want the slide to take. I also specified that I wanted it to slide down via the y-axis. There are several more options you can specify here, simply refer to the scrollTo documenation.
Step 9: At this point we have a working prototype. However, the effect isn't complete. As the page scrolls from the question to the answer, it's quite likely your user gets lost along the way. To combat this, we need to add a highlight effect. This portion also differs than Bit Repository's in the sense that I didn't use a third-party plug-in for my highlighting. Instead, I just used the built-in one that comes bundled within jQuery UI. To add it, we need to add the "onAfter:" option to our $.scrollTo(...) declaration. The attached function will now execute AFTER the scroll.
<script type="text/javascript"> $(function() { $("#questions > li").click(function() { var qu = $(this).attr("id"); var an = "#" + qu.replace("q","a"); $.scrollTo(an, {duration: 800, axis:"y", onAfter:function(){ // step 9 $(an).effect("highlight", {color: "#99c0e1"}, 2000); } }); }); }); </script>
The .effect("highlight", {...}) method accepts a color argument which can be any hex color you'd like. The integer 2000 determines how long it will take the highlighted region to completely fade.
Step 10: At this point, you might think our work is done. However, if you have a cumbersome and detailed FAQ, it could be quite lengthy. We need to add a button for returning to the top. This requires a few changes to our answers HTML and an additional function within our script.
First, we'll add an image within our <h2> tag and give it a class of "return-top". Below I show how this is done for answer 1, but it should be done for ALL answers.
UPDATE: Thanks to Justin for pointing this out in the comments section. To make this unobtrusive the <img> should be wrapped with an anchor tag.
<div id="a1"> <h2><a href="#top" class="return-top" onclick="return false;"><img src="/path/faq-return-to-top.gif" alt="return to top" class="return-top" /></a>Question 1?</h2> ... </div>
NOTE: You can use text for this if you'd like. Just simply throw a <span> tag around it with a class of "return-top".
Step 11: Next we'll add the return to top functionality to our script. This requires adding a click event to "return-top" and giving it the ability to scroll back to the top.
<script type="text/javascript"> $(function() { $("#questions > li").click(function() { var qu = $(this).attr("id"); var an = "#" + qu.replace("q","a"); $.scrollTo(an, {duration: 800, axis:"y", onAfter:function(){ $(an).effect("highlight", {color: "#99c0e1"}, 2000); } }); }); // step 11 $(".return-top").click(function() { $.scrollTo("body", {duration: 800, axis:"y"}); }); }); </script>
NOTE: I simply referenced the "body" attribute so the page returns all the way back to the top of the page. Substitute "#questions" if you want it to just return to the top of the questions.
Step 12: In all honesty, we could consider ourselves finished. However, I'm a perfectionist. You see, this next fix keeps your page from jumping down to the answer, then back to the question only to finally scroll back down to the question. This happens extremely fast, so it's not too bad, but it is noticeable.
The issue comes from having a "#" in an href. A click will cause the page to jump to up to the top. Having a "#id" inside an href causes it to jump to that id. This is something we don't want, since we will be scrolling to what we want displayed. This step is also something I've added to the Bit Repositories functionality. I don't like the way I have to accomplish it, because i don't like adding onClick to links, but it's the only way I know how to resolve the issue.
Without further ado, this is what you need to do. You must add an onClick="return false;" to your Quesiton <li> anchor tags. This will eliminate the jumping around.
<ul id="questions"> <li id="q1"><a href="#" onClick="return false;">Question 1?</a></li> ... <li id="q5"><a href="#" onClick="return false;">Question 5?</a></li> </ul>
Bonus Step: No one wants to use the default browser styles, so I have taken the liberty and provided you some CSS. This version is included with the source files, so you can decide which version you'd rather use.
<style type="text/css"> body { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } #container { margin: 0 auto; width: 700px; } h1 { font-size: 18px; font-weight: normal; } h2 { color: #666; font-size: 14px; } #questions { margin-bottom: 20px; padding-left: 0; margin-left: 20px; } #questions li { background: url(question-mark.png) no-repeat left center; line-height: 14px; list-style-type: none; margin-bottom: 10px; padding: 5px 0 5px 30px; } div.faq-a { border-bottom: dotted 2px #ddd; padding: 10px 0 7px; } .return-top { float: right; cursor: pointer; padding-left: 20px; } </style>
You're Done!
If you have any questions/comments about how something is done or how I can improve this further, I'd love to hear from you. Just post a comment below and I'll respond, if necessary.
Great tutorial! I made the
Great tutorial! I made the "return to top" on my site a little different. I did it like this:
That way if javascript is disabled the link will still work.
Nice Catch
D'oh...I missed that one. For those using this, I'd recommend doing it as Justin did as well.
EDIT: I have updated the tutorial to include this
One more thing...
If you wanted to bookmark that anchor for future reference you can't unless JS is turned off. Might I suggest a change to allow for an append to the URL with the proper anchor in order to enable proper bookmarking:
http://www.example.com/article.html
- to -
http://www.example.com/article.html#anchor
First, change all of the question DIVs back to anchor tags and use spans and styles to generate the same look of the headers and paragraphs (set the anchors to display:block). In fact it probably better if you're making a list of questions to use an ordered list here. This way you can use the traditional internal anchors (#a1,#a2,etc.).
Second, no all you need to do to get the proper element to scrollTo is use:
var an = $(this).attr("href");
Third, drop all of the return false and onclick attributes, they're not needed. Replace this functionality by adding the event to the following line:
...
$("#questions > li").click(function(e) {
...
NOTE: the "e" as the function parameter.
Finally, to stop that back and forth you noted, in the onAfter event for scrollTo add the following line:
e.preventDefault();
That will stop the link from jumping, and the best part is the anchor will be properly appended to the URL to allow for proper bookmarking.
Having a problem..
Hi Cory, great tutorial! Well documented and very clear.
BUT .. I am having an issue:
I want to implement just the functionality up to Step 8 (just a simple scrollTo).
My markup is equivalent to the example you use:
My Index (your "Questions") looks like this:
My Sub-titles (your "Answers") looks like this:
My javascript file looks like this:
$(function() { $("#index > li").click(function() { var fromIndex = $(this).attr("id"); var toTitle = "#" + fromIndex.replace("index","sub"); $.scrollTo(toTitle, {duration: 800, axis:"y"}); }); });When I click on the anchor, it does a normal jump: ie. toScroll is not working.
I have checked all of the javascript code and it all outputs the correct value. The only issue is with the line:
$.scrollTo(toTitle, {duration: 800, axis:"y"});.. which it says is not defined.
I have checked and jQueryScrollTo.js is being loaded, as it appears in the header. (I am also using Drupal 6 so added the js in the my_theme.info file)
I have used several jQuery scripèts before with success so I am a little confused as to why I cannot get this one to work.
Any ideas?
Could you post all your code
Could you post all your code at JSBin.com or Pastie.org? Glancing at your code, it looks like that should work. I'm wondering about the order of your references.
References?
Hi Cory,
What do you mean by "references"?
I don't know what other code I can show you, I already pasted it all onto the previous post.
Your script references. Do
Your script references. Do you have them in the correct order?
Hi Cory, I checked the order
Hi Cory,
I checked the order of my references and all is fine. I even added the toScroll script to just before my code and it still didn't work.
I am certain that this error has something to do with the embedded YouTube flash I have on the page:
When I click on the anchor link (to scroll), the embedded players reload the thumbnail video image. When I remove the toScroll code, it does not.
Looks like I will need to find a work around.
I've resolved the issue
I've resolved the issue now.
The problem was with a dev module I had installed (Service Links 2.x) which can display a block using a js fisheye effect. It seems that this effect was creating some issues with the widths of my pages (I will need to investigate more).
After removing the code, the scrollTo effect works perfectly. Thanks for your help!
No problem! Glad you figured
No problem! Glad you figured out what the issue was!