0

Current best solution i have found:

ko.bindingHandlers.clickedIn = (function () {
    function getBounds(element) {
        var pos = element.offset();
        return {
            x: pos.left,
            x2: pos.left + element.outerWidth(),
            y: pos.top,
            y2: pos.top + element.outerHeight()
        };
    }

    function hitTest(o, l) {
        function getOffset(o) {
            for (var r = { l: o.offsetLeft, t: o.offsetTop, r: o.offsetWidth, b: o.offsetHeight };
                o = o.offsetParent; r.l += o.offsetLeft, r.t += o.offsetTop);
            return r.r += r.l, r.b += r.t, r;
        }

        for (var b, s, r = [], a = getOffset(o), j = isNaN(l.length), i = (j ? l = [l] : l).length; i;
            b = getOffset(l[--i]), (a.l == b.l || (a.l > b.l ? a.l <= b.r : b.l <= a.r))
                && (a.t == b.t || (a.t > b.t ? a.t <= b.b : b.t <= a.b)) && (r[r.length] = l[i]));
        return j ? !!r.length : r;
    }

    return {
        init: function (element, valueAccessor) {
            var target = valueAccessor();
            $(document).click(function (e) {
                if (element._clickedInElementShowing === false && target()) {
                    var $element = $(element);
                    var bounds = getBounds($element);

                    var possibleOverlays = $("[style*=z-index],[style*=absolute]").not(":hidden");
                    $.each(possibleOverlays, function () {
                        if (hitTest(element, this)) {
                            var b = getBounds($(this));
                            bounds.x = Math.min(bounds.x, b.x);
                            bounds.x2 = Math.max(bounds.x2, b.x2);
                            bounds.y = Math.min(bounds.y, b.y);
                            bounds.y2 = Math.max(bounds.y2, b.y2);
                        }
                    });


                    if (e.clientX < bounds.x || e.clientX > bounds.x2 ||
                        e.clientY < bounds.y || e.clientY > bounds.y2) {


                        target(false);

                    }
                }
                element._clickedInElementShowing = false;
            });

            $(element).click(function (e) {
                e.stopPropagation();
            });
        },
        update: function (element, valueAccessor) {
            var showing = ko.utils.unwrapObservable(valueAccessor());
            if (showing) {
                element._clickedInElementShowing = true;
            }
        }
    };
})();

It works by first query for all elements with either z-index or absolute position that are visible. It then hit tests those elemnts against the elemnet I want to hide if click outside. If its a hit I calculate a new bound retacle which takes into acount the overlay bounds.

Its not rock solid, but works. Please feel free to comment if you see problems with above approuch

Old question

I'm using Knockout but this applies to DOM/Javascript in general

Im trying to find a reliable way if detecting of you click outside of a element. My code looks like this

    ko.bindingHandlers.clickedIn = {
        init: function (element, valueAccessor) {
            var target = valueAccessor();
            var clickedIn = false;
            ko.utils.registerEventHandler(document, "click", function (e) {
                if (!clickedIn && element._clickedInElementShowing === false) {
                    target(e.target == element);
                }

                clickedIn = false;
                element._clickedInElementShowing = false;
            });

            ko.utils.registerEventHandler(element, "click", function (e) {
                clickedIn = true;
            });
        },
        update: function (element, valueAccessor) {
            var showing = ko.utils.unwrapObservable(valueAccessor());
            if (showing) {
                element._clickedInElementShowing = true;
            }
        }
    };

It works by both listening to click on target element and document. If you click on document but not target element you click outside of it. This works, but, not for overlay items like datepickers etc. This is because these are not inside the target element but in the body. Can I fix this? Are there better way of determine if clicking outside of element?

edit: This kind of works, but only if the overlay is smaller than the element i want to monitor

ko.bindingHandlers.clickedIn = {
    init: function (element, valueAccessor) {
        var target = valueAccessor();
        $(document).click(function (e) {
            if (element._clickedInElementShowing === false) {
                var $element = $(element);
                var pos = $element.offset();
                if (e.clientX < pos.left || e.clientX > (pos.left + $element.width()) ||
                    e.clientY < pos.top || e.clientY > (pos.top + $element.height())) {
                    target(false);

                }
            }
            element._clickedInElementShowing = false;
        });

        $(element).click(function (e) {
            e.stopPropagation();
        });
    },
    update: function (element, valueAccessor) {
        var showing = ko.utils.unwrapObservable(valueAccessor());
        if (showing) {
            element._clickedInElementShowing = true;
        }
    }
};

I would like a more rock solid approuch

Anders
  • 17,306
  • 10
  • 76
  • 144

2 Answers2

1

This is how I usually solve it:

http://jsfiddle.net/jonigiuro/KLxnV/

$('.container').on('click', function(e) {
    alert('hide the child');
});

$('.child').on('click', function(e) {
    alert('do nothing');
    e.stopPropagation(); //THIS IS THE IMPORTANT PART
});
Jonas Grumann
  • 10,438
  • 2
  • 22
  • 40
  • "This works, but, not for overlay items like datepickers etc. This is because these are not inside the target element but in the body" what do you mean? the .container could as well be the body: $('body').on('click', function(e) { alert('hide the child'); }); – Jonas Grumann Jun 18 '13 at 13:58
  • For example a datepicker, if you click in the datepicker you dont want your element to hide. My code in my question fixes that – Anders Jun 18 '13 at 14:06
  • I'm not sure I got what your saying.. does this work? http://jsfiddle.net/jonigiuro/Q8BKV/2/ – Jonas Grumann Jun 19 '13 at 07:50
  • You have a container element that you want to hide when clicking outside of it. In that element you can show overlays in different forms, datepickers, dialogs, popover, etc. If you click in one of those overlays it should not hide the container element. My edited question has a working solution. But its fragile, it only works if the overlay has inline style with z-index or absolute. I wpuld rather find a more solid way of deteing if a element is a overlay – Anders Jun 19 '13 at 07:54
  • could you provide a jsfiddle? – Jonas Grumann Jun 19 '13 at 08:08
  • Sorry, That wont work, there can be other overlays not over your element. Clicking those should hide element – Anders Jun 25 '13 at 12:29
0

I don't know how your overlay items are generated, but you could always check if the click target is a child of the element you want to constrain your clicks to.

Seiyria
  • 2,112
  • 3
  • 25
  • 51
  • In this case its a bootstrap datepicker, you cant do that because the dateopicker is in the body with position absolute – Anders Jun 14 '13 at 13:34