Revealing Details with jQuery
A week or so ago, someone posted a comment on one of my previous articles, asking if I could help her split up the textual content of an element, showing the first part and replacing the second with a link that, when clicked, would reveal the text. This behavior would appear in an FAQ using a definition list (<dl>
), with each question contained in a <dt>
and each answer contained in a <dd>
. I soon realized that the solution would be rather involved, so I decided to create a new entry out of it rather than simply answer her question in another comment.
Here is the simple definition list structure that I'll be using for the example:
[html] [/html]First Steps
As usual, we start the script with a "document.ready" line so that it can be fired when the DOM has finished loading. Next comes our primary selector expression. For this example, I've given the targeted <dl>
element a class of "expander" so that we don't inadvertently attach the behavior to other <dl>
s. And since we're manipulating the contents of each <dd>
independently of one another, we can make things happen inside an .each()
method:
We ought to declare some variables now. The first two will go right after the document.ready line, while the others will appear inside the .each()
method because they change with each <dd>
element.
- slicePoint (integer): the number of characters at which the contents will be sliced into two parts. Note: any tag names in the HTML that appear inside the
<dd>
before the slicePoint will be counted along with the text characters. - widow (integer): this is a threshold of sorts for whether we want to initially hide part of the
<dd>
's contents. If after slicing the contents in two there are fewer words in the second part than the value set by widow, we won't bother hiding anything.
- allText (string): the full contents of the
<dd>
- startText (string): the first part of the
<dd>
's contents — the part that is immediately visible. First, all characters up to the slicePoint are included, and then any "word" characters at the end of the string are removed to avoid ending with a partial word. Keep in mind that in almost every case this approach will leave us with fewer characters than we indicated in the slicePoint variable - endText (string): the second part of the
<dd>
's contents. This part is initially hidden from view, replaced by a "read more..." link.
Here is what we have so far:
[js]$(document).ready(function() { var slicePoint = 100; var widow = 4; $('dl.expander dd').each(function() { var allText = $(this).html(); var startText = allText.slice(0,slicePoint).replace(/\w+$/,''); var endText = allText.slice(startText.length); }); });[/js]Although jQuery has its own .slice()
method, which operates on a jQuery object, we're using JavaScript's native method, which can operate on both strings (as is the case here) and arrays. Our first use of .slice()
, declaring the startText variable, has two arguments — one for the beginning position of the string and one for the ending. Our second has only one argument, for the starting position; the (allText) string's length is the implied ending position. Also, note that for startText, we're chaining .slice()
and the .replace()
regular expression method.
Manipulating the Contents
Now that we have our two blocks of content, we're going to add a "read more..." link after the startText and wrap the endText in a span (with a class of "details"), but only if there are more words than the value defined by the widow variable. (tangent: am I the only one who can't type "widow" right the first time, always typing "window" instead?). There are many ways to create new elements and insert them into the DOM with jQuery. For this example, we're going to use the .html()
method, create an array of strings inside it, and join those strings.
I picked up that technique of creating an array and joining its elements from the jQuery Google Group. It seems cleaner than string concatenation somehow, and rumor has it that it's a bit faster, too.
Finishing Touches
We have everything in place now, so all we need to do is hide the content inside our newly created <span class="details">
and attach a click handler to the "read more" link. Here is the completed code:
I used a .fadeIn()
because I like the way it looks, but a simple .show()
would do. The return false;
line is important to prevent a jump to the top of the page on click.
the Demo
Here is a little demo: