How I Built It: Search Filters Block

an imac showing a webpage with the Crosswinds Blocks search filters blocks sitting on a wooden table

The WordPress core search functionality is good, but it really could be better.

When you use the search field block that comes in core, you are really limited with what you can do. Really, the only thing a user can do is simply search all of your content for a term. You’re not able to limit it to a specific post type or use taxonomies as search filters.

So that’s what I wanted to make easier by creating the Search Filters Block(s).

This parent block and series of child blocks allows a user to create search filters for any post type on their website. This, combined with the Post Types Search Results blocks can help anyone give their users a better search experience by getting to use taxonomies as filters and be able to limit results to just a specific post type.

So let’s dive in to see how I was able to create this Search Filters block.

What the Need Was for the Search Filters Block

As I mentioned above, the core WordPress search block is really limiting. And personally, a lot of my own websites have a number of different post types that are used, like videos, projects, resources, etc.

Selfishly, I wanted to have search filters for my own website that didn’t rely on things like FacetWP and that I could completely customize.

Also, the Crosswinds Blocks plugin has a number of different post types that users can enable, and I figured that those users would like a way to add search filtering for those post types instead of everything being lumped together.

So the need arose to create a custom Search Filters block for others (and myself) to use to create a better user experience for our visitors.

Initial Attempts at Similar Blocks

So this wasn’t the first attempt at creating a set of search filter blocks.

In fact, the initial release of the Crosswind Blocks plugin featured two sets of search filter blocks for the downloads post type (from Easy Digital Downloads) and the projects post type (from the Crosswinds Blocks plugin).

I built those two sets of blocks for the themes I was building for the release of the Crosswinds Framework. To be honest, at the time I wasn’t really thinking too far in the future. My main focus was just on getting the plugin and the first set of themes released.

But after its release and I thought about actually using the plugin and themes for my websites, I quickly realized that I needed to create something that was more robust and easier to use. Those aren’t the only two post types, obviously. So now the challenge was to create a set of blocks that worked no matter what post type was being used.

On the plus side, creating those two sets of blocks set me up for success with creating the more inclusive version of the blocks. I already knew what I had to do to get the block to work. Now the challenge would be to just make those blocks work no matter what post type or taxonomy was used.

The Plan for the Block

The plan to create the search filters block and all of the child blocks was simple: take what I had already created for the downloads and projects post type and change it to work with all post types on a site.

Codewise, the plan was to have a parent Search Filters block that would contain all of the necessary inner blocks for the filters. This block would also be where the user selected what post type was being searched for through these filters. As you’ll see in a moment, 

And then I would need a Taxonomy Search field that worked with all of the different taxonomies.

So that was the plan. Now it was time to make it a reality.

Creating the Parent Block

The first block I needed to create was the parent Search Filters block.

Overall, this was a simple block. Really it was a container block that housed all of the inner block.

On the JavaScript (or edit) side of things, I simply added in the InnerBlocks component so that it would accept inner blocks. 

The real challenge with this block (and I’m using the word challenge kind of loosely here) was adding an option for a user to select the post type the filters would search for.

On the one hand, I thought that getting the list of post types in PHP and then passing that into the JavaScript would be the way to go, but that got kind of complicated quickly. Fortunately, WordPress core data functionality makes it possible to query the different post types that are registered on a website.

So I was able to use that functionality to get a list of post types used by the site and add them to a dropdown for a user to select. And then on the front end, I simply added a form element similar to that of the WordPress core search form and added in a hidden input for the chosen input as well as the inner blocks.

export function Edit( props ) {

	const {
		attributes,
		setAttributes,
	} = props;

	const {
		postType,
	} = attributes;

	function getPostTypesList() {
		let options = [];
		const postTypes = wp.data.select( 'core' ).getPostTypes( { per_page: -1 } );
		if ( null === postTypes ) {
			return options;
		}
		postTypes.forEach( ( post ) => {
			options.push( { value: post.slug, label: post.name } );
		} );
		return options;
	}

	const [ filteredOptions, setFilteredOptions ] = useState( getPostTypesList() );

	const inspectorControls = (
		<>
			<InspectorControls>
				<PanelBody title={ __( 'Search Filters Settings', 'crosswinds-blocks' ) }>
					<>
						<ComboboxControl
							label={ __( 'Select a Post Type', 'crosswinds-blocks' ) }
							value={ postType }
							onChange={ ( name ) => setAttributes( { postType: name } ) }
							options={ getPostTypesList() }
							onFilterValueChange={ ( inputValue ) =>
								setFilteredOptions(
									getPostTypesList().filter( ( option ) =>
										option.label
											.toLowerCase()
											.startsWith( inputValue.toLowerCase() )
									)
								)
							}
						/>
					</>
				</PanelBody>
			</InspectorControls>
		</>
	);

	return (
		<>
			{ inspectorControls }
			<div { ...useBlockProps() }>
				<InnerBlocks />
			</div>
		</>
	);
}

export default Edit;

Oh, and just as a reminder, if you use Inner Blocks, you need to add in a save functionality to actually save the inner blocks, even if you’re creating a dynamic block like this. Forgot about that one for a bit and was really confused with why the inner blocks wouldn’t show up later on the front end.

Creating the Taxonomy Search Field

The first actual filter that I created was the Taxonomy Search Field Block.

For the first version, I wanted to keep it a simple dropdown.

But I also wanted to allow a user to select a taxonomy from a dropdown in the Inspector Controls or the block. Unfortunately, I couldn’t find a way for the WordPress core data functionality to do that. So instead I went with the block variation route, similar to the Single Content Block.

As for how the block works, essentially it just displays a dropdown of terms for the chosen taxonomy. The user can customize the label for the field.

export default function Edit( props ) {
	const {
		setAttributes,
		attributes,
	} = props;
	const {
		label,
		taxonomy,
		taxonomyName,
	} = attributes;

	let termsList = [];

	termsList = useSelect(
		( select ) => {
			return select( 'core' ).getEntityRecords( 'taxonomy', taxonomy );
		},
		[]
	);

	let options = [];

	if ( null !== termsList ) {
		options = termsList.map( ( singleTerm ) => (
			<option key={ singleTerm.id }>{ singleTerm.name }</option>
		) );
	}

	const inspector = (
		<>
			<InspectorControls>
				<PanelBody title={ __( 'Taxonomy Search Settings', 'crosswinds-blocks' ) }>
					<TextControl
						label={ __( 'Label', 'crosswinds-blocks' ) }
						value={ label }
						onChange={ ( value ) => setAttributes( {
							label: value,
						} ) }
					/>
				</PanelBody>

			</InspectorControls>
		</>
	);

	return (
		<>
			{ inspector }
			<div { ...useBlockProps() }>
				<label for={ taxonomy + '-search' }>{ label }</label>
				<select id={ taxonomy + '-search' }>
					{ options }
				</select>
			</div>
		</>
	);
}

Finally, I was able to create variations for the block for all registered taxonomies on a site with the following JavaScript as well as passing an array of taxonomies to the file while registering the file in PHP.

[ ...taxonomySearch.taxonomies ].forEach( function( taxonomy, index, arr ) {
	wp.blocks.registerBlockVariation(
		'crosswinds-blocks/search-filters-taxonomy',
		{
			name: taxonomy.name,
			title: 'Search Filters Taxonomy - ' + taxonomy.label,
			attributes: {
				taxonomy: taxonomy.name,
				taxonomyName: taxonomy.label,
			},
		}
	);
} );

( function() {
	const crosswindsBlocksSVG = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#171247" class="fa-primary" d="M500.3 443.7l-119.7-119.7c-15.03 22.3-34.26 41.54-56.57 56.57l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7z"/><path fill="#8200ff" class="fa-secondary" d="M207.1 0C93.12 0-.0002 93.13-.0002 208S93.12 416 207.1 416s208-93.13 208-208S322.9 0 207.1 0zM207.1 336c-70.58 0-128-57.42-128-128c0-70.58 57.42-128 128-128s128 57.42 128 128C335.1 278.6 278.6 336 207.1 336z"/></svg>;
	wp.blocks.updateCategory( 'crosswinds-blocks', { icon: crosswindsBlocksSVG } );
} )();

And that was it. Really, this ended up being a simple block.

Creating the Search Field

Next up was the Search Field Block. While I would have loved to just use the core WordPress search block, it comes with the search button and I just wanted something that was only the label and input field.

Of course, it was pretty easy to put together. The only option for the user is to customize the label to whatever they want it to be. Otherwise it’s just a simple input field that allows someone to search via a term.

Creating the Search Buttons Block

Finally, I needed buttons for the search filters. Originally I was thinking about having these be a part of the main Search Filters block, but considering I wanted to give the user complete control over how the search filters looked, I figured it was best to have them be their own separate block.

Essentially the block contains two buttons, a submit button and a clear or reset button. These are hard coded so that the submit button is above the reset button. And users can change the label for each button as well as the background color and background hover color for the buttons.

And that’s all I needed for the Search Filters Blocks.

How to Safely Display do_block in a Plugin

For the most part, displaying inner blocks of a dynamic block through the template.php file is pretty straightforward. All you have to do is use echo do_blocks( $content ); to get the inner blocks to show.

But for security purposes, you need to do more than just that echo. Instead, you have to use wp_kses to make sure that all of the output that you’re echoing is safe and secure.

And when you’re outputting input fields, there’s an extra step. The second argument for wp_kses can be an array of allowed HTML elements and attributes. Otherwise elements and attributes will be removed.

It took some experimentation, but below is what I ended up with for my allowed HTML array.

$allowed_html = array(
	'div' => array(
		'class' => array(),
		'style' => array(),
	),
	'label' => array(
		'for' => array(),
	),
	'select' => array(
		'id'   => array(),
		'name' => array(),
	),
	'option' => array(
		'value'    => array(),
		'selected' => array(),
	),
	'input' => array(
		'id'          => array(),
		'name'        => array(),
		'type'        => array(),
		'value'       => array(),
		'placeholder' => array(),
		'title'       => array(),
		'class'       => array(),
	),
);
?>

<div <?php echo wp_kses_data( get_block_wrapper_attributes() ); ?>>
	<form id="searchform" method="get" action="<?php echo esc_url( home_url( '/' ) ); ?>">
		<input type="hidden" name="post_type" value="<?php echo esc_attr( $attributes['postType'] ); ?>" />
		<?php echo wp_kses( do_blocks( $content ), $allowed_html ); ?>
	</form>
</div>

You should always make sure that you’re outputting secure HTML, and while yes, this is a bit of a challenge and it took some time to get right, users are better off being able to use secure code.

So here’s your reminder to make sure that the code you write is using as secure of code as possible.

What’s Next for the Block

While I feel good about where the block is right now, there are some improvements and changes that I will likely make over time.

The main one is to add options for how the taxonomy search fields are displayed. Right now it’s a simple dropdown, which I feel is good for the first version. But over time I would like to give the user more options, like checkboxes and multi select dropdowns.

Also, I would like to figure out a way to make the buttons more customizable similar to the button block in WordPress core.

And of course it would be cool to make custom fields be filters as well, but that might be down the road.

But right now I feel like the search filters blocks are in a really good place.

Leave a Reply

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