Javad M. Amiri
Javad M. Amiri

Reputation: 732

Knockout.js nested model and observable arrays

I'm new to Knockout and have some problems at the beginning. Consider following model:

// @param {String} content Content of the post
// @param {number} reply_to ID indicating to which post this post is replying
// @param {Array} replies Array of DiscussionPosts indicating replies to this post 
var DiscussionPost(content, reply_to, replies) {
    var self = this;
    self.content = content;
    self.reply_to = reply_to;
    self.replies = ko.observableArray(
        ko.utils.arrayMap(replies, function(reply) {
            return new DiscussionPost(reply.content, reply.reply_to, reply.replies);
        })

}

Now for my view model, consider I have all my posts in a flat array called allPosts and try to build a structure with previous model as follows:

var rootPosts = allPosts.filter(function (o) {
    return o.reply_to === null;
});
var result = (function iterate(posts) {
    var tree = [];
    $.each(posts, function(i, post){
        var replies = allPosts.filter(function(o) {
            return o.reply_to === post.id;
        });
        if (replies.length > 0) {
            replies = iterate(replies);
        }

        var tmpPost = new DiscussionPost(post.content, post.reply_to, replies);
        tree.push(tmpPost);
    });
    return tree;
})(rootPosts);

Theoretically, the result variable shall contain graph of all posts with those posts that don't have any parent at root. For instance for the following tree it shall return an array with one element i.e.the root, then its replies should be C1 and C2 and array for replies of C1 shall contain only C3. Problem is that root is filled as expected, and its replies are C1 and C2, but replies of C1 returns an array with four elements of type DiscussionPost which all properties (content, reply_to, and replies) are undefined. If instead of Knockouts' observable array I use regular javascript array in my model everything works fine even at 100 level deep.

    root
    / \ 
   /   \
  C1   C2
 /
C3  

Upvotes: 0

Views: 476

Answers (1)

Tomalak
Tomalak

Reputation: 338148

How about a non-recursive approach?

function DiscussionPost(post) {
    var self = this;
    self.content = ko.observable(post.content);
    self.reply_to = ko.observable(post.reply_to);
    self.replies = ko.observableArray([]);
}

var result = (function (posts) {
    var index = {}, root = "null";

    // root level
    index[root] = new DiscussionPost({});

    // transform into DiscussionPost objects, index by ID
    ko.utils.arrayForEach(posts, function (post) {
        index[post.id] = new DiscussionPost(post); 
    });

    // build tree
    ko.utils.arrayForEach(posts, function (post) {
        if (post.reply_to in index) {
            index[post.reply_to].replies.push(index[post.id]);
        } else {
            // orphaned post
        }
    });

    return index[root].replies;
})(allPosts);

This should also perform nicer for larger numbers of posts. It's also easier to read and debug than your approach.

Upvotes: 1

Related Questions