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
Để 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
Đâ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; } }