Filter content using jQuery + multiple tags

How do we let users use a tag cloud using multiple choices to filter through my design portfolio.

Filter tags example website portfolio

Background

I had seen solutions where you could click on a single tag, but felt like this was a bit limiting, so I decided to play. In this tutorial, you'll learn how to do it too. As always, any solution I provide can be improved upon. I hope that you'll use it, and then share with the community how to do it better than I did. That sharing is what makes MODX so great!

The ScreenCast

The Concept

This tutorial is geared towards the Revolution version of MODX, but it would easily work in EVO by substituting the Ditto snippet for get Resources. You'll notice below code blocks, I've included the EVO version of the code. Tip Click on Show Evo Code to see the code.

I want getResources to create my portfolio of list items inside an unordered list. Each list item will have a bunch of class names attached to it. If the user clicks a tag in the tag cloud above, any list item that has that class will be displayed, and any list item that doesn't, will disappear. Cool Right?

Code

The actual markup I used was:

 
<h1>Check Out My Work</h1>
<p><span class="blue button">Filter Portfolio by clicking on tags </span> <span class="blue button" style="background-color: purple;"> → Then Click on the thumbnails for more info about each project.</span></p>
<ul id="portfolio-list" class="clearfix span-24">[[getResources? &amp;tpl=`portfolio` &amp;parents=`26` &amp;limit=`0` &amp;includeTVs=`1` &amp;processTVs=`1` &amp;tvPrefix=`` &amp;sortbyTV=`mydate` &sortdir=`DESC`]]</ul>
<div id="portfolioCuteness" class="span-20" style="display: none;">
<h2>I haven't done a project with those requirements yet, but it sounds intriguing, Maybe we should talk? <br /> <br /> <a class="blue button" href="mailto:noahlearner@gmail.com">email me</a>  or   <span class="purple button" onclick="reset();">reset list</span></h2>
</div>

Show Evo Code

<h1>Check Out My Work</h1>
<p><span class="blue button">Filter Portfolio by clicking on tags </span> <span class="blue button" style="background-color: purple;"> → Then Click on the thumbnails for more info about each project.</span></p>
<ul id="portfolio-list" class="clearfix span-24">[!Ditto? &amp;tpl=`portfolio` &amp;parents=`26` &amp;display=`all` &amp;removeChunk=`Comments` &amp;orderBy=`tvmyDate DESC` !]</ul>
<div id="portfolioCuteness" class="span-20" style="display: none;">
<h2>I haven't done a project with those requirements yet, but it sounds intriguing, Maybe we should talk? <br /> <br /> <a class="blue button" href="mailto:noahlearner@gmail.com">email me</a>  or   <span class="purple button" onclick="reset();">reset list</span></h2>
</div>

The portfolio chunk is:

 
<ul>
<li class="[[+tags]]"><a href="[[~[[+id]]]]" rel="colorbox" target="_blank"><img src="[[+homeImg]]" alt="[[+pagetitle]]" /><br /> <strong>visit [[+jobTitle]]</strong></a><br /><br /></li>
</ul>

Show Evo Code

<ul>
<li class="[+tags+]"><a href="[~[+id+]~]" rel="colorbox" target="_blank"><img src="[+homeImg+]" alt="[+pagetitle+]" /><br /> <strong>visit [+jobTitle+]</strong></a><br /><br /></li>
</ul>

Notice the

 class="[[+tags]]"

This is where the magic happens. This is what creates the filterable list.

In each of the portfolio documents, I have a tv set up called tags. It is the auto-tag Input type, with default output. I then enter all the tags that I want the user to be able to sort by.

If you have a two word tag, separate the two words with an underscore(_) when you create the tags in the TV. Use the underscore because it is unlikely that you would ever need a tag that includes an underscore in name. The underscore will be removed before output by jQuery.This will keep the tags intact. Otherwise the javascript will break up the words and create two separate tags.

On the page we make the snippet call and then before the closing </body> tag we make sure to include jQuery followed by our custom code.

 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="[[++base_url]]assets/js/jquery.colorbox-min.js"></script>

Show Evo Code

 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="[(base_url)]assets/js/jquery.colorbox-min.js"></script>
<script type="text/javascript"> 
jQuery(document).ready(function ($) {

$("#portfolio-list a").colorbox({iframe: true,innerWidth:685, innerHeight:440});
    // map the classes for each item into a new array
    classes = $("#portfolio-list li").map(function () {
        
return $(this).attr("class").split(' ');
    });
    // create list of distinct items only
    var classList = distinctList(classes);
    // generate the list of filter links
    var tagList = '<ul id="tag-list"></ul>';
    tagItem = '<span id="reset" class="reset purple button" onclick="reset();">RESET</span>';
    tagFilter = '';
    
    // loop through the list of classes & add link and remove dashes output by autotag TV
    $.each(classList, function (index, value) {
        TagText = value.split('_').join(" ");
        tagItem += '<li id="' + value + '" >' + TagText + '</li>';
    });
    // add the filter links before the list of items
    $("#portfolio-list").before($(tagList).append(tagItem));
    $("#tag-list").before($(tagFilter));
    $("li#cboxElement").hide();
    // filter the demo list when the filter links are clicked
    $('#tag-list li').live('click', function (el) {
        $(this).toggleClass('active');
        var choice = $.map($(".active"), function (el) {
            return "." + el.id;
        }).join("");
        if (choice) {
            $("#portfolio-list").hide();
          $("#portfolio-list li").hide();
            $("#portfolio-list li" + choice).show();
          $("#portfolio-list").fadeIn(400);
            var portLength = $("#portfolio-list li:not(" + choice + ")").length;
            var portTotal = $("#portfolio-list li").length;
            if (portLength == portTotal) {
                $("#portfolioCuteness").fadeIn(400);
            }
            if ((portLength !== portTotal) && ($('#portfolioCuteness').length)) {
                $('#portfolioCuteness').hide();
            }
        } else {
            $("#portfolio-list li").show(400);
        }
        $("span.reset").click(function() {
 
    $("#tag-list li.active").removeClass('active');
    $('#portfolioCuteness').hide();
    $("#portfolio-list li").show(300);
          
});
        
    });
});
// Function to create a distinct list from array
function distinctList(inputArray) {
    var i;
    var length = inputArray.length;
    var outputArray = [];
    var temp = {};
    for (i = 0; i < length; i++) {
        temp[inputArray[i]] = 0;
    }
    for (i in temp) {
        outputArray.push(i);
    }
    var output = outputArray.sort();
    return output;
}
</script>

If it is over your head, you can pretty much cut and paste what I provide. You will also be very well served by installing firebug, which is a firefox add-on and by inspecting the source code on my home page to see how it all works together. Otherwise, bear with me.

So what is going on here?

The javascript code looks at the unordered list items created by the getResources call.

It then looks at all the different class names and creates a distinct list of classes in the function named distinctList. It sorts this list alphabetically.

It then outputs the tag cloud in an unordered list with an id of tag-list.

The first list item is the reset list item. When the user clicks that it resets the tag cloud and displays all the portfolio items by enacting the reset function.

When user clicks on a list item, other than the reset, it toggles the active class for that item. It then adds the active list class to map of checked items, and displays the list items that match this variable that is called "choice".

If the link in the ul#portfolio-list is not in choice it is hidden.

Is this not sweet?

I know there are many ways to refactor the code. I'm hoping that you can help do that in the comments below.

Enjoy