Tag: Lists

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

, , , , , , , ,

Rendering Lists Using React Virtualized

Working with data in React is relatively easy because React is designed to handle data as state. The hassle begins when the amount of data you need to consume becomes massive. For example, say you have to handle a dataset which is between 500-1,000 records. This can result in massive loads and lead performance problems. Well, we’re going to look at how we can make use of virtualized lists in React to seamlessly render a long list of data in your application.

We’re going to use the React Virtualized component to get what we need. It will allow us to take large sets of data, process them on the fly, and render them with little-to-no jank.

The setup

React Virtualized already has a detailed set of instructions to get it up and running, so please check out the repo to get started.

We’re going to want data to work with, so we will set up a function which uses faker to create a large data set.

function createRecord(count) {   let records = [];    for (let i = 0; i < count; i++) {     records.push({       username: faker.internet.userName(),       email: faker.internet.email()     });   }   return records; }

Next, we will pass it the number of data records we want to create, like so:

const records = createRecord(1000);

Alright, now we have what we need to work on rendering a list of those records!

Creating a virtualized list

Here’s the list we want to create, sans styling. We could make use of the few presentational styles that the library includes by importing the included CSS file, but we’re going to leave that out in this post.

See the Pen React Virtualized 1 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Go ahead and re-run that demo. Crazy fast, right?

You might wonder what the heck React Virtualized is doing behind the scenes to make that happen. Turns out it’s a bunch of crazy and cool sizing, positioning, transforms and transitions that allow the records to scroll in and out of view. The data is already there and rendered. React Virtualized creates a window frame that allows records to slide in and out of view as the user scrolls through it.

To render a virtualized list in React Virtualized, we make use of its List component, which uses a Grid component internally to render the list.

First, we start by setting up rowRenderer, which is responsible for displaying a single row and sets up an index that assigns an ID to each record.

rowRenderer = ({ index, isScrolling, key, style }) => {     return (       <div key={key} style={style}>         <div>{this.props.data[index].username}</div>         <div>{this.props.data[index].email}</div>       </div>     );   };

As you can see, this returns a single div node that contains two additional divs: one for the username and another for the email. You know, a common list pattern to display users.

rowRenderer accepts several parameters. Here’s what they are and what each one does:

  • index: The numeric ID of a record.
  • isScrolling: Indicates if the scrolling is occurring in the List component.
  • isVisible: Determines if a row is visible or out of view.
  • key: The records position in the array.
  • parent: Defines whether the list is a parent or a child of another list.
  • style: A style object to position the row.

Now that we know more about the rowRenderer function, let’s make put it to use in the List component:

<List   rowCount={this.props.data.length}   width={width}   height={height}   rowHeight={rowHeight}   rowRenderer={this.rowRenderer}   overscanRowCount={3} />

You may have noticed a few new parameters. Here’s what they are:

  • rowCount: This takes the numbers of a row in a list that we pass to calculate the length of our list.
  • width: The width of the list.
  • height: The height of the list.
  • rowHeight: This can be a number or a function that returns a row height given its index.
  • rowRenderer: This is responsible for rendering the row. the list is not supposed to be passed directly, so we pass the rowRenderer function that we created in this tutorial.
  • overscanRowCount: This is used to render additional rows in the direction the user scrolls. It reduces the chances of the user scrolling faster than the virtualized content is rendered.

At the end, your code should look something like this;

const { List } = ReactVirtualized  ...  const height = 700; const rowHeight = 40; const width = 800;  class App extends React.Component {   rowRenderer = ({ index, isScrolling, key, style }) => {     return (       <div key={key} style={style}>         <div>{this.props.data[index].username}</div>         <div>{this.props.data[index].email}</div>       </div>     );   };    render() {     return (       <div>         <h2>Details</h2>         <List           rowCount={this.props.data.length}           width={width}           height={height}           rowHeight={rowHeight}           rowRenderer={this.rowRenderer}           overscanRowCount={3}         />       </div>     );   } }

Cell measurer

According to the documentation, a cell measurer is a higher-order component that is used to temporarily render a list. It’s not yet visible to the user at this point, but the data is held and ready to display.

Why should you care about this? The popular use case is a situation where the value of your rowHeight is dynamic. React Virtualized can render the height of the row on render then cache that height so it no longer needs to calculate as data scrolls out of view — it’s always the right height, no matter the content it contains!

First, we create our cache, which can be done in our component’s constructor using CellMeasurerCache:

constructor() {   super()   this.cache = new CellMeasurerCache({     fixedWidth: true,     defaultHeight: 100   }) }

We make use of the cache when we set up the List component;

<List   rowCount={this.props.data.length}   width={rowWidth}   height={listHeight}   deferredMeasurementCache={this.cache}   rowHeight={this.cache.rowHeight}   rowRenderer={this.renderRow}   overscanRowCount={3} />

The value passed to deferredMeasurementCache will be used to temporarily rendering the data, then — as the calculated value for rowHeight comes in — additional rows will flow in like they were always there.

Next, though, we will make use of React Virtualized’s CellMeasurer component inside our rowRenderer function instead of the div we initially set up as a placeholder:

rowRenderer = ({ index, parent, key, style }) => {   return (     <CellMeasurer       key={key}       cache={this.cache}       parent={parent}       columnIndex={0}       rowIndex={index}     >       <div style={style}>         <div>{this.props.data[index].username}</div>         <div>{this.props.data[index].email}</div>       </div>     </CellMeasurer>   );   };

Now the data is fetched, cached and ready to display in the virtual window at will!

Virtualized table

Yeah, so the main point of this post is to cover lists, but what if we actually want to render data to a table instead? React Virtualized has you covered on that front, too. In this case, we will make use of Table and Column components that come baked into React Virtualized.

Here’s how we would put those components to use in our primary App component:

class App extends React.Component {   render() {     return (       <div>         <h2>Details</h2>         <Table           width={500}           height={300}           headerHeight={20}           rowHeight={40}           rowCount={this.props.data.length}           rowGetter={({ index }) => this.props.data[index]}         >           <Column             label='Username'             dataKey='username'             width={100}           />                        <Column             width={200}             label='Email'             dataKey='email'           />         </Table>       </div>     );   } }

The Table component accepts the following parameters:

  • width: The width of the table.
  • height: The height of the table.
  • headerHeight: The table header height.
  • rowHeight: The height of a row given its index.
  • rowCount: This is the initial number of rows we want in the table. It’s the same as the way we defined the number of records we wanted to start with in the List component example.
  • rowGetter: This returns the data of a specific row by its index.

If you take a look at the Column component, you will notice that we put a dataKey parameter to use. That passes the data for each column we called in the dataKey, which receives a unique identifier for that data. Remember that in the function where we create our random data, we make use of two keys; username and email. This is why we have the dataKey of one column set as username and the other set as email.

In conclusion

Hopefully, this walkthrough gives you a good idea of what React Virtualized is capable of doing, how it can make rendering large data sets into lists and tables super fast, and how to put it to use in a project.

We’ve only scratched the surface here. The library is capable of handling a lot of other use cases, like generating placeholders for the data records on scroll, an infinite loading component to fetch and cache data in real-time, a method for allowing arrow keys to navigate through the data, and a slick grid and masonry layouts that we didn’t even cover here.

That should give you a lot to play around with!

Plus, the package is highly maintained. In fact, you can join the Slack group to keep up with the project, contribute to it, and generally get to connect with other folks.

It’s also worth noting that React Virtualized has it own tag in StackOverflow and that can be a good resource to find questions other people have asked about it, or even post your own questions.

Oh, and if you’ve put React Virtualized to use on a project, we’d love to know it! Share it with us in the comments with some notes on how you approached it or what you learned from it.

The post Rendering Lists Using React Virtualized appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]