PNWDC Logo
 
 


 

Accordion FAQ with Searching and Highlighting

Wednesday, March 2, 2011 1:02:11 AM

Recently I was working on a website redesign and one of the items that was specified in the requirements was a Frequently Asked Questions (FAQ). These are pretty run of the mill these days. In the design they showed a search box for the FAQ in addition to the full site-wide search. They also wanted collapsible (accordion) sections with collapsible questions within each section. Aside from the search, not a biggie.

Now, normally I would go back to the customer and seek some clarification, but this time I decided I would try something, just to see if I could do it. I didn't want to implement a server-side solution to the searching, since everything was on this page and already loaded. I figured there would be a way to accomplish all of my goals with a little bit of JavaScript and the handy jQuery library. As it turns out it was not as easy as I thought it would be, yet the amount of code that it required was still incredibly minimal.

The FAQ structure

The structure of the FAQ that I was provided was pretty basic, nothing fancy or out of the ordinary to see here.

<div>
	<label for="FAQSearch">Frequently Asked Questions Search</label> 
	<input type="text" name="FAQSearch" id="FAQSearch" />
	<input type="submit" id="SearchFAQ" value="search" />
</div>

<div>
	<a id="ExpandAll" href="javascript:void(0);">Expand All</a> / 
	<a id="CollapseAll" href="javascript:void(0);">Collapse All</a>
</div>

<div id="FAQ">
	<h3 class="Topic">Topic 1</h3>
	<div class="TopicContents" style="display: none;">
		<h4 class="Question">Question #1-1</h4>
		<div class="Answer" style="display: none;">Answer #1-1</div>
		<h4 class="Question">Question #1-2</h4>
		<div class="Answer" style="display: none;">Answer #1-2</div>
		<h4 class="Question">Question #1-3</h4>
		<div class="Answer" style="display: none;">Answer #1-3</div>
		<h4 class="Question">Question #1-4</h4>
		<div class="Answer" style="display: none;">Answer #1-4</div>
		<h4 class="Question">Question #1-5</h4>
		<div class="Answer" style="display: none;">Answer #1-5</div>
		<h4 class="Question">Question #1-6</h4>
		<div class="Answer" style="display: none;">Answer #1-6</div>
	</div>
	<h3 class="Topic">Topic 2</h3>
	<div class="TopicContents" style="display: none;">
		<h4 class="Question">Question #2-1</h4>
		<div class="Answer" style="display: none;">Answer #2-1</div>
		<h4 class="Question">Question #2-2</h4>
		<div class="Answer" style="display: none;">Answer #2-2</div>
		<h4 class="Question">Question #2-3</h4>
		<div class="Answer" style="display: none;">Answer #2-3</div>
		<h4 class="Question">Question #2-4</h4>
		<div class="Answer" style="display: none;">Answer #2-4</div>
		<h4 class="Question">Question #2-5</h4>
		<div class="Answer" style="display: none;">Answer #2-5</div>
	</div>
</div>

Like I said, pretty run of the mill stuff. But what this lacks is functionality to actually perform the searching and expanding. Lets take a look at the JavaScript to see what I did.

The script that makes it all happen

<script>
	$('h3.Topic').click(function () {
		$(this).next().toggle(300);
	});
	$('h4.Question').click(function () {
		$(this).next().toggle(300);
	});
	$('#ExpandAll').click(function () {
		$('#FAQ').children('div.TopicContents').show(300).children('div.Answer').show(300);
	});
	$('#CollapseAll').click(function () {
		$('#FAQ').children('div.TopicContents').hide(300).children('div.Answer').hide();
	});
	jQuery.expr[':'].Contains = function (a, i, m) {
		return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
	};
	$('#SearchFAQ').click(function () {
		$('#FAQ').children('div.TopicContents').hide().children('div.Answer').hide();
		if ($('#FAQSearch').val() != '') {
			$('div.Answer:Contains(' + $('#FAQSearch').val().toUpperCase() + ')').show().parent().show(300);
			try {
				$('.highlight').removeClass("highlight");
				$('div.Answer:Contains(' + $('#FAQSearch').val().toUpperCase() + ')').each(function () {
					$(this).html(
						$(this).html().replace(
							new RegExp($('#FAQSearch').val(), "ig"), 
							function(match) {
								return '<span class="highlight">' + match + '</span>';
							}
						)
					)
				});
			}
			catch (err) {
			}
		}
		return false;
	});
</script>

Lets break this up into individual functions.

The Accordion Effect

	$('h3.Topic').click(function () {
		$(this).next().toggle(300);
	});

This should be pretty straight forward. When the user clicks on a Topic it toggles the visibility of the next tag, which should always be a div with class TopicContents.

	$('h4.Question').click(function () {
		$(this).next().toggle(300);
	});

This is almost identical to the code before, except this toggles the element following an h4 with class Question. This should be a div with class Answer.

	$('#ExpandAll').click(function () {
		$('#FAQ').children('div.TopicContents').show(300).children('div.Answer').show(300);
	});

When the user clicks the Expand All link at the top this will set all divs with the class TopicContents to visible and then set all child divs with class Answer to visible.

	$('#CollapseAll').click(function () {
		$('#FAQ').children('div.TopicContents').hide(300).children('div.Answer').hide();
	});

Much like the previous item but in reverse. This will set the visibility of all divs with class of TopicContents or Answer to hidden.

Searching

	jQuery.expr[':'].Contains = function (a, i, m) {
		return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
	};

This was a major piece of the magic that made the searching work. This adds a special selector, :Contains, that will match with case insensitivity. It does this by converting the text to search to upper case and the text you are searching for to upper case and then performs the actual comparison. I did not write this but do not recall which site I had pulled this off of. I believe it was on the jQuery forums or Stack Overflow.

	$('#SearchFAQ').click(function () {
		$('#FAQ').children('div.TopicContents').hide().children('div.Answer').hide();
		if ($('#FAQSearch').val() != '') {
			try {
				$('.highlight').removeClass("highlight");
				$('div.Answer:Contains(' + $('#FAQSearch').val().toUpperCase() + ')').each(function () {
					$(this).show().parent().show(300);
					$(this).html(
						$(this).html().replace(
							new RegExp($('#FAQSearch').val(), "ig"), 
							function(match) {
								return '' + match + '';
							}
						)
					)
				});
			}
			catch (err) {
				alert(err);
			}
		}
		return false;
	});

This is the entire searching function. Lets go through it line by line:

  • When the user clicks the search button
    • Collapse all Topics and Questions
    • If the user actually typed something into the search box
      • Try the next block of code (so we can handle any errors gracefully)
        • Remove all highlighted terms from previous searches
        • Using the previously mentioned selector, look for the term typed in the search box in all divs with class Answer and execute the following code
          • Set the answer and parent topic to visible
          • change the Answer's HTML contents to:
            • Perform a search and replace on the Answer's HTML
              • Create a new case insenstive regular expression with the search term
              • Perform the following when it finds the search term
                • Wrap the search term in a span with class highlight
      • Catch any errors
        • Show an alert with the error - for debugging purposes
    • return false so that it does not cause a page post-back/form submission.

In my css file I just set .highlight to 'background-color: yellow;' as a quick demo.

Hope you enjoyed this and learned something from it. It was quite an adventure getting all of the pieces to line up properly. The hardest parts to get working, mostly due to my lack of experience with JavaScript/jQuery, was the Regular Expression setup and the proper order to perform the HTML substitution. Even when it ran without errors it would not change any of the text. May my battle be your benefit.



View the demo


Comments


Leave Comment

  

  

  




Are you human? Prove it!


 
 
 
All content copyright © 2009 Pacific NW Data Consultants