JSFiddle - React, Tailwind, and code Playground

by Evan Sharp

HTML

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.min.js"></script>
<div data-ng-app="myApp" ng-controller="ctrl">
    <button id="toggleAll" data-ng-click="toggleAll()">Toggle All</button>
    <div ng-repeat="item in items track by $index">
        <button id="toggle{{$index}}" data-ng-click="items[$index] = !items[$index]">Toggle</button>
        <div id="box-thing" data-off-click="items[$index] = false" data-off-click-if="items[$index]" data-off-click-filter="'#toggleAll,#toggle'+$index" data-ng-show="items[$index]">Clicking outside of this will close this</div>
    </div>
</div>

CSS

#toggle {
    padding: 5px 10px;
    margin: 10px;
}
#box-thing {
    padding: 5px;
    border: 2px solid #ccc;
}

JavaScript

angular.module('offClick', [])
    .directive('offClick', ['$document', '$rootScope', '$parse', function ($document, $rootScope, $parse) {
    var id = 0;
    var listeners = {};

    $document.on('click', offClickEventHandler);

    function targetInFilter(target, elms) {
        if (!target || !elms) return false;
        var elmsLen = elms.length;
        for (var i = 0; i < elmsLen; ++i)
        if (elms[i].contains(target)) return true;
        return false;
    }

    function offClickEventHandler(event) {
        if (event.pageX == 0 && event.pageY == 0) return;
        var callbacks = [];
        var target = event.target || event.srcElement;
        angular.forEach(listeners, function (listener, i) {
            if (!(listener.elm.contains(target) || targetInFilter(target, listener.offClickFilter))) {
                callbacks.push(function () {
                    listener.cb(listener.scope, {
                        $event: event
                    });
                });
            }

        });

        $rootScope.$evalAsync(function () {
            angular.forEach(callbacks, function (callback, i) {
                callback();
            })
        });
    }

    return {
        restrict: 'A',
        compile: function ($element, attr) {
            var fn = $parse(attr.offClick);
            return function (scope, element) {
                var elmId = id++;
                var offClickFilter;

                if (attr.offClickIf) {
                    $rootScope.$watch(function () {
                        return $parse(attr.offClickIf)(scope);
                    }, function (newVal) {
                        if (newVal) {
                            on();
                        } else if (!newVal) {
                            off();
                        }
                    });
                } else {
                    on();
                }

                attr.$observe('offClickFilter', function (value) {
                   ...