Bài 10: Cách xây dựng trình điều khiển Block (Block Controls)

Như ở các bài trước các bạn đã biết thì trình soạn thảo dạng Block sẽ sử dụng edit.js (Javascript) để phụ trách việc tinh chỉnh ở trình editor(wp-admin)

  • Với Block động (dynamic block) file save.js là không cần thiết, lúc này dữ liệu sẽ được render và xử lý logic tại file render.php (sử dụng mã nguồn PHP)
  • Với Block tĩnh (static block) sẽ lưu kết quả từ edit.js bằng việc thiết lập file save.js. Lúc này dữ liệu sẽ được đẩy ra phía người dùng dưới dạng Block Markup (kèm các tham số – attributes)

Vậy ta hãy cùng đến với thành phần edit.js để tìm hiểu cách tinh chỉnh các tham số – attributes sử dụng giao diện Gutenberg do chính cộng đồng WordPress phát triển, những phần tinh chỉnh cũng được quy hoạch lại dưới 2 thành phần

  • Block Toolbar (Các lựa chọn tinh chỉnh tại khối, nằm trên trình soạn thảo)
  • Settings Sidebar (tinh chỉnh ở cột bên phải dành riêng cho các Block khác nhau – tinh chỉnh này đầy đủ các lựa chọn của Block)

Block Toolbar

Block toolbar mang tới nhiều tinh chỉnh giúp quá trình soạn thảo trở nên dễ dàng hơn, đặc biệt là đối với các khối nặng về content như RichText.

Khối Paragraph cơ bản của WordPress cũng mang tới khá đầy đủ những tinh chỉnh

Dưới đây là 1 ví dụ của việc sử dụng Block Toolbar (lưu ý đây là cách viết gộp và không quy hoạch khoa học thành các file như edit.js), Nam sẽ viết tách thành các files không theo như hướng dẫn của Block Editor Handbook nữa

Block.json

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "gutenberg-examples/example-04-controls-esnext",
    "title": "Example: Controls (esnext)",
    "category": "design",
    "parent": [ "core/group" ],
    "icon": "universal-access-alt",
    "textdomain": "my-plugin",
    "attributes": {
        "content": {
            "type": "string",
            "source": "html",
            "selector": "p"
        }
        "alignment": {
            "type": "string",
            "default": "none",
        }

    },
    "example": {
        "attributes": {
            "content": "Hello World",
            "alignment":"right",
        }
    },
    "editorScript": "file:./index.js",
}

Lúc này ta access vào file index.js sẽ có dạng như sau

import Edit from './edit';
import Save from './save';
import metadata from './block.json';

/**
 * Every block starts by registering a new block type definition.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
 */
registerBlockType( metadata.name, {
	/**
	 * @see ./edit.js
	 */
	edit: Edit,

        save: Save,
} );

Trong đó hàm registerBlockType sẽ gọi tới metadata.name của block.json, truyền ra 2 file chứa 2 hàm là Edit và Save

edit.js

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

        const onChangeContent = ( newContent ) => {
            setAttributes( { content: newContent } );
        };

        const onChangeAlignment = ( newAlignment ) => {
            setAttributes( {
                alignment: newAlignment === undefined ? 'none' : newAlignment,
            } );
        };
	return (
            <div { ...useBlockProps() }>
                {
                    <BlockControls>
                        <AlignmentToolbar
                            value={ attributes.alignment }
                            onChange={ onChangeAlignment }
                        />
                    </BlockControls>
                }
                <RichText
                    className={ attributes.className }
                    style={ { textAlign: attributes.alignment } }
                    tagName="p"
                    onChange={ onChangeContent }
                    value={ attributes.content }
                />
            </div>
        );
}

Đoạn này trông có vẻ khá phức tạp nhưng thật ra chúng đang set 2 attribute theo tinh chỉnh của người dùng

  • Nội dung soạn thảo
  • Alignment – căn lề văn bản

Sau khi set 2 biến đó, ta sẽ code thành phần return của Block này thứ nhất là div sẽ chứa tất cả những attribute Block. Kèm với đó là 1 block controls với:

  • AlignmentToolbar có giá trị bằng thuộc tính aligntment, thay đổi sẽ nghe theo biến onChangeAligntment
  • Khối Richtext đảm nhiệm vai trò soạn thảo, sẽ nghe theo thay đổi của biến onChangeContent

save.js

export default function Save( props ) {
        const blockProps = useBlockProps.save();
        return (
            <div { ...blockProps }>
                <RichText.Content
                    className={ `gutenberg-examples-align-${ props.attributes.alignment }` }
                    tagName="p"
                    value={ props.attributes.content }
                />
            </div>
        );
        
};

Hàm save.js sẽ lưu những thuộc tính tại Edit (do hàm edit chạy trước), đồng thời trả ra phía người nhìn nội dung tương ứng (Trước tiên là lưu vào database), khi người dùng yêu cầu sẽ đưa ra đoạn dữ liệu mong muốn.

Settings Sidebar

Settings Sidebar liệt kê tất cả các thuộc tính liên quan tới Block

Tại đây ta thấy có các thuộc tính

  • attributes.text
  • attributes.color
  • ….

Hãy cùng tìm hiểu về Block sử dụng Settings Sidebar nhé!

Block.json

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "gutenberg-examples/example-04-settings-sidebar",
    "title": "Example: Controls settings",
    "category": "design",
    "icon": "universal-access-alt",
    "textdomain": "my-plugin",
    "attributes": {
        "message": {
            "type": "string",
            "source": "text",
            "selector": "div"
            "default": "",
        }
        "bg_color": { "type": "string", "default": '#000000' },
        "text_color": { "type": "string", "default": "#ffffff" },

    },
    "editorScript": "file:./index.js",
}

Tương tự vậy thì index.js sẽ có dạng như sau:

import Edit from './edit';
import Save from './save';
import metadata from './block.json';

/**
 * Every block starts by registering a new block type definition.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
 */
registerBlockType( metadata.name, {
	/**
	 * @see ./edit.js
	 */
	edit: Edit,

        save: Save,
} );

edit.js

Tương tự vậy, ta cũng đổ ra 2 function Export là Edit và Save

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

       const onChangeBGColor = ( hexColor ) => {
            setAttributes( { bg_color: hexColor } );
        };

        const onChangeTextColor = ( hexColor ) => {
            setAttributes( { text_color: hexColor } );
        };

        return (
            <div { ...useBlockProps() }>
                <InspectorControls key="setting">
                    <div id="gutenpride-controls">
                        <fieldset>
                            <legend className="blocks-base-control__label">
                                { __( 'Background color', 'gutenpride' ) }
                            </legend>
                            <ColorPalette // Element Tag for Gutenberg standard colour selector
                                onChange={ onChangeBGColor } // onChange event callback
                            />
                        </fieldset>
                        <fieldset>
                            <legend className="blocks-base-control__label">
                                { __( 'Text color', 'gutenpride' ) }
                            </legend>
                            <ColorPalette // Element Tag for Gutenberg standard colour selector
                                onChange={ onChangeTextColor } // onChange event callback
                            />
                        </fieldset>
                    </div>
                </InspectorControls>
                <TextControl
                    value={ attributes.message }
                    onChange={ ( val ) => setAttributes( { message: val } ) }
                    style={ {
                        backgroundColor: attributes.bg_color,
                        color: attributes.text_color,
                    } }
                />
            </div>
        );
}

Như vậy ở Settings Sidebar ta muốn thay đổi 2 thuộc tính là bg_color và text_color

Sử dụng thành phần InspectorControls giúp ta lựa chọn 2 thuộc tính này dựa vào thành phần con ColorPalette, ở đây legend là chú giải của Block.

Ở Sidebar ta sử dụng khối TextControl thay vì soạn thẳng thẳng vào phần nội dung.

save.js

export default function Save( {attributes} ) {
        const blockProps = useBlockProps.save();
        return (
            <div { ...blockProps } style={ {
                    backgroundColor: attributes.bg_color,
                    color: attributes.text_color,
                } }>
                { attributes.message }
            </div>
        );
        
}; 
  

Chỗ này mình thấy có truyền vào tham số attributes thay vì props như ở ví dụ trên nhưng theo Nam thì mình có thể lựa chọn linh hoạt, nếu save cả props vào dữ liệu thì mình cũng phải lấy nó ra ở đúng vị trí.

  • Nếu dùng props truyền vào Save thì cần lấy ra props.attributes.text_color
  • Nếu dùng attributes thì sẽ access thẳng vào attributes.text_color luôn

Kết luận

Theo Nam thì để xây dựng các Block theo ý mình nhằm mục đích sử dụng lâu dài cho khách hàng, ta cần hoạch định Block gồm những Attributes trong Block.json, sau đó tiến hành xây dựng những controller tại edit.js và cuối cùng quyết định nó sẽ lưu ra sao ở save.js. Đối với Block động (Dynamic Block) yếu tố attributes sẽ được sử dụng nhằm xác định dữ liệu yêu cầu từ Server (Ví dụ khối latest post thì sẽ cần lấy ra bao nhiêu bài viết từ chuyên mục “Sức khỏe & đời sống” chẳng hạn).