0

Backbone 1.1.2
Underscore 1.7.0
jQuery 1.11.1

I have a single collection that holds messages. My messages can be be of different types (and the endpoints in the api are different for each type, but I have an endpoint that allows me to do one request and get all the messages)

When Collection.fetch() I need to be able to define which model to use when populating the collection based on existing properties.

I have tried as suggested here: A Backbone.js Collection of multiple Model subclasses as well as the backbone documentation backbonejs.org

My code looks like this

model: function (attr, options) {
    if(attr.hasOwnProperty('prop')){
        return new PropModel(attr,options);
    }
    else if(attr.hasOwnProperty('another_prop')){
        new AnotherPropModel(attr,options);
    }
},

the attr value is just one big array of objects, so without traversing somehow this solution makes no sense to me and its obvious why it doesn't work.

Am I handling this correctly is there another way to do this?

---UPDATE----

I have also tried doing this in the Parse Function of the collection and my collection is just empty

parse: function (resp, options) {
    _.each(resp, _.bind(function (r) {
        console.log(this);
        if(r.hasOwnProperty('prop')){
            this.add(new PropModel(r));
        }else{
            this.add(new AnotherPropModel(r));
        }
    },this));
}
Community
  • 1
  • 1
Beyerz
  • 787
  • 2
  • 9
  • 20
  • Are there key deference's between the models? Or is it just the URL? – anAgent Dec 30 '14 at 19:53
  • @anAgent The attributes for each are different, so I would like to typecast per model when rendering my template. I guess I could do this same logic in the template, but I prefer to keep logic out of the template so I can switch template engines easily if needed – Beyerz Dec 30 '14 at 19:57
  • Also the url is important for me as the attributes are different. Even though the messages can be grouped, they are also independent in other areas of my system – Beyerz Dec 30 '14 at 19:58
  • looks like you are on the right path - vary the fetch url of the collection if needed, and then in that function you should just need to return the model function (not newed) - `if (attr.modelType === 'mailItem') { return MailItemModel; }` – Dominic Dec 30 '14 at 20:02
  • @DominicTobias In the attr I dont have a model type. I have an array objects parsed from Json. – Beyerz Dec 30 '14 at 20:04
  • @Beyerz the server returns the json array of objects and in that structure (which is attrs) there should be information describing what model you want for it – Dominic Dec 30 '14 at 20:05
  • @DominicTobias I understand what you are saying, I tried r.hasOwnProperty('prop') to separate them and define the correct Model for each within the attrs – Beyerz Dec 30 '14 at 20:11
  • For parse to work, you still have to return the response. – anAgent Dec 30 '14 at 21:27

2 Answers2

0

You could do something like the following - reacting to the different type (if possible) and then provide a different URL. Then once the JSON models are in the template, then you can render the HTML the way you like:

Example Json

"[{"id":1,"Type":"Person"},{"id":2,"Type":"Business"}]"

Example Model

var Person = Backbone.Model.extend({

    keyTypes: {
        Person: 'Person',
        Business: 'Business'
    },

    url: function() {
        // we are assuming only two types. Change logic if there are three or more types.
        return this.get('Type') === this.keyTypes.Person ? '/api/people' : '/api/businesss';
    }

});

Collection

var Collection = Backbone.Collection.extend({
    model: Person
});

View

var View = Backbone.View.extend({

    initialize: function() {
        this.collection = new Collection()
            .on('fetch', this.render, this);
    },

    bootstrap: function() {
        this.collection.fetch();
    }

    render: function() {
        this.$el.html(_.template({
            models: this.collection.toJSON()
        }));
    }
})

** !! Update !! **

If you want to still use parse, it will could to look the following.

parse: function (data, options) {

    var models = [];
    _.each(data, function (entity) {
        // this is IE8 Safe....
        var model = Object.prototype.hasOwnProperty.call(entity,'prop') ? PropModel : AnotherPropModel;
        models.push(new model(entity));    
    });

    return models;
}
anAgent
  • 2,550
  • 24
  • 34
  • I see how this would resolve the multiple url endpoints, but I still dont understand how this defines the models for me so that I may typecast? – Beyerz Dec 30 '14 at 20:07
  • It answer the quest for type casting - so I agree with that it doesn't satisfy your exact question. I think if you start type casting you will start to run into other long term issues as well be harder to test. – anAgent Dec 30 '14 at 21:24
  • Im actually a fan of typecasting as this forces understanding and allows for quick failure. Your update makes sense, I forgot to mention that I am working on a chrome extension... so not too worried at the moment about cross browser compatibility. – Beyerz Jan 03 '15 at 18:51
0

So the solution was a mix of using model function and return.

Here goes the explanation:

First off we have the parse function which is just an entry point for us to alter a response that we receive from our server

parse: function (resp, options) {
    return resp;
}

In my case, the server was returning an Object of Object as

{{1:data},{2:data}}

Firstly this is strange and obviously needs to be resolved. The IMPORTANT POINT IS: When backbone assess the response return from parse, it needs to decide where to break off for each model as in what defines a new model.

Backbone sees objects as a single model and as in my case, I had one big object, I was getting one big model... this is why the attrs argument in model function was one big mush of data.

So I simply altered my response in the parse function and Voila!! everything in model function worked as expected:

Here is the code:

model: function (attr, options) {
    if(attr.hasOwnProperty('prop')){
        return new PropModel(attr,options);
    }
    else if (attr.hasOwnProperty('anotherProp')){
        return new AnotherPropModel(attr,options);
    }
},

parse: function (resp, options) {
    var response = [];
    _.each(resp, _.bind(function (r) {
        response.push(r);
    },this));
    return response;
}

Im sure there is a better way to resolve the object to array, but for now this works and I'm smiling again!!

This article lead me to the solution: A collection of muppets

Beyerz
  • 787
  • 2
  • 9
  • 20
  • Just a comment that you don't need to use _.bind since the third argument of _.each is the context. Secondly, you're not calling anything out of the scope of _.each so it's not needed. – anAgent Dec 30 '14 at 21:26
  • you are right!! I have removed the _.bind, I guess I just got into the habit of losing my scope, so I tend to bind callback functions as habit. – Beyerz Jan 03 '15 at 18:48