Yesterday I wrote a quick proof of concept that demonstrated jQuery code that would shorten a list of content to a set max and automatically add a 'Show More' link. The content was output by ColdFusion and could be any size. The JavaScript would check to see if there were more than 10 items and remove the ones after the 10th as well as adding the link to redisplay the content. This morning I quickly rewrote the code into a plugin (and took some of the advice I got on the last blog entry). Here is what I came up with.
First - let me begin by pointing you to the jQuery docs on plugin writing: Plugins/Authoring. This is a pretty straight forward guide to how plugins should be developed in jQuery. This was only my second plugin so I definitely had to reread this doc. I'll be honest and say that some of what the guide talks about is still a bit confusing to me - but I was able to understand enough to
(function($) {
$.fn.autoShortener = function(options) {
return this.each(
function() {
var settings = {
"length":10,
"message":"Show More"
};
if(options) $.extend(settings, options);
//get the children
var item = $(this);
var kids = item.children();
if(kids.length > settings.length) {
kids.slice(settings.length).hide();
$(this).append("<a href='' class='showMoreLink'>" + settings.message + "</a>");
$(".showMoreLink", item).click(function(e) {
item.children().show()
$(this).hide();
e.preventDefault();
});
}
}
);
};
})(jQuery);
For the most part this is the same logic as you saw in the blog entry yesterday - I just wrapped it in the proper format for plugins. I did make the length and message as options that you can override at runtime. Where things get real cool is on the front end. Check out the code now:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="jquery.autoshortener.js"></script>
<script>
$(document).ready(function() {
$(".list").autoShortener();
})
</script>
<ul id="list" class="list">
<cfloop index="x" from="1" to="25">
<cfoutput>
<li>Item #x#</li>
</cfoutput>
</cfloop>
</ul>
<div id="list2" class="list">
<cfloop index="x" from="1" to="25">
<cfoutput>
<div>Item #x#</div>
</cfoutput>
</cfloop>
</div>
That's a heck of a lot smaller. And if I want to override the options I could do something like this:
$("#list").autoShortener({length:8});
$("#list2").autoShortener({message:"More"});
You can see that version at the demo link below. Anyway - that's it. I'd love for this blog entry to be longer, more complex, etc., but jQuery just makes things to darn simple.
Archived Comments
It's better to move the var settings =.., and the $.extend out of your this.each loop. You only need to overwrite the options once not on each DOM node the each loop encounters. This saves you a few JS object creations and $.extend method calls.
Sure, it works the way it is atm.. but it's not optimal :)
See updated gist: http://gist.github.com/613397
I tell ya - I had the same though - but that's how the jQuery docs showed it. Glad to know my intuition was right this time. :) I've got a contact on the jQuery team. I'll see if I can get them to update the example. Or maybe they know something we don't - and if so - I'll share it back here.
There is a similar plugin called jTruncate that I've used successfully in one of my projects. If nothing else, might be fun to compare approaches to the issue:
http://blog.jeremymartin.na...
Just checked it out Joel and that is a darn good idea too.
IMO, you can improve the shortening kids, by replacing the inner if() block with:
var extraKids = kids.slice(settings.length).hide();
$("<a href='' class='showMoreLink'>" + settings.message + "</a>")
.bind("click.autoShortener", function(e) {
// always do this first
e.preventDefault();
// show the hidden kids
extraKids.show();
// remove the link from the DOM
$(this).remove();
})
.appendTo(item);
The key changes are:
* Cached the kids that are hidden. There's no point in running show() on all the kids--especially when some weren't hidden. This could also introduce bugs if for some reason code had hidden one of the other list items for some other reason. It improves performance too.
* e.preventDefault() should always be at the top of your event handlers so that if a JS error occurs, the default event is still canceled.
* Added namespace for the click event--that way you can remove it if you need to.
* Attached event handler and add to DOM in one piece of code
* The link is actually removed from the DOM, not just hidden. There's really no reason for it once you click on it. This isn't necessary, but if you're completely done with it, you might as well remove it--especially if this is being used on a page where the page is being updated by AJAX content and is rarely refreshed. You can begin to clutter the DOM with unused elements.
@Dan: Thanks. I agree on the extra Kids thing. And you are absolutely right on removing the link from the dom instead of just hiding it. Dumb move on my part. :)
@Arnout: I pinged my contact - and he agreed with you that in the demo it made sense for that block of code to be outside of the loop.
Very nice :) Simple but effective
A nice little feature would be to have an option to show the first X elements, and the last X elements, with a show all to show the elements missing in between.
I might do a little mod of this :)
@Richard: Heh, that _does_ sound cool. Do it! :)
Here we go:
http://www.rbrasier.com/exa...
Virtually the same, just with another variable and another parameter in the split function
Code follows:
(function($) {
$.fn.autoShortener = function(options) {
return this.each(
function() {
var settings = {
"showFirst":10,
"showLast":0,
"message":"Show More"
};
if(options) $.extend(settings, options);
//get the children
var item = $(this);
var kids = item.children();
if(kids.length > settings.showFirst) {
var hideTo = kids.length-settings.showLast;
kids.slice(settings.showFirst,hideTo).hide();
$(this).append("<a href='' class='showMoreLink'>" + settings.message + "</a>");
$(".showMoreLink", item).click(function(e) {
item.children().show()
$(this).hide();
e.preventDefault();
});
}
}
);
};
})(jQuery);
Im trying to insert the link in between the elements rather than at the end. The only problem im having is i cant seem to use $(this) with the insert after function. Any ideas? How can i use (this) with the nth-child?
$("<a href='' class='showMoreLink'>" + settings.message + "</a>").insertAfter(":nth-child(" + settings.showFirst +")");
This is almost what I am after:
$(this).children(":nth-child(" + settings.showFirst +")").append("<div><a href='' class='showMoreLink'>" + settings.message + "</a></div>");