简体   繁体   中英

Vuetify tooltip shows in top left corner when a link from a v-button is followed

I have encountered a strange behaviour of the Tooltip component in Vuetify. I am using this component inside Vuetify's DataTable component , where I use a slot for one of the data columns into which I add Vuetify's Button component and then I use the Tooltip component inside that button.

Problem definition:

The tooltip behavior is following:

  • When I move my mouse to the button and wait, the tooltip shows correctly.
  • When I click the button when the tooltip is shown, it redirects to the correct page and everything works as expected.
  • When I click the button when the tooltip is not shown, it redirects to the correct page, but the tooltip then shows up in the top left corner of the page and gets stuck.
  • When the tooltip gets stuck, I can redirect to different pages but the tooltip is still in the top left corner.
  • When the tooltip gets stuck, I can go back to the page with my table. If I move my mouse to the button that initially used to get redirected and then move away, the tooltip disappears.

Additional information:

I have found out that the behavior is probably caused by the fact that in my app, I have set this component to be kept alive via

<keep-alive :include="['PrintHistory']">
  <router-view />
</keep-alive>

When I remove the PrintHistory string, it behaves as expected.

I have also found out that when I set the open-delay parameter to a higher number, it shows the tooltip in the top left corner with higher delay.

It does not matter what page I'm redirecting to, the problem still prevales.

The code of the entire component:

<template>
  <div>
    <v-layout style="width:100%;" justify-space-between class="mb-0">
        <h2>{{ $t('print_history.title') }}</h2>
    </v-layout>
    <v-card class="elevation-1 my-1 pt-1" v-resize="onResize">

      <v-layout style="text-align: right;">
        <v-spacer></v-spacer>
        <v-text-field
          v-model="search"
          append-icon="mdi-magnify"
          :label="$t('print_history.search')"
          class="ma-2"
        ></v-text-field>
      </v-layout>

      <v-data-table :headers="headers"
                    :items="rows"
                    :loading="loading"
                    :loading-text="$t('v_big_table.loading')"
                    :sort-by="'action_at'"
                    :sort-desc="true"
                    :search="search"
                    :page.sync="page"
                    @page-count="page_count = $event"
                    hide-default-footer
                    :items-per-page="computed_items_per_page">
        <template v-for="header in headers" v-slot:[`header.`+header.value]="{ header }">
          <span class="table_header" v-bind:key="`header_customization_`+header.value">{{ $t(header.text) }}</span>
        </template>
        <template v-slot:item.show_file="{ item }">
          <!-- The redirecting is not yet finished -->
          <v-btn :to="`/pdfjs-viewer`" icon small class="ma-0" color="primary">
            <v-tooltip top open-delay="600" :color="tooltip_bg_color">
              <template v-slot:activator="{ on, attrs }">
                    <v-icon
                      v-bind="attrs"
                      v-on="on">mdi-file-search-outline</v-icon>
              </template>
              <span class="tooltip">{{ $t('print_history.show_file') }} {{ item['path_to_file'].split("/")[item['path_to_file'].split("/").length-1] }}</span>
            </v-tooltip>
          </v-btn>
        </template>
      </v-data-table>
      <v-pagination
        v-model="page"
        :length="page_count"
        :total-visible="9"
      ></v-pagination>
    </v-card>
  </div>
</template>

<script>

/**
 * @typedef {Object} Sorter - stores information about column sorting
 * @property {string} field - unique name of the column to sort
 * @property {string} dir - direction of sorting: ascending ('asc'), descending ('desc')
 */

/**
 * @typedef {Object} Filter - stores information about column sorting
 * @property {string} field - unique name of the column to filter
 * @property {string} operator - operator defining the filter
 * @property {string} value - direction of sorting: ascending ('asc'), descending ('desc')
 * @property {string} color - color of the v-chip when the filter is shown
 */

import { utcTimeStamp, utcToLocal } from '../../../utils'

/**
 * Shows a history of printing arranged in a table with sorting and filtering capabilities.
 */
export default {
  name: "PrintHistory",
  data () {
    return {
      tooltip_bg_color: 'rgba(0,0,0,0.7)',
      err_msg: '',
      search: '',
      row_count: 0,
      total_row_count: 0,
      rows: [],
      rows_filtered: [],
      loading: true,
      page: 1,
      page_count: null,
      default_items_per_page: 13,
      window_size: {
        x: 0,
        y: 0
      },
      initial_sorters: [{field: 'action_at', dir: 'desc'}],
      headers: [
        {
          text: 'print_history.date_and_time',
          value: 'action_at',
          align: 'end'
        },
        {
          text: 'print_history.report_type',
          value: 'translated_report_type'
        },
        {
          text: 'print_history.user_id',
          value: 'user_id'
        },
        {
          text: 'print_history.user_name',
          value: 'user_name'
        },
        {
          text: 'print_history.state',
          value: 'translated_state'
        },
        {
          text: 'print_history.show_file',
          value: 'show_file',
          sortable: false,
        },
      ],
      last_update: null,
    }
  },
  computed: {
    computed_items_per_page() {
      return Math.max(Math.floor((this.window_size.y - 300)/48), 1)
    }
  },
  methods: {
    utcToLocal,
    /**
     * Loads data from database. Assigns loading error in case the data cannot be loaded.
     */
    loadData() {
      this.loading = true
      this.last_update = utcTimeStamp()
      let start_time = Date.now()
      this.$store.dispatch('getPrintHistory')
      .then((response) => {
        this.rows = this.transformData(response.data)
        this.row_count = this.rows.length
        this.total_row_count = response.data.total_row_count
        console.log("Total time:" + (Date.now() - start_time))
      })
      .catch(() => {
        this.err_msg = 'errors.error_loading_data'
        this.data = []
      })
      .finally(() => {
        this.loading = false
      })
    },
    /**
     * This will show file in a javascript PDF browser once implemented.
     * @param absolute_path absolute path to the file to be shown
     */
    showFile(absolute_path) {
      console.log(absolute_path)
    },
    /**
     * Processes data before saving them to rows.
     * @param {object[]} data_to_transform array of objects that should be processed
     * @return {object[]} array of objects that are formatted for the data table
     */
    transformData(data_to_transform) {
      let data_to_return = data_to_transform
      data_to_return.forEach((entry) => {
        entry['user_name'] = entry.user.name
        entry['path_to_file'] = entry.misc.path_to_file
        entry['action_at'] = this.$d(utcToLocal(entry.action_at), 'datetime')
        entry['translated_report_type'] = this.$t(`print_history.report_types.` + entry.report_type)
        entry['translated_state'] = this.$t(`print_history.states.` + entry.state)
      })
      return data_to_return
    },
    onResize() {
      this.window_size = {x: window.innerWidth, y: window.innerHeight}
    }
  },
  beforeMount() {
    this.loadData()
  },
  activated() {
    // check if the server has new data, refresh component data if true
    this.$store.dispatch('lastDataUpdate')
    .then((response) => {
      if (response.data > this.last_update) {
        this.loadData()
      }
    })
    // on error refresh anyway
    .catch(() => {
      this.loadData()
    })
  },
}
</script>

<style scoped>
.table_header {
  font-weight: bold;
  color: black;
}
</style>

An example of the response.data retrieved from the database via the loadData method:

[{
   action_at: "2021-01-20T13:03:39.528843",
   id: "1",
   inventory_id: "1",
   reporty_type: "my_report_type",
   state: "archived",
   misc: {
     path_to_file: '/some/path/to/file.pdf'
   },
   user: {
     name: "John Doe",
     id: "123456",
     roles: {role_1: true}
   },
   user_id: "123456"
 },
 {
   action_at: "2021-01-20T13:05:39.528843",
   id: "2",
   inventory_id: "1",
   reporty_type: "my_other_report_type",
   state: "moved_to_print",
   misc: {
     path_to_file: '/some/path/to/file2.pdf'
   },
   user: {
     name: "Jane Doe",
     id: "123457",
     roles: {role_1: true}
   },
   user_id: "123457"
 }]

Question:

Is this tooltip behavior a bug or do I have to set some additional settings for it? Is there some workaround so it behaves correctly even when the component is kept alive?

In case of some additional information, ask away.

Seems like you have the same issue as this: https://github.com/vuetifyjs/vuetify/issues/2480 but with different versions of Vuetify.

There are many issues and requests for an attribute of tooltip for buttons, but for now the solution can be like in this fix: https://github.com/vuetifyjs/vuetify/pull/2780 :

  1. Define show in the data (I think it should be set to false if you use v-model for the tooltip)
  2. Add @click event to the button like this: @click="show = false"
  3. For the tooltip you have 2 options: Add either v-if="show" or v-model="show"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM