1

Scenario

I am working on backbone app. What is happening right now is when user clicks edit link on page then it should show a form. I am trying to implement this using backbone routers rather than events. With events object it works perfectly fine. To use routers, I am using global events.

Problem

The problem is that when user clicks on edit link, it shows me following error in console

Uncaught TypeError: Object 10 has no method 'toJSON'             views.js:57

This error is because on line 57 in views.js, I am using this.model.toJSON() whereas I am not passing model via router. I don't know how pass model through router

Here is my router. Note: All of the following codes are in separate files

App.Router = Backbone.Router.extend({
    routes: {
        'contacts/:id/edit': 'editContact'
    },

    editContact: function (id) {
        console.log('yahhhhh');
        vent.trigger('contact:edit', id);
    }
});

In above router I am triggering an event inside editContact function. Then I am listening to above event in following initialize function.

App.Views.App = Backbone.View.extend({
    initialize: function () {
        vent.on('contact:edit', this.editContact, this);
    },

    editContact: function (contact) {
        var editContactView = new App.Views.EditContact({ model: contact });
        $('#editContact').html(editContactView.render().el);
    }
});

Now in above after listening to event in initialize function, I am calling editContact function and I am also passing model using this keyword. Inside editContact function, I am creating an instance of EditContact, view which is following, and then rendering a form which needs to be shown.

App.Views.EditContact = Backbone.View.extend({
    template: template('editContactTemplate'),

    render: function () {
        var html = this.template(this.model.toJSON());  //<--- this is line 57
        this.$el.html(html);
        return this;
    }
});

After doing all of the above, the form is not shown and I am getting above mentioned error.

Question

How do I pass model to render function inside EditContact via router so that it starts working?

UPDATE

Here is my model

App.Models.Contact = Backbone.Model.extend({
    urlRoot : '/contacts'
});
Om3ga
  • 30,465
  • 43
  • 141
  • 221

1 Answers1

1

In your editContact method the argument contact refers to the id you pass onwards from the router. When you initialize a new view with new App.Views.EditContact({ model: contact }) the model of the view will, expectedly, be the id.

You need to map the id into a model instance. IMHO the correct place to do this is in the router:

editContact: function (id) {
    var contact = new App.Models.Contact({id:id});
    vent.trigger('contact:edit', contact);
}

Notice that at this point the model will only have the id property set. If you need to populate the model properties for editing, you should fetch the model from the server, and only then trigger the event:

editContact: function (id) {
    var contact = new App.Models.Contact({id:id});
    contact.fetch().done(function() {
      vent.trigger('contact:edit', contact);
    });
}

Edit based on comments: Generally speaking you shouldn't pass anything to the router. The router should be a starting point for every new request (url change). If you want to hold some state between page changes, you should store the data on the router level, and pass the models and collections "down" from the view.

In a simplified scenario this would mean initializing and storing a reference to the collection in the router. Something like:

var Router = Backbone.Router.extend({
  initialize: function() {
    this.contactCollection = new App.Collections.Contacts();
  },

  editContact: function (id) {
    id = parseInt(id, 10);
    if(_.isNaN(id)) {
      //error...
    }
    //try to get a model from the collection
    var contact = this.contactCollection.get(id);

    //if not found, create, add and fetch
    if(!contact) {
      contact = new App.Models.Contact({id:id});
      this.contactCollection.add(contact);
      contact.fetch().done(function() {
        vent.trigger('contact:edit', contact);
      });
    } else {
      vent.trigger('contact:edit', contact);
    }
  }
});

Please note that this is just example code, and not necessarily how you should implement it, line by line. You should consider whether it's OK to display a potentially stale model in the view, or whether you should always fetch it from the server. In practice you might also abstract the collection state in a nice class, instead of handling it directly in the router.

Hope this answers your questions.

jevakallio
  • 35,324
  • 3
  • 105
  • 112
  • I am using your second code above. But it is giving me this error `Uncaught Error: A "url" property or function must be specified`. Where can I specify `url`? – Om3ga Feb 03 '13 at 11:26
  • I am using urlRoot: '/contacts' but it is still giving the same error. – Om3ga Feb 03 '13 at 11:35
  • Is there any other way to achieve this? – Om3ga Feb 03 '13 at 12:31
  • @x4ph4r, it seems that if specifying `urlRoot` doesn't work, there's something else wrong and should be fixed. Are you creating the model with `new` as in my example, or are you getting it from a collection / adding to a collection? If model is part of a collection, then `Collection.url` should be used. – jevakallio Feb 03 '13 at 12:49
  • I am using the `new` keyword as in your example. Do you mean I need to create a new instance of Collection like this `var contact = new App.Collections.Contacts({id:id})`? – Om3ga Feb 03 '13 at 12:55
  • this does work `var contact = new App.Collections.Contact({id:id})`, however, when the form is shown I also want the form fields to have values in it but in this case they have these objects `[object HTMLInputElement]` – Om3ga Feb 03 '13 at 13:02
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/23856/discussion-between-fencliff-and-x4ph4r) – jevakallio Feb 03 '13 at 13:05
  • By the way one question, Why would I send request to the server to retrieve data as I already have that same data in a model? Isn't it a bad practice? – Om3ga Feb 03 '13 at 14:00
  • The answer says **if you need to**. It doesn't say you do. – jevakallio Feb 03 '13 at 14:43
  • Could you elaborate your response? I do not understand. – Om3ga Feb 03 '13 at 14:45
  • If you already have the model data on the client, then you do not need to fetch it. If you don't have it, you need to fetch it. – jevakallio Feb 03 '13 at 15:07
  • That was my question on this post btw. How can we pass the model in router to use it later? – Om3ga Feb 03 '13 at 15:18
  • @x4ph4r, added some more detailed explanation. – jevakallio Feb 03 '13 at 21:39