Skip to content

Latest commit

 

History

History
266 lines (200 loc) · 9.73 KB

File metadata and controls

266 lines (200 loc) · 9.73 KB
sidebar_label sidebar_position
12. Custom Blocks: Description Lists and Movie Runtime
12

12. Custom Blocks: Description Lists and Movie Runtime

Core blocks cover most layout needs, but some HTML structures have no core equivalent. Description lists (<dl>, <dt>, <dd>) and semantic time elements (<time>) are two examples. This lesson builds custom blocks for both and revisits the single templates to use them.

Learning Outcomes

  1. Understand a custom block's anatomy: block.json, index.js, markup.php, style.css.
  2. Know how to build parent/child block relationships using parent and allowedBlocks.
  3. Be able to create a dynamic block that renders via PHP.
  4. Understand how usesContext lets a block read data from the query loop.
  5. Know how get_block_wrapper_attributes() handles wrapper output.

Tasks

1. Copy blocks from the answer key

Copy the following directories from the fueled-movies theme:

  • blocks/dl/
  • blocks/dl-item/
  • blocks/dt/
  • blocks/dd/
  • blocks/movie-runtime/

Rebuild:

npm run build

The theme's src/Blocks.php already auto-registers any block with a block.json in the blocks/ directory, so no additional PHP registration is needed.

2. Walk through the DL block family

The description list system is a four-block hierarchy for displaying movie and person metadata:

tenup/dl              - Parent: the <dl> wrapper
└── tenup/dl-item     - Child: a term+description pair
    ├── tenup/dt      - Leaf: the <dt> term
    └── tenup/dd      - Leaf: the <dd> description (can contain other blocks)

Enforcing structure with parent and allowedBlocks

The nesting rules are defined in each block's block.json:

{
    "name": "tenup/dl-item",
    "title": "Description List Item",
    "parent": ["tenup/dl"]
}
{
    "name": "tenup/dt",
    "title": "Description List Term",
    "parent": ["tenup/dl-item"]
}
  • parent restricts where a block can be inserted. tenup/dl-item can only exist inside tenup/dl. tenup/dt and tenup/dd can only exist inside tenup/dl-item.
  • The DL block uses InnerBlocks to accept child blocks. The editor automatically filters the inserter to only show allowed children.

TODO_SUGGEST_SCREENSHOT

Dynamic rendering with markup.php

At 10up we build dynamic blocks: blocks that render on the server via PHP. The markup.php file is referenced as render in block.json:

{
    "name": "tenup/dl",
    "render": "file:./markup.php",
    "editorScript": "file:./index.js",
    "style": "file:./style.css"
}

The PHP render template receives three variables:

<?php
/**
 * @var array    $attributes Block attributes.
 * @var string   $content    Block content (inner blocks).
 * @var WP_Block $block      Block instance.
 */

if ( empty( trim( $content ) ) ) {
    return;
}

$block_wrapper_attributes = get_block_wrapper_attributes();
?>

<dl <?php echo $block_wrapper_attributes; ?>>
    <?php echo $content; ?>
</dl>
  • $attributes: the block's saved attributes (from block.json)
  • $content: the rendered HTML of inner blocks (already processed by WordPress)
  • $block: the full WP_Block instance, including context

get_block_wrapper_attributes() generates the correct wrapper attributes (classes, styles, IDs) based on block supports. Always use this instead of building class names manually.

:::tip Dynamic blocks avoid deprecation headaches: the markup isn't saved to the database, so you can change it anytime without migration scripts. This is why 10up uses dynamic blocks as the standard. :::

The editor component

The editor side uses useBlockProps and useInnerBlocksProps from @wordpress/block-editor:

import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks } from '@wordpress/block-editor';
import { BlockEdit } from './edit';
import metadata from './block.json';

registerBlockType(metadata, {
    edit: BlockEdit,
    save: () => <InnerBlocks.Content />,
});

For dynamic blocks, the save function returns <InnerBlocks.Content /> (if the block has inner blocks) or null (if it doesn't). The actual frontend markup comes from markup.php.

Auto-registration

You don't need to manually register blocks. src/Blocks.php globs dist/blocks/*/block.json and calls register_block_type_from_metadata() for each:

public function register_theme_blocks() {
    $block_json_files = glob( TENUP_BLOCK_THEME_BLOCK_DIST_DIR . '*/block.json' );

    foreach ( $block_json_files as $filename ) {
        $block_folder = dirname( $filename );
        register_block_type_from_metadata( $block_folder );
    }
}

Drop a folder with a block.json into blocks/ and it's automatically available.

3. Walk through the Movie Runtime block

The tenup/movie-runtime block demonstrates reading data from the post via block context:

{
    "name": "tenup/movie-runtime",
    "usesContext": ["postId", "postType"],
    "render": "file:./markup.php"
}
$post_id = $block->context['postId'] ?? null;
if ( ! $post_id ) {
    return;
}

$runtime = get_post_meta( $post_id, 'tenup_movie_runtime', true );
$hours   = $runtime['hours'] ?? '0';
$minutes = $runtime['minutes'] ?? '0';

if ( '0' === $hours && '0' === $minutes ) {
    return;
}
?>

<time <?php echo get_block_wrapper_attributes( [
    'datetime' => esc_attr( 'PT' . $hours . 'H' . $minutes . 'M' ),
] ); ?>>
    <?php // renders "2h 28m" with ARIA labels ?>
</time>

The usesContext declaration in block.json tells WordPress to pass postId and postType from the query loop context. This lets the block read meta for whatever post it's rendering inside, without hardcoding a post ID.

4. Revisit single templates

Update both single templates in the Site Editor to use the new blocks:

Single Movie (templates/single-tenup-movie.html):

  • Wrap plot, stars, and genre in tenup/dl blocks
  • Add tenup/movie-runtime to the metadata row
<!-- wp:tenup/dl {"style":{"layout":{"selfStretch":"fill"}},"layout":{"type":"default"}} -->
    <!-- wp:tenup/dl-item {"layout":{"type":"flex","flexWrap":"nowrap","verticalAlignment":"top"}} -->
        <!-- wp:tenup/dt {"content":"Genre","style":{"layout":{"selfStretch":"fixed","flexSize":"5.5rem"}},"textColor":"text-secondary"} /-->
        <!-- wp:tenup/dd {"style":{"layout":{"selfStretch":"fill"}}} -->
            <!-- wp:post-terms {"term":"tenup-genre"} /-->
        <!-- /wp:tenup/dd -->
    <!-- /wp:tenup/dl-item -->

    <!-- wp:tenup/dl-item {"layout":{"type":"flex","flexWrap":"nowrap","verticalAlignment":"top"}} -->
        <!-- wp:tenup/dt {"content":"Plot","textColor":"text-secondary"} /-->
        <!-- wp:tenup/dd -->
            <!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tenup_movie_plot"}}}}} -->
            <p></p>
            <!-- /wp:paragraph -->
        <!-- /wp:tenup/dd -->
    <!-- /wp:tenup/dl-item -->

    <!-- wp:tenup/dl-item {"layout":{"type":"flex","flexWrap":"nowrap","verticalAlignment":"top"}} -->
        <!-- wp:tenup/dt {"content":"Stars","textColor":"text-secondary"} /-->
        <!-- wp:tenup/dd -->
            <!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"tenup/block-bindings","args":{"key":"movieStars"}}}}} -->
            <p></p>
            <!-- /wp:paragraph -->
        <!-- /wp:tenup/dd -->
    <!-- /wp:tenup/dl-item -->
<!-- /wp:tenup/dl -->

Single Person (templates/single-tenup-person.html):

  • Wrap biography, born, birthplace, died, deathplace, and movies in tenup/dl blocks

Export the updated markup back to the theme files.

TODO_SUGGEST_SCREENSHOT

Files changed (fueled-movies delta)

File Change type What changes
blocks/dl/ New Block metadata, edit component, markup.php, styles
blocks/dl-item/ New parent: ["tenup/dl"], markup.php
blocks/dt/ New parent: ["tenup/dl-item"], inline editable term
blocks/dd/ New parent: ["tenup/dl-item"], inner blocks container
blocks/movie-runtime/ New usesContext: ["postId", "postType"], semantic <time> output
templates/single-tenup-movie.html Revisited Plot/Stars/Genre wrapped in tenup/dl blocks; tenup/movie-runtime added to metadata row
templates/single-tenup-person.html Revisited All metadata paragraphs wrapped in tenup/dl blocks

Ship it checkpoint

  • DL blocks enforce nesting: only dl-item inside dl, only dt/dd inside dl-item
  • Movie runtime displays as "2h 28m" with semantic <time> element
  • Single movie template has DL with Genre, Plot, Stars
  • Single person template has DL with Biography, Born, Birthplace, Died, Deathplace, Movies

Takeaways

  • Custom blocks: block.json for metadata, index.js for the editor, markup.php for the frontend.
  • Dynamic blocks (PHP-rendered) avoid deprecation problems: the 10up standard.
  • Use parent and allowedBlocks to enforce nesting rules in parent/child block systems.
  • get_block_wrapper_attributes() handles wrapper classes, styles, and IDs. Always use it.
  • usesContext in block.json lets blocks read data from query loop context.
  • Drop a folder with block.json into blocks/ and auto-registration handles the rest.

Further reading