Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: facetwp

  • Adding Sort Options to Facet

    Adding Sort Options to Facet

    As I implement more and more aspects of FacetWP, I find more and more ways to manipulate the searches. At first I only added in the features that let people easily search for multiple aspects at once. But I hadn’t yet added in any features to sorting.

    Sorting and Ordering

    The way Facets generally work is that you can easily organize all ‘types’ together, so if you wanted to search for everything that crossed four separate categories, it was very easy. In addition, you can extend it to search meta data as well.

    Sorting, on the other hand, is changing the order of the results. For example, if you wanted to search for everyone with terms A, B, and D, and post meta foo, but order them based on post meta bar, you can!

    A Practical Example

    I always do better with examples I can wrap my hands around.

    Take television shows. Take a list of 500 TV shows, and have them include the following taxonomies:

    • Genres (drama, sitcom, etc)
    • Airdates (Year to Year)
    • Tropes (common tropes)
    • Number of characters
    • Number of dead characters

    That’s enough for now.

    With that list, and a couple facets, you can concoct a smaller list of all sitcoms that aired in between 2014 and 2016 (inclusive), with a trope of ‘sex workers.’ The answer is 4 by the way. By default, the list displays alphabetically.

    But. What if you wanted to order them by the ones with the most characters first?

    That’s sorting.

    The Code

    Okay so how do we add this in? Functions!

    Facet comes with quite a few defaults, but it lets you add your own sort options. The two things I’m going to show below are how to rename the display labels for some of the defaults, and how to add in one new option for the most number of characters:

    add_filter( 'facetwp_sort_options', 'DOMAIN_facetwp_sort_options', 10, 2 );
    
    function facetwp_sort_options( $options, $params ) {
    
    	$options['default']['label']    = 'Default (Alphabetical)';
    	$options['title_asc']['label']  = 'Name (A-Z)';
    	$options['title_desc']['label'] = 'Name (Z-A)';
    
    	if ( is_post_type_archive( 'DOMAIN_shows' ) ) {
    
    		$options['most_characters'] = array(
    			'label' => 'Number of Characters (Descending)',
    			'query_args' => array(
    				'orderby'  => 'meta_value_num', // sort by numerical
    				'meta_key' => 'DOMAIN_char_count',
    				'order'    => 'DESC', // descending order
    			)
    		);
    	}
    

    I have this wrapped in a check for is_post_type_archive because I don’t want the options to show on other pages. The meta key is the name of the meta key you’re going to use to sort by (I have key that updates every time a post is saved with a count of characters attached) and the orderby value is one of the ones WP Query can use.

    End result?

    A dropdown with the options

    Looks nice!

  • FacetWP and Genesis Pagination

    FacetWP and Genesis Pagination

    If you use StudioPress’ Genesis themes, you may be used to that pretty numerical pagination instead of ye olde previous and next buttons. And if you use FacetWP, you may have found out you need to use their pagination to make things work right.

    Thankfully this can be fixed thanks to two people’s previous work.

    Replace the Navigation

    First we look to Sridhar Katakam who came up with an elegant way to replace the normal Genesis post navigation. The code is nearly the same, I just single-lined the returns:

    // Replace Genesis' Pagination with FacetWP's.
    // Context: Posts page, all Archives and Search results page.
    // @author Sridhar Katakam - http://sridharkatakam.com/facetwp-genesis/
    add_action( 'loop_end', 'MYSITE_replace_genesis_pagination' );
    function MYSITE_replace_genesis_pagination() {
    	if ( ! ( is_home() || is_archive() || is_search() ) ) return;
    	remove_action( 'genesis_after_endwhile', 'genesis_posts_nav' );
    	add_action( 'genesis_after_endwhile', 'MYSITE_facet_posts_nav' );
    }
    function MYSITE_facet_posts_nav() {
    	echo facetwp_display( 'pager' );
    }
    

    The nice part about this is that if you’re not using FacetWP on that page, it reverts to the default pager. I’m not doing a check on if Facet exists because I have this in a giant block that does it for me. If you need to, though, the code would be this:

    if ( function_exists( 'facetwp_display' ) ) { 
        add_action( 'loop_end', 'MYSITE_replace_genesis_pagination' ); 
    }
    

    And yes, you should always check if the plugin is loaded before you break things.

    Style the Navigation

    The styling magic comes from a slight fork of Matt Gibbs’ code to remove pagination if there’s only one post:

    // Style pagination to look like Genesis
    // @author Matt Gibbs
    // https://gist.github.com/mgibbs189/69176ef41fa4e26d1419
    function MYSITE_facetwp_pager( $output, $params ) {
        $output = '<div class="archive-pagination pagination"><ul>';
        $page = (int) $params['page'];
        $total_pages = (int) $params['total_pages'];
    
        // Only show pagination when > 1 page
        if ( 1 < $total_pages ) {
    
            if ( 1 < $page ) {
                $output .= '<li><a class="facetwp-page" data-page="' . ( $page - 1 ) . '">&laquo; Previous Page</a></li>';
            }
            if ( 3 < $page ) {
                $output .= '<li><a class="facetwp-page first-page" data-page="1">1</a></li>';
                $output .= ' <span class="dots">…</span> ';
            }
            for ( $i = 2; $i > 0; $i-- ) {
                if ( 0 < ( $page - $i ) ) {
                    $output .= '<li><a class="facetwp-page" data-page="' . ($page - $i) . '">' . ($page - $i) . '</a></li>';
                }
            }
    
            // Current page
            $output .= '<li class="active" aria-label="Current page"><a class="facetwp-page active" data-page="' . $page . '">' . $page . '</a></li>';
    
            for ( $i = 1; $i <= 2; $i++ ) {
                if ( $total_pages >= ( $page + $i ) ) {
                    $output .= '<li><a class="facetwp-page" data-page="' . ($page + $i) . '">' . ($page + $i) . '</a></li>';
                }
            }
            if ( $total_pages > ( $page + 2 ) ) {
                $output .= ' <span class="dots">…</span> ';
                $output .= '<li><a class="facetwp-page last-page" data-page="' . $total_pages . '">' . $total_pages . '</a></li>';
            }
            if ( $page < $total_pages ) {
                $output .= '<li><a class="facetwp-page" data-page="' . ( $page + 1 ) . '">Next Page &raquo;</a></li>';
            }
        }
    
        $output .= '</ul></div>';
    
        return $output;
    }
    add_filter( 'facetwp_pager_html', 'MYSITE_facetwp_pager', 10, 2 );
    

    This is somewhat obvious. It adds in <li></li> around each page item, and also wraps the entire block in the default Genesis code of <div class="archive-pagination pagination"><ul></ul></div> to produce the default Genesis display.

  • Multi Faceted Connections

    Multi Faceted Connections

    That’s a pun because I’m using FacetWP y’all.

    As you probably know by now, I have a site that has a lot of weird data. And one of the problems I ran into with FacetWP and my site was that I saved data in a serialized manner.

    Simple Arrays

    Since characters can have multiple actors, the data is saved in a serialized array like this: a:1:{i:0;s:13:"Lucy Lawless";}

    I want to be able to search for all the characters Lucy Lawless plays, even if someone else also played the role, so to do that I need to tell FacetWP to save the data twice, an entry for each actor. To do that, I use this code:

    add_filter( 'facetwp_index_row', 'filter_facetwp_index_row', 10, 2 );
    function filter_facetwp_index_row( $params, $class ) {	
    	// Actors
    	// Saves one value for each actor
    	if ( 'char_actors' == $params['facet_name'] ) {
    		$values = (array) $params['facet_value'];
    		foreach ( $values as $val ) {
    			$params['facet_value'] = $val;
    			$params['facet_display_value'] = $val;
    			$class->insert( $params );
    		}
    		return false; // skip default indexing
    	}
    	return $params;
    }
    

    That saves two entries in the FacetWP table like this:

    An example of two actors for one character

    Complex Arrays

    Buuuuut I also have a complex array where I list a show’s airdates: a:2:{s:5:"start";s:4:"1994";s:6:"finish";s:4:"2009";}

    Now that doesn’t look too weird, I know, but the problem is I wanted to be able to compare the start and end dates, so you could get a list of, say, all shows that were on air between 1950 and 1960 (two, by the way). In order to do that, I had to break the array apart into not only two values, but two separate sources!

    In order to make that work, I do this:

    // Airdates
    // Splits array value into two sources
    if ( 'show_airdates' == $params['facet_name'] ) {
    	$values = (array) $params['facet_value'];
    
    	$start = ( isset( $values['start'] ) )? $values['start'] : '';
    	$end   = ( isset( $values['finish'] ) )? $values['finish'] : date( 'Y' );
    
    	$params['facet_value']         = $start;
    	$params['facet_display_value'] = $start;
    	$class->insert( $params );
    
    	$params['facet_source']        = 'cf/lezshows_airdates_end';
    	$params['facet_name']          = 'show_airdates_end';
    	$params['facet_value']         = $end;
    	$params['facet_display_value'] = $end;
    	$class->insert( $params );
    	
    	return false; // skip default indexing
    }
    

    That gives me two database entries like so:

    An example of two values in two separate sources

    The reason this is done is because I have a facet that compares the datasets for lezshow_airdates_end with lezshow_airdates and if the numbers are between them, that’s what it shows.

    And this works because of this filter:

    // Filter Facet sources
    add_filter( 'facetwp_facet_sources', function( $sources ) {
        $sources['custom_fields']['choices']['cf/lezshows_airdates_end'] = 'Airdates End';
        return $sources;
    });
    

    That creates a new custom field based on the values in cf/lezshows_airdates_end so I can compare between the two. And with a snazzy slider, I can do this:

    Aired Between ... as a slider

  • FacetWP: Making Sorting Suck Less

    FacetWP: Making Sorting Suck Less

    Sorting data in WordPress is generally done in the most basic of ways. You want to see all posts that are in a specific category, you go to example.com/category/drinks/ and there you are. But if you want to see everything in the category ‘drinks’ with the tag ‘bourbon’ and the custom taxonomy of ‘ingredients’ and a value of ‘mint’ AND ‘simple syrup’ to get the recipe for a mint julep, then you have a pretty crazy complex query.

    Enter FacetWP

    FacetWP is a premium plugin that, for $79 a year, handles all that crazy sorting for you. And yes, it’s worth it.

    FacetWP introduces advanced filtering to WordPress, which lets you do things like get that list of all drinks made with bourbon that include a simple syrup, in a dynamic way! It’s incredibly fast, since it’s using ajax and javascript, and as long as you have enough server memory to index all the data in the first place, it’s faster than reloading a new category page.

    Downsides

    In order to be that fast, you do not get pretty URLs. Let’s say you have your drinks category at `example.com/category/drinks’ and you want to list all those things. Your URL will look like this:

    example.com/category/drinks/?fwp_alcohol=bourbun&fwp_ingredients=simple+syrup%2Cmint

    The realistic reason they don’t try to make it ‘pretty’ is that it would create a lot more rewrite rules than would be sustainable, if you have a lot of facets. The number of checks would slow your site down, and that would kind of suck.

    Compatibility Notes

    If you use CMB2 you’ll need FacetWP + CMB2.

    If you use Genesis themes, there are two tricks. First, you’ll want to use the following function to add FacetWP’s CSS to your theme:

    function YOURTHEMENAME_facetwp_class( $atts ) {
        $atts['class'] .= ' facetwp-template';
        return $atts;
    }
    add_filter( 'genesis_attr_content', 'YOURTHEMENAME_facetwp_class' );
    

    Second, if you’re like me and you use a lot of custom loops, they may not behave as expected. If you call the loop multiple times on a page (which is bad behavior in the first place and I know it), FacetWP has a bit of trouble knowing what javascript to apply to what section. That should be expected, and once I cleaned it up, it worked great.

    Should you use it?

    If you have a lot of complex intersectional queries to sort through, yes.

    If you need dynamic result updates, yes.

    It works.