Search code examples
javascripthtmlknockout.jsknockout-templating

Knockout JS failed to update observableArray


So I'm trying to add content to an observable array, but it doesn't update. The problem is not the first level content, but the sub array. It's a small comments section. Basically I've this function to declare the comments

function comment(id, name, date, comment) {
    var self = this;
    self.id = id;
    self.name = ko.observable(name);
    self.date = ko.observable(date);
    self.comment = ko.observable(comment);
    self.subcomments = ko.observable([]);
}

I've a function to retrieve the object by the id field

function getCommentByID(id) {
    var comment = ko.utils.arrayFirst(self.comments(), function (comment) {
        return comment.id === id;
    });
    return comment;
}

This is where I display my comments

<ul style="padding-left: 0px;" data-bind="foreach: comments">
    <li style="display: block;">
        <span data-bind="text: name"></span>
        <br>
        <span data-bind="text: date"></span>
        <br>
        <span data-bind="text: comment"></span>
        <div style="margin-left:40px;">
            <ul data-bind="foreach: subcomments">
                <li style="display: block;">
                    <span data-bind="text: name"></span>
                    <br>
                    <span data-bind="text: date"></span>
                    <br>
                    <span data-bind="text: comment"></span>
                </li>
            </ul>
            <textarea class="comment" placeholder="comment..." data-bind="event: {keypress: $parent.onEnterSubComment}, attr: {'data-id': id }"></textarea>
        </div>
    </li>
</ul>

And onEnterSubComment is the problematic event form

self.onEnterSubComment = function (data, event) {
    if (event.keyCode === 13) {
        var id = event.target.getAttribute("data-id");
        var obj = getCommentByID(parseInt(id));
        var newSubComment = new comment(0, self.currentUser, new Date(), event.target.value);
        obj.subcomments().push(newSubComment);
        event.target.value = "";
    }
    return true;
};

It's interesting, because when I try the same operation during initialization(outside of any function) it works fine

var subcomment = new comment(self.commentID, "name1", new Date(), "subcomment goes in here");
self.comments.push(new comment(self.commentID, "name2", new Date(), "some comment goes here"));
obj = getCommentByID(self.commentID);
obj.subcomments().push(subcomment);

If anyone can help me with this, cause I'm kind of stuck :(


Solution

  • You need to make two changes:

    1st, you have to declare an observable array:

    self.subcomments = ko.observableArray([]);
    

    2nd, you have to use the observable array methods, instead of the array methods. I.e. if you do so:

    obj.subcomments().push(subcomment);
    

    If subcomments were declared as array, you'd be using the .push method of Array. But, what you must do so that the observable array detect changes is to use the observableArray methods. I.e, do it like this:

    obj.subcomments.push(subcomment);
    

    Please, see this part of observableArray documentation: Manipulating an observableArray:

    observableArray exposes a familiar set of functions for modifying the contents of the array and notifying listeners.

    All of these functions are equivalent to running the native JavaScript array functions on the underlying array, and then notifying listeners about the change