1

I've designed a dropdown menu (in vain of attempting to style an actual <select> element consistently across browsers), which when clicked, shows an unordered list of dropdown options. This functionality is provided via Knockout.js which uses an observable to check whether the dropdown should be shown or hidden. The DOM structure is here:

<div id="actionsDropdown">
  <a data-bind="click: toggleDropdownVisibility, css: { active: showDropdown() == true }">Actions</a>
    <ul data-bind="visible: showDropdown">
      <li>Option 1</li>
      <li>Option 2</li>
    </ul>
</div>

And here is my Knockout.js code:

self.showDropdown = ko.observable(false);
self.toggleDropdownVisibility = function () {
    console.log(self.showDropdown());
    self.showDropdown(!self.showDropdown());
};

This works pretty well, except for a few things.

How can I implement logic to also hide the dropdown when anywhere else on the page is clicked if the dropdown is already visible?

I guess I could bind a click handler to the body element, but then it'd open it if it were closed, which is obviously not optimal.

marked-down
  • 9,958
  • 22
  • 87
  • 150
  • Can't you just call `showDropdown(false)` on page click? – mwerschy Nov 27 '14 at 00:12
  • @mwerschy Doesn't that register a click each time the page is clicked though? Seems like the wrong way of going about it... – marked-down Nov 27 '14 at 00:14
  • Yes, attach a click handler to body but only when the dropdown is visible. Then unbind it when showDropdown is false. – Jeff Nov 27 '14 at 01:30
  • you could just use a custom binding handler to do that – Jeff Nov 27 '14 at 01:45
  • related http://stackoverflow.com/questions/1403615/use-jquery-to-hide-a-div-when-the-user-clicks-outside-of-it/7385673#7385673 and also http://stackoverflow.com/questions/9122078/difference-between-onclick-vs-click – Adriano Nov 27 '14 at 12:17

1 Answers1

1

See this jsfiddle:

http://jsfiddle.net/dwp0etrg/9/

I just created a quick custom binding handler that accepts takes showDropdown and adds an event listener to document. When showDropdown in true, it binds the listener and when its false, it removes it.

ko.bindingHandlers.dropdown = {
    update: function (element, valueAccessor) {
        var value = valueAccessor();
        var valueUnwrapped = ko.unwrap(value);
        if (valueUnwrapped) {
            $(document).on('click.dropdown', function (e) {
                var $target = $(e.target);
                if ($target.parents('#actionsDropdown').length === 0) {
                    value(false);
                }
            });
        } else {
            $(document).off('click.dropdown');
        }
    }
};

Just change your template to use the binding and pass it showDropdown observable.

<div id="actionsDropdown"> 
    <a data-bind="dropdown: showDropdown, 
                  click: toggleDropdownVisibility,
                  css: { active: showDropdown() == true }">Actions</a>

    <ul data-bind="visible: showDropdown">
        <li>Option 1</li>
        <li>Option 2</li>
    </ul>
</div>
Jeff
  • 2,293
  • 4
  • 26
  • 43