Monthly Archives: February 2015

jQuery UI Widget: toggler

$.widget("scw.toggler", {
    options: {
        active: true,
        animated: true,
        duration: "_default",
        cssClass: "toggler-active"
    },
    
    _finish: function () {
        while (this.element.queue().length > 0) {
            this.element.dequeue();
        }
        this.element.finish(false);
        this.element.finish();
    },
    
    _refresh: function (immediate) {
        this._finish();
        var args = [
            this.options.cssClass,
            Boolean(this.options.active)
        ];
        if (!immediate) {
            args.push({
                duration: this.options.duration
            });
        }
        $.fn.toggleClass.apply(this.element, args);
    },
    
    _create: function () {
        this._refresh(true);
    },
    
    _setOption: function (key, value) {
        this._super(key, value);
        if (key === "active") {
            this._refresh(!this.options.animated);
        }
    }
});

Knockout Helper: subscribeOnce

ko.subscribable.fn.subscribeOnce = function (callback, target, event) {
    var ignore = false;
    this.subscribe(function (value) {
        if (ignore) {
            return;
        }
        ignore = true;
        callback.call(target, value);
        ignore = false;
    }, null, event);
};

function callback(value) {
    console.log(value);
    if (value < 3) {
        this(value + 1);
    }
    console.log(value);
}

var x = ko.observable(0);
x.subscribe(callback, x);
x(1);
console.log("x = " + x());

var y = ko.observable(0);
y.subscribeOnce(callback, y);
y(1);
console.log("y = " + y());

Output:

1
2
3
3
2
1
x = 3
1
1
y = 2

Update: February 18, 2015

There are a couple problems with changing an observable’s value in its own subscription. First, other subscriptions will see the intermediate writes to the observable. Second, the corresponding values will get sent to subsequent subscriptions (i.e., those made after the self-subscription) in reverse order.

var z = ko.observable(0);
z.subscribeOnce(function (value) {
    if (value < 3) {
        z(value + 1);
    }
});
z.subscribe(function (value) {
    console.log(value);
});
z(1);

Output:

2
1

So don’t use subscribeOnce. Instead, hide the observable behind a writable computed observable that performs the necessary preprocessing before setting a value.

var _z = ko.observable(0);
var z = ko.computed({
    read: _z,
    write: function (value) {
        if (value < 3) {
            value++;
        }
        _z(value);
    }
});
z.subscribe(function (value) {
    console.log(value);
});
z(1);

Output:

2