4
votes

(Using KnockoutJs 2.0.0)

I have a list of phone numbers in my viewmodel. Each phone number has a type (home, work, mobile, etc). I want to display an icon (based on a fontawesome class) next to each phone number.

If I hardcode the icons in the css binding, everything works:

<tbody data-binding="foreach: phoneList">
    <tr>
       <td><span data-bind="css: {'icon-home' : TypeId() == 1, 'icon-building': TypeId() == 2, ... , 'icon-phone': TypeId() >= 7></span></td>
    ...
</tbody>

I wanted to replace the hardcoded list with a call to a function. I initially tried adding the function to the parent but had no success, so then I tried adding the function directly to the phone object itself both as a function and as a ko.computed() -- but neither of these work for me.

I've dummied up some code here that demonstrates the problem. If you inspect the span element of the table items, you'll see that it almost appears as if the data-biding is treating the returned string as an array of characters and setting the class based on indexes rather than treating the returned string as a class.

I'm sure this is something completely obvious, but I've been beating my head to no avail.

3
When I updated your version of KO from 2.0 to 3.0, the bindings appear to work. This leads me to believe that you're using newer syntax for a version that relies on an older instance of KO. However, it looks like either way your computeds needs some work to evaluate correctly. - Daniel Brown
@DanielBrown I think they still don't work: plnkr.co/edit/j4DOkz4vnAJoNt4DfAko. You can see that both numbers are blue in the last three examples, while the desired effect is a red 1 and blue 2. - Drew Gaynor
Changing your computed to me.getClass = ko.computed(function() { return this.typeId() == 1 ? "mobile" : "business"; }, me); resolves this issue (with the update to your KO version) - Daniel Brown
Thanks Daniel. This was a big help. - RHarris

3 Answers

1
votes

Fix

Example

I updated your version of KO to 3.0.

Next, I changed your ko.computed binding for getClass from:

me.getClass = ko.computed(function() { return me.typeId == 1 ? "mobile" : "business"; });

to:

me.getClass = ko.computed(function() { return this.typeId() == 1 ? "mobile" : "business"; }, me);

Note

There may be a way to do this with KO 2.0, but I couldn't find documentation for previous versions. I imagine the issue is related to syntax if the feature exists.

1
votes

A computed observable should work just fine. The problem is what what you're returning from that computed observable. You need to return the definition of classes in the same format as the hard-coded version:

me.getClass = ko.computed(function() {
    return me.typeId() == 1 ? { 'mobile': true } : { 'business': true };
});

See the updated version here: http://plnkr.co/edit/qDjgMlZpXHjn5ixY3OCt

Or, you could define a custom binding to clean up the computed function a bit, though it should be noted that in this case all classes will be replaced by the output of the binding. This is probably not necessary in Knockout 3.0.0, as alluded to in the comments and other answers.

Binding:

ko.bindingHandlers.setClass = {
    update: function(element, valueAccessor, allBindings) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        element.className = value;
    }
};

Observable:

me.setClass = ko.computed(function() {
    return me.typeId() == 1 ? "mobile" : "business";
});

HTML:

<td data-bind="setClass: setClass, text: typeId"></td>

A version using a custom binding is here: http://plnkr.co/edit/ryaA4mIf7oh5Biu8bKj0?p=info

0
votes

An alternate way to do this is use an attr data-bind, instead of using a custom binding handler to set the class on the element.

So, you would still need to use a computed to set the observable:

me.setClass = ko.computed(function() {
    return me.typeId() === 1 ? "mobile" : "business";
});

Then use an attr binding to set the class on the html element:

<td data-bind="attr: { class: setClass }, text: typeId"></td>