skip to Main Content

I’m using Vuetify and have some content above a table. The table should have a fixed-header but this requires a height prop. A height of 100% didn’t work and I can’t use 100vh because I have some content above.

Reproduction link

<template>
  <v-app>
    <v-main>
      <v-alert type="info" title="some other content goes here" />
      <v-table :fixed-header="true" height="100%"> <!-- height=100% doesn't work -->
        <thead>
          <tr>
            <th>Header</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="i in 100">
            <td>x</td>
          </tr>
        </tbody>
      </v-table>
    </v-main>
  </v-app>
</template>

Do you have any ideas how to setup a fixed header with a correct height?


I think the answer of Igor Moraru is almost correct. The problem is that it is not able to deal with new table rows joining the table.

Reproduction link

2

Answers


  1. You’ll have to compute the table height anyway. One way is to substract the alert height from the container height. What is left is the table height.

    <template>
      <v-app>
        <v-main ref="maincontainer">
          <v-alert ref="alert" type="info" title="some other content goes here" />
          <v-table :fixed-header="true" :height="height">
            <thead>
              <tr>
                <th>Header</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="i in 100">
                <td>x</td>
              </tr>
            </tbody>
          </v-table>
        </v-main>
      </v-app>
    </template>
    
    <script>
    import {ref, onMounted} from "vue"
    export default {
      setup(){
        let maincontainer = ref(null)
        let alert = ref(null)
        let height = ref('500px')
        
        onMounted(() => {
          let mainHeight = maincontainer.value.$el.clientHeight
          let alertHeight = alert.value.$el.clientHeight
          height.value = `${mainHeight - alertHeight}px`
        })
        
        return {
          maincontainer,
          alert,
          height
        }
      }
    }
    </script>
    

    Fixed reproduction link

    UPDATE:


    For a more generic solution (independent of the intermediate elements between the table and the container) you can get the position of the table into the viewport (using getBoundingClientRect) and calculate the top and bottom offsets, then subtract them from the viewport height. This way you don’t care about what elements are above or below the table but only about the space that they require.

    Note: For this solution to work, initially the table should not have a fixed height (or 100% height).

    UPDATE:


    Refactored the solution to allow recomputing the table height on demand.

    The main points to consider: using a computed is not applicable for this situation because the css props are not reactive in Vue. Instead make the computing in a dedicated function and call it when needed (you can trigger it even from a window resize watcher).

    Used the OP setup for a more focused solution.

    <script setup lang="ts">
    import { ref, computed, onMounted, nextTick } from "vue";
    import TheAlert from "./TheAlert.vue";
    
    const props = defineProps<{ mainContainer: VMain }>();
    
    const tableComponent = ref<VTable>();
    const values = ref([]);
    
    const tableHeight = ref(null)
      
    const refreshTableHeight = async () => {
      if (!tableComponent.value) {
       tableHeight.value = null;
      }
      // Unset table height and wait for it to rerender
      tableHeight.value = null
      await nextTick()
      
      // Recompute the table height with new data
      const mainRectangle = props.mainContainer.$el.getBoundingClientRect();
      const tableRectangle = tableComponent.value.$el.getBoundingClientRect();
    
      const topOffset = tableRectangle.top;
      const bottomOffset = mainRectangle.bottom - tableRectangle.bottom;
    
      tableHeight.value = window.innerHeight - bottomOffset - topOffset;
    
    }
    
    function insertNewRow() {
      values.value.push(new Date());
      refreshTableHeight()
    }
    onMounted(() => {
      refreshTableHeight()
    })
    
    </script>
    
    <template>
        <the-alert />
        <v-container>
            <v-btn @click="insertNewRow">Insert new row</v-btn>
        </v-container>
        <v-table ref="tableComponent" fixed-header :height="tableHeight"> <!-- Do not format here the height to allow for null values -->
          <thead>
            <tr>
              <th>Header</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="v in values" :key="v">
              <td>{{ v }}</td>
            </tr>
          </tbody>
        </v-table>
    </template>
    

    FIXED reproduction link.

    Login or Signup to reply.
  2. You can simply achieve this by calculating the height on the fly using calc() function. You have to calculate the pixels of the elements that are above the v-data-table and then subtract it from 100vh.

    height="calc(100vh - 60px)"
    

    In Vue – You can get the element height dynamically by attaching a ref to it and then access the clientHeight.

    this.$refs.alertBox.$el.clientHeight
    

    This will return 60 as per the alert box component you added.

    Live Demo :

    const { createApp } = Vue
    const { createVuetify } = Vuetify
    
    const vuetify = createVuetify()
    
    const app = createApp({
      template: '#app-template'
    }).use(vuetify).mount('#app')
    <script src="https://unpkg.com/vue@next/dist/vue.global.js"></script>
    <script src="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@vuetify/[email protected]/dist/vuetify.css"/>
    
    <script type="text/x-template" id="app-template">
      <v-app>
        <v-alert type="info" title="some other content goes here" />
          <v-table fixed-header height="calc(100vh - 60px)">
            <thead>
              <tr>
                <th>Header</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="i in 100">
                <td>x</td>
              </tr>
            </tbody>
          </v-table>
      </v-app>
    </script>
    
    <div id="app"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search