I write a lot of “how-to” posts. This is fine and I actually think it’s fun…until I have a month like this past one, in which I worry that I have no business telling anyone “how-to” do anything. In which I have written the following comment multiple times:
//MF - this is a bad way to do this.
In the last four weeks I have struggled mightily to make changes to the JavaScript in a Drupal module (the aforementioned Views Isotope). I have felt lost, I have felt jubilation, I have sworn profusely at my computer and in the end, I have modifications that mostly work. They basically do what we need. But the changes I made perform differently in different browsers, are likely not efficient, and I’m not sure the code is included in the right place. Lots of it might be referred to as “hacky.”
However, the work needed to be done and I did not have time to be perfect. This needed to mostly work to show that it could work and so I could hand off the concepts and algorithms to someone else for use in another of our sites. Since I have never coded in JavaScript or jQuery, I needed to learn the related libraries on the fly and try to relate them back to previous coding experiences. I needed to push to build the house and just hope that we can paint and put on doors and locks later on.
I decided to write this post because I think that maybe it is important to share the narrative of “how we struggle” alongside the “how-to”. I’ll describe the original problem I needed to tackle, my work towards a solution, and the remaining issues that exist. There are portions of the solution that work fine and I utilize those portions to illustrate the original requirements. But, as you will see, there is an assortment of unfinished details. At the end of the post, I’ll give you the link to my solution and you can judge for yourself.
The Original Problem
We want to implement a new gallery to highlight student work on our department’s main website. My primary area of responsibility is a different website – the digital library – which is the archival home for student work. Given my experience in working with these items and also with Views Isotope, the task of developing a “proof of concept” solution fell to me. I needed to implement some of the technically trickier things in our proposed solution for the gallery pages in order to prove that the features are feasible for the main website. I decided to use the digital library for this development because
- the digital library already has the appropriate infrastructure in place
- both the digital library and our main website are based in Drupal 7
- the “proof of concept” solution, once complete, could remain in the digital library as a browse feature for our historical collections of student work
The full requirements for the final gallery are outside of the scope of this post, but our problem area is modifying the Views Isotope module to do some advanced things.
First, I need to take the existing Views Isotope module and modify it to use hash history on the same page as multiple filters. Hash history with Isotope is implemented using the jQuery BBQ plugin, as is demonstrated on the Isotope website. This essentially means that when a user clicks on a filter, a hash value is added to the URL and becomes a line in the browser’s history. This allows one to click the back button to view the grid with the previous filters applied.
Our specific use case is the following: when viewing galleries of student work from our school, a user can filter the list of works by several filter options, such as degree or discipline, (i.e., Undergraduate Architecture). These filter options are powered by term references in Drupal, as we saw in my earlier Isotope post. If the user clicks on an individual work to see details, clicking on the back button should return them to the already filtered list – they should not have to select the filters again.
Let’s take a look at how the end of the URL should progress. If we start with:
.../student-work-archives
Then, we select Architecture as our discipline, we should see the URL change to :
.../student-work-archives#filter=.architecture
Everything after the # is referred to as the hash. If we then click on Undergraduate, the URL will change to:
.../student-work-archives#filter=.undergraduate.architecture
If we click our Back button, we should go back to
.../student-work-archives#filter=.architecture
With each move, the Isotope animation should fire and the items on the screen should only be visible if they contain term references to the vocabulary selected.
Further, the selected filter options should be marked as being currently selected. Items in one vocabulary which require an item in another vocabulary should be hidden and shown as appropriate. For example, if a user selects Architecture, they should not be able to select PhD from the degree program, because we do not offer a PhD degree in Architecture, therefore there is no student work. Here is an example of how the list of filters might look.
Once Architecture is selected, PhD is removed as a option. Once Undergraduate is selected, our G1, G2 and G3 options are no longer available.
A good real-world example of the types of features we need can be seen at NPR’s Best Books of 2013 site. The selected filter is marked, options that are no longer available are removed and the animation fires when the filter changes. Further, when you click the Back button, you are taken back through your selections.
The Solution
It turns out that the jQuery BBQ plugin works quite nicely with Isotope, again as demonstrated on the Isotope website. It also turns out that support for BBQ is included in Drupal core for the Overlay module. So theoretically this should all play nicely together.
The existing views-isotope.js file handles filtering as the menu items are clicked. The process is basically as follows:
- When the document is ready
- Identify the container of items we want to filter, as well as the class on the items and set up Isotope with those options.
- Pre-select All in each set of filters.
- Identify all of the possible filter options on the page.
- If a filter item is clicked,
- first, check to make sure it isn’t already selected, if so, escape
- then remove the “selected” class from the currently selected option in this set
- add the “selected” class to the current item
- set up an “options” variable to hold the value of the selected filter(s)
- check for other items in other filter sets with the selected class and add them all to the selected filter value
- call Isotope with the selected filter value(s)
To add the filter to the URL we can use bbq.pushState, which will add “a ‘state’ into the browser history at the current position, setting location.hash and triggering any bound hashchange event callbacks (provided the new state is different than the previous state).”
$bbq.pushState( options);
We then want to handle what’s in the hash when the browser back button is clicked, or if a user enters a URL with the hash value applied. So we add an option for handling the hashchange event mentioned above. Instead of calling isotope from the link click function, we call it from the hashchange event portion. Now our algorithm looks more like this, with the items in bold added:
- Include the misc/jquery.ba-bbq.js for BBQ (I have to do this explicitly because I don’t use Overlay)
- When the document is ready
- identify the container of items we want to filter, as well as the class on the items and set up Isotope with those options.
- Pre-select All in each set of filters.
- Identify all of the possible filter options on the page.
- If a filter item is clicked,
- first, check to make sure it isn’t already selected, if so, escape
- then remove the “selected” class from the currently selected option in this set
- add the “selected” class to the current item
- set up an “options” variable to hold the value of the selected filter(s)
- check for other items in other filter sets with the selected class and add them all to the selected filter value
- push “options” to URL and trigger hashchange (don’t call Isotope yet)
- If a hashchange event is detected
- create new “hashOptions” object according to what’s in the hash, using the deparam.fragment function from jQuery BBQ
- manipulate css classes such as “not-available” (ie. If Architecture is selected, apply to PhD) and “selected” based on what’s in “hashOptions”
- call Isotope with “hashOptions” as the parameter
- trigger hashchange event to pick up anything that’s in the URL when the page loads
I also updated any available pager links so that they link not just to the appropriate page, but also so that the filters are included in the link. This is done by appending the hash value to the href attribute for each link with a class “pager”.
And it works. Sort of…
The Unfinished Details
Part of the solution described above only works on Chrome and – believe it or not – Internet Explorer. In all browsers, clicking the back button works just as described above, as long as one is still on the page with the list of works. However, when linking directly to page with the filters included (as we are doing with the pager) or hitting back from a page that does not have the hash present (say, after visiting an individual item), it does not work on Firefox or Safari. I think this may have to do with the deparam.fragment function, because that appears to be where it gets stuck, but so far can’t track it down. I could directly link to window.location.hash, but I think that’s a security issue (what’s to stop someone from injecting something malicious after the hash?)
Also, in order to make sure the classes are applied correctly, it feels like I do a lot of “remove it from everywhere, then add it back”. For example, if I select Architecture, PhD is then hidden from the degree list by assigning the class “not-available”. When a user clicks on City and Regional Planning or All, I need that PhD to appear again. Unfortunately, the All filter is handled differently – it is only present in the hash if no other options on the page are selected. So, I remove “not-available” from all filter lists on hashchange and then reassign based on what’s in the hash. It seems like it would be more efficient just to change the one I need, but I can’t figure it out. Or maybe I should alter the way All is handled completely – I don’t know.
I also made these changes directly in the views-isotope.js, which is a big no-no. What happens when the module is updated? But, I have never written a custom module for Drupal which included JavaScript, so I’m not even sure how to do it in a way that makes sense. I have only made changes to this one file. Can I just override it somewhere? I’m not sure. Until I figure it out, we have a backup file and lots and lots of comments.
All of these details are symptoms of learning on the fly. I studied computer science, so I understand conceptual things like how to loop through an array or return a value from a function, but that was more than ten years ago and my practical coding experience since then has not involved writing jQuery or Javascript. Much of what I built I pieced together from the Drupal documentation, the Views Isotope documentation and issue queue, the Isotope documentation, the jQuery BBQ documentation and numerous visits to the w3schools.com pages on jQuery and Javascript. I also frequently landed on the jQuery API Documentation page.
It is hard to have confidence in a solution when building while learning. When I run into a snag, I have to consider whether or not the problem is the entire approach, as opposed to a syntax error or a limitation of the library. Frequently, I find an answer to an issue I’m having, but have to look up something from the answer in order to understand it. I worry that the code contains rookie mistakes – or even intermediate mistakes – which will bite us later, but it is difficult to do an exhaustive analysis of all the available resources. Coding elegantly is an art which requires more than a basic understanding of how the pieces play together.
Inelegant code, however, can still help make progress. To see the progress I have made, you can visit https://ksamedia.osu.edu/student-work-archives and play with the filters. This solution is good because it proves we can develop our features using Isotope, BBQ and Views Isotope. The trick now is figuring out how to paint and put locks and doors on our newly built house, or possibly move a wall or two.