Earlier today I was looking at some HTML that was generated by ColdFusion. The HTML was simply a list of months and years where content existed in the database. Because the database was a bit old, the list was quite long. I wondered if I could use jQuery to create an automatic shortening of the list as well as creating a way to expand the list based on some user interface. Here is what I came up with.
First, let's create a long list from ColdFusion:
<ul id="list">
<cfloop index="x" from="1" to="25">
<cfoutput>
<li>Item #x#</li>
</cfoutput>
</cfloop>
</ul>
And the result display is - as you expect - quite large:

Ok, so next I loaded up jQuery and added a document.ready block:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
});
I began by selecting my UL:
//get the item
var item = $("#list");
Next I got the kids and checked to see if there were more than 10:
//get the children
var kids = item.children();
if(kids.length > 10) {
At this point we need to do 3 things. First, hide kids 11 through - whatever. Secondly add a link to let the user see the rest of the list. Finally - listen for a click on that link so we can actually show the items. Here is the first block:
for(var i=10; i<kids.length; i++) {
$(kids[i]).hide();
}
I could do that on one line, but I don't really see much benefit in that. I could also write this other ways. jQuery has a nice each() function that will iterate over stuff. But since I only want to go from 11-N, this seemed better to me.
item.append("<a href='' class='showMoreLink'>Show More</a>");
This - obviously - adds the link. Note the use of append. This will actually put the text in the same 'tab' as the rest of the list. I could have used after() but I kinda like how 'Show More' lined up.
$(".showMoreLink", item).click(function(e) {
item.children().show()
$(this).hide();
e.preventDefault();
});
And that is the final block. Note the use of item within the jQuery selector. This means that if I repeated this code multiple times then the event listener will only respond to the one of this particular list. I end up showing all the children which is a bit wasteful, but that syntax was too easy to pass up. I then hide the link. That means the list can be opened, but not closed. That seemed ok to me as I can't imagine wanting to toggle it back and forth - but I may come back to that. Finally I prevent the default click action. So how does it work? Here is the display:

And it works! You can click the big ole Demo button below to see. It also works fine with other types of parent/child relationships:
<div id="list2">
<cfloop index="x" from="1" to="25">
<cfoutput>
<div>Item #x#</div>
</cfoutput>
</cfloop>
</div>
But it did not work well with tables at all. Again, I'll come back to it. So - this works - but is pretty simplistic. In the next blog entry I'll convert this to a plugin so I can simply do: $("#list").autoShorten() while allowing for optional values for the number of items to show and what to use for the link text.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
//get the item
var item = $("#list");
//get the children
var kids = item.children();
console.log(kids.length);
if(kids.length > 10) {
for(var i=10; i<kids.length; i++) {
$(kids[i]).hide();
}
item.append("<a href='' class='showMoreLink'>Show More</a>");
$(".showMoreLink", item).click(function(e) {
item.children().show()
$(this).hide();
e.preventDefault();
});
}
})
</script>
<ul id="list">
<cfloop index="x" from="1" to="25">
<cfoutput>
<li>Item #x#</li>
</cfoutput>
</cfloop>
</ul>
Archived Comments
The demo doesnt seem to work, Ray.
Because - once again - I left a darn console.log in. Try in 60 seconds. Sorry.
Interesting bit of code. Not sure I'll use it immediately but it did spark an idea on how to handle something in the website redesign I'm doing now.
Also if you do a .live you won't have to re-bind this if you dynamically add a list of this nature to your page :)
jQuery is addictive :-) And every time I think "there ought to be a selector for that", there usually already is. A possible improvement to your script would be to replace your for loop with the :gt selector.
kids.filter(":gt(9)").hide()
Or, actually, now that I have looked through the jQuery docs some more:
kids.slice(10).hide()
Though slicing kids sounds extremely cruel.
@Dave: True - if I used a class instead of an ID, then I could make this work for N lists.
@David: Dude - awesome. I knew of gt, but I did not think I could apply it here - but your syntax makes perfect sense. When I do the plugin version I'll use it - take credit - and rule the world!
@David: What if we changed from
var kids = item.children();
kids.slice(10).hide()
to
var anklebiters = item.children();
Would that change your mind about slicing? ;-)
I agree with you - I don't use jQuery nearly as much as I could, and every time I look there's more I could use.
@Matt: You're right, that does sound less cruel! It does, unfortunately, still sound petty and vindictive.
Guys, follow up posted: http://www.coldfusionjedi.c...
I'm sorry for the question, but I am trying to learn more about jQuery. Even if you are not displaying the data, the large amount of data is still being dumped to the web page. Couldn't you use jQuery and JSON and use the "Show More" link to retrieve more data from the database? Wouldn't this be a more efficient means of controlling the data?
@Wade: Yes, you could switch to an Ajax based solution, and if my list was truly large, then it would make sense. In my mind, this was more a visual thing then anything else. 50 items in a list, for example, would be long to a reader, but as for HTML download speed, it really is trivial.
Like in most CF things, there are almost always multiple ways to do things in jQuery. :)