Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping JSON to backbone.js collections

Alright, it looks like I need a hint to point me in the right direction. This question is two part - working with mult-dimensional JSON and Collections of Collections from JSON.

Background

I have some JSON that is going to be retrieved from a server and have control over how it could be formatted.

Multi-Dimentional JSON

I'm having some trouble being able connecting the model to the parts in the JSON. Say I wanted to render just each of the posts author name, and the content of status in the sample JSON below. I'm having no problem getting the status into the model, but the author name I'm a bit confused how to get to it. From my understanding I have to override the parse.

Is this bad standards / is there a better JSON structure I should use? Would it be better to keep it as flat as possible? That is move the author name and photo up one level?

I was reading How to build a Collection/Model from nested JSON with Backbone.js but it is still a little unclear to me.

Collection in Collections

Is there a nice way to make a collection within a collection for backbone.js? I will have a collection of posts, and then would have a collection of comments on that post. As I'm developing in backbone is that even possible?

From what I understand in Backbone.js Collection of Collections and Backbone.js Collection of Collections Issue, it would look something like this?

var Comments = Backbone.Model.extend({
    defaults : {
      _id : "",
      text : "",
      author : ""
    }
})

var CommentsCollection = Backbone.Collection.extend({ model : Comments })

var Posts = Backbone.Model.extend({
    defaults : {
        _id : "",
        author : "",
        status : "",
        comments : new CommentsCollection
    }
})

var PostsCollection = Backbone.Collection.extend({ model : Posts })

Sample JSON

{
"posts" : [
    {
        "_id": "50f5f5d4014e045f000002",
        "author": {
            "name" : "Chris Crawford",
            "photo" : "http://example.com/photo.jpg"
        },
        "status": "This is a sample message.",
        "comments": [
                {
                    "_id": "5160eacbe4b020ec56a46844",
                    "text": "This is the content of the comment.",
                    "author": "Bob Hope"
                },
                {
                    "_id": "5160eacbe4b020ec56a46845",
                    "text": "This is the content of the comment.",
                    "author": "Bob Hope"
                },
                {
                ...
                }
        ]
    },
    {
        "_id": "50f5f5d4014e045f000003",
        "author": {
            "name" : "Chris Crawford",
            "photo" : "http://example.com/photo.jpg"
        },
        "status": "This is another sample message.",
        "comments": [
                {
                    "_id": "5160eacbe4b020ec56a46846",
                    "text": "This is the content of the comment.",
                    "author": "Bob Hope"
                },
                {
                    "_id": "5160eacbe4b020ec56a46847",
                    "text": "This is the content of the comment.",
                    "author": "Bob Hope"
                },
                {
                ...
                }
        ]
    },
    {
    ...
    }
]}

I appreciate even any hints to guild me. Thanks!

like image 924
Sir.Nathan Stassen Avatar asked Jul 26 '13 20:07

Sir.Nathan Stassen


People also ask

Is Backbone JS still used?

Backbone. Backbone has been around for a long time, but it's still under steady and regular development. It's a good choice if you want a flexible JavaScript framework with a simple model for representing data and getting it into views.

What is collections in Backbone JS?

Collections are ordered sets of Models. We just need to extend the backbone's collection class to create our own collection. Any event that is triggered on a model in a collection will also be triggered on the collection directly.

Is Backbone JS frontend or backend?

Backend Synchronization BackboneJS is use with the front-end and back-end systems, allows the synchronization with the backend to provide support to RESTful APIs.


2 Answers

It can be overwhelming when trying to write up code to make it work for nested objects. But to make it simpler lets break it up into smaller manageable pieces.

I would think in these lines.

Collections

 Posts
 Comments

Models

 Post
 Comment
 Author

Main collection --  Posts collection
                    (Which contains list of Post Models)

And each model in the Posts collection will have 3 sets of attributes(May not be the right term).

1st - level of attributes (status , id).

2nd - Author attribute which can be placed in a separate Model(Authod Model).

3rd - Collection of comments for each Post Model.

Collection in Collections would be a bit confusing here. As you would have Models in Collection(Post Model inside Posts Collection) and each Model will nest a collection again(Comments collection inside Post Model). Basically you would be Handling a Collection inside a Model.

From my understanding I have to override the parse.

Is this bad standards / is there a better JSON structure I should use?

It is a perfectly plausible solution to handle the processing this in the Parse method. When you initialize a Collection or a Model , Parse methods is first called and then initialize is called. So it is perfectly logical to handle the logic inside the Parse method and it is not at all a bad standard.

Would it be better to keep it as flat as possible?

I don't think it is a good idea to keep this flat at a single level, as the other data is not required on the first level in the first place.

So the way I would approach this problem is write up the parse method in the Post Model which processes the response and attach the Author model and Comments collection directly on the Model instead as an attribute on the Model to keep the attributes hash clean consisting of 1st level of Post data. This I feel will be cleaner and lot more scalable on the long run.

var postsObject = [{
    "_id": "50f5f5d4014e045f000002",
        "author": {
        "name": "Chris Crawford",
        "photo": "http://example.com/photo.jpg"
    },
        "status": "This is a sample message.",
        "comments": [{
        "_id": "5160eacbe4b020ec56a46844",
            "text": "This is the content of the comment.",
            "author": "Bob Hope"
    }, {
        "_id": "5160eacbe4b020ec56a46845",
            "text": "This is the content of the comment.",
            "author": "Bob Hope"
    }]
}, {
    "_id": "50f5f5d4014e045f000003",
        "author": {
        "name": "Brown Robert",
            "photo": "http://example.com/photo.jpg"
    },
        "status": "This is another sample message.",
        "comments": [{
        "_id": "5160eacbe4b020ec56a46846",
            "text": "This is the content of the comment.",
            "author": "Bob Hope"
    }, {
        "_id": "5160eacbe4b020ec56a46847",
            "text": "This is the content of the comment.",
            "author": "Bob Hope"
    }]
}];

// Comment Model
var Comment = Backbone.Model.extend({
    idAttribute: '_id',
    defaults: {
        text: "",
        author: ""
    }
});

// Comments collection
var Comments = Backbone.Collection.extend({
    model: Comment
});

// Author Model
var Author = Backbone.Model.extend({
    defaults: {
        text: "",
        author: ""
    }
});

// Post Model
var Post = Backbone.Model.extend({
    idAttribute: '_id',
    defaults: {
        author: "",
        status: ""
    },
    parse: function (resp) {
        // Create a Author model on the Post Model
        this.author = new Author(resp.author || null, {
            parse: true
        });
        // Delete from the response object as the data is
        // alredy available on the  model
        delete resp.author;
        // Create a comments objecton model 
        // that will hold the comments collection
        this.comments = new Comments(resp.comments || null, {
            parse: true
        });
        // Delete from the response object as the data is
        // alredy available on the  model
        delete resp.comments;

        // return the response object 
        return resp;
    }
})
// Posts Collection 
var Posts = Backbone.Collection.extend({
    model: Post
});

var PostsListView = Backbone.View.extend({
    el: "#container",
    renderPostView: function(post) {
        // Create a new postView
        var postView = new PostView({
            model : post
        });
        // Append it to the container
        this.$el.append(postView.el);
        postView.render();
    },
    render: function () {
        var thisView = this;
        // Iterate over each post Model
        _.each(this.collection.models, function (post) {
            // Call the renderPostView method
            thisView.renderPostView(post);
        });
    }
});


var PostView = Backbone.View.extend({
    className: "post",
    template: _.template($("#post-template").html()),
    renderComments: function() {
        var commentsListView = new CommentsListView({
            // Comments collection on the Post Model
            collection : this.model.comments,
            // Pass the container to which it is to be appended
            el : $('.comments', this.$el)
        });
        commentsListView.render();        
    },
    render: function () {
        this.$el.empty();
        //  Extend the object toi contain both Post attributes
        // and also the author attributes
        this.$el.append(this.template(_.extend(this.model.toJSON(),
            this.model.author.toJSON()
       )));
       // Render the comments for each Post
       this.renderComments();
    }
});

var CommentsListView = Backbone.View.extend({
    renderCommentView: function(comment) {
        // Create a new CommentView
        var commentView = new CommentView({
            model : comment
        });
        // Append it to the comments ul that is part
        // of the view
        this.$el.append(commentView.el);
        commentView.render();
    },
    render: function () {
        var thisView = this;
        // Iterate over each Comment Model
        _.each(this.collection.models, function (comment) {
            // Call the renderCommentView method
            thisView.renderCommentView(comment);
        });
    }
});


var CommentView = Backbone.View.extend({
    tagName: "li",
    className: "comment",
    template: _.template($("#comment-template").html()),
    render: function () {
        this.$el.empty();
        this.$el.append(this.template(this.model.toJSON()));
    }
});

// Create a posts collection 
var posts = new Posts(postsObject, {parse: true});

// Pass it to the PostsListView
var postsListView = new PostsListView({
    collection: posts
});
// Render the view
postsListView.render();

Check Fiddle

like image 200
Sushanth -- Avatar answered Oct 07 '22 16:10

Sushanth --


(Edited to correct my initial misreading of the question.)

There's no need to override parse method of the model unless you want to change its structure. But it sounds like you don't need to -- to render the author name, just use author.name in the view:

<%= author.name %>

As far as initializing the nested collection, your approach is exactly right. All you have to do is convert the JSON object to Backbone models, and pass them to the PostsCollection (the Backbone.Collection constructor accepts an array of Backbone models, not raw JSON). One way of doing this is to use map:

var postModels = json.posts.map(function(post) { return new Posts(post); });
var posts = new PostsCollection(postModels);

Note that you'll need to do something similar in the initialize method of the Posts model -- retrieve the comments JSON array, and convert it to an array of Comments models:

initialize: function() {
    if (attributes.comments && attributes.comments.length > 0) {
        var commentModels = attributes.comments.map(function(comment) { 
            return new Comments(comment); 
        });
        this.set("comments", new CommentsCollection(commentModels));
    }
}

Here is working example.

like image 40
McGarnagle Avatar answered Oct 07 '22 15:10

McGarnagle