Reputation: 6373
I have a number of ko.observable and ko.observableArray objects - some of which can be complex / nested and which are defined early on in one of my applications but get called and populated asynchronously by data pushed when ready by the server. My client has callback methods which receive this data from the server and I then populate the various KO objects as and when required. (all this data is pushed via signalr).
I have a number of user entry forms where I need to ensure that the data is present before rendering the form. In the main this works quiet well as the data is populated early and the form accessed after some time.
However an issue arises when the user hits F5 and refreshes the browser. Now the background data has to be requested again and the form drop-downs populated, but the various data items take a second or so to get sent back by the server.
So, I need to be able to be able to delay the population of the form until ALL currently applicable observables have been updated.
I wrote an extension to Knockout observableArray as follows:
ko.observableArray.fn.await = function (callback) {
var items = this();
var hasValue = false;
function observe() {
if (typeof items !== "undefined" && items !== null && typeof items.length !== "undefined") {
for (var i = 0, len = items.length; i < len; i++) {
hasValue = (items[i]._destroy !== true);
if (hasValue) break;
}
}
if (!hasValue) {
setTimeout(observe, 80);
return;
}
callback();
}
observe();
};
a simple examples hows how this is used:
var categories = ko.observableArray([]);
...
OpenFormClicked = function(e) {
// delay if the data is not yet ready
categories.await( function() {
// any code that used category data
// Now show the popup;
popup.show();
});
}
This works well and I was about to write a similar ko.observabley.fn.await, but it then occurred to me that ko.subscribable.fn would be a better choice as I would then only have a single extension for all KO's in my app.
However, at this point I'm unsure what to check for. Really I just want to know if the subscribable has been populated and it's contents have been updated in some way, such that and that they are not the original / default values its was created with.
If I define an KO like this:
var userTaskData = ko.observable({
id: ko.observable(),
tasks: ko.observableArray([])
});
I would issue the callback from the await the above case if either the ID, Tasks or both have been updated with something...
Upvotes: 1
Views: 2156
Reputation: 6373
In the end I solved this using two wrapper classes; one for ko,observable and another for ko.observableArray and JQuery promise and I also wrote a utility function to allow more than one deferred KO to be awaited...
ko.deferredObservable = function (initValue) {
var result = ko.observable(initValue);
var dfd = new jQuery.Deferred();
var promise = dfd.promise();
result.subscribe(function (value) {
dfd.resolve();
});
result.await = function (callback) {
promise.then(callback);
};
result._promise = promise;
return result;
};
ko.deferredObservableArray = function (initValue) {
var result = ko.observableArray(initValue);
var dfd = new jQuery.Deferred();
var promise = dfd.promise();
result.subscribe(function (value) {
dfd.resolve();
});
result.await = function (callback) {
promise.then(callback);
};
result._promise = promise;
return result;
};
// Utility Extension for awaiting a defined KO or Array of KO's
ko.utils.await = function (arr, callback) {
var promisesArray = ([].concat(arr)).map(function(item) { return item._promise; });
var deff = $.when.apply($, promisesArray);
deff.then(callback);
};
Upvotes: 2