diff --git a/.babelrc b/.babelrc index 002b4aa..f0fccfd 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,7 @@ { - "presets": ["env"] -} + "presets": ["@babel/preset-env"], + "plugins": [ + "@babel/plugin-transform-runtime" + ], + "comments": false +} \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e69de29 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..34d1fa4 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,12 @@ +module.exports = { + root: true, + parserOptions: { + sourceType: 'module' + }, + + extends: 'standard', + // required to lint *.vue files + plugins: [ + 'html' + ] +} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..6ef6d3d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +patreon: fka diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml new file mode 100644 index 0000000..b2e704a --- /dev/null +++ b/.github/workflows/npmpublish.yml @@ -0,0 +1,49 @@ +name: Node.js Package + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + - run: yarn + - run: yarn test + + publish-npm: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + - run: yarn publish + if: github.event == 'push' + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} + + publish-gpr: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://npm.pkg.github.com/ + scope: '@f' + - run: yarn publish + if: github.event == 'push' + env: + NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.travis.yml b/.travis.yml index 8bc6524..3178091 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - 5 + - 10 cache: yarn diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0de81bf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# vue-wait changelog + +## v1.2.0 +- Rename `isWaiting` to `is` to make the code less crowded. +- Better array matching + +## v1.1.0 +- Rename package to `vue-wait` +- `anyLoading` to `any` + +## v1.0.0 + +- A complete rewrite, more extensible. +- Readable and better code. +- Update to Webpack 4 +- Remove built-in loaders. Maybe we can create another repository including custom spinners. +- Remove `width` and `height` props. +- Strict props. +- `isWaiting` supports matchers now `creating.*`, `!creating` etc. Please see [/sindresorhus/matcher](/sindresorhus/matcher). +- Rename `registerComponents` to `registerComponent` +- Added `accessorName` option to change `$wait` key. +- Removed `createActionHelpers`, use `mapWaitingActions` or `waitFor` instead. +- Added `v-loading:visible`, `v-loading:hidden`, `v-loading:disabled`, `v-loading:enabled`, `v-loading:click` directives. + +## v0.4.0 + +- rename v-loading slot `spinner` to `loading` #30 +- added `waitFor` helper function for easy integration of vue-wait in vue component methods #30 + +## v0.3.0 + +- Rename `$vuexLoading` to `$wait` to be consistent with class name #25 diff --git a/LICENSE b/LICENSE index 9d6fd27..424e153 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2017 Fatih Kadir Akın +Copyright (c) 2018 Fatih Kadir Akın Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 32d415d..f1b4c0c 100644 --- a/README.md +++ b/README.md @@ -1,255 +1,623 @@ -# ⌛️ vuex-loading +

+ +

+

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

-Multiple Process Loader Management for [Vue](http://vuejs.org/) and [Vuex](http://vuex.vuejs.org/). +

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

- +[![npm version](https://badge.fury.io/js/vue-wait.svg)](https://badge.fury.io/js/vue-wait) -> [Play with demo above](https://f.github.io/vuex-loading/). +--- -**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. +![vue-wait](https://user-images.githubusercontent.com/196477/42170484-4d91e36a-7e1f-11e8-9cee-816bfe857db2.gif) -## Requirements +> [Play with demo above](https://f.github.io/vue-wait/). + +**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](https://codesandbox.io). + +[![Edit VueWait Sandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/85q3vpm42?autoresize=1&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FMyList.vue) + +### 1. Install: +```bash +yarn add vue-wait +``` + +### 2. Require: +#### For Vue 2.x +```js +import VueWait from 'vue-wait' + +Vue.use(VueWait) + +new Vue({ + // your vue config + wait: new VueWait(), +}) +``` + +#### For Vue 3.x +```js +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 + +```vue + + + +``` + +> **vue-wait has more abilities to make the management easier, please read the complete documentation.** + +# ▶️Detailed Start + +## 📦 Requirements - [Vue.js](https://vuejs.org) (v2.0.0+) -- [Vuex](http://vuex.vuejs.org) (v2.0.0+) -## Installation +## 🚀 Power Supplies +- [Vuex](http://vuex.vuejs.org), optionally (v2.0.0+) + +## 🔧 Installation + +via CLI: ```bash -$ npm install vuex-loading -# or if you using Yarn -$ yarn add vuex-loading +$ yarn add vue-wait +# or if you using npm +$ npm install vue-wait ``` -## Usage +via Vue UI: + + + +## 📖 Usage ```js -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', -}); +import VueWait from 'vue-wait' -Vue.use(Vuex) -Vue.use(VuexLoading) +Vue.use(VueWait) // add VueWait as Vue plugin +``` + +Then you should register `wait` property (`VueWait` instance) to the Vue instance: + +```js +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. -const store = new Vuex.Store({ - plugins: [VuexLoading.Store], + registerDirective: true, // Registers `v-wait` directive + directiveName: 'wait', // directive name, you can set `my-loader` etc. + + }), }); ``` -Then you should register loading module: +## ♻️ Usage with Vuex + +Simply set `useVuex` parameter to `true` and optionally override +`vuexModuleName` + +```js +import VueWait from 'vue-wait' + +Vue.use(Vuex) +Vue.use(VueWait) // add VueWait as Vue plugin +``` + +Then you should register `VueWait` module: ```js 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'); - }, - }, + wait: new VueWait({ + useVuex: true, // You must pass this option `true` to use Vuex + vuexModuleName: 'vuex-example-module' // It's optional, `wait` by default. + }), }); ``` -## Global Template Helpers +Now `VueWait` will use `Vuex` store for data management which can be traced in `Vue DevTools > Vuex` -**vuex-loading** provides some helpers to you to use in your templates. +## ♻️ Usage with Nuxt.js -#### `$anyLoading` +Add `vue-wait/nuxt` to modules section of `nuxt.config.js` + +```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. -```html +```vue ``` -#### `$isLoading(loader String)` +#### `.is(loader String | Matcher)` or `.waiting(loader String | Matcher)` Returns boolean value if given loader exists in page. -```html +```vue + +``` + +You can use **`waiting`** alias instead of **`is`**. + +```vue + +``` + +Also you can use matcher to make it more flexible: + +Please see [matcher](https://github.com/sindresorhus/matcher/) library to see how to use matchers. + +```vue ``` -#### `$startLoading(loader String)` +#### `.is(loaders Array)` or `.waiting(loaders Array)` + +Returns boolean value if some of given loaders exists in page. + +```vue + +``` + +#### `.start(loader String)` Starts the given loader. -```html +```vue ``` -#### `$endLoading(loader String)` +#### `.end(loader String)` Stops the given loader. -```html +```vue ``` -## Global Action Helpers +#### `.progress(loader String, current [, total = 100])` -**vuex-loading** provides some helpers to you to use in your Vuex stores. +Sets the progress of the given loader. -```js -import { createActionHelpers } from 'vuex-loading' -const { startLoading, endLoading } = createActionHelpers({ - moduleName: 'loader' -}); +```vue + +``` + +##### 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`. + +```vue + +``` + +or + +```vue + +``` + +#### `.percent(loader String)` + +Returns the percentage of the given loader. + +```vue + +``` + +## 🏹 Directives + +You can use directives to make your template cleaner. + +#### `v-wait:visible='"loader name"'` + +Shows if the given loader is loading. + +```vue + +``` + +#### `v-wait:hidden='"loader name"'` or `v-wait:visible.not='"loader name"'` + +Hides if the given loader is loading. + +```vue + +``` + +#### `v-wait:disabled='"loader name"'` + +Sets `disabled="disabled"` attribute to element if the given loader is loading. + +```vue + +``` + +#### `v-wait:enabled='"loader name"'` or `v-wait:disabled.not='"loader name"'` + +Removes `disabled="disabled"` attribute to element if the given loader is loading. + +```vue + ``` -#### `startLoading(dispatcher, loader String [,async callback])` +#### `v-wait:click.start='"loader name"'` + +Starts given loader on click. + +```vue + +``` -You can trigger loader from the action. This will make your templates cleaner and you will have an accurate loader status. -`startLoading` will trigger a loading and will end loader after the optional async callback is finished. +#### `v-wait:click.end='"loader name"'` + +Ends given loader on click. + +```vue + +``` + +#### `v-wait:toggle='"loader name"'` + +Toggles given loader on click. + +```vue + +``` + +#### `v-wait:click.progress='["loader name", 80]'` + +Sets the progress of given loader on click. + +```vue + +``` + +## 🔌 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 **action**s called `createUser` and `updateUser`. +It will call the methods you map and will start loaders while action is resolved. -_Example using the Promise returning callback function_ ```js -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) - } +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', + }), + } +// ... ``` -_Example call without a provided callback_ +You can also map **action** to custom method and customize loader name like in example below: + ```js -export default { - actions: { - createUser({ commit, dispatch }) { - startLoading(dispatch, 'creating user'); - request('/create-user', (response) => { - endLoading(dispatch, 'creating user') - commit(types.CREATE_USER, response); - }); - } +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: +```js +// ... + methods: { + ...mapWaitingActions('users', [ + 'getUsers', + { method: 'createUser', action: 'createUser', loader: 'creating user'}, + { method: 'createSuperUser', action: 'createUser', loader: 'creating super user' }, + ]), + }, +// ... + + ``` -#### `endLoading(dispatcher, loader String)` +### ☢️Advanced Getters and Actions Usage -Ends given loading from actions. +> 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. ```js -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') - } +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. + +```js +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_ + +```js +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() + }) } +... ``` -## Using `v-loading` Component +See also `examples/wrap-example` + +## 💧 Using `v-wait` Component + +If you disable `registerComponent` option then import and add `v-wait` into components + +```js +import vLoading from 'vue-wait/src/components/v-wait.vue' +components: { + 'v-wait': vLoading +} +``` -In template, you should wrap your content with `v-loading` component to show loading on it. +In template, you should wrap your content with `v-wait` component to show loading on it. -```html - -