Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

f/vue-wait

Repository files navigation

Multiple Process Loader Management for Vue and (optionally) Vuex.

Read the Medium post "Managing Complex Waiting Experiences on Web UIs".


Play with demo above.

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

Quick Start

If you are a try and learn developer, you can start trying the vue-wait now using codesandbox.io.

1. Install:

yarn add vue-wait

2. Require:

For Vue 2.x

import VueWait from 'vue-wait'

Vue.use(VueWait)

new Vue({
// your vue config
wait: new VueWait(),
})

For Vue 3.x

import { createApp } from 'vue'
import { createVueWait } from 'vue-wait'
import App from './App.vue'

const VueWait = createVueWait()

createApp(App) // Create app with root component
.use(VueWait) // Register vue-wait
.mount('#app')

3. Use in Your Components

"><template>
<v-wait for="my list is to load">
<template slot="waiting">
<div>
<img src="loading.gif" />
Loading the list...
div>
template>
<ul>
<li v-for="item in myList">{{ item }}li>
ul>
v-wait>
template>

<script>
export default {
data() {
return {
myList: []
}
},
async created() {
// start waiting
this.$wait.start('my list is to load');

this.myList = await fetch('/my-list-url');

// stop waiting
this.$wait.end('my list is to load');
},
};
script>

vue-wait has more abilities to make the management easier, please read the complete documentation.

>Detailed Start

Requirements

Power Supplies

  • Vuex, optionally (v2.0.0+)

Installation

via CLI:

$ yarn add vue-wait
# or if you using npm
$ npm install vue-wait

via Vue UI:

Usage

import VueWait from 'vue-wait'

Vue.use(VueWait) // add VueWait as Vue plugin

Then you should register wait property (VueWait instance) to the Vue instance:

component name, you can set `my-loader` etc. registerDirective: true, // Registers `v-wait` directive directiveName: 'wait', // directive name, you can set `my-loader` etc. }), });">new Vue({
el: '#app',
store,
wait: new VueWait({
// Defaults values are following:
useVuex: false, // Uses Vuex to manage wait state
vuexModuleName: 'wait', // Vuex module name

registerComponent: true, // Registers `v-wait` component
componentName: 'v-wait', // component name, you can set `my-loader` etc.

registerDirective: true, // Registers `v-wait` directive
directiveName: 'wait', // directive name, you can set `my-loader` etc.

}),
});

Usage with Vuex

Simply set useVuex parameter to true and optionally override vuexModuleName

import VueWait from 'vue-wait'

Vue.use(Vuex)
Vue.use(VueWait) // add VueWait as Vue plugin

Then you should register VueWait module:

new Vue({
el: '#app',
store,
wait: new VueWait({
useVuex: true, // You must pass this option `true` to use Vuex
vuexModuleName: 'vuex-example-module' // It's optional, `wait` by default.
}),
});

Now VueWait will use Vuex store for data management which can be traced in Vue DevTools > Vuex

Usage with Nuxt.js

Add vue-wait/nuxt to modules section of nuxt.config.js

{
modules: [
// Simple usage
'vue-wait/nuxt'

// Optionally passing options in module configuration
['vue-wait/nuxt', { useVuex: true }]
],

// Optionally passing options in module top level configuration
wait: { useVuex: true }
}

VueWait Options

You can use this options for customize VueWait behavior.

Option Name Type Default Description
accessorName String "$wait" You can change this value to rename the accessor. E.g. if you rename this to $w, your VueWait methods will be accessible by $w.waits(..) etc.
useVuex Boolean false Use this value for enabling integration with Vuex store. When this value is true VueWait will store data in Vuex store and all changes to this data will be made by dispatching actions to store
vuexModuleName String "wait" Name for Vuex store if useVuex set to true, otherwise not used.
registerComponent Boolean true Registers v-wait component.
componentName String "v-wait" Changes v-wait component name.
registerDirective Boolean true Registers v-wait directive.
directiveName String "v-wait" Changes v-wait directive name.

Global Template Helpers

vue-wait provides some helpers to you to use in your templates. All features can be obtained from $wait property in Vue components.

.any

Returns boolean value if any loader exists in page.

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

.is(loader String | Matcher) or .waiting(loader String | Matcher)

Returns boolean value if given loader exists in page.

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

You can use waiting alias instead of is.

Fetching users...
"><template>
<div v-if="$wait.waiting('fetching users')">
Fetching users...
div>
template>

Also you can use matcher to make it more flexible:

Please see matcher library to see how to use matchers.

Creating something... "><template>
<progress-bar v-if="$wait.is('creating.*')">Creating something...progress-bar>
template>

.is(loaders Array) or .waiting(loaders Array)

Returns boolean value if some of given loaders exists in page.

Creating User... "><template>
<progress-bar v-if="$wait.is(['creating user', 'page loading'])">Creating User...progress-bar>
template>

.start(loader String)

Starts the given loader.

Create User "><template>
<button @click="$wait.start('creating user')">Create Userbutton>
template>

.end(loader String)

Stops the given loader.

Cancel "><template>
<button @click="$wait.end('creating user')">Cancelbutton>
template>

.progress(loader String, current [, total = 100])

Sets the progress of the given loader.

"><template>
<progress min="0" max="100" :value="$wait.percent('downloading')" />
<button @click="$wait.progress('downloading', 10)">Set progress to 10button>
<button @click="$wait.progress('downloading', 50)">Set progress to 50button>
<button @click="$wait.progress('downloading', 50, 200)">Set progress to 50 of 200 (25%)button>
template>
Completing the Progress

To complete the progress, current value should be set bigger than 100. If you total is given, current must be bigger than total.

Set as downloaded (101 of 100)"><button @click="$wait.progress('downloading', 101)">Set as downloaded (101 of 100)button>

or

Set as downloaded (6 of 5)"><button @click="$wait.progress('downloading', 5, 6)">Set as downloaded (6 of 5)button>

.percent(loader String)

Returns the percentage of the given loader.

"><template>
<progress min="0" max="100" :value="$wait.percent('downloading')" />
template>

Directives

You can use directives to make your template cleaner.

v-wait:visible='"loader name"'

Shows if the given loader is loading.

Creating User... "><template>
<progress-bar v-wait:visible='"creating user"'>Creating User...progress-bar>
template>

v-wait:hidden='"loader name"' or v-wait:visible.not='"loader name"'

Hides if the given loader is loading.

Some Content
"><template>
<main v-wait:hidden='"creating *"'>Some Contentmain>
template>

v-wait:disabled='"loader name"'

Sets disabled="disabled" attribute to element if the given loader is loading.

"><template>
<input v-wait:disabled="'*'" placeholder="Username" />
<input v-wait:disabled="'*'" placeholder="Password" />
template>

v-wait:enabled='"loader name"' or v-wait:disabled.not='"loader name"'

Removes disabled="disabled" attribute to element if the given loader is loading.

Abort Request "><template>
<button v-wait:enabled='"creating user"'>Abort Requestbutton>
template>

v-wait:click.start='"loader name"'

Starts given loader on click.

Start loader "><template>
<button v-wait:click.start='"create user"'>Start loaderbutton>
template>

v-wait:click.end='"loader name"'

Ends given loader on click.

End loader "><template>
<button v-wait:click.end='"create user"'>End loaderbutton>
template>

v-wait:toggle='"loader name"'

Toggles given loader on click.

Toggles the loader "><template>
<button v-wait:toggle='"flip flop"'>Toggles the loaderbutton>
template>

v-wait:click.progress='["loader name", 80]'

Sets the progress of given loader on click.

Set the "downloading" loader to 80 "><template>
<button v-wait:click.progress='["downloading", 80]'>Set the "downloading" loader to 80button>
template>

Loading Action and Getter Mappers

vue-wait provides mapWaitingActions and mapWaitingGetters mapper to be used with your Vuex stores.

Let's assume you have a store and async actions called createUser and updateUser. It will call the methods you map and will start loaders while action is resolved.

import { mapWaitingActions, mapWaitingGetters } from 'vue-wait'

// ...
methods: {
...mapWaitingActions('users', {
getUsers: 'loading users',
createUser: 'creating user',
updateUser: 'updating user',
}),
},
computed: {
...mapWaitingGetters({
somethingWithUsers: [
'loading users',
'creating user',
'updating user',
],
deletingUser: 'deleting user',
}),
}
// ...

You can also map action to custom method and customize loader name like in example below:

import { mapWaitingActions, mapWaitingGetters } from 'vue-wait'

// ...
methods: {
...mapWaitingActions('users', {
getUsers: { action: 'getUsers', loader: 'loading users' },
createUser: { action: 'createUser', loader: 'creating user'},
createSuperUser: { action: 'createUser', loader: 'creating super user' },
}),
},
// ...

There is also possibility to use array as a second argument to mapWaitingActions:

// ...
methods: {
...mapWaitingActions('users', [
'getUsers',
{ method: 'createUser', action: 'createUser', loader: 'creating user'},
{ method: 'createSuperUser', action: 'createUser', loader: 'creating super user' },
]),
},
// ...

Advanced Getters and Actions Usage

The Vuex module name is wait by default. If you've changed on config, you should get it by rootGetters['/is'] or rootGetters['/any'].

You can access vue-wait's Vuex getters using rootGetters in Vuex.

getters: {
cartOperationInProgress(state, getters, rootState, rootGetters) {
return rootGetters['wait/is']('cart.*');
}
},

And you can start and end loaders using wait actions. You must pass root: true option to the dispatch method.

actions: {
async addItemToCart({ dispatch }, item) {
dispatch('wait/start', 'cart.addItem', { root: true });
await CartService.addItem(item);
dispatch('wait/end', 'cart.addItem', { root: true });
}
},

waitFor(loader String, func Function [,forceSync = false])

Decorator that wraps function, will trigger a loading and will end loader after the original function (func argument) is finished.

By default waitFor return async function, if you want to wrap default sync function pass true in last argument

Example using with async function

setTimeout(resolve, ms)); } // do work here await sleep(3000); // simulate some api call this.fetchResponse = Math.random() }) } ...">import { waitFor } from 'vue-wait';

...
methods: {
fetchDataFromApi: waitFor('fetch data', async function () {
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// do work here
await sleep(3000);
// simulate some api call
this.fetchResponse = Math.random()
})
}
...

See also examples/wrap-example

Using v-wait Component

If you disable registerComponent option then import and add v-wait into components

import vLoading from 'vue-wait/src/components/v-wait.vue'
components: {
'v-wait': vLoading
}

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

This will be shown when "fetching data" loader ends. "><v-wait for='fetching data'>


This will be shown when "fetching data" loader ends.
v-wait>

Better example for a button with loading state:

Create User "><button :disabled='$wait.is("creating user")'>


Create User

button>

Transitions

You can use transitions with v-wait component.

Just pass props and listeners to the v-wait with transition prop.

My content "><v-wait for="users"
transition="fade"
mode="out-in"
:duration="1000"
enter-active-class="enter-active"
@leave='someAwesomeFinish()'
>

My content
v-wait>

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 vue-wait, you will be able to manage these two seperated loading processes easily:

"><template lang='pug'>
<div>
<v-wait for="fetching tabs">
<template slot="waiting">
<b-tabs>
<template slot="tabs">
<b-nav-item active="active" disabled>
<v-icon name="circle-o-notch" spin="spin" />
b-nav-item>
template>
b-tabs>
template>
<b-tabs>
<template slot="tabs">
<b-nav-item v-for="tab in tabs">{{ tab.name }}b-nav-item>
template>
b-tabs>
v-wait>
<v-wait for="fetching data">
<table-gradient-spinner slot="waiting" />
<table>
<tr v-for="row in data">

tr>
table>
v-wait>
div>
template>

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

">
<i18n>
tr:
loading: Yukleniyor...
en:
loading: Loading...
i18n>

<template>
<div class="loading-spinner">
<v-icon name="refresh" spin="spin" />
<span>{{ $t('loading') }}span>
div>
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='waiting' attribute:

<template lang="pug">
<v-wait for="fetching data">
<my-waiter slot="waiting" />
<div>
<p>My main content after fetching data...p>
div>
v-wait>
template>

Using with external spinner libraries

You can use vue-wait with another spinner libraries like epic-spinners or other libraries. You just need to add slot="waiting" to the component and Vue handles rest of the work.

First register the component,

import { OrbitSpinner } from 'epic-spinners';
Vue.component('orbit-spinner', OrbitSpinner);

Then use it in your as a v-wait's waiting slot.

"><v-wait for='something to load'>
slot='waiting'
:animation-duration="1500"
:size="64"
:color="'#ff1d5e'"
/>
v-wait>

... and done!

For other libraries you can use, please see Loaders section of vuejs/awesome-vue.

Run example

Use npm run dev-vuex, npm run dev-vue or npm run dev-wrap commands. for running examples locally.

Testing components

You can test components using vue-wait but it requires configuration. Let's take a basic component for instance:

<v-wait for="loading">


  • {{ suggestion.Name }}


v-wait>
{ const wrapper = shallowMount(Suggestions, { localVue }); expect(wrapper.find('.suggestions').exists()).toBe(true); });">const localVue = createLocalVue();
localVue.use(Vuex); // optionally when you use Vuex integration

it('uses vue-wait component', () => {
const wrapper = shallowMount(Suggestions, { localVue });
expect(wrapper.find('.suggestions').exists()).toBe(true);
});

vue-test-utils will replace v-wait component with an empty div, making it difficult to test correctly.

First, make your local Vue instance use vue-wait,

const localVue = createLocalVue();
localVue.use(Vuex); // optionally when you use Vuex integration
localVue.use(VueWait);

Then inject the wait property using VueWait constructor,

{ const wrapper = shallowMount(SuggestedAddresses, { localVue, wait: new VueWait() }); expect(wrapper.find('.suggestions').exists()).toBe(true); // it works! });">it('uses vue-wait component', () => {
const wrapper = shallowMount(SuggestedAddresses, {
localVue,
wait: new VueWait()
});
expect(wrapper.find('.suggestions').exists()).toBe(true); // it works!
});

For Development on vue-wait

Install packages

$ yarn install
# or if you using npm
$ npm install

Bundle it

$ yarn bundle
# or if you using npm
$ npm run bundle

Contributors

Other Implementations

Since vue-wait based on a very simple idea, it can be implemented on other frameworks.

License

MIT (c) Fatih Kadir Akin

About

Complex Loader and Progress Management for Vue/Vuex and Nuxt Applications

Topics

Resources

Readme

License

MIT license

Stars

Watchers

Forks

Sponsor this project

Packages

Contributors