Complex Loader Management for Vue/Vuex Applications
JavaScript HTML
Latest commit 3420806 Jul 14, 2017 @f Merge branch 'master' of github.com:f/vuex-loading
* 'master' of github.com:f/vuex-loading:
  add demo link
Permalink
Failed to load latest commit information.
build add built files Jul 12, 2017
dist build Jul 13, 2017
example update example Jul 13, 2017
resources Add files via upload Jul 13, 2017
src hotfix of loader message typo Jul 13, 2017
.babelrc Initial version Jul 7, 2017
.gitignore Initial version Jul 7, 2017
.travis.yml Initial version Jul 7, 2017
LICENSE Initial version Jul 7, 2017
README.md add demo link Jul 13, 2017
package.json 0.1.11 Jul 13, 2017
yarn.lock add built files Jul 12, 2017

README.md

⌛️ vuex-loading

Multiple Process Loader Management for Vue and Vuex.

Play with demo above.

vuex-loading helps to manage multiple loading states on the page without any conflict. It's based on a very simple idea that manages a Vuex store with multiple loading states. The built-in loader component listens its registered loader and immediately become loading state.

Requirements

Installation

$ npm install vuex-loading
# or if you using Yarn
$ yarn add vuex-loading

Usage

import { createVuexLoader } from 'vuex-loading'

const VuexLoading = createVuexLoader({
  // The Vuex module name, 'loading' by default.
  moduleName: 'loading',
  // The Vue component name, 'v-loading' by default.
  componentName: 'v-loading',
  // Vue component class name, 'v-loading' by default.
  className: 'v-loading',
});

Vue.use(Vuex)
Vue.use(VuexLoading)

const store = new Vuex.Store({
  plugins: [VuexLoading.Store],
});

Then you should register loading module:

new Vue({
  el: '#app',
  store,
  computed: {
    ...mapGetters('loading', [
      /*
        `isLoading` returns a function with a parameter of loader name.
        e.g. `isLoading('creating user')` will return you a boolean value.
      */
      'isLoading',
      /*
        `anyLoading` returns a boolean value if any loader name exists on store.
      */
      'anyLoading',
    ])
  },
  methods: {
    startLoading() {
      /*
        VuexLoading registers $startLoading method with loader name.
        When you start a loader, it pushes the loader name to loading state.
      */
      this.$startLoading('fetching data');
    },
    endLoading() {
      /*
        VuexLoading registers $startLoading method with loader name.
        When you stop a loader, it pulls the loader name from loading state.
      */
      this.$endLoading('fetching data');
    },
  },
});

Global Template Helpers

vuex-loading provides some helpers to you to use in your templates.

$anyLoading

Returns boolean value if any loader exists in page.

<template>
  <progress-bar v-if="$anyLoading">Please wait...</progress-bar>
</template>

$isLoading(loader String)

Returns boolean value if given loader exists in page.

<template>
  <progress-bar v-if="$isLoading('creating user')">Creating User...</progress-bar>
</template>

$startLoading(loader String)

Starts the given loader.

<template>
  <button @click="$startLoading('creating user')">Create User</button>
</template>

$endLoading(loader String)

Stops the given loader.

<template>
  <button @click="$endLoading('creating user')">Cancel</button>
</template>

Global Action Helpers

vuex-loading provides some helpers to you to use in your Vuex stores.

import { createActionHelpers } from 'vuex-loading'
const { startLoading, endLoading } = createActionHelpers({
  moduleName: 'loader'
});

startLoading(dispatcher, loader String, async callback)

You can trigger loader from the action. This will make your templates more cleaner and you will have a accurate loader status. startLoading will trigger a loading and will end loader after async callback is finished.

export default {
  actions: {
    async createUser({ commit, dispatch }) {
      const response = await startLoading(dispatch, 'creating user', () => {
        return fetch("...") // Some async job that returns Promise instance.
      });
      commit(types.CREATE_USER, response)
    }
  },
  // ...
}

endLoading(dispatcher, loader String)

Ends given loading from actions.

export default {
  actions: {
    async createUser({ commit, dispatch }) {
      try {
        const response = await startLoading(dispatch, 'creating user', () => { /* ... */ });
        commit(types.CREATE_USER, response)
      } catch (e) {
        // In any unexpected thing occurs on runtime, end the loading.
        endLoading(dispatch, 'creating user')
      }
    }
  },
  // ...
}

Using v-loading Component

In template, you should wrap your content with v-loading component to show loading on it.

<v-loading loader='fetching data'>
  <template slot='spinner'>
    This will be shown when "fetching data" loader starts.
  </template>
  
  This will be shown when "fetching data" loader ends.
</v-loading>

Better example for a button with loading state:

<button :disabled='$isLoading("creating user")'>
  <v-loading loader='creating user'>
    <template slot='spinner'>Creating User...</template>
    Create User
  </v-loading>
</button>

Making Reusable Loader Components

With reusable loader components, you will be able to use custom loader components as example below. This will allow you to create better user loading experience.

In this example above, the tab gets data from back-end, and the table loads data from back-end at the same time. With vuex-loading, you will be able to manage these two seperated loading processes easily:

<template lang='pug'>
div
  v-loading(loader='fetching tabs')
    template(slot='spinner')
      b-tabs
        template(slot='tabs')
          b-nav-item(active disabled)
            v-icon(name='circle-o-notch', spin)
    b-tabs
      template(slot='tabs')
        b-nav-item(v-for='tab in tabs') {{ tab.name }}

  v-loading(loader='fetching data')
    table-gradient-spinner(slot='spinner')
    table
      tr(v-for='row in data')
        // ...
</template>

You may want to design your own reusable loader for your project. You better create a wrapper component called my-spinner:

<!-- MySpinner.vue -->
<i18n>
  tr:
    loading: Yükleniyor...
  en:
    loading: Loading...
</i18n>

<template lang="pug">
  div.loading-spinner
    //- Uses vue-awesome spinner
    v-icon(name='refresh', spin)
    span {{ $t('loading') }}
</template>

<style scoped lang="scss">
  .loading-spinner {
    opacity: 0.5;
    margin: 50px auto;
    text-align: center;
    .fa-icon {
      vertical-align: middle;
      margin-right: 10px;
    }
  }
</style>

Now you can use your spinner everywhere using slot='spinner' attribute:

<template lang="pug">
  v-loading(loader='fetching data')
    my-spinner(slot='spinner')
    div
      p My main content after fetching data...
</template>

Built-in Loaders (WIP)

Also you can use built in loaders:

  • v-loading-spinner
  • v-loading-heart
  • ... more to come.

You need to put them into a template tag.

<v-loading loader='fetching data'>
  <template slot="spinner">
    <v-loading-spinner height='30px' width='30px' />
  </template>
  
  This will be shown when "fetching data" loader ends.
</v-loading>

Please see example for more detailed example.

License

MIT © Fatih Kadir Akın