tabulator: Vue Composition API not working

https://tabulator.info/docs/5.4/frameworks

As with many newbie simpley copy and paste the code from Composition API example for Vue 3.

No devs will able to understand what is gong on and create a negative experience that made us spent hours without success.

[plugin:vite:vue] [@vue/compiler-sfc] 'return' outside of function. (18:2)

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 1
  • Comments: 21 (4 by maintainers)

Most upvoted comments

@olifolkerd Apologies after a little more real-world testing there is actually a valid issue

I’ll try my best to give an accurate description.

<template>
  {{tableData}}
</template>

<script setup>
  import {ref} from 'vue';
  const tableData = ref([]);

  fetch("https://api.nationalize.io/?name=aaron",{method:"GET",headers:{"Content-Type":"application/json"}})
  .then(response => response.json())
  .then(data => {
    tableData.value.push(...data.country)
    console.log(tableData);
  }).catch(error => { console.error(error)})
</script>
<style></style>

Take the basic example above this is not using Tabulator if you run this SFC Playground Demo

You can see an api call has been sent, the data has been returned and the tableData ref has automatically updated it’s value on the page, this is an example of everything working as expected.

If we now introduce Tabulator :

<template>
  <div ref="table"></div>
  {{tableData}}
</template>

<script setup>
  import {ref, onMounted} from 'vue';
  import {TabulatorFull as Tabulator} from 'tabulator-tables';

  const table = ref(null);
  const tabulator = ref(null);
  const tableData = ref([]);

  fetch("https://api.nationalize.io/?name=aaron",{method:"GET",headers:{"Content-Type":"application/json"}})
  .then(response => response.json())
  .then(data => {
    tableData.value.push(...data.country)
  }).catch(error => { console.error(error)})

  onMounted(() => {
      tabulator.value = new Tabulator(table.value, {
        data: tableData.value, //link data to table
        reactiveData:true, //enable data reactivity
        columns: [
            { title: "Country ID", field: "country_id" },
            { title: "Probability", field: "probability"},
        ],
      });
  }) 

</script>
<style></style>

What happens now is the api call is made, the data in the table is updated as you would expect but the tableData reference does not update as you would expect indicating that the definition has lost reactivity.

Next I tried to confirm it was Tabulator breaking the reactivity. So I looked in ReactiveData.js to see how it worked and noticed you were re-defining methods temporarily then running a .apply to call the original method once you had done your processing. I found a method you hadn’t redefined so I created an example with both an Object.assign and a .push the theory being that since Tabulator doesn’t touch Object.assign it will retain reactivity and on the flip side using .push will break reactivity but will still update the table content.

<template>
  <div ref="table"></div>
  {{tableData}}
</template>

<script setup>
  import {ref,reactive, onMounted} from 'vue';
  import {TabulatorFull as Tabulator} from 'tabulator-tables';

  const table = ref(null);
  const tabulator = ref(null);
  const tableData = reactive([]);

  fetch("https://api.nationalize.io/?name=aaron",{method:"GET",headers:{"Content-Type":"application/json"}})
  .then(response => response.json())
  .then(data => {
    Object.assign(tableData,data.country)
    tableData.push(...data.country);
  }).catch(error => { console.error(error)})

  onMounted(() => {
      tabulator.value = new Tabulator(table.value, {
        data: tableData, //link data to table
        reactiveData:true, //enable data reactivity
        columns: [
            { title: "Country ID", field: "country_id" },
            { title: "Probability", field: "probability"},
        ],
      });
  }) 

</script>
<style></style>

This confirmed that it related to the Tabulator reactivity module, so I stepped through the code for the .push. As expected it does the standard processing then does a .apply to call the vanilla method passing in the context and arguments result = self.origFuncs.push.apply(data, arguments);

Which then runs Vue’s version which does a very similar thing…

 ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
        instrumentations[key] = function (...args) {
            pauseTracking();
            const res = toRaw(this)[key].apply(this, args);
            resetTracking();
            return res;
        };
    });

From the minimal debugging I have conducted it looks like the tabulator data reactivity module runs it’s version of .push Part of that process includes a self.block to prevent infinite looping.

It then runs a .apply - which triggers a call to Vue which does it’s own version of .apply ( as seen in the snippet above) which in turn triggers another instance of the tabular reactivity module but since the very first instance added a self.block the processing fails the if statement which means Vue’s version of .apply is essentially terminated by tabulators function.

I hope that makes as much sense as it does in my head!

I got a small test working locally with Nuxt and vue 3

<script lang="ts" setup>
/**
 *
 * Component Description:Desc
 *
 * @author Reflect-Media <Leamsigc>
 * @version 0.0.1
 *
 * @todo [ ] Test the component
 * @todo [ ] Integration test.
 * @todo [✔] Update the typescript.
 */
import "tabulator-tables/dist/css/tabulator.min.css";
import {
  TabulatorFull as Tabulator,
  type ColumnDefinition,
} from "tabulator-tables";
const table = ref(null); //reference to your table element
const tabulator = ref<Tabulator | null>(null); //variable to hold your table

const columns = ref<ColumnDefinition[]>([
  { title: "Name", field: "name", width: 150 },
  { title: "Age", field: "age", hozAlign: "left", formatter: "progress" },
  { title: "Favourite Color", field: "col" },
  { title: "Date Of Birth", field: "dob", hozAlign: "center" },
  { title: "Rating", field: "rating", hozAlign: "left", formatter: "star" },
  {
    title: "Passed?",
    field: "passed",
    hozAlign: "center",
    formatter: "tickCross",
  },
]);
const data = ref<Record<string, any>[]>([]);

onMounted(() => {
  if (!table.value) return;
  tabulator.value = new Tabulator(table.value, {
    data: data.value, //link data to table
    reactiveData: true, //enable data reactivity
    columns: columns.value, //define table columns
    layout: "fitDataStretch",
  });

  // Set time out and create a mock promise
  setTimeout(() => {
    console.log("set timeout");

    data.value = [
      { id: 1, name: "Oli Bob", age: "12", col: "red", dob: "" },
      { id: 2, name: "Mary May", age: "1", col: "blue", dob: "14/05/1982" },
      {
        id: 3,
        name: "Christine Lobowski",
        age: "42",
        col: "green",
        rating: "5",
        dob: "22/05/1982",
      },
      {
        id: 4,
        name: "Brendon Philips",
        age: "125",
        col: "orange",
        dob: "01/08/1980",
      },
      {
        id: 5,
        name: "Margret Marmajuke",
        age: "16",
        col: "yellow",
        dob: "31/01/1999",
      },
    ];
    tabulator.value?.setData(data.value);
  }, 5000);
});
</script>

<template>
  <div class="max-w-screen-xl w-full">
    <h3>Table here</h3>
    <div ref="table"></div>
  </div>
</template>
<style scoped>
</style>

Hope this help, This project seems to be a really nice project

What I don’t understand why they label it as suggested feature and not as bug fix.

Seem to have the same issue, the header is appear but tableData is not showing.

<script setup>
  import {ref, reactive, onMounted} from 'vue';
  import {TabulatorFull as Tabulator} from 'tabulator-tables';

  const table = ref(null);
  const tabulator = ref(null);
  const tableData = reactive([
    { id: 1, name: "Oli Bob", age: "1" },
    { id: 2, name: "Mary May", age: "1", col: "blue", dob: "14/05/1982" },
  ]);

  onMounted(() => {
      tabulator.value = new Tabulator(table.value, {
        data: tableData.value, //link data to table
        reactiveData:true, //enable data reactivity
        columns: [
            { title: "Name", field: "name", width: 150 },
            { title: "Age", field: "age", hozAlign: "left", formatter: "progress" },
            { title: "Favourite Color", field: "col" },
        ],
      });

      console.log(table.value)
      return { tabulator, table };
  }) 
</script>

<template>
  <div ref="table"></div>
</template>