Question

So I've got an observablearray that works fine, but the UI doesn't update. I've read lots of people running into this TYPE of issue, but I'm not seeing it.

So the HTML is

            <tbody data-bind="foreach: tweets">
            <tr>
                <td>
                    <span data-bind="visible: created_at" class="label label-success">Yup</span>
                </td>
                <td><p><b data-bind="text: screen_name"></b></p></td>
                <td><p><b data-bind="text: id"></b></p></td>
                <td><p data-bind="text: text"></p></td>
                <td><p data-bind="text: created_at"></p></td>
            </tr>
            </tbody>

And the Javascript is a function that calls an API and builds an array from it.

<script type="text/javascript">
        function TweetsViewModel() {
            var self = this;
            self.tasksURI = 'http://localhost:8000/api/v1/tweet/';
            self.tweets = ko.observableArray();

            self.ajax = function(uri, method, data) {
                var request = {
                    url: uri,
                    type: method,
                    contentType: "application/json",
                    Accept: "application/json",
                    cache: false,
                    dataType: 'json',
                    data: JSON.stringify(data)
                };
                return $.ajax(request);
            }

            self.ajax(self.tasksURI, 'GET').done(function(data) {
                for (var i = 0; i < data.objects.length; i++) {
                    var tweet = this;
                    tweet.created_at = ko.observable(data.objects[i].created_at)
                    tweet.text = ko.observable(data.objects[i].text)
                    tweet.id = ko.observable(data.objects[i].id)
                    tweet.screen_name = ko.observable(data.objects[i].twitteruser.screen_name)
                    self.tweets.push(tweet)
                }
            });
            setTimeout(TweetsViewModel, 10000)
        }
        ko.applyBindings(new TweetsViewModel());
    </script>

Just for testing purposes, I'm using the setTimeout to just rerun the call to the API and update the array. The Array gets updated properly, but the UI doesn't. I apologize for my ignorance (been a backend dev for a longtime, this is my first run at Javascript in 10 years). Any help much appreciated!

Was it helpful?

Solution

The problem is that you're never updating the observableArray.

To start with, you're calling the constructor again, but the this reference is probably not pointing to the object you think. When you call the TweetsViewModel constructor again (in the setTimeout call) the this reference will point to the window object.

Even if you get the this reference to point to the right object (which is possible using the apply method that functions have, but that's beside the point) you would still not be updating the observableArray, since you'd be setting the self.tweets variable to a new observableArray on the line

self.tweets = ko.observableArray();

All subscriptions, e.g. the UI DOM elements, would still be subscribed to the old observableArray since they would not know that the variable has changed.

So what you should probably do is to create a function which performs reload of the data, like the following:

self.reloadData = function(){
    self.ajax(self.tasksURI, 'GET').done(function(data) {
        for (var i = 0; i < data.objects.length; i++) {
            // Important: Create a new tweet object instead of using 'this' here.
            var tweet = {};
            tweet.created_at = ko.observable(data.objects[i].created_at)
            tweet.text = ko.observable(data.objects[i].text)
            tweet.id = ko.observable(data.objects[i].id)
            tweet.screen_name = ko.observable(data.objects[i].twitteruser.screen_name)
            self.tweets.push(tweet)
        }
    });
}
setTimeout(self.reloadData, 10000);

Also, be aware that this would always add tweets to the observableArray (never clear it) and also it would cause the subscriptions to the observableArray to fire once per tweet added. If you would want to replace the array you should probably create a replacement array, push tweets into that array and finally replace the array in the observableArray by doing self.tweets(replacementArray);.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top