How to Generate an Expandable Archive List in WordPress

on in WordPress
Last modified on

Expandable Archive List in WordPress

Creating an expandable and collapsible archive list in WordPress is a great way to provide users with an organized view of your site’s content, allowing them to explore posts by year and month. WordPress natively supports yearly or monthly archives, but with a bit of custom code, you can easily generate both within an expandable format. This guide will walk you through building a lightweight, responsive, and accessible expandable archive list using PHP, vanilla JavaScript, and CSS.

This method adheres to the most recent WordPress and PHP versions, PHP 8+, while following the WordPress Coding Standards (WPCS).

Table of Contents

Step 1: Filter to Query Archives by Year

We first need to create a custom filter to retrieve archive posts by year. By default, WordPress doesn’t provide a built-in way to show both yearly and monthly archives in an expandable format, so we will use the getarchives_where filter to handle this.

/**
 * Filter to query archives by year.
 *
 * @param string $where SQL WHERE clause.
 * @param array  $args  Query arguments.
 * @return string Filtered WHERE clause.
 */
function whiskey_getarchives_filter( string $where, array $args ) : string {
    if ( isset( $args['year'] ) ) {
        $where .= ' AND YEAR(post_date) = ' . intval( $args['year'] );
    }

    return $where;
}
add_filter( 'getarchives_where', 'whiskey_getarchives_filter', 10, 2 );

In this code, the whiskey_getarchives_filter function checks for a year parameter in the arguments. If found, it appends an SQL WHERE clause that limits the query to posts from that year.

Step 2: Display the Archive List

Now that we’ve created the filter to query by year, we can set up the HTML and PHP to display the expandable archive list. We’ll generate a list of years, and for each year, we’ll show a list of months (if available) with the corresponding post count. The output will be formatted as a definition list (<dl>).

<dl class="tree-accordion">
    <?php
    $current_year = (int) date( 'Y' );
    $years        = range( $current_year, 1950 ); // Adjust the starting year accordingly.
    
    foreach ( $years as $year ) {
        ?>
        <dt>
            <a href="#">
                <i class="fa fa-fw fa-plus-square-o" aria-hidden="true"></i> 
                <?php echo esc_html( $year ); ?>
            </a>
        </dt>
        <?php
        $archive = wp_get_archives( [
            'echo'           => 0,
            'show_post_count'=> 1,
            'type'           => 'monthly',
            'year'           => $year,
        ] );
        $archive = explode( '</li>', $archive );
        $links   = [];

        foreach ( $archive as $link ) {
            $clean_link = trim( strip_tags( $link ) );
            if ( ! empty( $clean_link ) ) {
                $links[] = $clean_link;
            }
        }

        $flip_links = array_reverse( $links );

        if ( ! empty( $flip_links ) ) {
            echo '<dd>';
            foreach ( $flip_links as $link ) {
                echo '<span>' . esc_html( $link ) . '</span>';
            }
            echo '</dd>';
        } else {
            echo '<dd class="tree-accordion-empty"></dd>';
        }
    }
    ?>
</dl>

Explanation

  • $current_year and $years: Generates a list of years starting from the current year and going back to 1950.
  • wp_get_archives: Retrieves the monthly archives for each year, displaying the post count.
  • HTML Structure: We use a <dl> element for better accessibility, with <dt> elements for years and <dd> elements for months.

Now, the code above is based on an older project of mine. Since then, I made some improvements to both the speed and accessibility of the queries. Instead of querying all the years back to 1950 (as I needed them for my project), you could limit the archive to only years where there are posts. This reduces the amount of data generated. Adding structured data helps search engines understand the content better. For an archive list, we could add Breadcrumb List Schema, which enhances the way search engines understand the relationship between archives.

You can try the code below:

<dl class="tree-accordion" role="list" itemscope itemtype="https://schema.org/BreadcrumbList">
    <?php
    global $wpdb;
    $years_with_posts = $wpdb->get_col( "
        SELECT DISTINCT YEAR(post_date) 
        FROM $wpdb->posts 
        WHERE post_status = 'publish' 
        ORDER BY post_date DESC
    " );
    $position = 1;

    foreach ( $years_with_posts as $year ) {
        ?>
        <dt role="listitem" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
            <a href="#" aria-expanded="false" aria-controls="year-<?php echo esc_attr( $year ); ?>" itemprop="item">
                <span itemprop="name">
                    <i class="fa fa-fw fa-plus-square-o" aria-hidden="true"></i>
                    <?php echo esc_html( $year ); ?>
                </span>
            </a>
            <meta itemprop="position" content="<?php echo esc_attr( $position ); ?>" />
        </dt>
        <?php
        $archives = wp_get_archives( [
            'echo'           => 0,
            'show_post_count'=> 1,
            'type'           => 'monthly',
            'format'         => 'custom',
            'before'         => '<span>',
            'after'          => '</span>',
            'year'           => $year,
        ] );
        if ( ! empty( $archives ) ) {
            ?>
            <dd id="year-<?php echo esc_attr( $year ); ?>" aria-hidden="true">
                <?php echo wp_kses_post( $archives ); ?>
            </dd>
        <?php } else { ?>
            <dd id="year-<?php echo esc_attr( $year ); ?>" class="tree-accordion-empty" aria-hidden="true"></dd>
            <?php
        }
        $position++;
    }
    ?>
</dl>

Step 3: Expandable Archive List with Vanilla JavaScript

To make the archive list expandable and collapsible, we will use vanilla JavaScript. This will eliminate any need for external libraries like jQuery, ensuring better performance and reduced dependencies.

document.addEventListener('DOMContentLoaded', function () {
    const allPanels = document.querySelectorAll('.tree-accordion > dd');
    const links = document.querySelectorAll('.tree-accordion > dt > a');

    // Hide all panels initially
    allPanels.forEach(panel => {
        panel.style.display = 'none';
    });

    // Add click event listeners to toggle panels
    links.forEach(link => {
        link.addEventListener('click', function (event) {
            event.preventDefault();
            
            const targetPanel = link.parentElement.nextElementSibling;
            const isActive = targetPanel.classList.contains('active');

            // Hide all panels
            allPanels.forEach(panel => {
                panel.classList.remove('active');
                panel.style.display = 'none';
            });

            // Toggle the clicked panel
            if (!isActive) {
                targetPanel.classList.add('active');
                targetPanel.style.display = 'block';
            }
        });
    });

    // Hide empty accordion items
    document.querySelectorAll('.tree-accordion-empty').forEach(emptyEl => {
        const previousEl = emptyEl.previousElementSibling;
        if (previousEl) {
            previousEl.style.display = 'none';
        }
    });
});

Explanation

  • Event Listener: We use addEventListener('click') to handle toggle actions for each year link.
  • Toggle Mechanism: On click, the corresponding month list expands, while any previously expanded list is collapsed.
  • Efficient DOM manipulation: Vanilla JavaScript is used for faster execution and better performance.

Step 4: Style the Archive List

Finally, we’ll add some basic CSS to style the archive list. Font Awesome is used for the plus/minus icons, which will visually indicate expandable sections.

.tree-accordion {
    line-height: 1.5;
}

.tree-accordion dt a {
    display: block;
    text-decoration: none;
}

.tree-accordion .fa {
    color: #666666;
}

.tree-accordion dd span {
    display: block;
    margin-left: 20px;
}

.tree-accordion dd {
    margin: 0 0 0 20px;
}

This basic CSS will ensure the expandable list is displayed cleanly, with appropriate spacing and icons to enhance user experience.

Step 5: Enqueue Font Awesome Icons

To ensure the icons are available, we’ll enqueue the Font Awesome library in your theme’s functions.php file:

function whiskey_enqueue_scripts() {
    wp_enqueue_style( 'font-awesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css', [], '6.6.0' );
}
add_action( 'wp_enqueue_scripts', 'whiskey_enqueue_scripts' );

If you want to use minimal CSS and remove JavaScript altogether, you can use the <details> element and put each year inside. It might not look as fancy, and it might still require a bit of JavaScript to only have one <details> element open at a time. It all depends on your project and your implementation.

Also, the Font Awesome icons can be replaced with Unicode icons, or even emojis.

Related posts

Leave a Reply

Your email address will not be published. Required fields are marked *