vue.jsvuejs3vuetify.jsrefv-data-table

Adding "View More" functionality for truncated long text in Vuetify v-data-table


I am working on a Vuetify 3 data tabe and need to implement a "View More" button for description that exceed three lines. I want to use scrollHeight and clientHeight to determine if the text is truncated and provide an option to expand it.

Vuetify Playground: Playground Link

Here is a simplified version of my implementation:

<template>
  <v-data-table :headers="headers" :items="items">
    <template #item.description="{ item }">
      <div>
        <div
          ref="descriptionRef"
          class="three_lines_description"
          :style="{ overflow: isExpanded[item.name] ? 'visible' : 'hidden' }"
        >
          {{ item.description }}
        </div>
        <v-btn
          v-if="isTruncated[item.name]"
          @click="toggleExpand(item.name)"
          text
        >
          {{ isExpanded[item.name] ? 'View Less' : 'View More' }}
        </v-btn>
      </div>
    </template>
  </v-data-table>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  data() {
    return {
      headers: [
        { title: "Name", key: "name" },
        { title: "Email", key: "email" },
        { title: "Description", key: "description" },
      ],
      items: [
        // Sample items...
      ],
      isExpanded: {},
      isTruncated: {},
    };
  },
  methods: {
    toggleExpand(name) {
      this.isExpanded[name] = !this.isExpanded[name];
    },
    checkTruncation() {
      // Logic to check if the text is truncated...
    }
  },
  mounted() {
    this.checkTruncation();
  }
};
</script>

<style scoped>
.three_lines_description {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
}
</style>

Issue

This is the simplified design of my code now which changes the format for the description column only. What is the correct approach to implement this "View More" functionality in a Vuetify data table?

If the solution works, I will accept it as answer and give an upvote.

Pagination not working


Solution

  • This is a Vue.js 3 possible solution:

    <script setup lang="ts">
      import { ref, onMounted, nextTick } from "vue";
    
      const headers = [
        { title: 'Name', key: 'name' },
        { title: 'Email', key: 'email' },
        { title: 'Description', key: 'description' },
      ];
    
      const items = ref([
        {
          name: 'John Rambo',
          email: 'johnrambo@example.com',
          description: 'This a very short description.',
          showButton: false,
          showButtonText: "View More",
        },
        {
          name: 'John Doe',
          email: 'john@example.com',
          description: 'This a short test description for the sake of testing.',
          showButton: false,
          showButtonText: "View More",
        },
        {
          name: 'Jane Doe',
          email: 'jane@example.com',
          description:
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi et nunc id augue imperdiet tempor. Duis leo nisl, aliquet et dapibus ut, eleifend sed nunc. Aenean vulputate purus tellus, ac rutrum quam mattis ac. Quisque et nunc hendrerit, pretium quam eget, dictum mauris. Aliquam pulvinar velit ac tellus porttitor, quis sagittis diam consequat. Nulla facilisi. Morbi a tempor ex. Integer tempus est ipsum, id pretium magna maximus at. Praesent finibus tempus tempus. Proin fringilla imperdiet odio eget semper. Maecenas tempus ipsum sit amet luctus tincidunt. Mauris consectetur risus in lacus placerat congue. Quisque quis arcu ut ligula finibus dapibus eu sit amet nisl. Maecenas a mi rhoncus, bibendum erat vel, pretium arcu. Vestibulum eu lectus laoreet, efficitur quam sed, ultricies risus. Morbi pharetra mauris vel venenatis lacinia.',
          showButton: false,
          showButtonText: "View More",
        },
        {
          name: 'Random Guy',
          email: 'random@example.com',
          description:
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi et nunc id augue imperdiet tempor. Duis leo nisl, aliquet et dapibus ut, eleifend sed nunc. Aenean vulputate purus tellus, ac rutrum quam mattis ac. Quisque et nunc hendrerit, pretium quam eget, dictum mauris. Aliquam pulvinar velit ac tellus porttitor, quis sagittis diam consequat. Nulla facilisi. Morbi a tempor ex. Integer tempus est ipsum, id pretium magna maximus at. Praesent finibus tempus tempus. Proin fringilla imperdiet odio eget semper. Maecenas tempus ipsum sit amet luctus tincidunt. Mauris consectetur risus in lacus placerat congue. Quisque quis arcu ut ligula finibus dapibus eu sit amet nisl. Maecenas a mi rhoncus, bibendum erat vel, pretium arcu. Vestibulum eu lectus laoreet, efficitur quam sed, ultricies risus. Morbi pharetra mauris vel venenatis lacinia.',
          showButton: false,
          showButtonText: "View More",
        },
      ]);
      const descriptionRefs = ref([]);
    
      function increaseSize(index: number) {
        const divElement: HTMLDivElement = descriptionRefs.value[index];
    
        if (divElement.dataset.expanded === "true") {
          nextTick(() => {
            divElement.dataset.expanded = "false"
            divElement.classList.add('three_lines_description');
            items.value[index].showButtonText = "View More";
          })
          return;
        }
    
        nextTick(() => {
          divElement.dataset.expanded = "true"
          divElement.dataset.showButton = "true"
          divElement.classList.remove("three_lines_description");
          items.value[index].showButtonText = "Show less";
        });
      }
    
      function checkIfNeedsButton() {
        descriptionRefs.value.forEach((item: HTMLDivElement, index) => {
          if (item.scrollHeight > item.clientHeight) {
            items.value[index].showButton = true;
          }
        });
      }
    
      onMounted(() => {
        nextTick(() => {
          checkIfNeedsButton();
        })
      });
    </script>
    
    <template>
      <v-data-table :headers="headers" :items="items">
        <template #item.description="{ item, index }">
          <div>
            <div
              :ref="(el) => {
                descriptionRefs.push(el);
              }"
              class="three_lines_description px-1 py-1"
            >
              {{ item.description }}
            </div>
            <v-btn
              v-if="item.showButton"
              class="mt-2 mb-2"
              @click="increaseSize(index)"
            >
              {{ item.showButtonText }}
            </v-btn>
          </div>
        </template>
      </v-data-table>
    </template>
    
    <style scoped>
      .three_lines_description {
        overflow: hidden;
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        line-clamp: 3;
      }
    </style>
    

    Using the following tools:

    Vuetify Playground: example

    PD: Also there is a expandable Row feature in Vuetify