Handling Multiple DataTables with Editable Fields in a Vue 3 Application

Handling Multiple DataTables with Editable Fields in a Vue 3 Application

IvanJouikovIvanJouikov Posts: 1Questions: 1Answers: 0

Hello DataTables community,

I am working on a Vue.js application where I need to render multiple DataTables with editable fields. Each table has identical column configurations but different datasets. The main challenge I am facing is ensuring that each table's input fields have unique identifiers to avoid conflicts and to correctly manage focus and updates. Here’s a detailed breakdown of my setup and the issues I am encountering:

Main Component

I have a main component that renders two instances of a TableComponent, each with its own dataset but sharing the same column configuration.

<template>
  <div>
    <TableComponent :id="'table1'" :tableData="tableData1" :columns="columns" />
    <TableComponent :id="'table2'" :tableData="tableData2" :columns="columns" />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import TableComponent from './TableComponent.vue'; // Assume the table component code is in this file

const tableData1 = ref([
  { name: 'John', age: 28 },
  { name: 'Jane', age: 32 },
]);

const tableData2 = ref([
  { name: 'Alice', age: 24 },
  { name: 'Bob', age: 27 },
]);

const columns = [
  { data: 'name', title: 'Name', type: 'input' },
  { data: 'age', title: 'Age', type: 'input' },
];
</script>

Table Component

The TableComponent is responsible for rendering each DataTable instance. Here’s how I have structured this component:

<template>
  <div ref="panel">
    <DataTable
      ref="dataTable"
      class="display nowrap"
      :data="tableData"
      :columns="computedColumns"
      :options="options"
    >
    </DataTable>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue';
import DataTable from 'datatables.net-vue3';
import DataTablesCore from 'datatables.net';
import 'datatables.net-buttons';
import 'datatables.net-buttons/js/buttons.html5';
import 'datatables.net-responsive';
import 'datatables.net-select';
import 'datatables.net-dt';
import 'datatables.net-responsive-dt';
import 'datatables.net-buttons-dt';
import 'datatables.net-select-dt';

const props = defineProps({
  tableData: Array,
  columns: Array,
});

DataTable.use(DataTablesCore);
let dt;
let expandedRows = [];
const dataTable = ref(null);
const panel = ref(null);
let lastUpdatedField = null;

onMounted(() => {
  dt = dataTable.value.dt;

});

const options = {
  responsive: true,
  colReorder: true,
  searching: false,
  paging: false,
  lengthChange: false,
  info: false,
  order: [],
  deferRender: false,
};

const updateField = (rowIndex, field, value) => {
  const columnIndex = props.columns.findIndex((col) => col.data === field);
  expandedRows = [];
  dt.rows().every(function (rowIdx) {
    if (this.child.isShown()) {
      expandedRows.push(rowIdx);
    }
  });
  dt.cell(rowIndex, columnIndex).data(value);
  dt.row(rowIndex).invalidate();

  // Using unique data attributes instead of IDs
  lastUpdatedField = panel.value.querySelector(`[data-id="${props.id}"][data-field="${field}"][data-row="${rowIndex}"]`);

  setTimeout(() => {
    expandedRows.forEach((rowIdx) => {
      dt.cell(rowIdx, 0).node().click();
    });
  }, 10);

  setTimeout(() => {
    if (lastUpdatedField) {
      lastUpdatedField.focus();
    }
  }, 100);
};

const handleTabKey = () => {
  // Custom logic for handling tab key
};

const getFieldHtml = (type, data, field, rowIndex, options = []) => {
  const uniqueId = `field-${field}-${rowIndex}`;

  switch (type) {
    case 'input':
      return `<input  data-field="${field}" data-row="${rowIndex}" type="text" class="p-inputtext" value="${data}" onchange="updateField(${rowIndex}, '${field}', this.value)" onkeydown="handleTabKey(event)" />`;
    case 'select':
      return `
        <select  data-field="${field}" data-row="${rowIndex}" class="p-dropdown" onchange="updateField(${rowIndex}, '${field}', this.value)" onkeydown="handleTabKey(event)">
          ${options
            .map(
              (option) =>
                `<option value="${option.value}" ${data === option.value ? 'selected' : ''}>${
                  option.label
                }</option>`
            )
            .join('')}
        </select>
      `;
    case 'checkbox':
      return `
        <div class="p-checkbox p-component">
          <div class="p-checkbox-box p-highlight">
            <input  data-field="${field}" data-row="${rowIndex}" type="checkbox" class="p-checkbox-input" ${
        data ? 'checked' : ''
      } onchange="updateField(${rowIndex}, '${field}', this.checked)" onkeydown="handleTabKey(event)" />
          </div>
        </div>
      `;
    case 'radio':
      return `
        <div>
          ${options
            .map(
              (option) => `
            <div class="p-radio p-component">
              <input  data-field="${field}" data-row="${rowIndex}" type="radio" class="p-radio-input" name="${uniqueId}" value="${
                option.value
              }" ${
                data === option.value ? 'checked' : ''
              } onchange="updateField(${rowIndex}, '${field}', this.value)" onkeydown="handleTabKey(event)" />
              <label>${option.label}</label>
            </div>
          `
            )
            .join('')}
        </div>
      `;
    case 'string':
      return `<span>${data}</span>`;
    default:
      return '';
  }
};

const computedColumns = computed(() => {
  return props.columns.map((col) => ({
    ...col,
    render(data, type, row, meta) {
      return getFieldHtml(col.type, data, col.data, meta.row, col.options || []);
    },
  }));
});
</script>

<style>
/* Import DataTables styles */
@import 'datatables.net-dt';
@import 'datatables.net-responsive-dt';
@import 'datatables.net-buttons-dt';
@import 'datatables.net-select-dt';
</style>

Explanation:

updateField Function: This function updates the correct cell in the correct table and then focuses on the updated input field using the unique data attributes. The function:

Finds the column index based on the field name.
Stores the indices of currently expanded rows.
Updates the data in the specified cell.
Re-expands any previously expanded rows.
Focuses on the last updated field using the unique data attributes.
Field HTML Generation: The getFieldHtml function generates the input fields with unique data attributes (data-id, data-field, and data-row) instead of IDs. This prevents conflicts and allows precise targeting for updates and focus management.

Question:

Is there a better or more efficient way to handle multiple DataTables with editable fields in a Vue.js application to ensure unique identification and correct focus management? Are there best practices or additional techniques that could improve this setup?

Any advice or suggestions would be greatly appreciated.

Thank you!

Answers

  • colincolin Posts: 15,234Questions: 1Answers: 2,597

    For editable fields, it would be worth looking at Editor - this plugin is designed to simplify CRUD operations on a DataTable.

    Colin

Sign In or Register to comment.