You have two issues with order:
for.in
does not guarantee order;
- you fill the
observableArray
asynchronously in callbacks, but there is no guarantee that they will arrive in order you fire them off!
The latter is probably the most prominent issue. You currently push
results into your observable array, but you do so in the order by which the browser returns results.
Here's a slightly more minimal repro of your problem, with the differences callback timings exagerated:
// Mock Ajax stuff:
var $ = {
ajax: function(options) {
// Simulate unknown return moment with setTimeout:
setTimeout(function() {
options.success(options.url + " is a great city...");
}, Math.random() * 1500 + 50);
}
}
function ViewModel() {
var self = this;
self.items = ko.observableArray([]);
var places = ["london", "brussels", "paris"];
function wikiInit(place, callback) {
$.ajax({
url: "http://en.wikipedia.org/w/" + place,
success: function(result) {
callback(result);
}
});
}
for (var i = 0; i < places.length; i++) {
wikiInit(places[i], function(wikiString) {
self.items.push(wikiString);
console.log(self.items());
});
}
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: items">
<li data-bind="text: $data"></li>
</ul>
As you can see, every time your run this you have a chance the results are out of order.
Option 1 is to splice new items into the array in the position you want them in (instead of using push
). This is IMO a bit convoluted approach, and I'll leave the code to do so to the reader.
Option 2, and a probably easier approach is to prepare the array beforehand, and on the callbacks fill the array. E.g. like this:
// Mock Ajax stuff:
var $ = {
ajax: function(options) {
// Simulate unknown return moment with setTimeout:
setTimeout(function() {
options.success(options.url + " is a great city...");
}, Math.random() * 1500 + 50);
}
}
function Place(data) {
this.place = data.place;
this.wikistuff = ko.observable("");
}
function ViewModel() {
var self = this;
self.items = ko.observableArray([]);
var places = ["london", "brussels", "paris"];
function wikiInit(place, callback) {
var placeVm = new Place({ place: place });
self.items.push(placeVm);
$.ajax({
url: "http://en.wikipedia.org/w/" + place,
success: function(result) {
callback(result, placeVm);
}
});
}
for (var i = 0; i < places.length; i++) {
wikiInit(places[i], function(wikiString, placeVm) {
placeVm.wikistuff(wikiString);
});
}
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: items">
<li data-bind="text: wikistuff"></li>
</ul>
This result is always in order of the original array.
It currently always shows the bullets, even before the callback completed. I only did so to demonstrate how the solution works, but you could easily do this...
<li data-bind="text: wikistuff, visible: !!wikistuff()"></li>
...if needed.