fbpx

Bài 9: Static Block (Block tĩnh) và Dynamic Block (Block động)

Block Markup trả về giao diện đầu nhìn (Frontend) có thể được tạo động từ Server (khi Block đó được yêu cầu) (Dynamic Block) hoặc được tạo theo dạng tĩnh và lưu trực tiếp trong Block Editor (Static Block)

Static Block

Lý do Nam đưa hình này vào là bởi vì nó thực sự quan trọng trong việc thực sự hiểu về loại Block và cách nó được tạo ra như thế nào.

Hình trên cho thấy khi Update những thuộc tính (attrs) sẽ được lưu lại vào CSDL dưới dạng Block Markup. Lúc này khi người dùng thực hiện yêu cầu lấy dữ liệu từ Server, Block Markup sẽ được Server trả cho người dùng và thực hiện render ra giao diện mong muốn (Server-Rendering – bước này sử dụng ngôn ngữ PHP)

Nguyên tắc xác định Static Block

Như đã theo dõi ở các bài trước thì một Block được tạo ra sẽ có 3 kiểu dữ liệu được định nghĩa

  • Kiểu dữ liệu dạng Editor (thường là edit.js), một thành phần dạng React để giải quyết vấn đề soạn thảo ở trang tinh chỉnh (Ví dụ: edit một post, page…)
  • Kiểu dữ liệu nhằm lưu trữ Block vào CSDL (thường là save.js)
  • Kiểu dữ liệu nhằm render loại Block đó khi có yêu cầu từ phía người dùng…(Dùng cho các khối bài viết nổi bật – latest post) – (thường là file render.php)

Để làm rõ hơn, bạn có thể tham khảo trong bài Block Wrapper

Như vậy, Static Block sẽ được xác định khi bạn tinh chỉnh các thuộc tính (Định nghĩa bởi edit.js) và lưu vào (save.js) thì dữ liệu sẽ theo đó lưu vào CSDL dưới dạng Block Markup

Ngược lại, Dynamic Block sẽ sử dụng kiểu dữ liệu render loại Block theo yêu cầu từ phía người dùng, lúc này lệnh từ save.js sẽ bị bỏ qua.

Dưới đây là ví dụ về cách save.js xử lý khối preformatted core block

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

export default function save( { attributes } ) {
    const { content } = attributes;

    return (
        <pre { ...useBlockProps.save() }>
            <RichText.Content value={ content } />
        </pre>
    );
}
  • Gọi thư viện RichText và userBlockProps từ Block Editor
  • Biến content thuộc thành phần attributes
  • Khi lưu vào CSDL, thành phần RichText sẽ đổ ra giá trị { content }

Lúc này từ trình Editor sẽ lưu vào kết quả khi ta soạn thảo xong

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">This is some preformatted text</pre>
<!-- /wp:preformatted -->

Và đây là đích đến từ đầu người dùng khi có yêu cầu.

<pre class="wp-block-preformatted">This is some preformatted text</pre>

Dynamic Block

Dynamic Block là block động và sẽ có dữ liệu thay đổi thường xuyên tùy vào yêu cầu của người dùng, ví dụ khối core/latest-post – bài viết mới nhất

Dynamic Rendering không sử dụng dụng các thuộc tính từ edit mà sử dụng hàm render (thường trong file render.php) để tạo ra html.

Theo Nam thì Dynamic Block khá giống với vòng loop post của cách làm giao diện truyền thống – Bạn có thể tham khảo tại link Nam trỏ tới để tìm hiểu về bài viết của anh Thạch Phạm.

Nguyên tắc xác định Dynamic Block

Thông thường có thể xác định 1 block là Dynamic Block khi nó có sự xuất hiện của render callback hoặc có file render.php riêng

Lưu ý là render call back sẽ là file php bởi nó liên quan tới Server Rendering

function render_block_core_site_title( $attributes ) {
    $site_title = get_bloginfo( 'name' );
    if ( ! $site_title ) {
        return;
    }

    $tag_name = 'h1';
    $classes  = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}";
    if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
        $classes .= ' has-link-color';
    }

    if ( isset( $attributes['level'] ) ) {
        $tag_name = 0 === $attributes['level'] ? 'p' : 'h' . (int) $attributes['level'];
    }

    if ( $attributes['isLink'] ) {
        $aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : '';
        $link_target  = ! empty( $attributes['linkTarget'] ) ? $attributes['linkTarget'] : '_self';

        $site_title = sprintf(
            '<a href="%1$s" target="%2$s" rel="home"%3$s>%4$s</a>',
            esc_url( home_url() ),
            esc_attr( $link_target ),
            $aria_current,
            esc_html( $site_title )
        );
    }
    $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classes ) ) );

    return sprintf(
        '<%1$s %2$s>%3$s</%1$s>',
        $tag_name,
        $wrapper_attributes,
        // already pre-escaped if it is a link.
        $attributes['isLink'] ? $site_title : esc_html( $site_title )
    );
}

Ở đoạn code trên hàm render_block_core_site_title sẽ lấy bloginfo là tên của trang

  • biến $tag_name là thẻ h1
  • $class (thuộc tính thể hiện style của block), nếu trống sẽ mặc định là thuộc tính textAlign,
    • Nếu đã được chọn thuộc tính phong cách (style) và thành phần (elements) thì $classes sẽ có thuộc tính ‘has-link-color’
    • Nếu đã chọn thuộc tính level, tag_name và level sẽ đều bằng 0
    • Nếu thuộc isLink có tồn tại, nếu trang hiện tại là trang chủ thì lúc này thuộc tính aria-current sẽ có tên là “page”. Biến $link_target nếu không trống thì sẽ mặc định bằng “self”
  • Biến $site_title là một hàm ghép biến (sprintf) nhằm tạo ra tiêu đề trang
  • Cuối cùng hàm render callback sẽ trả về tiêu đề trang.

Tại database, block markup được lưu vào như sau

<!-- wp:site-title /-->

Tại đầu Frontend của người dùng nó sẽ trả về kết quả

<h1 class="wp-block-site-title"><a href="https://www.wp.org" target="_self" rel="home">My WordPress Website</a></h1>

Lưu ý là đoạn render callback kia khá phức tạp, nhưng bản chất nó chỉ xác định từng trường hợp và các thuộc tính cần có mà thôi

Save.js của dynamic block

Đối với Dynamic Block thì hàm save.js sẽ trả về null tức là không có giá trị, điều này chỉ dẫn trình editor chỉ lưu những thuộc tính (attributes) mà thôi, thuộc tính này sẽ được truyền vào hàm rendering callback (render.php) nhằm thể hiện block theo cách mong muốn.

Khi hàm save là null, Block Editor sẽ bỏ qua bước xác minh, tránh trường hợp thường xuyên kiểm tra để thay đổi Markup.

Tương tự như vòng loop, dynamic block sẽ có một đoạn HTML như dạng backup nếu trong trường hợp

  • Vòng loop lỗi
  • Không có dữ liệu…

Ví dụ về Dynamic Block

Dưới đây là ví dụ của Dynamic Block đối với plugins giúp gọi ra 1 vòng lặp bài viết (Post Loop)

import { registerBlockType } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { useBlockProps } from '@wordpress/block-editor';

registerBlockType( 'gutenberg-examples/example-dynamic', {
    apiVersion: 3,
    title: 'Example: last post',
    icon: 'megaphone',
    category: 'widgets',

    edit: () => {
        const blockProps = useBlockProps();
        const posts = useSelect( ( select ) => {
            return select( 'core' ).getEntityRecords( 'postType', 'post' );
        }, [] );

        return (
            <div { ...blockProps }>
                { ! posts && 'Loading' }
                { posts && posts.length === 0 && 'No Posts' }
                { posts && posts.length > 0 && (
                    <a href={ posts[ 0 ].link }>
                        { posts[ 0 ].title.rendered }
                    </a>
                ) }
            </div>
        );
    },
} );

Đoạn code này nằm trong file index.js, trong trường hợp không sử dụng block.json để lấy dữ liệu, mà khai báo trực tiếp luôn, lưu ý rằng dynamic block không có hàm save() để xử lý dữ liệu trước khi lưu vào Database (do vốn chúng sẽ lôi dữ liệu từ Backend ra xử lý – gọi bài viết, ảnh đại diện, logo của trang…)

Ở hàm này thì biến posts sẽ được gọi bằng hàm useSelect (từ thư viện @wordpress/data), trả về lựa chọn bản ghi hướng tới loại postType là post.

Kết quả của hàm edit sẽ trả về trình soạn thảo một danh sách bài viết

  • Nếu không có bài viết nào, sẽ có đoạn text là Loading
  • Nếu có post, nhưng chưa có bài, đoạn text sẽ ghi là No Posts
  • Nếu posts.length > 0 (có bài viết) trả về hyperlink hướng tới bài viết đó

Tuy vậy như đã nói, Dynamic Block cần phải render ở đầu server thì mới trả ra kết quả ở trình Frontend – giao diện người dùng được.

<?php

/**
 * Plugin Name: Gutenberg examples dynamic
 */

function gutenberg_examples_dynamic_render_callback( $block_attributes, $content ) {
    $recent_posts = wp_get_recent_posts( array(
        'numberposts' => 1,
        'post_status' => 'publish',
    ) );
    if ( count( $recent_posts ) === 0 ) {
        return 'No posts';
    }
    $post = $recent_posts[ 0 ];
    $post_id = $post['ID'];
    return sprintf(
        '<a class="wp-block-my-plugin-latest-post" href="%1$s">%2$s</a>',
        esc_url( get_permalink( $post_id ) ),
        esc_html( get_the_title( $post_id ) )
    );
}

function gutenberg_examples_dynamic() {
    // automatically load dependencies and version
    $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');

    wp_register_script(
        'gutenberg-examples-dynamic',
        plugins_url( 'build/index.js', __FILE__ ),
        $asset_file['dependencies'],
        $asset_file['version']
    );

    register_block_type( 'gutenberg-examples/example-dynamic', array(
        'api_version' => 3,
        'editor_script' => 'gutenberg-examples-dynamic',
        'render_callback' => 'gutenberg_examples_dynamic_render_callback'
    ) );

}
add_action( 'init', 'gutenberg_examples_dynamic' );

2 hàm này sẽ được đặt ở plugin-name.php, một hàm để đăng ký index.js (chứa hàm edit) và xác định render_callback là hàm gutenberg_examples_dynamic_render_callback

Nam là 1 Growth Hacker, Developer đam mê với sự nghiệp phát triển web