├── .eslintrc.json ├── .gitignore ├── .npmrc ├── LICENSE ├── dist ├── css │ ├── demo.css │ └── ngpopover.css └── js │ ├── app.min.js │ └── ngPopover.min.js ├── gulpfile.js ├── index.html ├── package-lock.json ├── package.json ├── readme.md ├── releaseCondition.js └── src ├── js ├── app.js └── ngPopover.js └── scss ├── demo.scss └── ngpopover.scss /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "jasmine": true, 7 | "jquery": true 8 | }, 9 | "rules": { 10 | "indent": [ 11 | "error", 12 | 4 13 | ], 14 | "linebreak-style": [ 15 | "error", 16 | "unix" 17 | ], 18 | "quotes": [ 19 | "error", 20 | "single" 21 | ], 22 | "semi": [ 23 | "error", 24 | "always" 25 | ], 26 | // disallow reassignment of function parameters 27 | // allow parameter object manipulation 28 | // rule: http://eslint.org/docs/rules/no-param-reassign.html 29 | "no-param-reassign": [ 30 | 2, 31 | { 32 | "props": false 33 | } 34 | ], 35 | "max-len": ["error", 150], 36 | "new-cap": 0, 37 | "no-use-before-define": ["error", { "functions": false, "classes": true }] 38 | }, 39 | "globals": { 40 | "angular": true, 41 | "_": true, 42 | "Rx": true 43 | }, 44 | "parserOptions": { 45 | "ecmaFeatures": { 46 | "experimentalObjectRestSpread": true 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Fauzan Khan 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/css/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #F5F5F5; 3 | font-family: "Source Sans Pro", arial; 4 | overflow-y: hidden; 5 | min-height: 100vh; } 6 | body header { 7 | text-align: center; 8 | color: #444; 9 | padding-top: 10px; 10 | font-size: 20px; } 11 | body header h1 { 12 | margin-top: 8px; 13 | line-height: 45px; } 14 | body header small { 15 | color: #999; 16 | font-size: 25px; 17 | font-weight: 300; } 18 | body header small span { 19 | text-decoration: none; 20 | color: #BCBCBC; 21 | font-weight: 400; 22 | transition: color 500ms ease; 23 | border-bottom: 1px dotted #BCBCBC; } 24 | body header small span:hover { 25 | text-decoration: none; 26 | color: #666; 27 | border-bottom: 1px dotted #666; } 28 | body .popover-demo-container { 29 | margin: 0 auto; 30 | text-align: center; 31 | padding-top: 30px; } 32 | body .popover-demo-container input { 33 | height: 32px; 34 | border-radius: 3px; 35 | border: 1px solid #EAEAEA; 36 | padding: 0px 10px; } 37 | body .flash-message { 38 | /*background: rgb(0, 113, 181, .8);*/ 39 | padding: 20px; 40 | max-width: 280px; 41 | border: 1px solid #CCC; 42 | border-radius: 3px; 43 | background: #FFF; 44 | position: fixed; 45 | bottom: 50px; 46 | border-radius: 0px; 47 | font-size: 14px; 48 | color: #666; 49 | transition: all 900ms ease; 50 | right: 50px; 51 | font-size: 18px; } 52 | body .flash-message.vis { 53 | opacity: 1; } 54 | body .flash-message.hidden { 55 | opacity: 0; } 56 | body .flash-message h3 { 57 | color: #999; 58 | font-size: 16px; 59 | margin: 0; 60 | margin-bottom: 10px; } 61 | body footer { 62 | text-align: center; 63 | position: absolute; 64 | bottom: 0; 65 | left: 0; 66 | right: 0; 67 | padding-bottom: 40px; } 68 | body footer img { 69 | width: 20px; 70 | vertical-align: -4px; 71 | margin-right: 3px; } 72 | body footer a { 73 | color: #008CCD; 74 | border-radius: 3px; 75 | text-decoration: none; 76 | padding: 14px 18px; 77 | background: #FFF; 78 | border: 1px solid #EAEAEA; 79 | transition: background 200ms ease; } 80 | body footer a:hover { 81 | background: transparent; } 82 | 83 | .button { 84 | color: #008CCD; 85 | border-radius: 3px; 86 | text-decoration: none; 87 | padding: 14px 18px; 88 | background: #FFF; 89 | border: 1px solid #EAEAEA; 90 | transition: background 200ms ease; 91 | cursor: pointer; } 92 | .button:hover { 93 | background: transparent; } 94 | 95 | .custom-popover { 96 | max-width: 400px; } 97 | .custom-popover .popover-header { 98 | background: #FAFAFA; 99 | border-bottom: 1px solid #EAEAEA; } 100 | .custom-popover .popover-header .popover-header-content { 101 | max-width: 75rem; 102 | margin-left: auto; 103 | margin-right: auto; } 104 | .custom-popover .popover-header .title { 105 | padding-top: 15px; 106 | padding-bottom: 15px; 107 | padding-left: 20px; 108 | font-size: 17px; 109 | font-weight: 600; 110 | width: 18%; 111 | float: left; } 112 | .custom-popover .popover-header .options { 113 | border-left: 1px solid #EAEAEA; 114 | padding-top: 16px; 115 | padding-bottom: 16px; 116 | float: right; 117 | width: 65%; 118 | text-align: left; 119 | padding-left: 30px; } 120 | .custom-popover .popover-header .option { 121 | display: inline-block; 122 | margin: 0 5px; } 123 | .custom-popover .popover-header .option input { 124 | margin-bottom: 0; } 125 | .custom-popover .popover-header .option label { 126 | vertical-align: 1px; } 127 | .custom-popover .popover-body { 128 | min-height: 100px; 129 | padding: 40px 20px; 130 | text-align: center; 131 | font-weight: 300; 132 | line-height: 34px; 133 | font-size: 24px; } 134 | .custom-popover .popover-body span { 135 | font-weight: 400; } 136 | .custom-popover .popover-footer { 137 | text-align: right; 138 | padding: 40px 30px; } 139 | 140 | .clearfix { 141 | overflow: auto; 142 | zoom: 1; } 143 | .clearfix::before, .clearfix::after { 144 | content: " "; 145 | /* 1 */ 146 | display: table; 147 | /* 2 */ } 148 | .clearfix::after { 149 | clear: both; } 150 | 151 | .heading-popover, .normal-text-popover { 152 | color: #999; 153 | padding: 20px; 154 | min-width: 200px; 155 | max-width: 300px; } 156 | -------------------------------------------------------------------------------- /dist/css/ngpopover.css: -------------------------------------------------------------------------------- 1 | .ng-popover { 2 | border: 1px solid #EAEAEA; 3 | position: relative; 4 | border-radius: 4px; 5 | min-width: 50px; 6 | min-height: 20px; 7 | background: #FFF; 8 | z-index: 10; } 9 | .ng-popover .ng-popover-wrapper { 10 | position: relative; } 11 | .ng-popover .ng-popover-wrapper.bottom::before { 12 | content: ' '; 13 | position: absolute; 14 | top: -9px; 15 | left: 9px; 16 | border-bottom: 9px solid #FFF; 17 | border-left: 9px solid transparent; 18 | border-right: 9px solid transparent; 19 | z-index: 2; } 20 | .ng-popover .ng-popover-wrapper.bottom::after { 21 | content: ' '; 22 | position: absolute; 23 | top: -10px; 24 | left: 9px; 25 | border-bottom: 9px solid #EAEAEA; 26 | border-left: 9px solid transparent; 27 | border-right: 9px solid transparent; 28 | z-index: 1; } 29 | .ng-popover .ng-popover-wrapper.top::before { 30 | content: ' '; 31 | position: absolute; 32 | bottom: -7px; 33 | left: 12px; 34 | border-top: 7px solid #FFF; 35 | border-left: 7px solid transparent; 36 | border-right: 7px solid transparent; 37 | z-index: 2; } 38 | .ng-popover .ng-popover-wrapper.top::after { 39 | content: ' '; 40 | position: absolute; 41 | bottom: -9px; 42 | left: 10px; 43 | border-top: 9px solid #EAEAEA; 44 | border-left: 9px solid transparent; 45 | border-right: 9px solid transparent; 46 | z-index: 1; } 47 | .ng-popover .ng-popover-wrapper.left::before { 48 | content: ' '; 49 | position: absolute; 50 | right: -8px; 51 | top: 5px; 52 | border-left: 8px solid #FFF; 53 | border-top: 8px solid transparent; 54 | border-bottom: 8px solid transparent; 55 | z-index: 2; } 56 | .ng-popover .ng-popover-wrapper.left::after { 57 | content: ' '; 58 | position: absolute; 59 | right: -9px; 60 | top: 4px; 61 | border-left: 9px solid #EAEAEA; 62 | border-top: 9px solid transparent; 63 | border-bottom: 9px solid transparent; 64 | z-index: 1; } 65 | .ng-popover .ng-popover-wrapper.right::before { 66 | content: ' '; 67 | position: absolute; 68 | top: 5px; 69 | left: -8px; 70 | border-right: 8px solid #FFF; 71 | border-top: 8px solid transparent; 72 | border-bottom: 8px solid transparent; 73 | z-index: 2; } 74 | .ng-popover .ng-popover-wrapper.right::after { 75 | content: ' '; 76 | position: absolute; 77 | top: 4px; 78 | left: -9px; 79 | border-right: 9px solid #EAEAEA; 80 | border-top: 9px solid transparent; 81 | border-bottom: 9px solid transparent; 82 | z-index: 1; } 83 | .ng-popover .ng-popover-content { 84 | overflow: auto; } 85 | 86 | .ng-popover.hide { 87 | display: none; } 88 | -------------------------------------------------------------------------------- /dist/js/app.min.js: -------------------------------------------------------------------------------- 1 | var app=angular.module("popoverApp",["ngPopover"]);app.controller("masterController",["$scope","ngPopoverFactory",function(o,e){o.message="Click on any of the above buttons to see the popovers in action",o.openCallback=function(e){o.message="You just opened "+e},o.closeCallback=function(e){o.message="You just closed "+e},o.closePopover=function(o){e.closePopover(o)}}]); -------------------------------------------------------------------------------- /dist/js/ngPopover.min.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";var t=e.module("ngPopover",[]);t.directive("ngPopover",function(){return{restrict:"EA",scope:{direction:"@",trigger:"@",onClose:"&",onOpen:"&",popoverClass:"@"},replace:!0,transclude:!0,link:function(e,t,r,n){e.popoverClass=r.popoverClass,e.dropDirection=r.direction||"bottom";var i,s,c=document.querySelector("#"+e.trigger),l=document.querySelector('.ng-popover[trigger="'+e.trigger+'"]');c.addEventListener("click",function(t){var r=this,i=document.querySelector('.ng-popover[trigger="'+e.trigger+'"]');t.preventDefault(),a(r,i),o(r),i.classList.toggle("hide"),i.classList.contains("hide")?(n.unregisterBodyListener(),e.onClose(),e.$apply()):(n.registerBodyListener(),e.onOpen(),e.$apply())});var p=function(){var e=c.getBoundingClientRect();document.body.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},a=function(t,o){o.classList.toggle("hide");var r=o.offsetWidth,n=o.offsetHeight;o.classList.toggle("hide");var c=t.offsetWidth,l=t.offsetHeight;switch(e.dropDirection){case"left":i=p().left-r-10+"px",s=p().top+"px";break;case"right":i=p().left+c+10+"px",s=p().top+"px";break;case"top":i=p().left+"px",s=p().top-n-10+"px";break;default:i=p().left+"px",s=p().top+l+10+"px"}o.style.position="absolute",o.style.left=i,o.style.top=s};a(c,l)},controller:["$scope",function(e){var t=function(r){var n=r.target,i=!1;do if(n!=document&&n.classList&&(n.classList.contains("ng-popover")||n.classList.contains("ng-popover-trigger"))){i=!0;break}while(n=n.parentNode);i||(o(),document.body.removeEventListener("click",t),e.onClose(),e.$apply())};this.registerBodyListener=function(){document.body.addEventListener("click",t)},this.unregisterBodyListener=function(){document.body.removeEventListener("click",t)}}],template:'
'}}),t.factory("ngPopoverFactory",function(){return{closePopover:function(e){document.querySelector(".ng-popover[trigger="+e+"]").classList.add("hide")},closeAll:function(){for(var e=document.querySelectorAll(".ng-popover"),t=0;t", 30 | })) 31 | }); 32 | 33 | gulp.task('minify-js', function(){ 34 | gulp.src(path.src.js) 35 | .pipe(uglify()) 36 | .pipe(rename({'suffix': '.min'})) 37 | .pipe(gulp.dest(path.dest.js)) 38 | .pipe(notify({ 39 | message: "Generated <%= file.relative %>", 40 | })); 41 | }); 42 | 43 | gulp.task('minify', ['sass', 'minify-js']); 44 | 45 | gulp.task('watch', function(){ 46 | gulp.watch(path.src.scss, ['sass']); 47 | gulp.watch(path.src.js, ['minify-js']); 48 | }) 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ngPopover | A simple, elegant and easily customizable popover in angular that transcludes custom HTML | angular, popover, generic, elegant, material, angular-popover, bootstrap, foundation, tranclude, custom popover, custom, transclude, popover directive 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

ngPopover
An elegant, easily customizable generic popover in Angular
that transcludes your custom HTML.

15 |
16 |
17 | Popover with Text 18 | Popover with Heading 19 | Popover with Custom Template 20 |
21 |
22 | {{message}} 23 |
24 | 29 | 30 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur nulla incidunt provident quibusdam laborum suscipit. Vel laborum cupiditate, harum hic error ipsum dolorum, facilis culpa quo tenetur fugit eveniet illum. 31 | 32 | 33 |

This text is inside an <h4> tag!

34 |
35 | 36 |
37 |
38 |
39 |
40 | Field 41 |
42 |
43 |
44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 |
56 |
57 |
58 |
You can do some super crazy stuff here and this popover wont break.
Believe it!
59 | 63 |
64 |
65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | {"name":"fzn-angular-popover","version":"0.0.0-development","description":"An easy to customize popover written in angular","main":"index.html","dependencies":{},"devDependencies":{"@semantic-release/condition-travis":"^6.1.0","gulp-notify":"^2.2.0","gulp-rename":"^1.2.2","gulp-sass":"^2.1.1","gulp-uglify":"^1.5.1","gulp-watch":"^4.3.5","semantic-release":"^8.2.0","semver":"^5.4.1"},"scripts":{"test":"echo \"Error: no test specified\" && exit 1","semantic-release":"semantic-release pre && npm publish && semantic-release post"},"repository":{"type":"git","url":"https://github.com/FauzanKhan/angular-popover.git"},"keywords":["angular","popover","ng","customizable","popover","tranclude"],"author":"FauzanKhan","license":"ISC","bugs":{"url":"https://github.com/FauzanKhan/angular-popover/issues"},"homepage":"https://github.com/FauzanKhan/angular-popover#readme","release":{"verifyConditions":{"path":"./releaseCondition.js"}}} 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ngPopover 2 | An elegant, easily customizable generic popover in Angular that transcludes custom HTML 3 | 4 | ### Description 5 | * An angular directive for generic popovers. 6 | * Popovers can contain simple text to complex html templates. 7 | * Popovers can have callback functions for opening and closing. 8 | * Popovers can have custom directions. 9 | * The script also provides a factory to close all or specific directives manually. 10 | * Works well with/without Bootstrap/Foundation. 11 | ![](http://s21.postimg.org/8zwq3pzzb/popover1.png) 12 | 13 | ### Dependecies 14 | * Angular.js 15 | 16 | ### Installation 17 | * Copy ngPopover.js and ngPopover.css from the repository and use it the way you like. 18 | 19 | ### Usage 20 | * Make sure you include the '''ngPopover''' module in you angular app: 21 | 22 | ``` 23 | angular.module('myApp', ['ngPopover']); 24 | ``` 25 | 26 | * once you've added the module in your app. Use the code below to get the popover up and running: 27 | 28 | ```html 29 | 30 | // Function to be called when the popover is hidden 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | * The trigger element for above popover would look like this: 43 | 44 | ```html 45 | 46 | // Add this class to your trigger elements 49 | 50 | Open Popover 51 | 52 | 53 | ``` 54 | 55 | * Ideally all your popovers should be present at the bottom of the body 56 | 57 | * To manually close a popover use ```ngPopoverFactory``` 58 | * Inject ```ngPopoverFactory``` in you controller : 59 | 60 | ```javascript 61 | angular.module("myApp").controller("myController", function(ngPopoverFactory){ 62 | 63 | }); 64 | ``` 65 | 66 | * Once you've injected the factory you can manually close the dropdown by calling the closePopover function of the factory: 67 | 68 | ``` 69 | ngPopoverFactory.closePopover(triggerId) 70 | ``` 71 | 72 | * You can also close all the popovers at the same time by calling closeAll function of thr factory: 73 | 74 | ``` 75 | ngPopoverFactory.closeAll(); 76 | ``` 77 | 78 | ###Customization 79 | You can easily customize the look and feel of the popover by changing the following variables in the ngPopover.scss file as per your requirement: 80 | 81 | ```css 82 | $background-color: #FFF; // Background color of the popovers 83 | $border-color: #EAEAEA; // Border color of the popovers 84 | ``` 85 | 86 | 87 | ###Demo 88 | visit http://fauzankhan.github.io/angular-popover/ to see the popover in action 89 | 90 | -------------------------------------------------------------------------------- /releaseCondition.js: -------------------------------------------------------------------------------- 1 | function releaseCondition (pluginConfig, config, callback) { 2 | callback(null); 3 | } 4 | 5 | module.exports = releaseCondition; 6 | -------------------------------------------------------------------------------- /src/js/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('popoverApp', ['ngPopover']); 2 | 3 | app.controller('masterController', ['$scope', 'ngPopoverFactory', function($scope, ngPopoverFactory){ 4 | $scope.message = "Click on any of the above buttons to see the popovers in action" 5 | $scope.openCallback = function(popoverName){ 6 | $scope.message = "You just opened "+popoverName; 7 | } 8 | 9 | $scope.closeCallback = function(popoverName){ 10 | $scope.message = "You just closed "+popoverName; 11 | }; 12 | 13 | $scope.closePopover = function(trigger){ 14 | ngPopoverFactory.closePopover(trigger); 15 | } 16 | }]); -------------------------------------------------------------------------------- /src/js/ngPopover.js: -------------------------------------------------------------------------------- 1 | ;(function(angular){ 2 | 'use strict' 3 | var app = angular.module('ngPopover', []); 4 | app.directive('ngPopover', function() { 5 | return { 6 | restrict: 'EA', 7 | scope: { 8 | direction: '@', 9 | trigger: '@', 10 | onClose: '&', 11 | onOpen: '&', 12 | popoverClass: '@', 13 | }, 14 | replace: true, 15 | transclude: true, // we want to insert custom content inside the directive 16 | link: function($scope, element, attrs, ctrl) { 17 | $scope.popoverClass = attrs.popoverClass; 18 | $scope.dropDirection = attrs.direction || 'bottom'; 19 | var left, top; 20 | var trigger = document.querySelector('#'+$scope.trigger); 21 | var target = document.querySelector('.ng-popover[trigger="'+$scope.trigger+'"]'); 22 | 23 | // Add click event listener to trigger 24 | trigger.addEventListener('click', function(ev){ 25 | var left, top; 26 | var trigger = this; //get trigger element 27 | var target = document.querySelector('.ng-popover[trigger="'+$scope.trigger+'"]'); //get triger's target popover 28 | ev.preventDefault(); 29 | calcPopoverPosition(trigger, target); //calculate the position of the popover 30 | hideAllPopovers(trigger); 31 | target.classList.toggle('hide'); //toggle display of target popover 32 | // if target popover is visible then add click listener to body and call the open popover callback 33 | if(!target.classList.contains('hide')){ 34 | ctrl.registerBodyListener(); 35 | $scope.onOpen(); 36 | $scope.$apply(); 37 | } 38 | //else remove click listener from body and call close popover callback 39 | else{ 40 | ctrl.unregisterBodyListener(); 41 | $scope.onClose(); 42 | $scope.$apply(); 43 | } 44 | }); 45 | 46 | var getTriggerOffset = function(){ 47 | var triggerRect = trigger.getBoundingClientRect(); 48 | var bodyRect = document.body.getBoundingClientRect(); 49 | return { 50 | top: triggerRect.top + document.body.scrollTop, 51 | left: triggerRect.left + document.body.scrollLeft 52 | } 53 | }; 54 | 55 | // calculates the position of the popover 56 | var calcPopoverPosition = function(trigger, target){ 57 | target.classList.toggle('hide'); 58 | var targetWidth = target.offsetWidth; 59 | var targetHeight = target.offsetHeight; 60 | target.classList.toggle('hide'); 61 | var triggerWidth = trigger.offsetWidth; 62 | var triggerHeight = trigger.offsetHeight; 63 | switch($scope.dropDirection){ 64 | case 'left': { 65 | left = getTriggerOffset().left - targetWidth - 10 + 'px'; 66 | top = getTriggerOffset().top + 'px'; 67 | break; 68 | } 69 | 70 | case 'right':{ 71 | left = getTriggerOffset().left + triggerWidth + 10 + 'px'; 72 | top = getTriggerOffset().top + 'px'; 73 | break; 74 | } 75 | 76 | case'top':{ 77 | left = getTriggerOffset().left + 'px'; 78 | top = getTriggerOffset().top - targetHeight - 10 + 'px'; 79 | break; 80 | } 81 | 82 | default:{ 83 | left = getTriggerOffset().left +'px'; 84 | top = getTriggerOffset().top + triggerHeight + 10 + 'px' 85 | } 86 | } 87 | target.style.position = 'absolute'; 88 | target.style.left = left; 89 | target.style.top = top; 90 | } 91 | calcPopoverPosition(trigger, target); 92 | }, 93 | 94 | controller: ['$scope', function($scope){ 95 | // logic to hide popover on click of body 96 | var bodyListenerLogic = function(e){ 97 | var clickedElement = e.target; 98 | var insidePopover = false; 99 | do { 100 | if(clickedElement != document && (clickedElement.classList && (clickedElement.classList.contains('ng-popover') || clickedElement.classList.contains('ng-popover-trigger')))) { 101 | insidePopover = true; 102 | break; 103 | } 104 | } while ((clickedElement = clickedElement.parentNode)); 105 | if(!insidePopover) { 106 | hideAllPopovers(); 107 | document.body.removeEventListener('click', bodyListenerLogic); 108 | $scope.onClose(); 109 | $scope.$apply(); 110 | } 111 | } 112 | this.registerBodyListener = function(){ 113 | document.body.addEventListener('click', bodyListenerLogic); 114 | } 115 | 116 | this.unregisterBodyListener = function(){ 117 | document.body.removeEventListener('click', bodyListenerLogic) 118 | } 119 | }], 120 | template: '
' 121 | } 122 | }); 123 | 124 | app.factory('ngPopoverFactory', function(){ 125 | return { 126 | closePopover : function(trigger){ 127 | document.querySelector('.ng-popover[trigger='+trigger+']').classList.add('hide'); 128 | }, 129 | closeAll : function(){ 130 | var allPopovers = document.querySelectorAll('.ng-popover'); 131 | for(var i=0; i