Filter Views with Ajax in Drupal 8

When displaying Drupal content through Views you’d sometimes like the user to be able to dynamically filter the content from a list of links. Ajax is already part of Drupal 8 Views. This is how you change the default <select> list into themable links.

In a previous post I've written about how you can do this same thing in Drupal 7. This time around we are going to explore how it can be done in Drupal 8, which is currently in beta (8.0.0-beta15). The nice thing about Drupal 8 is that the Views module which we are going to use is now part of Drupal core, which means you can get a lot more done "out of the box" without installing any contrib-modules.

This is the end result we are looking for. Buttons (links) that filter by taxonomy terms.
This is the end result we are looking for. Buttons (links) that filter by taxonomy terms.

 

This is the default select list from Views for exposed filters.
This is the default select list from Views for exposed filters.

This is the default select list from Views for exposed filters.[/caption]   To turn the default Views Exposed Filter <select> into a list of links we'll create a custom module and make use of the powerful Drupal form_alter hook. We are also going to add some javascript and a tiny pinch of css. If this sounds intimidating, don't worry, I'll walk you through it step by step. I'm sure there are other methods to achieve the same result, but in my experience, a leaner solution, with a relatively simple override is usually the quickest and most reliable way to go. The alternative of adding one or more contrib-modules to a site for a small thing such as this means that those modules have to work (in themselves and with each other) and be without bugs, patches, dead-end issue queues and so on. That's not always the case. You could wind up spending hours trying to solve a tiny problem only to find in the end that it can't be done with the current state of the modules.  

The View

We're assuming that you've already created articles and tagged them. In this clean install of Drupal 8 beta I created three articles and tagged them alfa, beta and gamma. Next we need to create a View. In the Drupal 8 admin section you choose Structure -> Views and then click the blue button that says + Add new view.

Create a basic view listing articles type.
Create a basic view listing articles type.

Choose Show Content of type Article. Check Create a page for this example. Display format should be Unformatted list of fields. Remove (empty) the default number of items to display and uncheck Use a pager. When you’re done click Save and edit.    

This is what the basic view looks like before we add exposed filters and ajax.
This is what the basic view looks like before we add exposed filters and ajax.

Next we need to create the exposed filter which will let the user click and sort the content.

The Exposed Filter

Add a filter criteria to your view by clicking the add button in Filter Criteria section of the views interface. [caption id="attachment_347" align="alignnone" width="838"]

Step 1/3. Start typing the name of the taxonomy term field in the search field to narrow the list of possible fields and then check the checkbox for the field – Content: Tags (field_tags). When you’re done click Apply (all displays).
Step 1/3. Start typing the name of the taxonomy term field in the search field to narrow the list of possible fields and then check the checkbox for the field – Content: Tags (field_tags). When you’re done click Apply (all displays).

 

Step 2/3. Select Dropdown. Click Apply and continue.
Step 2/3. Select Dropdown. Click Apply and continue.
Step 3/3. Check Expose this filter to visitors, to allow them to change it. Filter type shold be Single filter. Operator should be is one of. Click Apply (all displays) when you’re done.
Step 3/3. Check Expose this filter to visitors, to allow them to change it. Filter type shold be Single filter. Operator should be is one of. Click Apply (all displays) when you’re done.

 

 

The AJAX

At this point we have our View and our Exposed Filter, but we still need to activate AJAX to make the view filter the content dynamically. In the Advanced section of your View configuration screen you will find what you need. Per default these settings are collapsed. Open it by clicking the Advanced section label. 

 In the Advanced section of the Views interface you'll find the setting Use AJAX.
In the Advanced section of the Views interface you'll find the setting Use AJAX.

 

Now change the setting for Use AJAX. This should simply be switched from No to Yes.

 

 

 You should now be able to play with the filter in your views preview.
You should now be able to play with the filter in your views preview.

To make the effect of the filtering more visually clear while developing I've added the fields image and tags to the fields section (and removed title). I also changed the Sort Criteria to Tags (desc) to have the tags display in alphabetical order. Simply because it looks more orderly to me. 

This is what the view looks like after we removed the title field and added the fields image and tags.

So up to this point we've created our content, our view, added the filter criteria and activated AJAX in the view. So we have the basic "stuff" we need:

  • we can display the content of our choice, with the presentation of our choice (teaser, fields or something else)
  • we can filter the content by the terms the editor has tagged it with
  • the content gets refreshed with AJAX without reloading the entire document

Now all we need is to tweak the presentation to suit our taste – or the taste of the designer who handed us their fancy design.

The Custom Module

To be able to use hook_form_alter and do our magic we need to create a custom module. Start by creating a new folder called custom in the modules folder. The purpose of this is to set custom made modules apart from contributed modules (downloaded from Drupal.org). Now in the custom folder create your new custom module folder. In this example we are naming the module popolo_custom. You can name it anything you want. But remember to replace all occurrences of popolo_custom in the following code examples with the name you've chosen.

This is the structure of your custom module. Place it inside modules/custom/. The top level folder drupal8.dev is the root of this site.
This is the structure of your custom module. Place it inside modules/custom/. The top level folder drupal8.dev is the root of this site.

Now begin by declaring your module with a yml-file: popolo_custom.info.yml

name: Popolo Custom
type: module
description: 'Example module for Drupal 8.'
package: Custom
version: 8.x
core: 8.x

Next we need to find out the form id of the exposed filter we're trying to manipulate. To to this inspect the form in your browser.

Inspect the views exposed form in your browser to find the form id.
Inspect the views exposed form in your browser to find the form id.

Inspect the views exposed form in your browser to find the form id. Make a note of the id, so that you can copy and paste it into the code. If you've followed the steps of this tutorial exactly the id should be views-exposed-form-filter-articles-page-1. If you're implementing this on a different view, obviously the id will be something different.[/caption]   Now let's create the popolo_custom.module file. And paste the following code into it. Make sure the id on row 8 matches the id we just inspected:

<?php
function popolo_custom_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id){
    
  /**
   * Apply the form_alter to a specific form #id
   * the form #id can be found through inspecting the markup
   */
  if($form['#id'] == 'views-exposed-form-filter-articles-page-1') {
  /**
   * Include a js and css, which was defined in popolo_custom.libraries.yml
   */
  $form['#attached']['library'][] = 'popolo_custom/popolo_custom.enable';	
  $form['#attached']['library'][] = 'popolo_custom/popolo_custom.forms';
	/**
	 * Extract the options from the Views Exposed Filter <select>-list 
	 */
  $links = $form['field_tags_target_id']['#options'];
  /**
   * Iterate over the options ($links) to build an array ($pop_array) of links
   */
  $i = 0; // Initiate counter/index
  $pop_array = array();
	foreach ($links as $tid => $term_name) {
		if ($tid == 'All') {
			$pop_array[$i]['#markup'] = '<span class="filter-tab"><a href="" class="active" id="' .$tid . '">' . $term_name . '</a></span>';
		}
		else {
			$pop_array[$i]['#markup'] = '<span class="filter-tab"><a href="" id="' .$tid . '">' . $term_name . '</a></span>';
		}
		$i++; // Increase counter/index
	} 
	/**
	 * Create the item-list the form should render
	 */
		$form['links'] = [
			'#theme' => 'item_list',
		  '#items' => $pop_array,
		  '#attributes' => array('class' => array('pop-list')),
		]; 
	}
  
  else {
  	//nothing...
  } 
}

The code identifies the form we want to alter with a simple "if the id equals [our id] then do the following" (row 8). Next on rows 13 and 14 we add some javascript and css. After that we pull out the options from the select list and construct our own list items with some markup and css-classes that we want. This particular example uses some classes that I usually like to have, such as pop-list and filter-tab. Needless to say, you could choose to create different markup and different classes. Now we need to add some javascript (and css) which we did on rows 13 and 14. New in Drupal 8 is that you need to define your external javascript and css files with a libraries.yml-file. So lets do that by creating a file called popolo_custom.libraries.yml.

popolo_custom.forms:
   version: VERSION
   css:
     theme:
       css/popolo_custom.theme.css: {}

popolo_custom.enable:
  version: VERSION
  js:
    js/popolo_custom.js: {}
  dependencies:
    - core/jquery
    - core/drupalSettings

Thanks to Earl Fong for explaining how this works in this post. Now we create the file popolo_custom.js in a new subfolder named js:

(function ($) {

	/**
   * Set active class on Views AJAX filter 
   * on selected category
   */
  Drupal.behaviors.exposedfilter_buttons = {
    attach: function(context, settings) {
      $('.filter-tab a').on('click', function(e) {
        e.preventDefault();
        
        // Get ID of clicked item
        var id = $(e.target).attr('id');
        
        // Set the new value in the SELECT element
        var filter = $('#views-exposed-form-filter-articles-page-1 select[name="field_tags_target_id"]');
        filter.val(id);

        // Unset and then set the active class
        $('.filter-tab a').removeClass('active');
        $(e.target).addClass('active');

        // Do it! Trigger the select box
        //filter.trigger('change');
        $('#views-exposed-form-filter-articles-page-1 select[name="field_tags_target_id"]').trigger('change');
        $('#views-exposed-form-filter-articles-page-1 input.form-submit').trigger('click');

      });
    }
  };
	

	jQuery(document).ajaxComplete(function(event, xhr, settings) {
		switch(settings.extraData.view_name){
      
      case "filter_articles":
        var filter_id = $('#views-exposed-form-filter-articles-page-1 select[name="field_tags_target_id"]').find(":selected").val();

        $('.filter-tab a').removeClass('active');
        $('.filter-tab').find('#' + filter_id).addClass('active');

        break;

      default:
        break;
    };
	});


})(jQuery);

Notice the id of our view being used in the above code. Finally to polish the look of the view we create popolo_custom.css in a new subfolder named css. This css hides the select list and apply button. The additional styling would normally go into your sites theme.

/**
 * Hide <select> and submit-button
 */
#views-exposed-form-filter-articles-page-1 .form-type-select {
	display: none;
}
#views-exposed-form-filter-articles-page-1 .form-submit {
	display: none;
}

/**
 * Pure styling, should probably be put in your theme stylesheet
 * instead of in this module
 */ 
.pop-list {
	display: block;
  text-align: left;
  margin: 0 auto;
  padding: 0;
}
.pop-list li {
  margin: 25px 5px 25px 0;
  float: none;
  display: inline-block;
  margin: 25px 5px 25px 0;
  font-size: 16px;
  text-transform: uppercase;
}
.pop-list li a {
  background-color: #CCC;
  height: 40px;
  border-radius: 40px;
  padding: 10px 16px 9px;
  color: #FFF;
  line-height: 23px;
  text-decoration: none;
  border-bottom: none;
}
.pop-list li a.active {
	background-color: #0071b3;
}
.view-filter-articles .views-row{
	float: left;
	padding: 15px;
}

Next activate the module in Extend (admin/modules), empty the sites cache and then load the page. If everything works your page should now look something like this:

The finished views exposed filter themable links...
The finished views exposed filter themable links...

That's it – you're done!

Categories