How to Create Post Grid Block With Settings Sidebar(Inspector Control)

If you’re following this blog to learn a block development, you know we’ve covered so far

If this is the first time you’re visiting this blog, I’d suggest reading the above articles to become familiar with block development.

In this tutorial, we’ll study how to build a post grid using a custom block. On your WordPress websites, it’s common to display a post grid. Here I used the word post but it can be a page or custom post type. Following this tutorial, you’ll be able to build a grid of any post type.

Our final block will be displayed as a screenshot below.

post-grid-block

As you can see in a screenshot, we’re also going to provide settings for the post grid block. This way you’ll have more control over the output and also you’ll learn how to use Inspector Control in your block.

With the help of Inspector Control, you can add a settings sidebar to your block. The Inspector Control is a wrapper for the fields going to be placed in the sidebar.

Getting Started

First, let’s define attributes to hold the values of the fields present in the sidebar. With these fields, we can manage the layout of the post grid.

{
	...
	...
	"name": "artisansweb/post-grid",
	"title": "Artisans Web - Post Grid",
	"description": "This block will create a Post Grid.",
	"attributes": {
		"number_of_posts": {
			"type": "string",
			"default": "3"
		},
		"show_image": {
			"type": "boolean",
			"default": true
		},
		"show_title": {
			"type": "boolean",
			"default": true
		},
		"show_excerpt": {
			"type": "boolean",
			"default": true
		},
		"show_readmore": {
			"type": "boolean",
			"default": true
		}
	},
}

The attributes added here are quite self-explanatory. By default, we’re fetching 3 posts, the user can obviously change this value from the settings sidebar. For the other attributes, I used a boolean type to control whether to show a specific value or not(post title, excerpt, image, and a read more link).

Next, let’s define the <PostsList> component in edit.js:

function PostsList( { hasResolved, posts, attributes } ) {

	if ( !hasResolved ) 
	  	return <Spinner />;
   
	if ( !posts?.length ) 
	  	return <div>No posts to display</div>;

	return (
		<div className='post-grid'>
			{ posts?.map( (post) => {

				const { mediaId, media } = useSelect( select => {
					return {
						mediaId: post.featured_media,
						media: select('core').getMedia(post.featured_media)
					}
				}, [post.featured_media] )

				return (
					<div className='post' key={ post.id }>
						{ attributes.show_image && !! mediaId && ! media && <Spinner /> }
						{ attributes.show_image && !! media && media && <img src={ media.media_details.sizes.thumbnail.source_url } /> }
						{ attributes.show_title && <h3>{ post.title.rendered }</h3> }
						{ attributes.show_excerpt && <p>{ post.excerpt.raw }</p> }
						{ attributes.show_readmore && <a className='read-more'>Read More</a> }
					</div>
				)
 			} ) }
		</div>
	);
}

This component requires some props which we’ll pass later. What this component does – it loops through the posts array and prints the post grid. While displaying the grid, it checks for boolean values and prints the specific markup only if the attribute’s value is set to true.

Take note, I fetched the featured image of each post in the loop with the help of useSelect hook. This is because, in the posts array(which will fetch in the next step), we’ll not have the URL of the post thumbnail. Instead, WordPress keeps the attachment id of the thumbnail. So here, using this id I’m fetching the actual image.

To get a look of the post-grid layout, add the following code to the editor.scss file.

.wp-block-artisansweb-post-grid {
	$post-grid-columns: 3;
	$post-gutter: 20px;

	.post-grid {
	    display: grid;
	    grid-template-columns: repeat($post-grid-columns, 1fr);
	    grid-gap: $post-gutter;
	    padding: $post-gutter;
	}

	.post {
	    border: 1px solid #ddd;
	    padding: 20px;
	    text-align: center;

	    img {
	        max-width: 100%;
	        height: auto;
	        margin-bottom: 10px;
	    }

	    h3 {
	        font-size: 1.2rem;
	        margin: 10px 0;
	    }

	    p {
	        font-size: 1rem;
	    }

	    .read-more {
	        background-color: #007bff;
	        color: #fff;
	        border: none;
	        padding: 5px 10px;
	        text-decoration: none;
	        transition: color 0.3s ease;
	    }
	}
}

}

Fetch Posts In Block

In order to fetch posts, I’ll use the useEntityRecords hook. This React hook returns an object with four properties:

  • hasResolved: Boolean. It means records are fetched.
  • isResolving: Boolean. The request is in progress.
  • records: Array. The final result.
  • status: String. Any one value from – Resolving, Success, Error, Idle.

Out of these four properties, we’ll require hasResolved and records. Let’s do it as follows.

import { useEntityRecords } from '@wordpress/core-data';
import { BaseControl } from '@wordpress/components';

export default function Edit({ attributes, setAttributes }) {

	const { hasResolved, records } = useEntityRecords( 'postType', 'post', { per_page: attributes.number_of_posts} );

	return (
		<div { ...useBlockProps() }>
			<BaseControl label={__("Artisans Web - Post Grid")}>
				<PostsList hasResolved={ hasResolved } posts={ records } attributes={attributes} />
			</BaseControl>
		</div>
	);
}

Remember, a React hook must be called at the top level of the component(we called it in the first statement). You can’t call this hook within conditional or loops.

Here, I set the post as a second argument of useEntityRecords. You can set any other post type’s name as per your needs. We are also passing a few props to the <PostsList/> components defined earlier.

Inspector Control

We’re done with getting posts into the editor dynamically. Now, as mentioned earlier we have to add the settings sidebar to our block. In the settings sidebar, I’ll use <TextControl/> and <ToggleControl/> components to manage the settings.

So, after adding the settings sidebar, the final edit.js will be as follows.

import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { useEntityRecords } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { BaseControl, Spinner, ToggleControl, TextControl, PanelBody, PanelRow } from '@wordpress/components';
import './editor.scss';

export default function Edit({ attributes, setAttributes }) {

	const { hasResolved, records } = useEntityRecords( 'postType', 'post', { per_page: attributes.number_of_posts} );

	return (
		<>
			<InspectorControls>
				<PanelBody title="Post Grid Settings" initialOpen={ true }>
					<PanelRow>
						<TextControl
							label="Number of Posts"
							onChange={ ( number_of_posts ) => setAttributes( { number_of_posts } ) }
							value={ attributes.number_of_posts }
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label="Show Image"
							onChange={ () => setAttributes( { show_image: ! attributes.show_image } ) }
							checked={ attributes.show_image }
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label="Show Title"
							onChange={ () => setAttributes( { show_title: ! attributes.show_title } ) }
							checked={ attributes.show_title }
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label="Show Excerpt"
							onChange={ () => setAttributes( { show_excerpt: ! attributes.show_excerpt } ) }
							checked={ attributes.show_excerpt }
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label="Show Read More"
							onChange={ () => setAttributes( { show_readmore: ! attributes.show_readmore } ) }
							checked={ attributes.show_readmore }
						/>
					</PanelRow>
				</PanelBody>
			</InspectorControls>
			<div { ...useBlockProps() }>
				<BaseControl label={__("Artisans Web - Post Grid")}>
					<PostsList hasResolved={ hasResolved } posts={ records } attributes={attributes} />
				</BaseControl>
			</div>
		</>
	);
}

function PostsList( { hasResolved, posts, attributes } ) {

	if ( !hasResolved ) 
	  	return <Spinner />;
   
	if ( !posts?.length ) 
	  	return <div>No posts to display</div>;

	return (
		<div className='post-grid'>
			{ posts?.map( (post) => {

				const { mediaId, media } = useSelect( select => {
					return {
						mediaId: post.featured_media,
						media: select('core').getMedia(post.featured_media)
					}
				}, [post.featured_media] )

				return (
					<div className='post' key={ post.id }>
						{ attributes.show_image && !! mediaId && ! media && <Spinner /> }
						{ attributes.show_image && !! media && media && <img src={ media.media_details.sizes.thumbnail.source_url } /> }
						{ attributes.show_title && <h3>{ post.title.rendered }</h3> }
						{ attributes.show_excerpt && <p>{ post.excerpt.raw }</p> }
						{ attributes.show_readmore && <a className='read-more'>Read More</a> }
					</div>
				)
 			} ) }
		</div>
	);
}

As you can see, I placed the components <TextControl /> and <ToggleControl /> within the InspectorControls, PanelBody, and PanelRow. These are the wrapper for our fields.

To the PanelBody, I set initialOpen={ true } which will keep our setting open once someone enters the block.

You’re now ready with the backend part of the post grid block. Next, let’s render the output in the front end.

Post Grid On Front End

We’ve already stored the attributes of the block. Next, with the help of these attributes and WP_Query class, you can easily render the front end.

src/render.php

<?php
$args = array(
    'post_type' => 'post',
    'posts_per_page' => $attributes['number_of_posts']
);

$posts = new WP_Query( $args );

if ( $posts->have_posts() ) {

    $post_list = '<div class="post-grid">';

    while ( $posts->have_posts() ) {
        $posts->the_post();

        $post_list .= '<div class="post">';

        if ( $attributes['show_image'] ) {
            $post_list .= wp_get_attachment_image(get_post_thumbnail_id(get_the_ID()), 'medium');
        }

        if ( $attributes['show_title'] ) {
            $post_list .= '<h3>'. '<a href="'. get_permalink() .'">'. get_the_title() .'</a>'.'</h3>';
        }

        if ( $attributes['show_excerpt'] ) {
            $post_list .= '<p>'. get_the_excerpt() .'</p>';
        }

        if ( $attributes['show_readmore'] ) {
            $post_list .= '<a class="read-more" href="'. get_permalink() .'">Read More</a>';
        }

        $post_list .= '</div>';
    }

    $post_list .= '</div>';

} else {
    $post_list = 'No posts to display.';
}

wp_reset_postdata();
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
    <?php echo $post_list; ?>
</div>

Finally, to keep parity between the back end and front end, add the following code to the style.scss file.

.wp-block-artisansweb-post-grid {

	$post-grid-columns: 3;
    $post-gutter: 20px;

    .post-grid {
        display: grid;
        grid-template-columns: repeat($post-grid-columns, 1fr);
        grid-gap: $post-gutter;
        padding: $post-gutter;
    }

    .post {
        border: 1px solid #ddd;
        padding: 20px;
        text-align: center;
        
        img {
            max-width: 100%;
            height: auto;
            margin-bottom: 10px;
        }

        h3 {
            font-size: 1.2rem;
            margin: 10px 0;
        }

        p {
            font-size: 1rem;
        }

        .read-more {
            background-color: #007bff;
            color: #fff;
            border: none;
            padding: 5px 10px;
            text-decoration: none;
            transition: color 0.3s ease;
        }
    }
}

On the front end, I used the same CSS from the editor.scss file. Feel free to change the styling of the front end as per your requirement.

If you liked this article, then please subscribe to our YouTube Channel for video tutorials.

Leave a Reply

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