[Part 2] Chèn Emoji vào Trix editor

Đăng bởi Lưu Đại vào ngày 08-01-2023

4. Code 👨‍💻

Mặc định stimulus sẽ tạo ra một thư mục app/javascript/controllers
Để load các file trong thư mục này thêm dòng này vào app/javascript/packs/application_controller.js
...
import "controllers"
...
Thêm data: { controller: 'emoji-picker' } vào form_with để khai báo emoji-picker controller
Trong file _form.html.erb (file này mình sử dụng rich_text_area), thêm như sau 
<%= content_tag :div, "", class: "pickerContainer", data: {
  emoji_picker_target: "pickerContainer"
} %>

<div class="form-group">
  <%= form.label :content, 'Content' %>
  <%= form.rich_text_area :content, class: 'min-vh-60' %>
  <%= form.rich_text_area :content, class: 'min-vh-60', data: {
    emoji_picker_target: "trixEditor"
  } %>
</div>

<%= form.submit('Create', class: 'btn btn-primary') %>
  • Phần content_tag :div emoji_picker_target sẽ generate ra dòng html như sau
    content tag

    Đây chính là div chứa phần popup emoji để người dùng chọn. Bình thường nó sẽ rỗng khi nào người dùng click vào sẽ xử lý sự kiện để fill emoji vào trong div này.
  • Phần data: { emoji_picker_target: "trixEditor" } để xử lý chèn một emoji sau khi người dùng chọn emoji. 
  • Tạo một js controller để xử lý phần 2 phần data mới thêm vào. Tên file của mình là emoji_picker_controller.js
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  // Khai báo 2 target nó sẽ tự tìm tới element có data = 'trixEditor', 'pickerContainer'
  static targets = ["trixEditor", "pickerContainer"]; 

  connect() {
    const buttonString = this.emojiButtonString();
    const emojiButton = this.emojiButtonTemplate(buttonString);
    let picker;
    // class RichText sẽ chèn button emoji vừa tạo vào trong thanh công cụ của trix và add event xử lý bật / tắt hộp thoại chọn emoji khi người dùng click buton
    let richText = new RichText(picker, emojiButton) 

    picker = createPopup({
      rootElement: this.pickerContainerTarget // DOM element chứa picker của mình
    }, {
      triggerElement: emojiButton, // HTML element trigger popup picker
      referenceElement: emojiButton, // picker sẽ hiển thị theo vị trí của button emoji (relative position)
      position: "bottom-start", // position được dùng cho popup picker. bottom-start có nghĩa là mặc định hiển thị dưới  tuy nhiên nếu k đủ màn hình thì sẽ hiển thị lên trên
    });

    // Thêm sự kiện khi người dùng chọn emoji thì sẽ insert emoji vào vị trí con trỏ của text area (trixEditorTarget.editor)
    picker.addEventListener("emoji:select", (event) => {
      this.trixEditorTarget.editor.insertString(event.emoji)
    })

    // Set picker cho class RichText
    richText.setPicker(picker);
  }

  emojiButtonTemplate(buttonString) {
    const domParser = new DOMParser();
    const emojiButton = domParser
      .parseFromString(buttonString, "text/html")
      .querySelector("button");RichText

    return emojiButton;
  }

  // Tạo một html button sau đó đưa vào hàm emojiButtonTemplate ở đây button sẽ được parse ra thành một html element
  emojiButtonString() {
    const buttonString = `<button class="trix-button" id="emoji-picker" data-trix-action="popupPicker" tab-index="1">Emoji</button>`;

    return buttonString;
  }
}
* Note: Khi tạo controller mới thì cần import nó vào app/javascript/controller/index.js
import EmojiPickerController from "./emoji_picker_controller"
application.register("emoji-picker", EmojiPickerController)

  • Cuối cùng là class RichText 
export class RichText {
  constructor(picker, emojiButton) {
    this.picker = picker
    this.emojiButton = emojiButton
    this.createEmojiPickerButton();
  }

  // Đoạn này ta chèn button emoji vào thanh công cụ của Trix tham khảo ảnh dưới để thấy cấu trúc thanh công cụ
  createEmojiPickerButton() { 
    this.emojiButton.addEventListener('click', this.toggleEmojiPicker.bind(this));
    document
      .querySelector("[data-trix-button-group=block-tools]")
      .prepend(this.emojiButton);
  }

  toggleEmojiPicker(event) {
    this.picker.toggle();
  }

  // Hàm gán cho biến picker
  setPicker(picker) {
    this.picker = picker;
  }
}
Cấu trúc thanh công cụ của Trix


5. Tài liệu tham khảo 📄