Tạo block Card trong Gutenberg

Giới thiệu

Trong bài viết trước mình có giới thiệu về page builder Gutenburg sắp tích hợp vào WordPress và cách để tạo 1 plugin code các block nhúng vào trong page builder này (Link). Bài viết nãy mình sẽ hướng dẫn cụ thể cách tạo 1 block, mà ở đây là 1 block Card, sẽ có các tính năng là Nhập ảnh, tiêu đề và nội dung để hiển thị trên trình soạn thảo và ngoài trang Frontend phía người dùng.

Bắt đầu

Cấu trúc thư mục

Chi tiết về cấu trúc thư mục thì mình đã nói rõ trong bài viết trước, mình sẽ tạo 1 thư mục card-block nằm trong thư mục src, cụ thể như sau

first-block/src
--card-block
 ----block.js
 ----style.scss
 ----editor.scss

hoặc xem ảnh sau, phần thêm là phần mình bôi viền đỏ:

Javascript

Trong file src/blocks.js cần import file card-block/block.js vừa tạo, thêm dòng sau vào cuối file. Code để tạo block sẽ nằm trong file card-block/block.js này.

import './card-block/block.js';

Ở file card-block/block.js, gọi một số component cần thiết trong ví dụ

const { RichText, MediaUpload, PlainText } = wp.editor; // Gọi 1 số component từ Editor
const { registerBlockType } = wp.blocks; // Gọi component đăng ký block
const { Button } = wp.components; // Gọi component Button

Tiếp theo là import các file style đã được tạo

import './style.scss';
import './editor.scss';

Đăng ký block card, khai báo với Gutenberg rằng tôi muốn đăng ký 1 block có tiêu đề là Card, với icon heart (icon này dùng bộ Dashicon của WordPress), nằm trong category common của Gutenberg.

registerBlockType('first-block/card', {   
  title: 'Card',
  icon: 'heart',
  category: 'common'
});

Tiếp theo khai báo một số thuộc tính có thể chỉnh sửa nội dung của block, nó hoạt động tương tự với state của ReactJS với phương thức setAttributes để cập nhật các thuộc tính đó, đoạn code này viết tiếp vào đoạn ở trên, phía dưới phần category: 'common' và được thêm dấu , sau common

attributes: {
  title: {
    source: 'text',
    selector: '.card__title'
  },
  body: {
    type: 'array',
    source: 'children',
    selector: '.card__body'
  },
  imageAlt: {
    attribute: 'alt',
    selector: '.card__image'
  },
  imageUrl: {
    attribute: 'src',
    selector: '.card__image'
  }
}

Thên edit function ở phía sau phần attributes ở trên (nhớ kèm theo dấu , ở sau dấu đóng ngoặc nhọn }). Function này sẽ trả về phần sẽ hiển thị trên trình soạn thảo.

edit({ attributes, className, setAttributes }) {

    // Khai báo dữ liệu trả về cho ImageButton. Có link ảnh sẽ hiển thị ra ảnh, nếu không sẽ hiển thị ra button chọn ảnh
    const getImageButton = (openEvent) => {
      if(attributes.imageUrl) {
        return (
          <img 
            src={ attributes.imageUrl }
            onClick={ openEvent }
            className="image"
          />
        );
      }
      else {
        return (
          <div className="button-container">
            <Button 
              onClick={ openEvent }
              className="button button-large"
            >
              Chọn Ảnh
            </Button>
          </div>
        );
      }
    };

    // Trả về dữ liệu để hiển thị lên trình soạn thảo
    return (
      <div className="container">
        // Component này có nhiệm vụ hiển thị button Chọn Ảnh hoặc hiển thị ảnh đã được chọn 
        <MediaUpload
          onSelect={ media => { setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } } // Khi chọn ảnh các attributes sẽ được cập nhật
          type="image"
          value={ attributes.imageID }
          render={ ({ open }) => getImageButton(open) }
        />
        // Component này hiển thị ra inputtext để nhập tiêu đề, và set dữ liệu khi nhập vào inputtext
        <PlainText
          onChange={ content => setAttributes({ title: content }) }
          value={ attributes.title }
          placeholder="Your card title"
          className="heading"
        />
        // Component này hiển thị ra textarea để nhập nội dung của box, tương tự với các component trên, nó cũng set dữ liệu khi người dùng nhập thông tin.
        <RichText
          onChange={ content => setAttributes({ body: content }) }
          value={ attributes.body }
          multiline="p"
          placeholder="Your card text"
          formattingControls={ ['bold', 'italic', 'underline'] }
          isSelected={ attributes.isSelected }
        />
      </div>
    );

  },

Tiếp theo là function save. Đây là function để xác định sẽ lưu gì vào database, mà cụ thể là trường post_content của post/page.

save({ attributes }) {

  // Function hiển thị Ảnh
  const cardImage = (src, alt) => {
    if(!src) return null;

    if(alt) {
      return (
        <img 
          className="card__image" 
          src={ src }
          alt={ alt }
        /> 
      );
    }
    
    // No alt set, so let's hide it from screen readers
    return (
      <img 
        className="card__image" 
        src={ src }
        alt=""
        aria-hidden="true"
      /> 
    );
  };
  
  // Function hiển thị HTML sẽ được lưu vào database
  return (
    <div className="card">
      { cardImage(attributes.imageUrl, attributes.imageAlt) }
      <div className="card__content">
        <h3 className="card__title">{ attributes.title }</h3>
        <div className="card__body">
          { attributes.body }
        </div>
      </div>
    </div>
  );
}

Style

Thêm 1 chút style để block hiển thị đẹp hơn trên trình soạn thảo, mở file card-block/editor.scss và sửa thảnh:


.gutenberg {
    
  .container {
    border: 1px solid #ccc;
    padding: 1rem;
  }

  .button-container {
    text-align: center;
    padding: 22% 0;
    background: #ccc;
    border: 1px solid #f1f1f1;
    border-radius: 2px;
    margin: 0 0 1.2rem 0;
  }

  .heading {
    font-size: 1.5rem;
    font-weight: 600;
  }

  .image {
    height: 15.7rem;
    width: 100%;
    object-fit: cover;
  }
}

Kết quả

Đây là kết quả của ví dụ trên, bài viết sau mình sẽ hướng dẫn các bạn sử dụng block và render dữ liệu động, hẹn gặp lại các bạn nhé

Tham khảo: here