Search code examples
javascriptjquerycssknockout.jscustom-binding

How can I make the <dd> items of a <dl> to be initially hidden via knockout's custom databinding?


I've created a <dl> where the <dd> can be slideDown/slideUp by a click on the corresponding <dt> by using knockout's databinding. I derived my solution from the tutorial about creating custom bindings.

So far I managed to wire everything so that I can click on a <dt> and toggle the <dd>. The plan was to keep the <dd>s initially hidden but somehow I am not able achieve this.

Here's a simplified sample demonstrating the problem:

HTML:

<dl data-bind="foreach: items">
    <dt data-bind="click: toggleDefinition">DT: Lorem ipsum</dt>
    <dd data-bind="slideVisible: definitionVisible, slideDuration: 300">DD: dolor sit amet</dd>    
</dl>

JavaScript:

ko.bindingHandlers.slideVisible = {
    init: function (element, valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        $(element).toggle(value);
    },
    update: function (element, valueAccessor, allBindings) {
        var value = ko.unwrap(valueAccessor());
        var duration = allBindings.get('slideDuration') || 400;
        if (value) {
            $(element).slideUp(duration);
        } else {
            $(element).slideDown(duration);
        }
    }
};

var model = function(){
    var self = this;
    self.items = ko.observableArray([
        new item(),
        new item(),
        new item()
    ]);
};

var item = function(){
    var self = this;
    self.definitionVisible = ko.observable(false);
    self.toggleDefinition = function (e)
    {
        self.definitionVisible(!self.definitionVisible());
    };
};

ko.applyBindings(new model());

What I've tried:

to hide the <dd>s by CSS but it doesn't work like I had hoped (I didn't notice any difference):

dd {
    display: none;
}

to set the following property to true - it partialy works - it hides the <dd>s but you can see the animation hiding them which is not desirable:

self.definitionVisible = ko.observable(true);

I also build a working demo at jsfiddle

Do you know what else I could try or change to keep the <dd>s hidden until I click on a <dt>?

I don't understand why the update function gets called that shows the items again. I was trying to find what triggers it but the call stack does not help much (everything comes from knockout which cannot be traced).


Solution

  • The update function is triggered correctly as described in the documentation:

    Knockout will call the update callback initially when the binding is applied to an element and track any dependencies (observables/computeds) that you access

    Beside that your handler is not working because you missed some cases in your logic: you try to show/hide based on the current value but you should check the following instead:

    • if the element is currently hidden and the new value of definitionVisible is true -> then show the element
    • if the element is currently visible and the new value of definitionVisible is false -> then hide the element

    Translating it to your code:

    update: function (element, valueAccessor, allBindings) {
        var value = ko.unwrap(valueAccessor());
        var duration = allBindings.get('slideDuration') || 400;
        if ($(element).css('display') != 'none' && !value) {
            $(element).slideUp(duration);
        }
        if ($(element).css('display') == 'none' && value) {
            $(element).slideDown(duration);
        }
    }
    

    Demo JSFiddle.