├── .babelrc ├── .gitignore ├── Makefile ├── dist ├── clickoutside.common.js └── clickoutside.js ├── examples ├── index.html └── vue2.html ├── karma.conf.js ├── package.json ├── readme.md ├── rollup.config.js ├── src └── index.js └── tests └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015-rollup" ] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | build 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dist 2 | 3 | default: dist 4 | 5 | dist: 6 | npm run dist 7 | 8 | test: 9 | npm test 10 | -------------------------------------------------------------------------------- /dist/clickoutside.common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * v-clickoutside 5 | * @desc 点击元素外面才会触发的事件 6 | * @example 7 | * ```vue 8 | *
9 | * ``` 10 | */ 11 | var index = { 12 | id: 'clickoutside', 13 | 14 | bind: function bind() { 15 | var _this = this; 16 | 17 | this.handler = function (e) { 18 | if (_this.vm && !_this.el.contains(e.target)) { 19 | _this.vm.$eval(_this.expression); 20 | } 21 | }; 22 | document.addEventListener(this.arg || 'click', this.handler); 23 | }, 24 | unbind: function unbind() { 25 | document.removeEventListener(this.arg || 'click', this.handler); 26 | }, 27 | install: function install(Vue) { 28 | Vue.directive('clickoutside', { 29 | bind: this.bind, 30 | unbind: this.unbind 31 | }); 32 | } 33 | }; 34 | 35 | module.exports = index; -------------------------------------------------------------------------------- /dist/clickoutside.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.VueClickOutside = factory()); 5 | }(this, function () { 'use strict'; 6 | 7 | /** 8 | * v-clickoutside 9 | * @desc 点击元素外面才会触发的事件 10 | * @example 11 | * ```vue 12 | *
13 | * ``` 14 | */ 15 | var index = { 16 | id: 'clickoutside', 17 | 18 | bind: function bind() { 19 | var _this = this; 20 | 21 | this.handler = function (e) { 22 | if (_this.vm && !_this.el.contains(e.target)) { 23 | _this.vm.$eval(_this.expression); 24 | } 25 | }; 26 | document.addEventListener(this.arg || 'click', this.handler); 27 | }, 28 | unbind: function unbind() { 29 | document.removeEventListener(this.arg || 'click', this.handler); 30 | }, 31 | install: function install(Vue) { 32 | Vue.directive('clickoutside', { 33 | bind: this.bind, 34 | unbind: this.unbind 35 | }); 36 | } 37 | }; 38 | 39 | return index; 40 | 41 | })); -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DEMO 6 | 7 | 12 | 13 | 14 |

点击黑框外面会隐藏 dropdown 元素

15 |
16 | 17 |
dropdown
18 |
19 |
20 | 21 | 22 | 23 | 35 | -------------------------------------------------------------------------------- /examples/vue2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DEMO 6 | 7 | 12 | 13 |
14 |

点击黑框外面会隐藏 dropdown 元素

15 |
16 | 17 |
dropdown
18 | 19 | {{ show }} 20 |
21 |
22 | 23 | 24 | 25 | 47 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Apr 08 2016 14:38:26 GMT+0800 (CST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'tests/**/*.js' 19 | ], 20 | 21 | 22 | // list of files to exclude 23 | exclude: [ 24 | ], 25 | 26 | 27 | // preprocess matching files before serving them to the browser 28 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 29 | preprocessors: { 30 | 'tests/**/*.js': ['rollup'] 31 | }, 32 | 33 | rollupPreprocessor: { 34 | rollup: { 35 | plugins: [ 36 | require('rollup-plugin-babel')({ 37 | exclude: 'node_modules/**', 38 | presets: [ 39 | require('babel-preset-es2015-rollup') 40 | ] 41 | }), 42 | require('rollup-plugin-node-resolve')(), 43 | require('rollup-plugin-commonjs')(), 44 | require('rollup-plugin-env')({}) 45 | ] 46 | } 47 | }, 48 | 49 | 50 | // test results reporter to use 51 | // possible values: 'dots', 'progress' 52 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 53 | reporters: ['progress'], 54 | 55 | 56 | // web server port 57 | port: 9876, 58 | 59 | 60 | // enable / disable colors in the output (reporters and logs) 61 | colors: true, 62 | 63 | 64 | // level of logging 65 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 66 | logLevel: config.LOG_INFO, 67 | 68 | 69 | // enable / disable watching file and executing tests whenever any file changes 70 | autoWatch: true, 71 | 72 | 73 | // start these browsers 74 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 75 | browsers: ['PhantomJS'], 76 | 77 | 78 | // Continuous Integration mode 79 | // if true, Karma captures browsers, runs the tests and exits 80 | singleRun: true, 81 | 82 | // Concurrency level 83 | // how many browser should be started simultaneous 84 | concurrency: Infinity 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-clickoutside", 3 | "version": "0.2.0", 4 | "description": "A Vue directive about listener a click outside an element.", 5 | "main": "dist/clickoutside.common.js", 6 | "files": [ 7 | "src", 8 | "dist" 9 | ], 10 | "scripts": { 11 | "test": "npm run dist && karma start", 12 | "dist": "rm -rf dist && rollup -c", 13 | "publish": "npm run dist" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/element-component/vue-clickoutside.git" 18 | }, 19 | "keywords": [ 20 | "vue", 21 | "click", 22 | "outside", 23 | "directive" 24 | ], 25 | "author": "qingwei.li ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/element-component/vue-clickoutside/issues" 29 | }, 30 | "homepage": "https://github.com/element-component/vue-clickoutside#readme", 31 | "devDependencies": { 32 | "babel-preset-es2015-rollup": "^1.1.1", 33 | "karma": "^0.13.22", 34 | "karma-chrome-launcher": "^0.2.3", 35 | "karma-jasmine": "^0.3.8", 36 | "karma-phantomjs-launcher": "^1.0.0", 37 | "karma-rollup-preprocessor": "^2.0.1", 38 | "rollup": "^0.34.1", 39 | "rollup-plugin-babel": "^2.6.1", 40 | "vue": "^1.0.21" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # vue-clickoutside 2 | > make a click outside event. 3 | 4 | Similar [vue-clickaway](https://github.com/simplesmiler/vue-clickaway), but more simpler. :P 5 | 6 | 7 | # Installation 8 | ```shell 9 | npm i vue-clickoutside -D 10 | ``` 11 | 12 | # Demo 13 | ![demo gif](http://g.recordit.co/pDjxMhZ1IA.gif) 14 | 15 | # Quick Start 16 | ```javascript 17 | import Vue from 'vue' 18 | import VueClickoutside from 'vue-clickoutside' 19 | 20 | Vue.use(VueClickoutside) 21 | 22 | // or custom directive id 23 | Vue.directive('v-clickoutside', VueClickoutside) 24 | ``` 25 | 26 | ```html 27 |
28 | 29 |
dropdown
30 |
31 | ``` 32 | 33 | # Development 34 | If you using NPM 3.x, these will not be automatically installed. 35 | 36 | - jasmine-core * 37 | - phantomjs-prebuilt ^1.9 38 | 39 | # License 40 | [MIT](https://opensource.org/licenses/MIT) 41 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: 'src/index.js', 3 | moduleName: 'VueClickOutside', 4 | plugins: [ 5 | require('rollup-plugin-babel')() 6 | ], 7 | targets: [ 8 | { dest: "dist/clickoutside.js", format: "umd" }, 9 | { dest: "dist/clickoutside.common.js", format: "cjs" } 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * v-clickoutside 3 | * @desc 点击元素外面才会触发的事件 4 | * @example 5 | * ```vue 6 | *
7 | * ``` 8 | */ 9 | export default { 10 | id: 'clickoutside', 11 | 12 | bind() { 13 | this.handler = (e) => { 14 | if (this.vm && !this.el.contains(e.target)) { 15 | this.vm.$eval(this.expression); 16 | } 17 | }; 18 | document.addEventListener(this.arg || 'click', this.handler); 19 | }, 20 | 21 | unbind() { 22 | document.removeEventListener(this.arg || 'click', this.handler); 23 | }, 24 | 25 | install(Vue) { 26 | Vue.directive('clickoutside', { 27 | bind: this.bind, 28 | unbind: this.unbind 29 | }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueClickoutside from './../build/index.js' 3 | 4 | describe('check methods', () => { 5 | it('as a plugin', next => { 6 | expect(VueClickoutside.install).toBeDefined() 7 | next() 8 | }) 9 | 10 | it('as a directive', next => { 11 | expect(VueClickoutside.bind).toBeDefined() 12 | expect(VueClickoutside.unbind).toBeDefined() 13 | next() 14 | }) 15 | }) 16 | 17 | describe('directive is work', () => { 18 | let vm 19 | 20 | afterEach(next => { 21 | vm.$destroy() 22 | next() 23 | }) 24 | 25 | it('as a plugin', next => { 26 | vm = new Vue({ 27 | el: 'body', 28 | replace: false, 29 | template: `outside 30 | `, 31 | directives: { 32 | 'clickoutside': VueClickoutside 33 | }, 34 | methods: { 35 | handleClick() { 36 | console.log('23333') 37 | } 38 | } 39 | }) 40 | 41 | spyOn(vm, 'handleClick') 42 | document.querySelector('.outside').click() 43 | document.querySelector('.button').click() 44 | expect(vm.handleClick.calls.count()).toEqual(1) 45 | next() 46 | }) 47 | 48 | it('as a directive', next => { 49 | Vue.use(VueClickoutside) 50 | 51 | vm = new Vue({ 52 | el: 'body', 53 | replace: false, 54 | template: `outside 55 | `, 56 | methods: { 57 | handleClick() { 58 | console.log('23333') 59 | } 60 | } 61 | }) 62 | 63 | spyOn(vm, 'handleClick') 64 | document.querySelector('.outside').click() 65 | document.querySelector('.button').click() 66 | expect(vm.handleClick.calls.count()).toEqual(1) 67 | next() 68 | }) 69 | 70 | it('namespaces', next => { 71 | Vue.use(VueClickoutside) 72 | 73 | vm = new Vue({ 74 | el: 'body', 75 | replace: false, 76 | template: `outside1 77 | 78 | outside2 79 | 80 | outside3 81 | `, 82 | methods: { 83 | handleClick1() { 84 | console.log('23333') 85 | }, 86 | handleClick2() { 87 | console.log('6666') 88 | }, 89 | handleClick3() { 90 | console.log('99999') 91 | } 92 | } 93 | }) 94 | spyOn(vm, 'handleClick1') 95 | spyOn(vm, 'handleClick2') 96 | spyOn(vm, 'handleClick3') 97 | 98 | document.querySelector('.outside1').click() 99 | document.querySelector('.button1').click() 100 | document.querySelector('.outside3').click() 101 | document.querySelector('.outside1').click() 102 | document.querySelector('.button2').click() 103 | 104 | expect(vm.handleClick1.calls.count()).toEqual(4) 105 | expect(vm.handleClick2.calls.count()).toEqual(4) 106 | expect(vm.handleClick3.calls.count()).toEqual(5) 107 | next() 108 | }) 109 | }) 110 | --------------------------------------------------------------------------------