Bài 12: Nested Blocks: Sử dụng InnerBlocks

Vừa rồi Nam có triển khai xây dựng một nút bấm Mega menu và coi nó là thách thức không nhỏ đối với bản thân bởi dựng nó với html/css hoặc Bootstrap thì dễ, nhưng chuyển về dạng Block thì chưa có hướng triển khai.

Áp dụng thực tế: Mega menu Block

Dưới đây là cách Nam triển khai viết một Block Megamenu như sau:

Ở phía file edit.js, cứ mặc định chúng ta triển khai xây dựng Block Plugins theo chuẩn của WordPress, file index.js thì mình không ghi ở đây, nhưng cơ bản là nó gọi tới Edit và save(Đối với static block) hoặc ở block.json có gọi tới render.php

import { __ } from '@wordpress/i18n';
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { useState } from '@wordpress/element';

export default function Edit() {
	const blockProps = useBlockProps();
	const [ isVisible, setIsVisible ] = useState( true );

	const onToggleContent = () => {
		setIsVisible( ! isVisible );
	};

	return (
		<div { ...blockProps }>
			<Button			
				onClick={ onToggleContent } className='wp-block-button__link wp-element-button mega-menu-button'
			>
				{ __( 'Mega Menu', 'text-domain' ) }
			</Button>
			{ isVisible && (
				<InnerBlocks
				/>
			) }
		</div>
	);
}

Đoạn này mình có dùng Copilot để viết code cho nhanh, cơ bản nó gồm những thành phần sau

  • blockProps: các thuộc tính của blocks
  • setIsVisible sẽ trực tiếp thay đổi trạng thái của biến isVisible với thuộc tính là true
  • Hàm onToggleContent sẽ giúp thay đổi trạng thái isVisible từ true sang false hoặc ngược lại (setIsvisible là yếu tố thay đổi biến isVisible)
  • Tại edit.js khi gọi Block sẽ trả ra một nút bấm truyền vào hàm onToggleContent, hàm này sẽ trực tiếp thay đổi biến isVisible và hiển thị giá trị InnerBlocks

Ở save.js thì mình triển khai như sau:

import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';

export default function Save() {
	const blockProps = useBlockProps.save();
	// Get the button and the mega menu elements

	return (
		<div { ...blockProps }>
			<button id="button" class="wp-block-button__link wp-element-button mega-menu-button">Mega Menu</button>
			<div id="mega-menu" style="display: none;">
				<InnerBlocks.Content />
			</div>
		</div>
	);
}

Đây là thành phần được sử dụng trong các static blocks nhằm lưu dữ liệu Block markup, từ đó mang tới định dạng lưu vào CSDL của WordPress. Đoạn này mình sẽ tạo ra một nút bấm Mega Menu và gọi thành phần InnerBlocks.Content

Đoạn này mình không kế thừa thư viện Toggle như ở edit.js mà tạo ra thành phần tĩnh là nút bấm và một khối chứa mega menu, chính vì vậy nội dung sẽ in ra là dạng tĩnh và không có hiệu ứng toggle như ở edit.js, mình cần đẩy script vào view.js (1 file để chứa javascript dành riêng cho block – được định nghĩa ở block.json)

Nam Digital

Đây là file block.json, file index.js thì cơ bản nó vẫn là file dẫn tới việc đăng ký block theo block.json, nối với Edit và save

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "create-block/dropdown-button",
	"version": "0.1.0",
	"title": "Dropdown Button",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"supports": {
		"html": false
	},
	"textdomain": "dropdown-button",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js",
	"attributes": {
		"selectedBlock": {
			"type": "string",
			"default": ""
		}
	}
	
}

Như ta thấy file viewScript gọi tới view.js, đây là file view.js sẽ tạo ra hiệu ứng toggle ở frontend

// Get the button and the mega-menu elements
var button = document.getElementById("button");
var megaMenu = document.getElementById("mega-menu");
// Define a function to handle the button click
var toggleMenu = function () {
  // Toggle the mega-menu visibility
  megaMenu.style.display = megaMenu.style.display === "none" ? "block" : "none";
};
// Add a click event listener to the button
button.addEventListener("click", toggleMenu);

Block này có 1 hạn chế là chỉ gọi 1 lần thôi vì id sẽ dễ bị trùng lặp, bạn cũng có thể cải tiến bằng việc chọn các thành phần có class tương ứng, cái này thì Nam triển khai vậy thôi

Trên đây cũng là ví dụ rất rõ ràng về việc sử dụng InnerBlock, khối này sẽ được toggle toàn bộ khi ta bấm vào nút bấm “Mega menu”, giờ hãy cùng tới với lý thuyết về InnerBlock và NestedBlock

InnerBlocks dùng thế nào?

Bạn có thể tạo 1 block bao hàm nhiều Block nhỏ, trường hợp sử dụng sẽ ứng với các Block sau

  • Columns Block
  • Social Links Block…

Khái niệm sử dụng Repeater của ACF cũng là rất thông dụng, tuy vậy Nam thấy đội ngũ WordPress core có vẻ muốn phát triển InnerBlocks hơn

Dưới đây là một ví dụ của InnerBlocks, đây là cách viết gộp, trong thực tế bạn nên tách về function Edit và save cho rõ ràng như cấu trúc Block chuẩn

import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

registerBlockType( 'gutenberg-examples/example-06', {
    // ...

    edit: () => {
        const blockProps = useBlockProps();

        return (
            <div { ...blockProps }>
                <InnerBlocks />
            </div>
        );
    },

    save: () => {
        const blockProps = useBlockProps.save();

        return (
            <div { ...blockProps }>
                <InnerBlocks.Content />
            </div>
        );
    },
} );

Block này sẽ gọi ở tầng edit một không gian InnerBlocks để kéo thả đủ thành phần vào, ở đầu save vào CSDL InnerBlocks.Content sẽ là thành phần được tạo ra.

Xây dựng quan hệ parent, ancestor và children trong blocks

Đoạn này khá rối rắm nhưng theo mình thì

  • dữ liệu quan hệ parent cho phép block được tạo ở duy nhất tầng parent đó
  • dữ liệu tổ tiên (ancestor) cho phép block được tạo ở nhiều tầng thuộc ancestor
  • allowedBlocks: chỉ những thành phần con được cho phép insert vào block đang xây dựng

Bạn có thể xem Handbook để rõ hơn về ví dụ và cách sử dụng, cá nhân Nam không quan tâm phần này lắm, vì cũng chưa gặp trường hợp khách hàng yêu cầu quá chặt chẽ về kiểu Blocks

Sử dụng React Hook thay InnerBlocks.Content

Cách này cơ bản sẽ mang tới nhiều Block Markup của Inner Blocks hơn, giúp bạn kiểm soát được thông tin in ra một cách phù hợp và rõ ràng

import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

registerBlockType( 'gutenberg-examples/example-06', {
    // ...

    edit: () => {
        const blockProps = useBlockProps();
        const innerBlocksProps = useInnerBlocksProps();

        return (
            <div { ...blockProps }>
                <div {...innerBlocksProps} />
            </div>
        );
    },

    save: () => {
        const blockProps = useBlockProps.save();
        const innerBlocksProps = useInnerBlocksProps.save();

        return (
            <div { ...blockProps }>
                <div {...innerBlocksProps} />
            </div>
        );
    },
} );

Hook useInnerBlocksProps giúp in ra các phần tử con nằm trong InnerBlock tương tự như việc sử dụng blockProps để rải các thuộc tính.