Loading

Grouping and Sorting Posts in the WordPress Query Block

When designing a complex WordPress layout, achieving custom sorting and grouping in the core/query block can be challenging. In this post, we’ll show how to modify the core/query block to group posts by category and sort them by a custom field, such as menu_order, within each category. We’ll also add a custom control to enable this functionality directly from the block editor, so users can toggle grouping by category as needed.

Here’s how we tackled the challenge and enhanced the core/query block’s capabilities.

The Problem: Grouping Posts by Category and Sorting by Custom Order

The default core/query block in WordPress provides flexibility to display posts based on various parameters, such as date or title. However, it lacks a built-in feature to group posts by category and then apply a custom sorting order within each category.

To achieve this, we need:

  1. Grouping by Category: All posts should first be grouped by category.
  2. Sorting by Custom Field (menu_order): Within each category, posts should be ordered by menu_order rather than by date or other typical fields.

This can be useful for layouts where structured navigation is required, such as:

  • Product catalogues
  • Content directories
  • Featured content lists

Solution Overview: Extending the Core Query Block

To solve this, we need to:

  1. Add a Custom Toggle for Grouping by Category: In the block editor, we’ll add a new toggle control, Group by Category, in the query settings of the core/query block.
  2. Modify the Query Logic: When the Group by Category toggle is enabled, we’ll adjust the query parameters to sort by category and then by menu_order.

Step 1: Adding the Group by Category Control to the core/query Block

To give users the option to toggle this feature, we use a higher-order component (HOC) to inject the Group by Category setting into the block’s inspector panel. This HOC adds the control, allowing users to turn grouping on or off from the editor:

const withCustomQueryControls = (BlockEdit) => {
    return (props) => {
        // Only apply to the Query block
        if (props.name !== 'core/query') {
            return <BlockEdit {...props} />;
        }

        const { attributes, setAttributes } = props;

        return (
            <>
                <BlockEdit {...props} />
                <InspectorControls>
                    <PanelBody title="Query Settings">
                        <ToggleControl
                            label="Group by Category"
                            checked={attributes.groupByCategory}
                            onChange={(value) => setAttributes({ groupByCategory: value })}
                        />
                    </PanelBody>
                </InspectorControls>
            </>
        );
    };
};

// Apply the higher-order component to the block
addFilter('editor.BlockEdit', 'my-plugin/sort-by-category-group', withCustomQueryControls);

Explanation

  • Conditional Application: This HOC only applies to the core/query block. If a different block is being edited, it skips applying the control.
  • Adding the Toggle Control: In the InspectorControls, we add a PanelBody with a ToggleControl for Group by Category. This control is linked to an attribute, groupByCategory, which we’ll define in the next step.

Step 2: Registering Custom Attributes

We add groupByCategory and two gap-related attributes, rowGap and columnGap, to the core/query block using a filter on blocks.registerBlockType. These attributes will be stored with the block and accessible in the editor.

const addGapSizeAttributes = (settings, name) => {
    if (name !== 'core/query') {
        return settings;
    }

    // Add the gapSize and columnGap attributes to the block
    settings.attributes = {
        ...settings.attributes,
        groupByCategory: {
            type: 'boolean',
            default: false, // Default value for grouping by category
        },
    };

    return settings;
};

addFilter('blocks.registerBlockType', 'my-custom-query-extension/add-gap-size-attributes', addGapSizeAttributes);

Explanation

  • Attribute Definitions: The groupByCategory attribute is a boolean, initially set to false. When enabled, it will trigger a grouped and sorted display. rowGap and columnGap attributes provide additional layout flexibility, allowing users to adjust spacing directly.

Step 3: Modifying the Query Logic

Next, we intercept the block rendering process with the pre_render_block filter and modify the query using query_loop_block_query_vars. This allows us to adjust the query to group by category and sort within each category by menu_order.

add_filter('pre_render_block', 'myplugin_pre_render_block', 10, 2);

function myplugin_pre_render_block($pre_render, $parsed_block)
{
    // Check if the core/query block has the groupByCategory attribute enabled
    if (
        isset($parsed_block['blockName'], $parsed_block['attrs']['groupByCategory']) &&
        'core/query' === $parsed_block['blockName'] &&
        true === $parsed_block['attrs']['groupByCategory']
    ) {
        // Modify the query parameters for grouped and sorted display
        add_filter(
            'query_loop_block_query_vars',
            function ($query, $block) use ($parsed_block) {

                // Set query ordering to group by category and then by menu_order
                $query['orderby'] = [
                    'category' => 'ASC',    // Group by category
                    'menu_order' => 'ASC'   // Sort within each category by menu_order
                ];
                $query['order'] = 'ASC';

                // Optional: Filter by star rating if specified in block attributes
                if (isset($parsed_block['attrs']['query']['starRating'])) {
                    $query['meta_key']   = 'rating';
                    $query['meta_value'] = absint($parsed_block['attrs']['query']['starRating']);
                }

                return $query;
            },
            10,
            2
        );
    }

    return $pre_render;
}

Explanation of the Query Modifications

  • Query Sorting Logic: The query’s orderby parameter specifies both category and menu_order. This ensures that posts are first grouped by category and then sorted within each category by menu_order.
  • Optional Filter: We include a conditional filter based on a starRating attribute. This allows users to filter posts by specific criteria when needed.

Benefits of This Approach

  • User-Friendly Control: The custom Group by Category toggle allows users to apply complex grouping and sorting without coding.
  • Adaptable Layout: With additional attributes like rowGap and columnGap, users can further customise the block layout for a polished and professional appearance.
  • Enhanced Query Flexibility: This solution leverages pre_render_block and query_loop_block_query_vars filters for precise control over WordPress queries, enabling advanced displays for any block that relies on dynamic content.