├── .gitignore ├── .babelrc ├── .prettierrc.js ├── .eslintrc.js ├── index.js ├── utils.js ├── LICENSE ├── rollup.config.js ├── package.json ├── test ├── index.spec.js ├── mixins.isSwitchTab.spec.js └── mixins.spec.js ├── README.md └── mixin.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | dist 5 | build 6 | .idea 7 | report 8 | npm-debug* 9 | .vscode 10 | index.min.js -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { "node": "6" } // change this to your node version 5 | }] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | parser: 'babylon', 4 | useTabs: false, 5 | printWidth: 80, 6 | singleQuote: true, 7 | bracketSpacing: true 8 | } 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'standard', 3 | 4 | env: { 5 | node: true, 6 | browser: true 7 | }, 8 | 9 | rules: { 10 | 'space-before-function-paren': 0 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import VueTimers from './mixin' 2 | import { assign } from './utils' 3 | 4 | export default function install(Vue) { 5 | Vue.config.optionMergeStrategies.timers = Vue.config.optionMergeStrategies.methods 6 | Vue.mixin(VueTimers) 7 | } 8 | 9 | if (typeof window !== 'undefined' && window.Vue) { 10 | install(window.Vue) 11 | } 12 | 13 | export function timer(name, time, options) { 14 | return assign({ name: name, time: time }, options) 15 | } 16 | 17 | export const mixin = VueTimers 18 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | export const set = function(key, value, obj) { 2 | const clone = assign({}, obj) 3 | clone[key] = value 4 | return clone 5 | } 6 | 7 | /** 8 | * Polyfill for Object.assign for IE11 support 9 | */ 10 | export const assign = 11 | Object.assign || 12 | function assign(to) { 13 | for (var s = 1; s < arguments.length; s += 1) { 14 | var from = arguments[s] 15 | for (var key in from) { 16 | to[key] = from[key] 17 | } 18 | } 19 | return to 20 | } 21 | 22 | export const isArray = 23 | Array.isArray || 24 | function(arg) { 25 | return Object.prototype.toString.call(arg) === '[object Array]' 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Anton Kosykh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import babel from 'rollup-plugin-babel' 3 | import replace from 'rollup-plugin-replace' 4 | const terser = require('rollup-plugin-terser').terser 5 | 6 | export default [ 7 | { 8 | input: 'index.js', 9 | output: { 10 | file: 'dist/vue-timers.esm.js', 11 | format: 'es' 12 | }, 13 | plugins: [ 14 | resolve(), 15 | // use own babel config to compile 16 | babel({ 17 | exclude: 'node_modules/**', 18 | presets: [ 19 | [ 20 | '@babel/preset-env', 21 | { 22 | modules: false 23 | } 24 | ] 25 | ], 26 | plugins: ['@babel/plugin-external-helpers'], 27 | babelrc: false 28 | }), 29 | terser() 30 | ] 31 | }, 32 | { 33 | input: 'index.js', 34 | output: { 35 | file: 'dist/vue-timers.umd.js', 36 | format: 'umd', 37 | name: 'vueTimers' 38 | }, 39 | plugins: [ 40 | resolve(), 41 | // use own babel config to compile 42 | babel({ 43 | exclude: 'node_modules/**', 44 | presets: [ 45 | [ 46 | '@babel/preset-env', 47 | { 48 | modules: false 49 | } 50 | ] 51 | ], 52 | plugins: ['@babel/plugin-external-helpers'], 53 | babelrc: false 54 | }), 55 | replace({ 56 | 'process.env.NODE_ENV': JSON.stringify('production') 57 | }), 58 | terser() 59 | ] 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-timers", 3 | "version": "2.0.0", 4 | "license": "MIT", 5 | "description": "Mixin to manage your intervals in Vue.js", 6 | "main": "dist/vue-timers.umd.js", 7 | "module": "dist/vue-timers.esm.js", 8 | "jsdelivr": "dist/vue-timers.umd.js", 9 | "repository": "https://github.com/kelin2025/vue-timers", 10 | "author": { 11 | "email": "kelin2025@yandex.ru", 12 | "name": "Anton Kosykh" 13 | }, 14 | "scripts": { 15 | "lint": "eslint --fix index.js mixin.js utils.js", 16 | "size": "size-limit", 17 | "test": "jest", 18 | "build": "rollup -c rollup.config.js" 19 | }, 20 | "size-limit": [ 21 | { 22 | "path": "./index.min.js", 23 | "limit": "600 B" 24 | } 25 | ], 26 | "devDependencies": { 27 | "@babel/core": "^7.4.3", 28 | "@babel/plugin-external-helpers": "^7.2.0", 29 | "@babel/preset-env": "^7.4.3", 30 | "@vue/test-utils": "^1.0.0-beta.29", 31 | "babel-jest": "^24.7.1", 32 | "eslint": "^4.12.1", 33 | "eslint-config-standard": "^10.2.1", 34 | "eslint-plugin-import": "^2.8.0", 35 | "eslint-plugin-node": "^5.2.1", 36 | "eslint-plugin-promise": "^3.6.0", 37 | "eslint-plugin-standard": "^3.0.1", 38 | "jest": "^24.7.1", 39 | "rollup": "^0.67.4", 40 | "rollup-plugin-babel": "^4.3.2", 41 | "rollup-plugin-node-resolve": "^4.0.0", 42 | "rollup-plugin-replace": "^2.2.0", 43 | "rollup-plugin-terser": "^3.0.0", 44 | "size-limit": "^1.0.1", 45 | "vue": "^2.5.17", 46 | "vue-template-compiler": "^2.5.17" 47 | }, 48 | "dependencies": {} 49 | } 50 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | import VueTimers from '../index' 2 | import { mount, createLocalVue } from '@vue/test-utils' 3 | 4 | const localVue = createLocalVue() 5 | localVue.use(VueTimers) 6 | 7 | const component = { 8 | template: '
', 9 | data() { 10 | return { 11 | count: 0 12 | } 13 | }, 14 | methods: { 15 | log() { 16 | this.count++ 17 | } 18 | } 19 | } 20 | 21 | describe('global import', () => { 22 | beforeAll(() => { 23 | jest.useFakeTimers() 24 | }) 25 | 26 | afterEach(() => { 27 | jest.clearAllTimers() 28 | }) 29 | 30 | afterAll(() => { 31 | jest.useRealTimers() 32 | }) 33 | 34 | it('test setTimeout', () => { 35 | const wrapper = mount(component, { 36 | localVue, 37 | timers: { 38 | log: { time: 1000 } 39 | } 40 | }) 41 | 42 | wrapper.vm.$timer.start('log') 43 | expect(wrapper.vm.timers.log.isRunning).toBe(true) 44 | expect(setTimeout).toHaveBeenCalledTimes(1) 45 | expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000) 46 | 47 | expect(wrapper.vm.count).toBe(0) 48 | jest.advanceTimersByTime(1000) 49 | expect(wrapper.vm.count).toBe(1) 50 | 51 | wrapper.vm.$timer.stop('log') 52 | expect(wrapper.vm.timers.log.isRunning).toBe(false) 53 | }) 54 | 55 | it('test setInterval', () => { 56 | const wrapper = mount(component, { 57 | localVue, 58 | timers: { 59 | log: { time: 1000, repeat: true } 60 | } 61 | }) 62 | 63 | wrapper.vm.$timer.start('log') 64 | expect(wrapper.vm.timers.log.isRunning).toBe(true) 65 | 66 | expect(wrapper.vm.count).toBe(0) 67 | jest.advanceTimersByTime(1000) 68 | expect(wrapper.vm.count).toBe(1) 69 | jest.advanceTimersByTime(1000) 70 | expect(wrapper.vm.count).toBe(2) 71 | 72 | wrapper.vm.$timer.stop('log') 73 | expect(wrapper.vm.timers.log.isRunning).toBe(false) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /test/mixins.isSwitchTab.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import {mixin as VueTimers} from '../index' 3 | 4 | const component1 = { 5 | template: '
', 6 | name: 'component1', 7 | mixins: [VueTimers], 8 | timers: { 9 | log: { time: 1000, autostart: true, isSwitchTab: true } 10 | }, 11 | data() { 12 | return { 13 | count: 0 14 | } 15 | }, 16 | methods: { 17 | log() { 18 | this.count++ 19 | } 20 | } 21 | } 22 | 23 | const component2 = { 24 | template: '
', 25 | name: 'component2' 26 | } 27 | 28 | const view = { 29 | template: ` 30 | 31 | `, 32 | components: { component1, component2 }, 33 | data() { 34 | return { 35 | view: 'component1' 36 | } 37 | } 38 | } 39 | 40 | describe('isSwitchTab: true', () => { 41 | beforeAll(() => { 42 | jest.useFakeTimers() 43 | }) 44 | 45 | afterEach(() => { 46 | jest.clearAllTimers() 47 | }) 48 | 49 | afterAll(() => { 50 | jest.useRealTimers() 51 | }) 52 | 53 | it('test switch tab', async () => { 54 | const wrapper = mount(view) 55 | const child = wrapper.vm.$children[0] 56 | 57 | expect(child.timers.log.isRunning).toBe(true) 58 | expect(child.count).toBe(0) 59 | jest.advanceTimersByTime(1000) 60 | expect(child.count).toBe(1) 61 | 62 | // swtich tab to component2 63 | wrapper.setData({ view: 'component2' }) 64 | await wrapper.vm.$nextTick() 65 | expect(child.timers.log.isRunning).toBe(false) 66 | // timer will not execute when deactivated 67 | jest.advanceTimersByTime(1000) 68 | expect(child.count).toBe(1) 69 | 70 | // swtich tab back to component1 71 | wrapper.setData({ view: 'component1' }) 72 | await wrapper.vm.$nextTick() 73 | expect(child.timers.log.isRunning).toBe(true) 74 | // timer will execute when activated 75 | jest.advanceTimersByTime(1000) 76 | expect(child.count).toBe(2) 77 | }) 78 | 79 | it('test switch tab with immediate', async () => { 80 | // change timers`s config 81 | component1.timers = { 82 | log: { time: 1000, autostart: true, isSwitchTab: true, immediate: true } 83 | } 84 | const wrapper = mount(view) 85 | const child = wrapper.vm.$children[0] 86 | 87 | expect(child.timers.log.isRunning).toBe(true) 88 | expect(child.count).toBe(1) 89 | jest.advanceTimersByTime(1000) 90 | expect(child.count).toBe(2) 91 | 92 | // swtich tab to component2 93 | wrapper.setData({ view: 'component2' }) 94 | await wrapper.vm.$nextTick() 95 | expect(child.timers.log.isRunning).toBe(false) 96 | // timer will not execute when deactivated 97 | jest.advanceTimersByTime(1000) 98 | expect(child.count).toBe(2) 99 | 100 | // swtich tab back to component1 101 | wrapper.setData({ view: 'component1' }) 102 | await wrapper.vm.$nextTick() 103 | expect(child.timers.log.isRunning).toBe(true) 104 | // timer will execute when activated 105 | expect(child.count).toBe(3) 106 | jest.advanceTimersByTime(1000) 107 | expect(child.count).toBe(4) 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /test/mixins.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import {mixin as VueTimers} from '../index' 3 | 4 | const component = { 5 | template: '
', 6 | mixins: [VueTimers], 7 | timers: { 8 | log: { time: 1000 } 9 | }, 10 | data() { 11 | return { 12 | count: 0 13 | } 14 | }, 15 | methods: { 16 | log() { 17 | this.count++ 18 | } 19 | } 20 | } 21 | 22 | describe('default options', () => { 23 | // Now mount the component and you have the wrapper 24 | const wrapper = mount(component) 25 | 26 | beforeAll(() => { 27 | jest.useFakeTimers() 28 | }) 29 | 30 | afterEach(() => { 31 | jest.clearAllTimers() 32 | }) 33 | 34 | afterAll(() => { 35 | jest.useRealTimers() 36 | }) 37 | 38 | it('start the timer which not exist', () => { 39 | try { 40 | wrapper.vm.$timer.start('notExist') 41 | } catch (e) { 42 | expect(e).toEqual( 43 | new ReferenceError('[vue-timers.start] Cannot find timer notExist') 44 | ) 45 | } 46 | }) 47 | 48 | it('stop the timer which not exist', () => { 49 | try { 50 | wrapper.vm.$timer.stop('notExist') 51 | } catch (e) { 52 | expect(e).toEqual( 53 | new ReferenceError('[vue-timers.stop] Cannot find timer notExist') 54 | ) 55 | } 56 | }) 57 | 58 | it('should be has default options', () => { 59 | expect(wrapper.vm.$options.timers.log).toEqual({ 60 | name: 'log', 61 | time: 1000, 62 | repeat: false, 63 | immediate: false, 64 | autostart: false, 65 | isSwitchTab: false, 66 | callback: wrapper.vm.log 67 | }) 68 | }) 69 | 70 | it('test start and stop', () => { 71 | wrapper.vm.$timer.start('log') 72 | expect(wrapper.vm.timers.log.isRunning).toBe(true) 73 | expect(wrapper.emitted()['timer-start:log']).toBeTruthy() 74 | expect(wrapper.vm.count).toBe(0) 75 | jest.advanceTimersByTime(1000) 76 | expect(wrapper.vm.count).toBe(1) 77 | 78 | wrapper.vm.$timer.stop('log') 79 | expect(wrapper.vm.timers.log.isRunning).toBe(false) 80 | expect(wrapper.emitted()['timer-stop:log']).toBeTruthy() 81 | }) 82 | 83 | it('test restart', () => { 84 | wrapper.vm.$timer.restart('log') 85 | expect(wrapper.vm.timers.log.isRunning).toBe(true) 86 | expect(wrapper.emitted()['timer-restart:log']).toBeTruthy() 87 | }) 88 | }) 89 | 90 | describe('execute start or stop 2 times', () => { 91 | // Now mount the component and you have the wrapper 92 | const wrapper = mount(component) 93 | 94 | it('test execute start or stop 2 times', () => { 95 | wrapper.vm.$timer.start('log') 96 | wrapper.vm.$timer.start('log') 97 | expect(wrapper.emitted()['timer-start:log'].length).toBe(1) 98 | 99 | wrapper.vm.$timer.stop('log') 100 | wrapper.vm.$timer.stop('log') 101 | expect(wrapper.emitted()['timer-stop:log'].length).toBe(1) 102 | }) 103 | }) 104 | 105 | describe('autoStart: true', () => { 106 | // Now mount the component and you have the wrapper 107 | const wrapper = mount(component, { 108 | timers: { 109 | log: { time: 1000, autostart: true } 110 | } 111 | }) 112 | 113 | it('test start and stop', () => { 114 | expect(wrapper.vm.timers.log.isRunning).toBe(true) 115 | expect(wrapper.emitted()['timer-start:log']).toBeTruthy() 116 | }) 117 | }) 118 | 119 | describe('immediate: true', () => { 120 | // Now mount the component and you have the wrapper 121 | const wrapper = mount(component, { 122 | timers: { 123 | log: { time: 1000, immediate: true } 124 | } 125 | }) 126 | 127 | beforeAll(() => { 128 | jest.useFakeTimers() 129 | }) 130 | 131 | afterAll(() => { 132 | jest.clearAllTimers() 133 | jest.useRealTimers() 134 | }) 135 | 136 | it('test immediate: true', () => { 137 | wrapper.vm.$timer.start('log') 138 | expect(wrapper.vm.count).toBe(1) 139 | jest.advanceTimersByTime(1000) 140 | expect(wrapper.vm.count).toBe(2) 141 | }) 142 | }) 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## vue-timers 2 | Simple mixin to manage timers or intervals for Vue.js 3 | 4 | ## Installation 5 | 6 | #### 1.1 Use CDN 7 | ``` 8 | 9 | ``` 10 | 11 | #### 1.2 Install from package manager 12 | ``` 13 | npm install vue-timers 14 | yarn add vue-timers 15 | ``` 16 | 17 | #### 2.1. Global import 18 | ```javascript 19 | import Vue from 'vue' 20 | import VueTimers from 'vue-timers' 21 | 22 | Vue.use(VueTimers) 23 | ``` 24 | 25 | #### 2.2. Or use mixin for the specific component 26 | ```javascript 27 | import {mixin as VueTimers} from 'vue-timers' 28 | 29 | export default { 30 | mixins: [VueTimers] 31 | } 32 | ``` 33 | 34 | #### 2.3. Nuxt plugins 35 | `nuxt.config.js`: 36 | ```javascript 37 | export default { 38 | plugins: [ 39 | { src: '~/plugins/vue-timers', mode: 'client' } 40 | ] 41 | } 42 | ``` 43 | `plugins/vue-timers.js`: 44 | ```javascript 45 | import Vue from 'vue' 46 | import VueTimers from 'vue-timers' 47 | 48 | Vue.use(VueTimers) 49 | ``` 50 | 51 | ## What it does? 52 | It creates timer instances in components and slightly reduces boilerplate code with their handling. 53 | See the following code 54 | ```javascript 55 | export default { 56 | methods: { 57 | log () { 58 | console.log('Hello world') 59 | } 60 | }, 61 | created () { 62 | // It looks OK for the first look 63 | // But imagine that you have more than one timer 64 | this.$options.interval = setInterval(this.log, 1000) 65 | // Ok? What about check if timer works? 66 | // And it's not reactive so you should create data option 67 | console.log(this.$options.interval !== null) 68 | // Still ok? So what about reusable timers? 69 | // New method for that? Rly? 70 | }, 71 | // Did you forget that it should be destroyed? 72 | beforeDestroy () { 73 | clearInterval(this.$options.interval) 74 | } 75 | } 76 | ``` 77 | It's ugly, isn't it? So let's try to fix this :) 78 | 79 | Same code with `vue-timers`: 80 | ```javascript 81 | export default { 82 | timers: { 83 | log: { time: 1000, autostart: true } 84 | }, 85 | methods: { 86 | log () { 87 | console.log('Hello world') 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | ## Configuration 94 | 95 | ### Timer object 96 | ```javascript 97 | { 98 | // Name of timer 99 | // Default: timer key (with object notation) 100 | name: String, 101 | 102 | // Tick callback or method name from component 103 | // Note: callback is binded to component instance 104 | // Default: name 105 | callback: Function/String, 106 | 107 | // Autostart timer from created hook 108 | // Default: false 109 | autostart: Boolean, 110 | 111 | // Set true to repeat (with setInterval) or false (setTimeout) 112 | // Default: false 113 | repeat: Boolean, 114 | 115 | // Set true to call first tick immediate 116 | // Note: repeat must be true too 117 | // Default: false 118 | immediate: Boolean, 119 | 120 | // Time between ticks 121 | // Default: 1000 122 | time: Number 123 | 124 | // Switch timer`s status between activated and deactivated 125 | // Default: false 126 | isSwitchTab: Boolean 127 | } 128 | ``` 129 | 130 | ### Changing timer duration 131 | ```javascript 132 | this.timers.log.time = 2000 133 | ``` 134 | > **NOTE:** you should restart timer to apply changes 135 | 136 | ### Component methods 137 | ```javascript 138 | // Starts `log` timer 139 | this.$timer.start('log') 140 | // Stops `log` timer 141 | this.$timer.stop('log') 142 | ``` 143 | 144 | ### isRunning property 145 | ```javascript 146 | this.timers.log.isRunning 147 | ``` 148 | 149 | ### Events 150 | 151 | #### TimerComponent.vue 152 | ```javascript 153 | 154 | import { timer } from 'vue-timers' 155 | 156 | export default { 157 | timers: [ 158 | timer('log', 1000) 159 | ], 160 | methods: { 161 | log () { 162 | console.log('It works!') 163 | } 164 | } 165 | } 166 | ``` 167 | 168 | #### App.vue 169 | ```vue 170 | 177 | ``` 178 | 179 | ### 3 ways of timers declaration 180 | 181 | #### Object notation 182 | ```javascript 183 | export default { 184 | timers: { 185 | log: { time: 1000, ...options } 186 | } 187 | } 188 | ``` 189 | 190 | #### Array notation 191 | ```javascript 192 | export default { 193 | timers: [ 194 | { name: 'log', time: 1000, ...options } 195 | ] 196 | } 197 | ``` 198 | 199 | #### Helper function 200 | ```javascript 201 | import { timer } from 'vue-timers' 202 | 203 | export default { 204 | timers: [ 205 | timer('log', 1000, { ...options }) 206 | ] 207 | } 208 | ``` 209 | 210 | ## Author 211 | [Anton Kosykh](https://github.com/kelin2025) 212 | 213 | ## License 214 | MIT 215 | -------------------------------------------------------------------------------- /mixin.js: -------------------------------------------------------------------------------- 1 | import { set, isArray } from './utils' 2 | 3 | function generateData(timers) { 4 | return Object.keys(timers).reduce(function(acc, cur) { 5 | return set( 6 | cur, 7 | { 8 | isRunning: false, 9 | time: timers[cur].time || 0, 10 | instance: null 11 | }, 12 | acc 13 | ) 14 | }, {}) 15 | } 16 | 17 | function setTimer(repeat) { 18 | return repeat ? setInterval : setTimeout 19 | } 20 | 21 | function clearTimer(repeat) { 22 | return repeat ? clearInterval : clearTimeout 23 | } 24 | 25 | function generateTimer(options, vm) { 26 | return setTimer(options.repeat)(function() { 27 | vm.$emit('timer-tick:' + options.name) 28 | options.callback() 29 | }, options.time) 30 | } 31 | 32 | function normalizeConfig(config, vm) { 33 | if (process.env.NODE_ENV !== 'production') { 34 | if (!config.name) { 35 | throw new Error('[vue-timers.create] name is required') 36 | } 37 | if (!config.callback && typeof vm[config.name] !== 'function') { 38 | throw new ReferenceError( 39 | '[vue-timers.create] Cannot find method ' + config.name 40 | ) 41 | } 42 | if (config.callback && typeof config.callback !== 'function') { 43 | throw new TypeError( 44 | '[vue-timers.create] Timer callback should be a function, ' + 45 | typeof config.callback + 46 | ' given' 47 | ) 48 | } 49 | } 50 | return { 51 | name: config.name, 52 | time: config.time || 0, 53 | repeat: 'repeat' in config ? config.repeat : false, 54 | immediate: 'immediate' in config ? config.immediate : false, 55 | autostart: 'autostart' in config ? config.autostart : false, 56 | isSwitchTab: 'isSwitchTab' in config ? config.isSwitchTab : false, 57 | callback: (config.callback && config.callback.bind(vm)) || vm[config.name] 58 | } 59 | } 60 | 61 | function normalizeOptions(options, vm) { 62 | return isArray(options) 63 | ? options.reduce(function(res, config) { 64 | return set(config.name, normalizeConfig(config, vm), res) 65 | }, {}) 66 | : Object.keys(options).reduce(function(res, key) { 67 | return set( 68 | key, 69 | normalizeConfig(set('name', key, options[key]), vm), 70 | res 71 | ) 72 | }, {}) 73 | } 74 | 75 | export default { 76 | data: function() { 77 | if (!this.$options.timers) return {} 78 | this.$options.timers = normalizeOptions(this.$options.timers, this) 79 | return { 80 | timers: generateData(this.$options.timers) 81 | } 82 | }, 83 | 84 | created: function() { 85 | if (!this.$options.timers) return 86 | var vm = this 87 | var data = vm.timers 88 | var options = vm.$options.timers 89 | vm.$timer = { 90 | start: function(name) { 91 | if (process.env.NODE_ENV !== 'production' && !(name in options)) { 92 | throw new ReferenceError( 93 | '[vue-timers.start] Cannot find timer ' + name 94 | ) 95 | } 96 | if (data[name].isRunning) return 97 | data[name].isRunning = true 98 | data[name].instance = generateTimer( 99 | set('time', data[name].time, options[name]), 100 | vm 101 | ) 102 | if (options[name].immediate) { 103 | vm.$emit('timer-tick:' + name) 104 | options[name].callback() 105 | } 106 | vm.$emit('timer-start:' + name) 107 | }, 108 | 109 | stop: function(name) { 110 | if (process.env.NODE_ENV !== 'production' && !(name in options)) { 111 | throw new ReferenceError( 112 | '[vue-timers.stop] Cannot find timer ' + name 113 | ) 114 | } 115 | if (!data[name].isRunning) return 116 | clearTimer(options[name].repeat)(data[name].instance) 117 | data[name].isRunning = false 118 | vm.$emit('timer-stop:' + name) 119 | }, 120 | 121 | restart: function(name) { 122 | if (process.env.NODE_ENV !== 'production' && !(name in options)) { 123 | throw new ReferenceError( 124 | '[vue-timers.restart] Cannot find timer ' + name 125 | ) 126 | } 127 | this.stop(name) 128 | this.start(name) 129 | vm.$emit('timer-restart:' + name) 130 | } 131 | } 132 | }, 133 | 134 | mounted: function() { 135 | if (!this.$options.timers) return 136 | var vm = this 137 | var options = vm.$options.timers 138 | Object.keys(options).forEach(function(key) { 139 | if (options[key].autostart) { 140 | vm.$timer.start(key) 141 | } 142 | }) 143 | }, 144 | 145 | activated: function() { 146 | if (!this.$options.timers) return 147 | var vm = this 148 | var data = vm.timers 149 | var options = vm.$options.timers 150 | Object.keys(options).forEach(function(key) { 151 | if (options[key].isSwitchTab && data[key].instance) { 152 | vm.$timer.start(key) 153 | } 154 | }) 155 | }, 156 | 157 | deactivated: function() { 158 | if (!this.$options.timers) return 159 | var vm = this 160 | var data = vm.timers 161 | var options = vm.$options.timers 162 | Object.keys(options).forEach(function(key) { 163 | if (options[key].isSwitchTab && data[key].instance) { 164 | vm.$timer.stop(key) 165 | } 166 | }) 167 | }, 168 | 169 | beforeDestroy: function() { 170 | if (!this.$options.timers) return 171 | var vm = this 172 | Object.keys(vm.$options.timers).forEach(function(key) { 173 | vm.$timer.stop(key) 174 | }) 175 | } 176 | } 177 | --------------------------------------------------------------------------------