167

I'd like to use a property on my ViewModel to toggle which icon to display without creating a separate computed property of the inverse. Is this possible?

<tbody data-bind="foreach: periods">
  <tr>
    <td>
      <i class="icon-search" data-bind="visible: !charted, click: $parent.pie_it"></i>
      <i class="icon-remove" data-bind="visible: charted, click: $parent.pie_it"></i>
    </td>
  </tr>
</tbody>

My ViewModel has a property periods which is an array of month, like this:

var month = function() {
    this.charted = ko.observable(false);
};
Jeroen
  • 60,696
  • 40
  • 206
  • 339
agradl
  • 3,536
  • 2
  • 18
  • 16
  • 3
    @Niko: It's not really a duplicate question. The OP of the question you refer to already knew **that** it's possible to data-bind the negation of an observable, but wonders why it needs to be called like a function. This question's OP here didn't know how to do that in the first place and obviously didn't find that other question. I'm glad I found this question here - which is mostly thanks to its descriptive title. – Oliver May 14 '13 at 11:32

8 Answers8

287

When using an observable in an expression you need to access it as a function like:

visible: !charted()

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • 34
    Maybe we should make a hidden binding :) We have enable and disable. – John Papa Apr 11 '12 at 23:21
  • Does the documentation disagree with this, or am I completely misunderstanding this page: http://knockoutjs.com/documentation/css-binding.html –  Jun 15 '13 at 20:17
  • Nevermind, I guess "isSevere" is not an observable but a plain old property, thus my confusion. –  Jun 15 '13 at 20:19
  • 4
    When using !charted, you're getting ![Function]. [Function] is truthy, ![Function] becomes false and will always be false if you use that syntax. http://jsfiddle.net/datashaman/E58u2/3/ – datashaman Jan 28 '14 at 05:31
  • 1
    They actually added `hidden` binding in [v3.5.0](https://knockoutjs.com/documentation/visible-binding.html) – Grin Mar 26 '19 at 10:10
53

I agree with John Papa's comment that there should be a built-in hidden binding. There are two benefits to a dedicated hidden binding:

  1. Simpler syntax, ie. hidden: charted instead of visible: !charted().
  2. Less resources, since Knockout can observe the observable charted directly, rather than creating a computed to observe !charted().

It's simple enough to create a hidden binding, though, like this:

ko.bindingHandlers.hidden = {
  update: function(element, valueAccessor) {
    ko.bindingHandlers.visible.update(element, function() {
      return !ko.utils.unwrapObservable(valueAccessor());
    });
  }
};

You can use it just like the built-in visible binding:

<i class="icon-search" data-bind="hidden: charted, click: $parent.pie_it"></i>
<i class="icon-remove" data-bind="visible: charted, click: $parent.pie_it"></i>
Dave
  • 631
  • 4
  • 5
  • 9
    this did not work for me without return `return !ko.utils.unwrapObservable(valueAccessor());` – Mehmet Ataş Mar 31 '13 at 17:31
  • Thanks @MehmetAtaş - I corrected the `hidden` binding per your comment. (BTW, I was using CoffeeScript in my project at the time I posted this originally. CoffeeScript's syntax doesn't make it obvious when a return is intentional.) – Dave Oct 30 '13 at 05:05
10

It's little confusing, as you have to do

visible:!showMe()

so, i did

<span data-bind="visible:showMe">Show</span>
<span data-bind="visible:!showMe()">Hide</span>
<label><input type="checkbox" data-bind="checked:showMe"/>toggle</label>​

my model is

var myModel={
    showMe:ko.observable(true)
}
ko.applyBindings(myModel);    

​ check in fiddle http://jsfiddle.net/khanSharp/bgdbm/

Jhankar Mahbub
  • 9,746
  • 10
  • 49
  • 52
4

You could use my switch/case binding, which includes case.visible and casenot.visible.

<tbody data-bind="foreach: periods">
    <tr>
        <td data-bind="switch: true">
        <i class="icon-search" data-bind="case.visible: $else, click: $parent.pie_it"></i>
        <i class="icon-remove" data-bind="case.visible: charted, click: $parent.pie_it"></i>
        </td>
    </tr>
</tbody>

You could also have it as

        <i class="icon-search" data-bind="casenot.visible: charted, click: $parent.pie_it"></i>
        <i class="icon-remove" data-bind="case.visible: $else, click: $parent.pie_it"></i>
Michael Best
  • 16,623
  • 1
  • 37
  • 70
1

In order to make the binding aware of changes to the property, I copied the visible binding handler and inverted it:

ko.bindingHandlers.hidden = {
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        var isCurrentlyHidden = !(element.style.display == "");
        if (value && !isCurrentlyHidden)
            element.style.display = "none";
        else if ((!value) && isCurrentlyHidden)
            element.style.display = "";
    }
};
Yogev Smila
  • 111
  • 1
  • 3
0

Disclaimer: this solution is for entertainment purposes only.

ko.extenders.not = function (target) {
    target.not = ko.computed(function () {
        return !target();
    });
};

self.foo = ko.observable(true).extend({ not: null });

<div data-bind="text: foo"></div>     <!-- true -->
<div data-bind="text: foo.not"></div> <!-- false -->

<!-- unfortunately I can't think of a way to be able to use:
    text: foo...not
-->
THX-1138
  • 21,316
  • 26
  • 96
  • 160
0

I was having the same issue about how to use an opposite of a boolean observable. I have found an easy solution:

var ViewModel = function () {
var self = this;

// When program start, this is set to FALSE
self.isSearchContentValid = ko.observable(false);


self.gatherPlacesData = function () {

   // When user click a button, the value become TRUE
   self.isSearchContentValid(true);

};

Now on your HTML you should do this

<p data-bind = "visible:isSearchContentValid() === false"> Text 1</p>
<p data-bind = "visible:isSearchContentValid"> Text 2</p>

When the program starts only "Text1" is visible because "false===false is TRUE" and Text2 is not visible.

Lets say we have a button which invokes the gatherPlacesData on click event. Now Text1 won't be visible because "true === false is FALSE" and Text 2 only be visible.

Another possible solution could be using computed observable but, I think is a overcomplicated solution for a so simple problem.

brasofilo
  • 25,496
  • 15
  • 91
  • 179
-1

Also can use hidden like this:

 <div data-bind="hidden: isString">
                            <input type="text" class="form-control" data-bind="value: settingValue" />
                        </div>
Dev-Systematix
  • 439
  • 6
  • 26