I show or hide a "Loading" indicator on my UI by binding its visibility to an observable named waiting
, which is defined like this:
// Viewmodel
var outstandingRequests = ko.observable(0);
// true if any requests are outstanding
var waiting = ko.computed(function() {
return outstandingRequests() > 0;
}.extend({ throttle: 500 });
// These are called when AJAX requests begin or end
function ajaxBegin() {
outstandingRequests(++outstandingRequests());
}
function ajaxEnd() {
outstandingRequests(--outstandingRequests());
}
<!-- View -->
<div data-bind="visible: waiting">Please wait, loading...</div>
I'm throttling the waiting
observable because I don't want the loading message to appear unless the request is taking a long time (>500ms in this case), to increase the perceived speed of the application. The problem is that once a long-running request finishes, the loading indicator doesn't disappear until an additional 500ms has passed. Instead, when the last outstanding request finishes, I want waiting
to flip to false immediately.
My first attempt at a fix involved using valueHasMutated()
, but the update is still delayed.
function ajaxEnd() {
outstandingRequests(--outstandingRequests());
// If that was the last request, we want the loading widget to disappear NOW.
outstandingRequests.valueHasMutated(); // Nope, 'waiting' still 500ms to update :(
}
How can I bypass the throttle extension and force waiting
to update immediately?
What you really want is to delay the notifications from the waiting
observable when it becomes true
. This can be done by intercepting the notifySubscribers
function of the observable:
var originalNotifySubscribers = this.isWaiting.notifySubscribers,
timeoutInstance;
this.isWaiting.notifySubscribers = function(value, event) {
clearTimeout(timeoutInstance);
if ((event === 'change' || event === undefined) && value) {
timeoutInstance = setTimeout(function() {
originalNotifySubscribers.call(this, value, event);
}.bind(this), 500);
} else {
originalNotifySubscribers.call(this, value, event);
}
};
jsFiddle: http://jsfiddle.net/mbest/Pk6mH/
EDIT:
I just thought of another, possibly better, solution for your particular case. Since the waiting
observable only depends on one other observable, you can create a manual subscription that updates the waiting
observable:
var timeoutInstance;
this.isLoading.subscribe(function(value) {
clearTimeout(timeoutInstance);
if (value) {
timeoutInstance = setTimeout(function() {
this.isWaiting(true);
}.bind(this), 500);
} else {
this.isWaiting(false);
}
}, this);
jsFiddle: http://jsfiddle.net/mbest/wCJHT/