DEV Community

Cover image for Build a Custom Archives Page in Plain WordPress in Minutes
Antonello Zanini for Writech

Posted on • Originally published at writech.run

Build a Custom Archives Page in Plain WordPress in Minutes

An archives page is a dedicated location on your WordPress site where you can group your content and put it on display for viewers, allowing you to bring together all of your old posts onto one page.

The goal of this tutorial is to show how to build a minimal archives page in plain WordPress without the need of a plugin. The custom archives page we are about to build will group our posts by year and month. It will also give users the ability to find articles by title.

Here's a sneak peek at what our archives page will look like:

Our archives page inspired by Zen Habit Archives page

1. Defining a Custom Archives Page Template

First of all, we need to define a custom template for our archives page. To do this, create and upload a file called archives.php to your current theme directory. This will contain the following code:

<?php
/*
 * Template Name: Archives
 */
?>

<?php
add_action('wp_enqueue_scripts', 'add_archives_dependencies');

function add_archives_dependencies() {
    $js_path = get_template_directory_uri() . '/assets/js/archives.js';
    $css_path = get_template_directory_uri() . '/assets/css/archives.css';

    // 'wp-util' is a required dependency
    wp_enqueue_script( 'archives', $js_path, [ 'wp-util' ]);
    wp_enqueue_style( 'archives', $css_path);
}
?>

<?php get_header(); ?>

    <div class="container">

        <h2><?php the_title(); ?></h2>

        <form>
            <p class="search-title">Search this site:</p>
            <p class="search-input">
                <input type="text" class="input-box" id="search-input"/>
            </p>
        </form>

        <div class="results" id="results">
        </div>

    </div>

<?php get_footer(); ?>
Enter fullscreen mode Exit fullscreen mode

The goal of the custom add_archives_dependencies function is to import archive.js and archive.css (which we will see later on) into our archives page. This is achieved by employing the following two functions: wp_enqueue_script and wp_enqueue_style.

As we can see in the animated GIF, the search feature does not reload the page. This means that AJAX technologies are used. To make an AJAX call, we need to add an extra dependency to the page. This dependency is wp-util, which is enqueued along with archives.js.

2. Creating the Script and Style File

Now, we are going to create the aforementioned files: archives.css and archives.js. The former aims to implement the search function, while the latter makes the archives page look stylish.

Here is archives.css:

html,
body,
div,
h2,
h3,
h4,
p,
form {
    margin: 0;
    padding: 0;
    border: 0;
    outline: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
}

body {
    line-height: 1;
}

a {
    margin: 0;
    padding: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
}

input {
    vertical-align: middle;
}

* {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body {
    font-family: nunito-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
    background-color: #ffffff;
    color: #333;
    font-size: 18px;
    line-height: 1.25em;
    font-weight: 300;
}

p {
    line-height: 1.5em;
    margin-bottom: 1em;
}

a:link,
a:visited,
a:hover {
    color: #333;
    text-decoration: none;
    border: none;
    -webkit-transition: all .3s linear;
    -moz-transition: all .3s linear;
    -ms-transition: all .3s linear;
    -o-transition: all .3s linear;
    transition: all .3s linear;
}

a:hover {
    color: #aaa;
}

h2,
h3,
h4 {
    font-weight: 300;
    text-align: center;
    line-height: 1.25;
}

h2 {
    font-size: 2em;
    margin: 2.5em 0 1.25em 0;
    text-align: center;
}

h3 {
    font-size: 1.5em;
    margin: 2em 0 1em 0;
    text-align: center;
}

h4 {
    font-size: 1em;
}

.container {
    overflow: hidden;
    max-width: 50%;
    margin: 0 auto;
    padding: 0 2.5em;
}

p.search-title {
    text-align: center;
    font-weight: 400;
}

form input.input-box {
    width: 94%;
    height: 30px;
    font-size: 18px;
    padding: 10px 15px;
    border: 1px solid #ddd;
    -webkit-border-radius:4px;
    -moz-border-radius:4px;
    border-radius:4px;
    -webkit-transition: all .3s linear;
    -moz-transition: all .3s linear;
    -ms-transition: all .3s linear;
    -o-transition: all .3s linear;
    transition: all .3s linear;
}

form input.input-box:hover {
    border: 1px solid #aaa;
}

form input.input-box:focus {
    outline: none;
    border: 1px solid #333;
}

.results {
    margin-bottom: 5em;
}

.day p,
.month h4 {
    font-weight: 400;
}

.month h4 {
    margin: 2em 0 1em 0;
}

.day p,
.post-title p {
    margin-bottom: 0;
}

.day p {
    padding-right: 60px;
    width: max-content;
}

.day-title {
    display: flex;
    align-items: flex-start;
    border-bottom: 1px solid #eee;
    padding: 10px 0;
}

@media screen and (max-width: 800px) {

    .container {
        max-width: 70em;
        margin: 0 auto;
        padding: 0 1.5em;
        overflow: hidden;
    }

    h2,
    h3,
    h3,
    .month h4,
    p.search-title {
        text-align: left;
    }

    h2 {
        font-size: 2em;
        margin: 2em 0 1em 0;
    }

}

@media only screen and (max-device-width: 480px) {
    h2, h3, h3, .month h4, p.search-title {
        text-align:center;
    }
}

@media print {
    body {
        font-family: Helvetica, sans-serif;
        font-size: 14px;
        background: white;
        color: black;
        margin: 10px;
        width: auto;
    }

    .container {
        display: block;
    }
}

@media all and (min-width:320px) and (max-width:667px) {
    ::-webkit-scrollbar {
        display: none;
    }
}
Enter fullscreen mode Exit fullscreen mode

And this is archives.js:

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

    // https://medium.com/@griffinmichl/implementing-debounce-in-javascript-eab51a12311e
    const debounce = (func, wait) => {
        let timeout;

        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            }

            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        }
    }

    const resultsDiv = document.querySelector('#results');

    const searchInput = document.querySelector('#search-input');

    const debouncedSearch = debounce(function() {
        resultsDiv.innerHTML = (`<p style="text-align:center">Loading...</p>`)

        wp.ajax
        .post("get_archives_data", {
            search: searchInput.value
        })
        .done(function(response) {
            let innerHTML = ``;

            if (response.length > 0) {
                let lastYear = 0;
                let lastMonth = 0;

                response.forEach(p => {
                    const year = p.year;
                    const month = p.month;

                    if (year !== lastYear) {
                        lastYear = year;
                        lastMonth = 0;

                        innerHTML += `<div class=year><h3>${year}</h3></div>`;
                    }

                    if (month !== lastMonth) {
                        lastMonth = month;
                        innerHTML += `<div class=month><h4>${p.month_name}</h4></div>`;
                    }

                    innerHTML += `
                    <div class="day-title">
                        <div class="day">
                            <p>${p.dayofmonth.padStart(2, '0')}</p>
                        </div>
                        <div class="post-title">
                            <p id=p${p.id}>
                                <a href="/${p.post_name}">
                                    ${p.encoded_title}
                                </a>
                            </p>
                        </div>
                    </div>
                    `;

                });
            } else {
                innerHTML = `<p style="text-align:center">Nessun articolo trovato</p>`;
            }

            resultsDiv.innerHTML = innerHTML;
        });
    }, 350);

    searchInput.addEventListener("input", function (e) {
        e.preventDefault();

        debouncedSearch();
    });

    // loading data the first time the page is rendered
    debouncedSearch();

}, false);
Enter fullscreen mode Exit fullscreen mode

As you can see based on how these files are referenced in archives.php, we uploaded these files to assets/js and assets/css of our current theme directory respectively. Feel free to place these two files wherever you want, but do not forget to update archives.php accordingly.

As we do not want our search function to suffer from performance issues, we used the debouncing technique. This way, we ensured that the AJAX call is not made too frequently, which is a good way to limit the number of HTTP requests.

As you can see, in archives.js we used the function: wp.ajax.post. This is one of the wp-util helpers and allowed us to make the AJAX call to retrieve the required data to populate the archives page.

3. Updating functions.php

In the wp.ajax.post function call, we passed "get_archives_data" as a parameter. That string represents the name of an action that I will show you how to properly register. To do this, append the following lines of code to functions.php:

<?php

// ...

add_action( 'wp_ajax_nopriv_get_archives_data', 'get_archives_data' );
add_action( 'wp_ajax_get_archives_data', 'get_archives_data' );

function get_archives_data() {
    global $wpdb, $wp_locale;

    $search = isset( $_POST["search"] ) ? $_POST["search"] : "";

    $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) as `month`, DAYOFMONTH(post_date) as `dayofmonth`, ID, post_name, post_title FROM $wpdb->posts WHERE post_type = 'post' AND post_status = 'publish' AND post_title LIKE '%$search%' ORDER BY post_date DESC";

    $results = $wpdb->get_results($query);

    $ajax_response = [];

    foreach ( $results as $result ) {
        $result->month_name = $wp_locale->get_month($result->month);
        $result->encoded_title = strip_tags(apply_filters('the_title', $result->post_title));
        $ajax_response[] = $result;
    }

    wp_send_json_success($ajax_response);
}
Enter fullscreen mode Exit fullscreen mode

Please note: if your current theme does not have a functions.php file, you have to create one before appending the lines of code onto it.

With these lines, we defined an AJAX endpoint thanks to the add_action function and wp_ajax hook. When reached, it returns the data required to populate the archives page (retrieved with a custom query and sent with the wp_send_json_success function).

4. Creating the Archives Page

Now, it is time to create the custom archives page. Go to your WordPress admin panel and add a new page (Pages » Add New). As you can see in the GIF, we called this page "Archives" but you are free to give it the name that you prefer.

On the right-hand side of your screen, you should see a meta box called "Page Attributes". Click on the drop-down menu below the "Template" field and choose "Archives" as your page template.

The Page Attributes meta box

Choose a permalink, save, and publish the page.

Et voila! Your custom archives page is ready to be visited!

Conclusion

The source code of his article can be found in my GitHub repository.

Allowing users to see all your posts in one elegant page is a great feature to add to your WordPress website. This article explored how to achieve this result, as well as how to provide the necessary code to implement a useful search function to help users find the content that interests them quickly.

I hope that you found this article helpful, thanks for reading!


The post "Build a Custom Archives Page in Plain WordPress in Minutes" appeared first on Writech.

Top comments (0)