Tag: Filtering

Filtering Lists Dynamically With Vue on the Server Side is Easier Than You’d Think

I recently attended the ARTIFACT conference in Austin, TX, and was inspired by a few talks about accessibility through the lens of site performance. It became clear to me that there is this tendency to rely on big JavaScript frameworks to handle the work — like React, Vue, and Angular — but that can be overkill in some cases. That is, negatively affecting site performance, and thus accessibility. At the same time, these frameworks can make development easier and more efficient for developers. My big takeaway from the conference was to see how a fast, performant experience can be balanced with my own development process.

This was on my mind as I was building a list-filtering feature on a project a few days after the conference. Pretty standard stuff: I needed a list of posts and some category filtering. I was using CraftCMS for front-end routes and templating as well as some Vue components here and there for some added JavaScript juiciness. Not a full-on “single page app” but more like a sprinkle of Vue.

The typical way one might approach this is to:

  1. render the page with an empty div using Craft/Twig
  2. mount a Vue component to that div
  3. make an Ajax call from the Vue component to an API to gather the posts as JSON
  4. render the posts and tie in the filtering.

Since the posts are held as an array within Vue, dynamic list rendering is a pretty cut and dry task.

Simple. Done, right? Well… that extra Ajax request means the user is presented with no content on the initial load depending on the user’s network, which might take some time. We could add a loading indicator, but maybe we can do better?

Preferably, the posts are rendered on the initial page request from the CMS.

But how do we get the static HTML “hooked up” with Vue for the filtering?

After taking a step back to rethink the problem, I realized I could use v-if directives to achieve the same thing with some inline JavaScript from Twig (“in the loop”). Below, I’ll show how I went about it.

My original project used CraftCMS, but I’m going to do the demos below in WordPress. This is just a concept. It can be applied to CraftCMS/Twig or any other CMS/templating engine combo.

First we need a filtering UI. This will likely go above the start of the loop in an archive template.

<?php $  terms = get_terms( [   'taxonomy' => 'post_tag', // I used tags in this example, but any taxonomy would do   'hide_empty' => true,   'fields' => 'names' ] );  if(!empty($  terms)): ?>   <div>     Filter:      <ul class="filters">       <li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''">All</button></li>       <?php foreach($  terms as $  term): ?>       <li class="filters__item">         <button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $  term; ?>'}" @click="tag = '<?php echo $  term; ?>'"><?php echo $  term; ?></button>       </li>       <?php endforeach; ?>     </ul>     <p aria-live="polite">Showing posts tagged {{ tag ? tag : 'all' }}.</p>   </div> <?php endif; ?>

Following along with the code, we get some tags from WordPress with get_terms() and output them in a foreach loop. You’ll notice the button for each tag has some Vue directives we’ll use later.

We have our loop!

    <div class="posts">       <?php       // Start the Loop.       while ( have_posts() ) : the_post();              <article id="post-<?php the_ID(); ?>"           <?php post_class(); ?>           v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(),  ['fields' => 'names'])); ?>.includes(tag) || tag === ""'         >           <header class="entry-header">             <h2><?php the_title(); ?></h2>           </header>                <div class="entry-content">             <?php the_excerpt(); ?>           </div>         </article>            // End the loop.       endwhile; ?>     </div>

This is a pretty standard WordPress loop for posts. You’ll notice some Vue directives that make use of PHP outputting CMS content.

Aside from some styling, all that’s left is the Vue “app.” Are you ready for it? Here it is:

new Vue({   el: '#filterablePosts',   data: {     'tag': ''   } });

Yes, really, that’s all that’s needed in the JavaScript file to get this to work!

So, what’s going on here?

Well, instead of some JSON array of posts that gets fed into Vue, we output the posts on the initial page load with WordPress. The trick is to use PHP to output what’s needed in the Vue directives: v-if and :class.

What’s happening on the filter buttons is an onclick event handler (@click) that sets the Vue variable “tag” to the value of the WordPress post tag.

@click="tag = '<?php echo $  term; ?>'"

Also, if that Vue variable equals the value of the button (in the :class directive), it adds an active class for the button. This is just for styling.

:class="{'filters__button--active': tag === '<?php echo $  term; ?>'}"

For the list of articles, we conditionally display them based on the value of the Vue “tag” variable:

v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(),  ['fields' => 'names'])); ?>.includes(tag) || tag === ""'

The PHP function json_encode allows us to output an array of post tags as JavaScript, which means we can use .includes() to see if the Vue “tag” variable is in that array. We also want to show the article if no tag is selected.

Here it is put together using the Twenty Nineteen theme template archive.php as a base:

<?php get_header(); ?>   <section id="primary" class="content-area">     <main id="main" class="site-main">       <?php if ( have_posts() ) : ?>         <header class="page-header">           <?php the_archive_title( '<h1 class="page-title">', '</h1>' ); ?>         </header>          <div class="postArchive" id="filterablePosts">           <?php $  terms = get_terms( [               'taxonomy' => 'post_tag',               'hide_empty' => true,               'fields' => 'names'           ] );            if(!empty($  terms)): ?>             <div class="postArchive__filters">               Filter:                <ul class="postArchive__filterList filters">                 <li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''" aria-controls="postArchive__posts">All</button></li>                    <?php foreach($  terms as $  term): ?>                   <li class="filters__item">                     <button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $  term; ?>'}" @click="tag = '<?php echo $  term; ?>'" aria-controls="postArchive__posts"><?php echo $  term; ?></button>                   </li>                 <?php endforeach; ?>                  </ul>                  <p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p>             </div>           <?php endif; ?>              <div class="postArchive__posts">               <?php               // Start the Loop.               while ( have_posts() ) : the_post(); ?>                 <article                   id="post-<?php the_ID(); ?>"                   <?php post_class(); ?>                   v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""'                 >                   <header class="entry-header">                     <h2><?php the_title(); ?></h2>                   </header>                            <div class="entry-content">                       <?php the_excerpt(); ?>                   </div>                  </article>               <?php endwhile; // End the loop. ?>           </div>         </div>               <?php       // If no content, include the "No posts found" template.       else :         get_template_part( 'template-parts/content/content', 'none' );       endif; ?>     </main>   </section>  <?php get_footer();

Here’s a working example on CodePen

See the Pen
Dynamic List Filtering in Vue using Server-side data fetching
by Dan Brellis (@danbrellis)
on CodePen.

Bonus time!

You may have noticed that I added in an aria-live="polite" notifier below the filter button list to let assistive tech users know the content has changed.

<p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p>

To get the postCount Vue variable, we add some extra JavaScript to our Vue component:

new Vue({   el: '#filterablePosts',   data: {     'tag': '',     'postCount': '' },   methods: {     getCount: function(){       let posts = this.$  el.getElementsByTagName('article');       return posts.length;   }   },   beforeMount: function(){     this.postCount = this.getCount();   },   updated: function(){     this.postCount = this.getCount();   } });</p>

The new method getCount is used to select the article elements in our component div and return the length. Before the Vue component mounts we get the count to add to our new Vue postCount variable. Then, when the component updates after the user selects a tag, we get the count again and update our variable.

CSS-Tricks

, , , , , , , ,

Filtering Data Client-Side: Comparing CSS, jQuery, and React

Say you have a list of 100 names:

<ul>   <li>Randy Hilpert</li>   <li>Peggie Jacobi</li>   <li>Ethelyn Nolan Sr.</li>    <!-- and then some --> </ul>

…or file names, or phone numbers, or whatever. And you want to filter them client-side, meaning you aren’t making a server-side request to search through data and return results. You just want to type “rand” and have it filter the list to include “Randy Hilpert” and “Danika Randall” because they both have that string of characters in them. Everything else isn’t included in the results.

Let’s look at how we might do that with different technologies.

CSS can sorta do it, with a little help.

CSS can’t select things based on the content they contain, but it can select on attributes and the values of those attributes. So let’s move the names into attributes as well.

<ul>   <li data-name="Randy Hilpert">Randy Hilpert</li>   <li data-name="Peggie Jacobi">Peggie Jacobi</li>   <li data-name="Ethelyn Nolan Sr.">Ethelyn Nolan Sr.</li>    ... </ul>

Now to filter that list for names that contain “rand”, it’s very easy:

li {   display: none; } li[data-name*="rand" i] {   display: list-item; }

Note the i on Line 4. That means “case insensitive” which is very useful here.

To make this work dynamically with a filter <input>, we’ll need to get JavaScript involved to not only react to the filter being typed in, but generate CSS that matches what is being searched.

Say we have a <style> block sitting on the page:

<style id="cssFilter">   /* dynamically generated CSS will be put in here */ </style>

We can watch for changes on our filter input and generate that CSS:

filterElement.addEventListener("input", e => {   let filter = e.target.value;   let css = filter ? `     li {       display: none;     }     li[data-name*="$  {filter}" i] {       display: list-item;     }   ` : ``;   window.cssFilter.innerHTML = css; });

Note that we’re emptying out the style block when the filter is empty, so all results show.

See the Pen
Filtering Technique: CSS
by Chris Coyier (@chriscoyier)
on CodePen.

I’ll admit it’s a smidge weird to leverage CSS for this, but Tim Carry once took it way further if you’re interested in the concept.

jQuery makes it even easier.

Since we need JavaScript anyway, perhaps jQuery is an acceptable tool. There are two notable changes here:

  • jQuery can select items based on the content they contain. It has a selector API just for this. We don’t need the extra attribute anymore.
  • This keeps all the filtering to a single technology.

We still watch the input for typing, then if we have a filter term, we hide all the list items and reveal the ones that contain our filter term. Otherwise, we reveal them all again:

const listItems = $  ("li");  $  ("#filter").on("input", function() {   let filter = $  (this).val();   if (filter) {     listItems.hide();     $  (`li:contains('$  {filter}')`).show();   } else {     listItems.show();   } });

It’s takes more fiddling to make the filter case-insensitive than CSS does, but we can do it by overriding the default method:

jQuery.expr[':'].contains = function(a, i, m) {   return jQuery(a).text().toUpperCase()       .indexOf(m[3].toUpperCase()) >= 0; };

See the Pen
Filtering Technique: jQuery
by Chris Coyier (@chriscoyier)
on CodePen.

React can do it with state and rendering only what it needs.

There is no one-true-way to do this in React, but I would think it’s React-y to keep the list of names as data (like an Array), map over them, and only render what you need. Changes in the input filter the data itself and React re-renders as necessary.

If we have an names = [array, of, names], we can filter it pretty easily:

filteredNames = names.filter(name => {   return name.includes(filter); });

This time, case sensitivity can be done like this:

filteredNames = names.filter(name => {   return name.toUpperCase().includes(filter.toUpperCase()); });

Then we’d do the typical .map() thing in JSX to loop over our array and output the names.

See the Pen
Filtering Technique: React
by Chris Coyier (@chriscoyier)
on CodePen.

I don’t have any particular preference

This isn’t the kind of thing you choose a technology for. You do it in whatever technology you already have. I also don’t think any one approach is particularly heavier than the rest in terms of technical debt.

The post Filtering Data Client-Side: Comparing CSS, jQuery, and React appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]