frClickOut.js 2.54 KB
/**
* A directive that will execute a function when user clicks out of the current
* element. By specifying filter, one can also extend this functionality to other
* elements, meaning that when user clicks on that element it will not count as
* an out click. By specifying include you can tell which elements will count as
* an out click. Include is useful only when you want to trigger an out click on
* of the children of the element itself.
*
* Example
*
* ```html
* <div fr-click-out="close()" fr-click-out-filter="#openBtn, .header-nav" fr-click-out-include=".close-btn">
* 	...
* </div>
* ```
*/
angular.module('fr.clickOut', [])

.directive('frClickOut', ['$document', '$parse', function ($document, $parse) {
    // Checks if any of elements defined by selectors in filter contain target.
    // In other words checks if one of the filtered elements has been clicked.
    function targetInFilter(target, filter) {
        var filteredElements = angular.element($document[0].querySelectorAll(filter));
        var totalElements = filteredElements.length;

        for (var i = 0; i < totalElements; ++i) {
            if (filteredElements[i].contains(target)) {
                return true;
            }
        }

        return false;
    }

    // Run the checks to see if we need to run click out handler or not.
    function isClickOut(element, event, filter, include) {
        // 	Make sure clicked element is not one of the included elements.
        if (include && targetInFilter(event.target, include)) {
            return true;
        }

        // Make sure clicked element is not inside our element.
        if (element[0].contains(event.target)) {
            return;
        }

        // Make sure clicked element is not one of filtered elements.
        if (filter && targetInFilter(event.target, filter)) {
            return;
        }

        return true;
    }

    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            var clickOutHandler = $parse(attrs.frClickOut)(scope);
            var filter			= attrs.frClickOutFilter;
            var include			= attrs.frClickOutInclude;

            // Listen for clicks on the whole document.
            $document.bind('click', handle);

            scope.$on('$destroy', function () {
                $document.off('click', handle);
            });

            function handle(event) {
                if (isClickOut(element, event, filter, include)) {
                    clickOutHandler(event);
                    scope.$apply();
                }
            }
        },
    };
}]);