0

I've spent the last 6 hours trying to solve this issue, and I can find so little documentation on how subscriptions in Knockout.js work, that it's now moved into from the "mildly annoying" category to "downright frustrating".

What am I trying to accomplish?

I need to animate a portion of the DOM so that it slides up and down as required. Here's the general structure of the DOM that gets animated in this manner:

<section id="add" class="manageOption">
</section>
<section id="replace" class="manageOption">
</section>
<section id="remove" class="manageOption">
</section>

Each .manageOption has the same size and dimensions and looks very similar. I have an observable in my ViewModel called self.managementState, which is initially set to false. A click binding on an irrelevant element sets this observable to either false if none of them are displayed or add / replace / remove depending on which one should be displayed. Only one .manageOption can be shown at a time.

However, a slideUp or slideDown effect should only occur if self.managementState is either going from false or to false. Therefore, I need to know not only the current value of self.managementState but also the previous one. If the value changes from add to remove, I don't need to animate the elements, the change will occur instantly.

What I have tried

To solve this, I've been wanting to build a custom binding called slideSwitcher that operates using the above logic I've described, and I also found that this portion of code supposedly fetches the previous value of an observable:

function subscribeToPreviousValue(observable, fn) {
    observable.subscribe(fn, this, 'beforeChange');
}

But I can't get it to play nicely in my update function inside my custom binding, it always returns the wrong result (false has changed to false, for example).

ko.bindingHandlers.slideSwitcher = {
    update: function (element, valueAccessor) {
        var observable = valueAccessor();
        var currentVal = ko.utils.unwrapObservable(valueAccessor());

        subscribeToPreviousValue(observable, function (previous) {
            console.log('value changed from', previous, 'to', currentVal);
        });
    }
}

Fundamentally, this is because I simply don't understand the concept of a 'subscription' and I have very little experience writing bindings myself. How can I use the subscription function above to track the previous value of an observable, and how should I structure my binding to achieve what I need?

marked-down
  • 9,958
  • 22
  • 87
  • 150

1 Answers1

1

First problem is that you setup the subscription in update callback. Update function is called when some dependency is changed. So it is too late to subscribe beforechange event. And also you setup a new subscription after each value change. You have to subscribe the value in init function.

I like the subscribeChanged function (Get previous value of an observable in subscribe of same observable - second answer), it is nice and clear...

ko.subscribable.fn.subscribeChanged = function (callback) {
    var oldValue;
    this.subscribe(function (_oldValue) {
        oldValue = _oldValue;
    }, this, 'beforeChange');

    this.subscribe(function (newValue) {
        callback(newValue, oldValue);
    });
};

And you can write the binding as follows

ko.bindingHandlers.slideSwitcher = {
    init: function (element, valueAccessor) {
        var observable = valueAccessor();
        observable.subscribeChanged(function (newValue, oldValue) {
            console.log('value changed from', oldValue, 'to', newValue);
        });          
    }
}
Community
  • 1
  • 1
Lukas.Navratil
  • 590
  • 4
  • 12
  • Okay, but then because `subscribeChanged` is inside the `init` function, how can I access it inside the `update` function? I was under the impression `init` is only called once, when I need to know the previous value every time it changes. – marked-down Jan 08 '15 at 19:46
  • I think you don't need to define `update` function - you subscribe the change of the observable value, so when value is changed, the function you setup in subscription is called (`update` function would be also called). The subscription imitates the `update` function, you can e.g. trigger the animation there (you have old and new value and also element). Or why do you think you need an `update` function? – Lukas.Navratil Jan 08 '15 at 20:53
  • I'll see if I cant get it working the way you describe, it's just the way the Knockout documentation describes the `init` function it makes me think it only occurs on page load: "`init` will be called when the binding is first applied to an element. Set up any initial state, event handlers, etc. here" – marked-down Jan 08 '15 at 21:30
  • Yes, it will be called just once but it is an intention. You will setup there a subscription (which lasts forever or until unsubscribe) - it means that when the value is changed the function you pass to `subscribeChanged` will be called. – Lukas.Navratil Jan 08 '15 at 21:35