primevue icon indicating copy to clipboard operation
primevue copied to clipboard

DataTable: Wrapper for custom columns not working when using slots

Open Terrijoo opened this issue 1 year ago • 17 comments

Describe the bug

With the latest few releases we got a way to use costum wrappers for Column components for DataTables. This seems to work if you use the custom wrapper directly as child component, but not if you use slots.

See my stackblitz link for a comparison:

  • The upper table only displays the normal columns, but not the two wrappers, because I use them as a slot to be more dynamic
  • The lower table displays all of them

I've tried to debug this a bit and it seems like this line is the issue: https://github.com/primefaces/primevue/blame/9c162ae9e67d2f7df825613db58773b49b1100f7/components/lib/utils/HelperSet.js#L41 The components are passed towards the recursive function, even though it should be the helper. If I change that locally to pass the helpers instead the components, it does work, but I haven't tested further if that breaks anything else.

Reproducer

https://stackblitz.com/edit/primevue-datable-slots-customcolumnwrapper-issue?file=src%2FApp.vue

PrimeVue version

3.47.2

Vue version

3.x

Language

TypeScript

Build / Runtime

Vite

Browser(s)

all

Steps to reproduce the behavior

  1. Simply go to the provided stackblitz
  2. Upper table does not display custom columns/wrappers
  3. Lower table does

Expected behavior

Both tables should display all columns

Terrijoo avatar Feb 01 '24 08:02 Terrijoo

Thanks a lot for your report! I set a milestone for it. We'll check it before the milestone is released.

mertsincan avatar Feb 09 '24 09:02 mertsincan

Why do I need to set the key for custom columns but not for normal columns:

<DataTable :value="rows">
  <Column field="name" header="Name"></Column>
  <CustomColumn key="phone" field="phone" header="Phone"></CustomColumn>
  <Column field="email" header="Email"></Column>
</DataTable>

If I remove the key="phone", the whole column disappears. I have been wasting so much time trying to figure out what is wrong with my code because of that. I'm leaving this here in case someone is having the same problem as I.

os-tohe avatar Mar 12 '24 12:03 os-tohe

Why do I need to set the key for custom columns but not for normal columns:

<DataTable :value="rows">
  <Column field="name" header="Name"></Column>
  <CustomColumn key="phone" field="phone" header="Phone"></CustomColumn>
  <Column field="email" header="Email"></Column>
</DataTable>

If I remove the key="phone", the whole column disappears. I have been wasting so much time trying to figure out what is wrong with my code because of that. I'm leaving this here in case someone is having the same problem as I.

Hey @os-tohe!

Sorry, may be you can clear it for me, have you been able to make CustomColumn work? For me, there is no difference, is there key prop or not. It just doesn't work.

May you share your CustomColumn component code here? I am trying to achieve same, but for some reason it just doesn't work :(

yarotskiu avatar Mar 18 '24 08:03 yarotskiu

May you share your CustomColumn component code here? I am trying to achieve same, but for some reason it just doesn't work :(

I just copied the code from the stackblitz link which is at the top of this thread: https://stackblitz.com/edit/primevue-datable-slots-customcolumnwrapper-issue?file=src%2FApp.vue

I am able to make CustomColumn work if I am using the DataTable from the primevue/datatable directly and I set the key for the CustomColumn. If I use my custom DataTable wrapper then even the CustomColumn with key does not work. Maybe you are using your DataTable wrapper and that is why your CustomColumn code does not work?

You can test that the key makes a difference by opening the stackblitz link and by removing the key in App.vue at line 48.

os-tohe avatar Mar 19 '24 06:03 os-tohe

Ah... yes, my bad, I understood it incorrectly Yes, I have a CustomTable wrapper, and I want to have CustomColumn, which, unfortunately, doesn't work :(

yarotskiu avatar Mar 19 '24 09:03 yarotskiu

I managed to make a custom column component inside a custom table component work after

  1. Applying the following patch (use patch-package or pnpm):
diff --git a/utils/utils.esm.js b/utils/utils.esm.js
index 8ca096225758e54c3418d08030ef63874caf077e..b12f6343098a2a7d08be96aa5a60cad4f61811c0 100644
--- a/utils/utils.esm.js
+++ b/utils/utils.esm.js
@@ -1200,7 +1200,7 @@ var _default = /*#__PURE__*/function () {
       var components = [];
       children.forEach(function (child) {
         if (child.children instanceof Array) {
-          components = components.concat(_this._recursive(components, child.children));
+          components = components.concat(_this._recursive(helpers, child.children));
         } else if (child.type.name === _this.type) {
           components.push(child);
         } else if (ObjectUtils.isNotEmpty(child.key)) {

  1. Adding unique keys to every custom column component usage (probably should be the same as field names?).

andreww2012 avatar May 17 '24 11:05 andreww2012

I managed to make a custom column component inside a custom table component work after

  1. Applying the following patch (use patch-package or pnpm):
diff --git a/utils/utils.esm.js b/utils/utils.esm.js
index 8ca096225758e54c3418d08030ef63874caf077e..b12f6343098a2a7d08be96aa5a60cad4f61811c0 100644
--- a/utils/utils.esm.js
+++ b/utils/utils.esm.js
@@ -1200,7 +1200,7 @@ var _default = /*#__PURE__*/function () {
       var components = [];
       children.forEach(function (child) {
         if (child.children instanceof Array) {
-          components = components.concat(_this._recursive(components, child.children));
+          components = components.concat(_this._recursive(helpers, child.children));
         } else if (child.type.name === _this.type) {
           components.push(child);
         } else if (ObjectUtils.isNotEmpty(child.key)) {
  1. Adding unique keys to every custom column component usage (probably should be the same as field names?).

That does not seem to work with every DataTable prop. For example if I use this advanced filter, it does not render the filter menu icon on the header. This is how my Column wrapper looks like:

<template>
  <Column v-bind="$props">
    <template v-for="slot of slotNames" :key="slot" #[slot]="slotProps">
      <!-- @vue-ignore -- For some reason slot can't infer the correct type -->
      <slot :name="slot" v-bind="slotProps ?? {}"></slot>
    </template>
  </Column>
</template>

<script setup lang="ts">
import Column, { type ColumnProps, type ColumnSlots } from 'primevue/column';
import { computed, useSlots } from 'vue';

// Get rid of unnecessary ts errors in template by casting slots
const slots = useSlots();
const slotNames = computed(() => {
  return Object.keys(slots) as (keyof ColumnSlots)[];
});

defineProps<ColumnProps>();
defineSlots<ColumnSlots>();
</script>

Do let me know if there is something wrong with my Column wrapper.

EDIT: I noticed that if I use v-bind="$attrs" then the filter menu icon is rendered on the header, but my props are not passed down at all anymore. If I try to combine them like this v-bind="($attrs, $props)" or this v-bind="{ ...$attrs, ...$props }" then only my props function correctly, so the filter menu icon is not rendered anymore.

os-tohe avatar Jun 18 '24 05:06 os-tohe

Why was this removed from 3.53.0 milestone? Is this now fixed? We have been waiting for a fix to this for months now, please don't tell me that this is not going to be fixed at all.

os-tohe avatar Jul 15 '24 05:07 os-tohe

I believe that I have found a working solution for the Column wrapper:

<template>
  <Column v-bind="($attrs, $props)">
    <template v-for="slot of slotNames" :key="slot" #[slot]="slotProps">
      <!-- @vue-ignore -- For some reason slot can't infer the correct type -->
      <slot :name="slot" v-bind="slotProps ?? {}"></slot>
    </template>
  </Column>
</template>

<script setup lang="ts">
import Column, { type ColumnProps, type ColumnSlots } from 'primevue/column';
import { computed, useSlots } from 'vue';

// Get rid of unnecessary ts errors in template by casting slots
const slots = useSlots();
const slotNames = computed(() => {
  return Object.keys(slots) as (keyof ColumnSlots)[];
});

withDefaults(defineProps<ColumnProps>(), {
  sortable: false,
  showFilterMenu: true,
  showFilterOperator: true,
  showClearButton: true,
  showApplyButton: true,
  showFilterMatchModes: true,
  showAddButton: true,
  maxConstraints: 2,
  excludeGlobalFilter: false,
  expander: false,
  rowReorder: false,
  reorderableColumn: true,
  rowEditor: false,
  frozen: false,
  alignFrozen: 'left',
  exportable: false,
  hidden: false,
  unstyled: false,
});
defineSlots<ColumnSlots>();
</script>

That in combination with the patch provided in https://github.com/primefaces/primevue/issues/5190#issuecomment-2117361030 and adding key prop to every column seems to function correctly. The problem with my previous solution was that I did not setup the default values for props correctly, so it set the showFilterMenu property to false, which caused it not to be visible.

os-tohe avatar Jul 19 '24 11:07 os-tohe

I managed to make a custom column component inside a custom table component work after

  1. Applying the following patch (use patch-package or pnpm):
diff --git a/utils/utils.esm.js b/utils/utils.esm.js
index 8ca096225758e54c3418d08030ef63874caf077e..b12f6343098a2a7d08be96aa5a60cad4f61811c0 100644
--- a/utils/utils.esm.js
+++ b/utils/utils.esm.js
@@ -1200,7 +1200,7 @@ var _default = /*#__PURE__*/function () {
       var components = [];
       children.forEach(function (child) {
         if (child.children instanceof Array) {
-          components = components.concat(_this._recursive(components, child.children));
+          components = components.concat(_this._recursive(helpers, child.children));
         } else if (child.type.name === _this.type) {
           components.push(child);
         } else if (ObjectUtils.isNotEmpty(child.key)) {
  1. Adding unique keys to every custom column component usage (probably should be the same as field names?).

In PrimeVue v4 that file is moved under @primevue package. It is found in @primevue/core/utils/index.mjs. You also need to install the @primevue/core package if you want to patch it with the patch-package.

os-tohe avatar Jul 23 '24 08:07 os-tohe