Paul Aldred-Bann
Paul Aldred-Bann

Reputation: 6020

Showing and hiding items based on a parent's value

Example jsFiddle


I have an array of questions in my model, with the following attributes:

I'm using ko.mapping to bind a JSON string (of which I'm retrieving from an asmx web method) to my view model.

I have a bunch of primary questions that have no parents or activators as these should always be visible, then a bunch of dependent questions that depend on their respective parent(s) having a certain value before the dependent is visible. This can extend as far as depdendents being dependent on dependents (if you see what I mean).

My initial thought (as I'm slowly getting there with knockout.js) was to subscribe to the answer property of my question and then grab a sub-set of questions that depend on the one just answered. Then have a look at the value, compare them with the activators and show / hide as necessary.

viewModel.questions().forEach(function (question) {
    question.answer.subscribe(function (value) {
        var dependents = viewModel.questions().filter(function (q) {
            return q.parents() && q.parents().indexOf(question.id()) !== -1;
        });
        
        dependents.forEach(function (d) {
            if (d.activators() && d.activators().indexOf(value) !== -1) {
                d.visible(true);
            } else {
                d.visible(false);
                d.answer(null);
            }
        });
    });
});

While this works, I can't help feeling that I'm missing a trick here. I have to use ko.mapping to bind the model to my view model as it's already been created for me in the asmx method, so I have to extend my view model (as i'm doing here by cycling through all questions and subscribing to each one.

Maybe I should be using a ko.computed for the visible property, or maybe I should be creating some kind of custom ko.bindingHandlers instead? I'm not sure. What is the correct way of trying to achieve what I want?

Upvotes: 3

Views: 550

Answers (1)

oddurad
oddurad

Reputation: 417

There are several ways of achieving what you're after, one of them is using custom bindings like you mentioned.

Whether or not that's considered the most "Knockout-correct" way, I won't say, but it's a different approach and might give you some ideas at the very least. Here's one way of creating the binding handler:

ko.bindingHandlers.hideIfIrrelevant = {
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var question = ko.unwrap(valueAccessor());

        if(!question.parents()) {
           $(element).show() // Always show the question if it has no parents
           return false;
        }

        var allQuestions = bindingContext.$root.questions();
        var parents = allQuestions.filter(function (q) {
            return question.parents().indexOf(q.id()) !== -1;
        });

        var shown = false;
        $.each(parents, function(i, parent) {
            if(parent.answer() && question.activators().indexOf(parent.answer()) != -1) {
                shown = true;
                return false;
            }
        });

        if(shown) {
            $(element).show();
        }
        else {
            $(element).hide();
        }
    }
};

Here's the updated fiddle. Note that by using the custom binding, you no longer need the visible property as part of your data model.

Upvotes: 1

Related Questions