How to define a custom binding who use previous va

2019-03-21 14:34发布

问题:

I need to bind a table with knockout, and I would like the table cell to get a different css class if the new value is higher or lower of the previous.

I have in mind different possibilities, such as storing the previous value in the bindingContext and have a function which returns the right class, but is it possible to add a custom binding handler which receives the previous value and the new value?

回答1:

Although Jeff's and Sławomir's answers would work, I found an alternative that doesn't need any change to the view model nor relies on altering the DOM element object.

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

ko.bindingHandlers['bindingWithPrevValue'] = {
  init: function (element, valueAccessor) {
    var observable = valueAccessor();
    var current = observable();

    console.log('initial value is', current);

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

Naturally, that will only work if the bound property is an observable.



回答2:

I looked into knockout source and I suppose that you can't access previous value inside update method of bindingHandler but you can store it inside element

ko.bindingHandlers['bindingWithPrevValue'] = {
    update: function (element, valueAccessor) {
        var prevValue = $(element).data('prevValue');
        var currentValue = valueAccessor();
        $(element).data('prevValue', currentValue());

        // compare prevValue with currentValue and do what you want
    }
};


回答3:

What you could do is create an extender to extend the observables that you wish to track the previous values of. You could then inspect the previous value to do as you wish.

Just pass in the name of the property that will hold the previous value.

ko.extenders.previousValue = function (target, propertyName) {
    var previousValue = ko.observable(null);

    target[propertyName] = ko.computed(previousValue);

    target.subscribe(function (oldValue) {
        previousValue(oldValue);
    }, target, 'beforeChange');

    return target;
};

Then to use it:

function ViewModel() {
    this.value = ko.observable('foo').extend({ previousValue: 'previousValue' });
}

var vm = new ViewModel();

console.log(vm.value()); // 'foo'
console.log(vm.value.previousValue()); // null

vm.value('bar');

console.log(vm.value()); // 'bar'
console.log(vm.value.previousValue()); // 'foo'

In your case, you could probably use something like this:

function TableCell(value) {
    this.value = ko.observable(value).extend({ previousValue: 'previousValue' });
    this.cssClass = ko.computed(function () {
        // I'm assuming numbers
        var current = Number(this.value()),
            previous = Number(this.value.previousValue());

        if (current < previous)
            return 'lower';
        else if (current > previous)
            return 'higher';
    }, this);
}