Vue how to have text as a placeholder in a v-card before loaded data populates the sheet?

Viewed 557

I have this sheet in vue that I would like to have some placeholder text such as "click to load" before a user actually clicks on something to load the data.

Currently I have this button that will populate the sheet with data:

      <v-btn @click="fetchData()"> Apply Filters </v-btn>

And this is the sheet that will load the data returned from fetchData():

      <v-card
           {{THE DATA}}
      </v-card>

How would I go about making the sheet have some placeholder text before the button is clicked?

4 Answers

Alternatively to Kellen's suggestion, you can also use the skeleton loader.

Here's example I found on codepen:

Template:

   <div id="app">
      <v-app>
        <v-content>
          <v-container class="grey lighten-4">
            <div class="text-center d-flex justify-center align-center mb-12 flex-wrap">
              <v-btn class="mx-12 my-4" @click="loading = !loading">
                Toggle
              </v-btn>
            </div>
    
            <v-row justify="center">
              <v-col class="mb-12" cols="12" md="4">
                <v-skeleton-loader 
                                   :loading="loading" 
                                   transition="scale-transition" 
                                   type="table-heading, list-item-two-line, table-tfoot">
                  <v-card>
                    <v-card-title>Title</v-card-title>
                    <v-card-text>Card Text</v-card-text>
                  </v-card>
                </v-skeleton-loader>
              </v-col>
            </v-row>
          </v-container>
        </v-content>
      </v-app>
    </div>

Script:

// Looking for the v1.5 template?
// https://codepen.io/johnjleider/pen/GVoaNe

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
      loading: true,
  }),
})

Source: https://codepen.io/piiner123/pen/abbBOgO?editors=1010

You can use a computed value to return some placeholder text if the user has not loaded any data yet. That might look something like this:

data() {
     dataFetched: false,
     data: {},
},
computed: {
     theData() {
          if (!dataFetched) {
               return 'placeholder text goes here'
          }
          return data
     }
},
methods: {
     async fetchData() {
         let data = await axios.get('/data')
         this.data = data.data
         this.dataFetched = true
    }
}

Introduce a status data element that can take different values depending on the actual status of the component. Initially, status could be 'ready'. In this case, replicate the v-card and use a custom string to generate the placeholder:

<v-card>
  Click to load
</v-card>

When the User clicks to load the data, and while you are processing it, set status = 'loading'. This allows you to show a preloader very easily:

<div v-if="status === 'loading'">
  Loading data...
</div>

Once the data loads, status becomes 'loaded' and you show the proper v-card with the data:

<v-card v-if="status === 'loaded'">
{{ data }}
</v-card>

Note that with this approach you can also show an error very easily if the data failed to load. Just set status to 'error' and display a proper message.

Doing it like this makes the HTML very explicit as each section gets its own html elements: the data variable is only used for showing the actual data, the placeholder text gets its own element, etc.

I use v-skeleton-loader and v-if and finally use watch to check the data status.

<div id="app">
  <v-app id="inspire">
    <div>
      {{options}}
      <v-skeleton-loader v-if="firstLoad" :loading="loading" type="table">. 
      </v-skeleton-loader>
      <v-data-table 
        :headers="headers"
        v-show="!firstLoad"
        :items="desserts"
        :options.sync="options"
        :server-items-length="totalDesserts"
        :loading="loading"
        class="elevation-1"
      ></v-data-table>
    </div>
  </v-app>
</div>

script:

    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      data() {
        return {
          totalDesserts: 0,
          desserts: [],
          loading: true,
          firstLoad: true,
          options: {},
          headers: [
            {
              text: 'Dessert (100g serving)',
              align: 'left',
              sortable: false,
              value: 'name',
            },
            { text: 'Calories', value: 'calories' },
            { text: 'Fat (g)', value: 'fat' },
            { text: 'Carbs (g)', value: 'carbs' },
            { text: 'Protein (g)', value: 'protein' },
            { text: 'Iron (%)', value: 'iron' },
          ],
        };
      },
      watch: {
        options: {
          handler() {
            console.log('options watcher called');
            this.getDataFromApi()
              .then(data => {
                this.desserts = data.items;
                this.totalDesserts = data.total;
              });
          },
          deep: true,
        },
      },
      methods: {
        getDataFromApi() {
          this.loading = true;
          return new Promise((resolve, reject) => {
            const { sortBy, sortDesc, page, itemsPerPage } = this.options;
    
            let items = this.getDesserts();
            const total = items.length;
    
            if ((sortBy && sortBy.length === 1) && (sortDesc && sortDesc.length === 1)) {
              items = items.sort((a, b) => {
                const sortA = a[sortBy[0]];
                const sortB = b[sortBy[0]];
    
                if (sortDesc[0]) {
                  if (sortA < sortB) return 1;
                  if (sortA > sortB) return -1;
                  return 0;
                } else {
                  if (sortA < sortB) return -1;
                  if (sortA > sortB) return 1;
                  return 0;
                }
              });
            }
    
            if (itemsPerPage > 0) {
              items = items.slice((page - 1) * itemsPerPage, page * itemsPerPage);
            }

        setTimeout(() => {
          if (this.firstLoad) this.firstLoad = false
          console.log('data is loaded');
          this.loading = false;
          resolve({
            items,
            total,
          });
        }, 1000);
      });
    },
    getDesserts() {
      return [
        {
          name: 'Frozen Yogurt',
          calories: 159,
          fat: 6.0,
          carbs: 24,
          protein: 4.0,
          iron: '1%',
        },
        {
          name: 'Ice cream sandwich',
          calories: 237,
          fat: 9.0,
          carbs: 37,
          protein: 4.3,
          iron: '1%',
        },
        {
          name: 'Eclair',
          calories: 262,
          fat: 16.0,
          carbs: 23,
          protein: 6.0,
          iron: '7%',
        },
       

      ];
    },
  },
});
Related