Isotope (filter) and Pagination using Php and Javascript

Isotope (filter) and Pagination using Php and Javascript

I wanted to implement Isoptope and Pagination in one of my websites. The idea was to display all my categories for filter options and then while clicking on those options they would dynamically filter the category items below.

Here's a diagram to visualize the concept,

For the filter logic,

For the Pagination Logic,

First of all, I all the categories from my website using get_terms('category'); function and saved it in a variable called $all_categories; . Then, I looped the variable using 'foreach' loop to dynamically generate lists of categories from the array.

$all_categories = get_terms('category');
            foreach ($all_categories as $category) {
                echo '<li><a href="#" data-filter="' . $category->slug . '" class="blue">' . $category->name . '</a></li>';
            }

I also passed '$category->slug' into the data-filter attribute.

I had wrapped the dynamically generated <li> tags with a <ul> tag with id="filters" which we will be using to get the 'id' in Javascript.

For the content part first I wrapped them with a div with id="isotope-list"

<div id="isotope-list">

<?php then call the wp_query to pull the post contents ?>

As for the category items to be displayed I simply pulled all the category posts using 'wp_query'.

$args = array(
                    'category_name' => '', // Replace 'post' with your desired post type
                    //'posts_per_page' => 2
                );
                $filter_posts = new WP_Query($args);
                if ($filter_posts->have_posts()) {
                    while ($filter_posts->have_posts()) {
                        $filter_posts->the_post();
$this_category_terms = get_the_terms($post->ID, "category");
                        //print_r($this_category_terms);
                        $this_category_terms_string = '';
                        foreach ($this_category_terms as $term) {
                            $this_category_terms_string .= $term->slug . " ";
                        }

//my div
<div class="<?php echo $this_category_terms_string; ?> >

I also passed the 'category-slug' into the div class for each of the dynamically pulled post contents.

After closing the while loop and if loop, I created a separate div for pagination.

<div id="pagination-container">
            <?php //pagination will be displayed here
            ?>
        </div>

Now we move onto the Javascript part. I tried this filtering without using isotope() function which is a javascript library for Isotope. If you want to use it, do remember to install it. But for my method, I used plain Javascript creating a separate 'isoptope.js' file.

document.addEventListener("DOMContentLoaded", function () {

    const container = document.getElementById('isotope-list');
    const items = container.querySelectorAll('.item');

    const optionLinks = document.querySelectorAll('#filters a');
    const paginationContainer = document.getElementById("pagination-container");

The above JS utilizes the DOMContentLoaded event listener, which ensures that the script is executed only after the HTML document has been completely loaded. Here's a breakdown of what the code does:

  1. It first waits for the DOM to be fully loaded using the DOMContentLoaded event listener.

  2. It selects an element with the id 'isotope-list' from the loaded document and assigns it to the variable container.

  3. It selects all elements with the class 'item' that are descendants of the element with the id 'isotope-list' and assigns them to the variable items.

  4. It selects all the <a> elements within the elements with the id 'filters' and assigns them to the variable optionLinks.

  5. It finds an element with the id "pagination-container" and assigns it to the variable paginationContainer.

Then, I created a handleFilterClick function, to handle the click event on the list tags that we created for categories option to filter the content.

function handleFilterClick() {
        optionLinks.forEach(function (link) {
            link.classList.remove('selected');
        });
        this.classList.add('selected');
        selector = this.getAttribute('data-filter');
        return false;
    }

This function, handleFilterClick, is used to handle the click event on specific elements, Let's break down what each part of the function does:

  1. optionLinks.forEach(function (link) {...}): This loop iterates through each element in the optionLinks array, which presumably contains a collection of <a> elements. For each element, it removes the 'selected' class. This step ensures that no other filter option is visually marked as selected when a new option is clicked.

  2. this.classList.add('selected'): After removing the 'selected' class from all the links, this line adds the 'selected' class to the currently clicked element. This will visually distinguish the currently selected filter option.

  3. selector = this.getAttribute('data-filter'): This line retrieves the value of the 'data-filter' attribute from the clicked element using the getAttribute method. The value of this attribute is assigned to the variable selector. Presumably, this attribute holds the specific filter that needs to be applied.

  4. return false: This line ensures that the default action of the clicked element is prevented. In the context of an event handler, return false prevents the default action that would typically occur after handling the click event.

Then I create a function called 'countItems' which counts the number of posts from that selected category and then break them into multiple pages. For simplicity, I initialized 4 posts per page.

I also pushed the items into an array which I would display later.

function countItems(selector) { //function to count the num of pages
        items.forEach(item => {
            if (item.classList.contains(selector)) {

                itemCount++;
                selectedItems.push(item); // Add the item to the array
                if (itemCount % 4 === 0) {

                    selectedItemsArrays.push(selectedItems); // Push the 4 items to the main array
                    selectedItems = []; // Reset the array

                }
            }

        });
        if (selectedItems.length > 0) {
            selectedItemsArrays.push(selectedItems); // push even thought the items might not be 4 but less
            selectedItems = [];
        }

To display page numbers, I initialized paginationContainer to get the pagination div with id="pagination-container".

const paginationContainer = document.getElementById("pagination-container");
paginationContainer.innerHTML = '';
        for (let index = 0; index < selectedItemsArrays.length; index++) {
            const newAtag = document.createElement("a");

            // Add some content to the new div
            const pageNumber = 1 + index;
            //newAtag.setAttribute("href", '#');
            newAtag.setAttribute("value", pageNumber);
            newAtag.classList.add("select");
            const newContent = document.createTextNode(`Page ${pageNumber}`);
            newAtag.appendChild(newContent);
            // Add the newly created element and its content to the DOM
            paginationContainer.appendChild(newAtag);
        }

Here, I dynamically created a tags inside the pagination container, giving them new attributes called "value" and stored the array[index] inside that attribute.

To control the click event in these a tags, I created another function handlePaginationClick.

const paginationLink = document.querySelectorAll('#pagination-container a');
        function handlePaginationClick() {

            paginationLink.forEach(function (link) {
                link.classList.remove('select');
            });
            this.classList.add('select');
            paginationLinkSelector = this.getAttribute('value');
            console.log(paginationLinkSelector);
            pageValue = parseInt(paginationLinkSelector);
            pageValue--;
            displayItems(pageValue);
        }

This helped me to get the value attribute from the clicked a tag and pass it to the function displayItems(); further to display the post contents.

 function displayItems(pageValue) {
            firstArray = selectedItemsArrays[pageValue];
            console.log(firstArray);
            console.log("firstArray");
            items.forEach(function (item) {
                item.style.display = 'none';
            });

            firstArray.forEach(function (eachSelectedItem) {
                eachSelectedItem.style.display = 'block';
            })
        }

This function helped me to display the posts from the array with index [pageValue] and hide all the other remaining posts.