Whenever you need to alter the DOM, you should try to do so via a knockout data-bind. Most of it can be done through knockout's default bindings; some stuff will require a custom binding.
For the first part: adding an open class should happen through the css
bind:
data-bind="css: { 'classname' : state }"
In your viewmodel, add an cartIsOpen
observable and an openCart
method.
self.isOpen = ko.observable(false);
Now, you can open your cart visually by adding this data-bind to its container element:
data-bind="css: {'open': isOpen }"
The jquery line can be replaced by:
self.isOpen(true);
and knockout will take care of the css.
Now, for the (more complicated) second part:
Listening to an event outside a container is not something knockout's regular click
or event
binding can do. So you'll have to write a custom binding.
Listening to events outside a certain element can be quite tricky; I'd suggest searching stack overflow for some general advice. You'll have to be really careful with event.stopPropagation()
for it to work.
In the example below I've coded a crude example of a clickOutside
binding. Note that it's not a "finished" example:
- Any click in your page will trigger the
closeOnClickOutside
method; you need to find a way to attach the listener when isOpen
is set to true
, and dispose it once it's set to false
.
- Whenever you want to open your menu you'll need to explicitly stop the click event from reaching the document. (
cancelBubble
in the example). Otherwise, the menu is opened and closed instantly.
var elementContainsChild = function(parent, child) {
// http://stackoverflow.com/a/2234986/3297291
var node = child.parentNode;
while (node !== null) {
if (node === parent) {
return true;
}
node = node.parentNode;
}
return false;
};
ko.bindingHandlers.clickOutside = {
init: function(element, valueAccessor) {
var cb = valueAccessor();
var closeOnClickOutside = function(e) {
if (e.target !== element &&
!elementContainsChild(element, e.target)) {
cb();
}
};
document.addEventListener("click", closeOnClickOutside);
}
}
var vm = {
isOpen: ko.observable(false)
};
ko.applyBindings(vm);
html, body { height: 100%; background: grey; }
.menu { background: yellow; display: none; }
.menu.open { display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<button data-bind="click: isOpen.bind(null, true), clickBubble: false">open menu</button>
<div class="menu" data-bind="css: {'open' : isOpen }, clickOutside: isOpen.bind(null, false)">
<ul>
<li>Menu item</li>
<li>Menu item</li>
<li>Menu item</li>
<li>Menu item</li>
</ul>
</div>