ป้ายกำกับ: Shallow

TabulatorPlus: Deep MergeTabulatorPlus: Deep Merge

ปัญหาที่เกิดจาก Shallow Merge (...spread) คือถ้าคุณส่ง userConfig ที่มี Object ซ้อนกัน เช่น persistence: { sort: false } มันจะไปเขียนทับ defaultConfig.persistence ทั้งก้อน ทำให้ค่า filter และ page ที่เราอุตส่าห์ตั้งไว้หายไปทันที

เราจะแก้ปัญหานี้ด้วยการสร้างฟังก์ชัน deepMerge เล็ก ๆ ไว้ในไฟล์เดียวกัน เพื่อให้ TabulatorPlus แข็งแกร่งขึ้นครับ


TabulatorPlus ให้รองรับ Deep Merge

/**
 * TabulatorPlus.js
 * A wrapper class for Tabulator-tables to provide standardized configurations
 * and deep-merge capabilities across different frameworks.
 */
import { TabulatorFull as Tabulator } from 'tabulator-tables';

/**
 * Helper function to check if an item is a plain object.
 * @param {any} item - The item to check.
 * @returns {boolean}
 */
function isObject(item) {
    return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 * Deeply merges two objects. 
 * This ensures nested properties (like 'persistence') aren't overwritten entirely
 * when only one sub-property is provided in the user configuration.
 * @param {Object} target - The default configuration object.
 * @param {Object} source - The user-provided configuration object.
 * @returns {Object} - The deeply merged configuration.
 */
function deepMerge(target, source) {
    let output = Object.assign({}, target);
    if (isObject(target) && isObject(source)) {
        Object.keys(source).forEach(key => {
            if (isObject(source[key])) {
                if (!(key in target)) {
                    Object.assign(output, { [key]: source[key] });
                } else {
                    output[key] = deepMerge(target[key], source[key]);
                }
            } else {
                Object.assign(output, { [key]: source[key] });
            }
        });
    }
    return output;
}

export default class TabulatorPlus extends Tabulator {
    /**
     * @param {HTMLElement|string} el - The DOM element or selector to attach the table to.
     * @param {Object} userConfig - Custom Tabulator options provided by the user.
     * @param {string|null} storageKey - Unique ID for persistence (sorting, filtering, etc.).
     */
    constructor(el, userConfig = {}, storageKey = null) {
        // 1. Define organization-wide default settings
        const defaultConfig = {
            layout: "fitColumns",
            movableColumns: true,
            pagination: "local",
            paginationSize: 10,
            paginationSizeSelector: [10, 25, 50, 100],
            persistence: storageKey ? {
                filter: true,
                page: true,
                sort: true
            } : false,
            persistenceID: storageKey,
            placeholder: "No Data Available",
            resizableRows: true,
            responsiveLayout: "collapse"
        };

        // 2. Perform Deep Merge to protect nested default settings
        const finalConfig = deepMerge(defaultConfig, userConfig);

        // 3. Initialize the parent Tabulator class
        super(el, finalConfig);
    }

    /**
     * Updates the table with a new dataset.
     * Using this method is preferred over re-initializing the whole class.
     * @param {Array} newData - The new array of objects to display.
     * @returns {Promise} - Resolves when data is loaded.
     */
    refreshData(newData) {
        if (newData && Array.isArray(newData)) {
            return this.setData(newData);
        }
        return Promise.reject("Invalid data format: Expected an Array.");
    }

    /**
     * Forces the table to recalculate its dimensions and redraw.
     * Useful when the table container size changes or after opening a modal.
     */
    forceRedraw() {
        this.redraw(true);
    }

    /**
     * Clears all active filters and header filters from the table.
     */
    resetFilters() {
        this.clearFilter(true);
        this.clearHeaderFilter();
    }
}

💡 ทำไมแบบนี้ถึงดีกว่า? ( เปรียบเทียบให้เห็นภาพ )

สมมติคุณเรียกใช้งานแบบนี้

new TabulatorPlus("#table", {
    persistence: { sort: false } // ต้องการปิดแค่ sort อย่างเดียว
});

❌ ผลลัพธ์แบบเก่า ( Shallow Merge )

Config ที่ได้จะเป็น

persistence: { sort: false } // filter และ page หายไป! เพราะโดนทับทั้งก้อน

✅ ผลลัพธ์แบบใหม่ ( Deep Merge )

Config ที่ได้จะฉลาดขึ้น

persistence: {
    filter: true, // ยังอยู่ (จาก Default)
    page: true,   // ยังอยู่ (จาก Default)
    sort: false   // ถูกเปลี่ยนเฉพาะตัวนี้ (จาก User)
}

🚀 ประโยชน์เพิ่มเติม

  1. ความปลอดภัย: คุณสามารถกำหนด Default Config ที่ซับซ้อน ( Nested Objects ) ได้โดยไม่ต้องกลัวว่าการแก้ไขเพียงจุดเดียวจากภายนอกจะทำให้ระบบรวน
  2. ความยืดหยุ่น: ช่วยให้ Dev คนอื่นในทีมส่ง Config เฉพาะจุดที่ต้องการแก้ไขจริง ๆ เข้ามาได้ง่ายขึ้น
  3. No Dependencies: ฟังก์ชัน deepMerge เป็น Pure JavaScript ไม่ต้องติดตั้ง Library ภายนอกอย่าง Lodash เพิ่มเติม ทำให้ไฟล์มีขนาดเล็กและรันได้ทุกที่