3

My component is like this :

<template>
    ...
        <input type="text" class="form-control" v-model="rawFilter" placeholder="Search" @keyup="getPlayers">
    ...
</template>

<script>
    import _ from 'lodash'
    ...
    export default {
        ...
        data() {
            return{
                msg:'hello vue',
                rawFilter:'',
                loading:false
            }
        },
        ...
        methods: {
            getPlayers: _.debounce(function(e) {
                const text = e.target.value.trim()
                this.$store.dispatch('getPlayers', {
                  q: text
                })
            },1000),
            ...
        }
    }
</script>

When I searching, before display the data, I want add a loading icon

How can I do it in vue.js 2?

3 Answers 3

10

For usability's sake, I'll suggest using a loader which has its own vuex state.

  1. This will allow you to have control over it from any component.
  2. You can use it easily by means of simple function calls.
  3. Naturally avoid props and events.

First define where you would need this particular loader:

  1. Is it to be used for all api calls?
  2. Some browser intensive task (like processing an loaded file).
  3. Or something more specific in nature (maybe show the loader only when the user is trying to login)

If your loader is not tightly coupled to any component like in case 1. Then it would make more sens to keep your loader in your main vue file (if you are using vue-cli then App.vue)

Something like so:

<template>
  <div id="app">
    <loader></loader>
    <router-view></router-view>
  </div>
</template>

<script>
import Loader from './components/shared/loader/Loader'

export default {
  name: 'app',
  components: {
    Loader
  }
}
</script>

With this, you don't have to add loader.vue in every other component file. But first, I'll show you the loader component and store I am using.

<template>
  <div class='loader-container' :class='{"show": show, "hidden": !show}'>
    <div class="curved-div">
      <div class="colour-magic">
        <i class='fa fa-circle-o-notch rotate'></i>
      </div>
      <div class="loading">
        {{ loading }}
      </div>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import * as NameSpace from '../../../store/NameSpace'

export default {
  data () {
    return {
      loading: 'Loading...'
    }
  },
  computed: {
    ...mapGetters({
      show: NameSpace.GET_LOADER_STATE
    })
  }
}
</script>

<style scoped>
.loader-container {
  position: fixed;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.8);
}

.curved-div {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateX(-50%);
  border-radius: .3rem;
  width: 20rem;
  padding:1rem;
  background: white;
  box-shadow: 0 0 .1rem #fefefe;
}

.curved-div > * {
  display: inline-block;
}

.rotate {
  border-radius: 50%;
  padding: .5rem;
  animation-name: rotate;
  animation-duration: .7s;
  animation-iteration-count: infinite;
  animation-delay: 0s;
}

.loading {
  text-align: center;
  width: 12rem;
  font-size: 1.8rem;
}

.show {
  visibility: visible;
  opacity: 1;
  z-index: 1;
  transition: opacity 0.5s ease-out, visibility 0.5s ease-out, z-index 0.5s ease-out;
}

.hidden {
  opacity: 0;
  visibility: hidden;
  z-index: 0;
  transition: opacity 0.5s ease-out, visibility 0.5s ease-out, z-index 0.5s ease-out;
}

@keyframes rotate {
  0% {
    transform: rotateZ(0deg);
  }
  100% {
    transform: rotateZ(360deg);
  }
}

.colour-magic {
  animation-name: colorMagic;
  animation-duration: 20s;
  animation-iteration-count: infinite;
  animation-delay: 0s;
}

@keyframes colorMagic {
  0% { color: rgb(179,10,10); }
  10% { color: rgb(227,132,22); }
  20% { color: rgb(164,153,7); }
  30% { color: rgb(26,171,19); }
  40% { color: rgb(19,144,177); }
  50% { color: rgb(14,16,221); }
  60% { color: rgb(27,9,98); }
  70% { color: rgb(58,11,111); }
  80% { color: rgb(126,14,129); }
  90% { color: rgb(208,19,121); }
  100% { color: rgb(198,18,18); }
}
</style>

Please note that I am using font-awesome for the loader.

and here is the store:

import * as NameSpace from '../NameSpace'
// you can also use the namespace: true in your store and eliminate the need of NameSpace.js    

const state = {
  [NameSpace.LOADER_STATE]: false
}

const getters = {
  [NameSpace.GET_LOADER_STATE]: state => {
    return state[NameSpace.LOADER_STATE]
  }
}

const mutations = {
  [NameSpace.MUTATE_LOADER_STATE]: (state, payload) => {
    state[NameSpace.LOADER_STATE] = payload
  }
}

const actions = {
  [NameSpace.LOADER_SHOW_ACTION]: ({ commit }, payload) => {
    commit(NameSpace.MUTATE_LOADER_STATE, payload)
  }
}

export default {
  state,
  getters,
  mutations,
  actions
}

A usage example:

// This is not a .vue file it is a .js file, therefore a different way of using the store.
import Vue from 'vue'
import * as NameSpace from 'src/store/NameSpace'
import loaderState from 'src/store/modules/loader'

/**
* Pass the mutation function to reduce the text length
* This function can now be used in the api calls to start/stop the loader
* as the api starts and finishes.
*/
let loaderSwitch = loaderState.mutations[NameSpace.MUTATE_LOADER_STATE].bind(null, loaderState.state)

login (username, password) {
  loaderSwitch(true)
  return new Promise((resolve, reject) => {
    SomeEndpoint.logIn(username, password, {
      success (user) {
        loaderSwitch(false)
        resolve(user.attributes)
      },
      error (user, error) {
        loaderSwitch(false)
        reject(errorHelper(error.code))
      }
    })
  })

Now, irrespective of the component where login is used, the loader component need not be kept there.

Sign up to request clarification or add additional context in comments.

Comments

4

You simply need to attach it to a flag and use v-if, if you're using vue resource, you can set the loading flag to true in the before callback and set it back to false after you receive the response:

Vue Instance

  methods: {
    loadData() {
      this.$http.get('/search', {
        before: () => {
          this.loading = true;
        }
      }).then(response => {
        // Deal with response
      }).then(() => {
          //set loading flag to false
          this.loading = false;
      })
    }
  },
  data: {
    loading: false
  }

HTML

<div id="app">
  <button @click="loadData">
    Get Data
  </button>

  <!-- Only show if loading is true -->
  <div v-if="loading" v-cloak>
    <i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
    <span>Loading...</span>
  </div>
</div>

Here's the JSFiddle: https://jsfiddle.net/hyeycoan/

Comments

2

You could add a loading div on your template, and toggle it's display based on loading flag. Something like this

new Vue({
  el: '#app',
  data: {
    show: true,
	isLoading: false,  
  },
	methods:{
		loadData: function(){
			this.isLoading = true;
			setTimeout(function(){
				this.isLoading = false;
			}.bind(this),1000);
		}
	}
})
.loading{
	display: none;
	position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    background: rgba(128, 128, 128, 0.5);
}
.loading.show{
	display: initial;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<div id="app">
	<div class="loading" v-bind:class="{ show: isLoading }">
		<span>Loading</span>
	</div>
	<button @click="loadData">Load</button>
</div>	
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
</html>

You could make the loading indicator pretty with css, or you can use already available one, for example you could use http://tobiasahlin.com/spinkit/, http://loading.io/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.