This is not excluded and will register as a click outside of the menu and so the drop down menu will close
52 |
53 |
This file input should also be excluded
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | 'use strict';
3 |
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 | bump: {
7 | options: {
8 | files: ['package.json', 'bower.json'],
9 | updateConfigs: [],
10 | commit: true,
11 | commitMessage: 'Bumped to release v%VERSION%',
12 | commitFiles: ['package.json', 'bower.json'],
13 | createTag: true,
14 | tagName: 'v%VERSION%',
15 | tagMessage: 'Version %VERSION%',
16 | push: false,
17 | pushTo: 'upstream',
18 | gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d',
19 | globalReplace: false,
20 | prereleaseName: false,
21 | regExp: false
22 | }
23 | }
24 | });
25 |
26 | grunt.loadNpmTasks('grunt-bump');
27 |
28 | /**\/
29 | $ grunt bump
30 | >> Version bumped to 0.0.2
31 | >> Committed as "Release v0.0.2"
32 | >> Tagged as "v0.0.2"
33 | >> Pushed to origin
34 |
35 | $ grunt bump:patch
36 | >> Version bumped to 0.0.3
37 | >> Committed as "Release v0.0.3"
38 | >> Tagged as "v0.0.3"
39 | >> Pushed to origin
40 |
41 | $ grunt bump:minor
42 | >> Version bumped to 0.1.0
43 | >> Committed as "Release v0.1.0"
44 | >> Tagged as "v0.1.0"
45 | >> Pushed to origin
46 |
47 | $ grunt bump:major
48 | >> Version bumped to 1.0.0
49 | >> Committed as "Release v1.0.0"
50 | >> Tagged as "v1.0.0"
51 | >> Pushed to origin
52 |
53 | $ grunt bump:patch
54 | >> Version bumped to 1.0.1
55 | >> Committed as "Release v1.0.1"
56 | >> Tagged as "v1.0.1"
57 | >> Pushed to origin
58 |
59 | $ grunt bump:git
60 | >> Version bumped to 1.0.1-ge96c
61 | >> Committed as "Release v1.0.1-ge96c"
62 | >> Tagged as "v1.0.1-ge96c"
63 | >> Pushed to origin
64 |
65 | $ grunt bump:prepatch
66 | >> Version bumped to 1.0.2-0
67 | >> Committed as "Release v1.0.2-0"
68 | >> Tagged as "v1.0.2-0"
69 | >> Pushed to origin
70 |
71 | $ grunt bump:prerelease
72 | >> Version bumped to 1.0.2-1
73 | >> Committed as "Release v1.0.2-1"
74 | >> Tagged as "v1.0.2-1"
75 | >> Pushed to origin
76 |
77 | $ grunt bump:patch # (major, minor or patch) will do this
78 | >> Version bumped to 1.0.2
79 | >> Committed as "Release v1.0.2"
80 | >> Tagged as "v1.0.2"
81 | >> Pushed to origin
82 |
83 | $ grunt bump:preminor
84 | >> Version bumped to 1.1.0-0
85 | >> Committed as "Release v1.1.0-0"
86 | >> Tagged as "v1.1.0-0"
87 | >> Pushed to origin
88 |
89 | $ grunt bump
90 | >> Version bumped to 1.1.0
91 | >> Committed as "Release v1.1.0"
92 | >> Tagged as "v1.1.0"
93 | >> Pushed to origin
94 |
95 | $ grunt bump:premajor (with prerelaseName set to 'rc' in options)
96 | >> Version bumped to 2.0.0-rc.0
97 | >> Committed as "Release v2.0.0-rc.0"
98 | >> Tagged as "v2.0.0-rc.0"
99 | >> Pushed to origin
100 |
101 | $ grunt bump
102 | >> Version bumped to 2.0.0
103 | >> Committed as "Release v2.0.0"
104 | >> Tagged as "v2.0.0"
105 | >> Pushed to origin
106 |
107 | $ grunt bump:prerelease # from a released version `prerelease` defaults to prepatch
108 | >> Version bumped to 2.0.1-rc.0
109 | >> Committed as "Release v2.0.1-rc.0"
110 | >> Tagged as "v2.0.1-rc.0"
111 | >> Pushed to origin
112 | /**/
113 | };
114 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-click-outside
2 |
3 | An Angular directive to detect a click outside of an elements scope. Great for closing dialogues, drawers and off screen menu's etc.
4 |
5 | ### Recent changes
6 |
7 | - Shortened Bower description to remove Bower warning on install (thanks [@jcubic](https://github.com/jcubic))
8 | - Thanks to [@Lorelei](https://github.com/Lorelei) for the pull request to pass the event back in the callback function
9 | - Thanks to [@CosticaPuntaru](https://github.com/CosticaPuntaru) for the improvement to now allow the directive to no longer need an id on the element for this to work
10 | - Added basic ngdocs documentation
11 | - Removed the addition of the element id to the classes array as now it no longer needs to be checked
12 |
13 | ### Roadmap
14 |
15 | - Addition of outside-if attribute. Ability to restrict click outside registering to defined elements (opposite of outside-if-not) rather than anywhere outside an element
16 | - Look into the worth of converting directive to Angular 1 component
17 | - Conversion of directive to Angular 2 component
18 |
19 | ### Installation
20 |
21 | There are two easy ways to install the clickoutside directive:
22 |
23 | #### Manual download
24 |
25 | Download the `clickoutside.directive.js` file, and include it in your `index.html` file with something like:
26 |
27 | ```html
28 |
29 | ```
30 |
31 | Also be sure to include the module in your `app.js` file with:
32 |
33 | #### npm
34 | =======
35 | ```javascript
36 | angular.module('yourAppName', ['angular-click-outside'])
37 | ```
38 |
39 | #### Bower
40 | =======
41 | ```shell
42 | npm install @iamadamjowett/angular-click-outside
43 | ```
44 |
45 | ### Usage
46 | =======
47 | ```shell
48 | bower install angular-click-outside --save
49 | ```
50 |
51 | ### Usage
52 |
53 | The directive will work with either ID's or classes, however, be wary of using classes as quite often some unwanted elements may have the same class, and so will be excluded/included unintentionally.
54 |
55 | If you are sure that you want to exclude/include all elements with a class however the directive will work just fine as it looks through the classNames as well as looking at the given ID list.
56 |
57 | General though ID's will suffice, but instances of dynamically inserted list items may require the use of classes.
58 |
59 | Add the directive via the `click-outside` attribute, and give exceptions via the `outside-if-not` attribute.
60 |
61 | Basic example:
62 |
63 | ```html
64 |
65 | ...
66 |
67 | ```
68 |
69 | This is of little use though without a callback function to do something with that click:
70 |
71 | ```html
72 |
73 | ...
74 |
75 | ```
76 |
77 | Where `closeThis()` is the function assigned to the scope via the controller such as:
78 |
79 | ```javascript
80 | angular
81 | .module('myApp')
82 | .controller('MenuController', ['$scope', MenuController]);
83 |
84 | function MenuController($scope) {
85 | $scope.closeThis = function () {
86 | console.log('closing');
87 | }
88 | }
89 | ```
90 |
91 | ```html
92 |
93 |
94 |
95 | ...
96 |
97 |
98 | ```
99 |
100 | ### Adding Exceptions
101 |
102 | You can also add exceptions via the `outside-if-not` tag, which executes the callback function, but only if the ID's or classes listed aren't clicked.
103 |
104 | In this case `closeThis()` will be called only if clicked outside _and_ `#my-button` wasn't clicked as well (note `.my-button` also would be an exception). This can be great for things like slide in menus that might have a button outside of the menu scope that triggers it:
105 |
106 | ```html
107 |
108 |
109 | ...
110 |
111 | ```
112 |
113 | You can have more than one exception by comma delimiting a list such as:
114 |
115 | ```html
116 |
117 |
118 | ...
119 |
120 |
121 | ```
122 |
--------------------------------------------------------------------------------
/clickoutside.directive.js:
--------------------------------------------------------------------------------
1 | /*global angular, navigator*/
2 |
3 | (function() {
4 | 'use strict';
5 |
6 | angular
7 | .module('angular-click-outside', [])
8 | .directive('clickOutside', [
9 | '$document', '$parse', '$timeout',
10 | clickOutside
11 | ]);
12 |
13 | /**
14 | * @ngdoc directive
15 | * @name angular-click-outside.directive:clickOutside
16 | * @description Directive to add click outside capabilities to DOM elements
17 | * @requires $document
18 | * @requires $parse
19 | * @requires $timeout
20 | **/
21 | function clickOutside($document, $parse, $timeout) {
22 | return {
23 | restrict: 'A',
24 | link: function($scope, elem, attr) {
25 |
26 | // postpone linking to next digest to allow for unique id generation
27 | $timeout(function() {
28 | var classList = (attr.outsideIfNot !== undefined) ? attr.outsideIfNot.split(/[ ,]+/) : [],
29 | fn;
30 |
31 | function eventHandler(e) {
32 | var i,
33 | element,
34 | r,
35 | id,
36 | classNames,
37 | l;
38 |
39 | // check if our element already hidden and abort if so
40 | if (angular.element(elem).hasClass("ng-hide")) {
41 | return;
42 | }
43 |
44 | // if there is no click target, no point going on
45 | if (!e || !e.target) {
46 | return;
47 | }
48 |
49 | // loop through the available elements, looking for classes in the class list that might match and so will eat
50 | for (element = e.target; element; element = element.parentNode) {
51 | // check if the element is the same element the directive is attached to and exit if so (props @CosticaPuntaru)
52 | if (element === elem[0]) {
53 | return;
54 | }
55 |
56 | // now we have done the initial checks, start gathering id's and classes
57 | id = element.id,
58 | classNames = element.className,
59 | l = classList.length;
60 |
61 | // Unwrap SVGAnimatedString classes
62 | if (classNames && classNames.baseVal !== undefined) {
63 | classNames = classNames.baseVal;
64 | }
65 |
66 | // if there are no class names on the element clicked, skip the check
67 | if (classNames || id) {
68 |
69 | // loop through the elements id's and classnames looking for exceptions
70 | for (i = 0; i < l; i++) {
71 | //prepare regex for class word matching
72 | r = new RegExp('\\b' + classList[i] + '\\b');
73 |
74 | // check for exact matches on id's or classes, but only if they exist in the first place
75 | if ((id !== undefined && r.test(id)) || (classNames && r.test(classNames))) {
76 | // now let's exit out as it is an element that has been defined as being ignored for clicking outside
77 | return;
78 | }
79 | }
80 | }
81 | }
82 |
83 | // if we have got this far, then we are good to go with processing the command passed in via the click-outside attribute
84 | $timeout(function() {
85 | fn = $parse(attr['clickOutside']);
86 | fn($scope, { event: e });
87 | });
88 | }
89 |
90 | // if the devices has a touchscreen, listen for this event
91 | if (_hasTouch()) {
92 | $document.on('touchstart', function () {
93 | setTimeout(eventHandler)
94 | });
95 | }
96 |
97 | // still listen for the click event even if there is touch to cater for touchscreen laptops
98 | $document.on('click', eventHandler);
99 |
100 | // when the scope is destroyed, clean up the documents event handlers as we don't want it hanging around
101 | $scope.$on('$destroy', function() {
102 | if (_hasTouch()) {
103 | $document.off('touchstart', eventHandler);
104 | }
105 |
106 | $document.off('click', eventHandler);
107 | });
108 |
109 | /**
110 | * @description Private function to attempt to figure out if we are on a touch device
111 | * @private
112 | **/
113 | function _hasTouch() {
114 | // works on most browsers, IE10/11 and Surface
115 | return 'ontouchstart' in window || navigator.maxTouchPoints;
116 | };
117 | });
118 | }
119 | };
120 | }
121 | })();
122 |
--------------------------------------------------------------------------------