Hi I am new to backbone and I have a page where I want to display information about a model. When I try to access it on the console it works out fine
Ex:
collection = new Poster.Colections.Posts()
collection.fetch({reset: true})
post = collection.get(1)
Above works fine
Now when I try to do it in a function on the routers page, it returns back undefined
posts_router.js
class Poster.Routers.Posts extends Backbone.Router
routes:
'posts':'index'
'':'index'
'posts/new': 'new'
'posts/:id': 'show'
initialize: ->
#collection = new Poster.Collections.Posts()
#collection.fetch({reset: true})
index: ->
view = new Poster.Views.PostsIndex(collection: #collection)
$('#index_container').html(view.render().el)
show: (id) ->
post = #collection.get(id)
view = new Poster.Views.PostsShow(model: post)
$('#index_container').html(view.render().el)
new: ->
view = new Poster.Views.PostsNew(collection: #collection)
$('#index_container').html(view.render().el)
I am really new to javascript and backbone and I am lost. Can someone please help
As mu already stated fetch is an asynchronous ajax call. So you have to wait for results and then do something. backbone gives you two options
First option: Call fetch with callback object
fetch takes a callback object with success or error
collection = new Poster.Collections.Posts()
collection.fetch
success: ->
console.log 'Collection fetched'
post = collection.get(1)
error: ->
console.error 'Unable to fetch collection'
Second Option: Listen for changes on the collection
When a model is added to a collection (which is done when fetching a collection) a add event is triggered
collection = new Poster.Collections.Posts.extend
initialize: ->
#on 'add', (model) =>
console.log "model #{model} added"
collection.fetch()
List of built-in events
Related
Can someone tell me how to re fetch a Backbone collection after calling collection's create function when I create a new model?
When I call fetch on my collection after creating new model, sometimes I'm getting that model and sometimes not.
My problem is when I create a new model in my collection, I'm not getting the id back of my model and then I can't update it immediately, I need to refresh the page and then I got the id of the created model.
I tried with listenTo but I can't use it because I need to send more collections to one function.
And that my view for my bootstrap modal, on save I'm creating my model it persists to database and I'm getting all attributes in my console when I create it except models id.
Backbone view:
app.types.EditView = Backbone.View.extend({
tagName: "div",
$container: $('#containerEdit'),
template: _.template($('#itemEdit-template').html()),
events:
{
"click .save": "save",
},
initialize: function(options)
{
this.options = options;
this.$container.html(this.render());
this.start();
this.end();
},
render: function()
{
this.$el.html(this.template());
return this.$el;
},
save: function()
{
console.log("save");
$('#openModal').modal('hide');
var dan = this.model.dan_u_tjednu_usera.datum;
var mjesec = this.model.dan_u_tjednu_usera.mjesecBrojevi;
var godina = this.model.dan_u_tjednu_usera.godina;
var start = $("#start").val();
var end = $("#end").val();
var user_id = this.model.user.id;
this.model.shifts.create({day: dan, month: mjesec, year: godina, time_from: start, time_to: end, user_id: user_id});
this.options.model.el.html($("<td href='#openModal' width='25%' align='center' class='list-group test' scope='row'>" + start + " - " + end + " " + "Admin" + "</td>"));
this.model.shifts.fetch({sync: true});
console.log("test", this.model.shifts);
}
Here you can see that in my response im not getting the id attribute, on create.
And here you can see when i click on my cell i log my collection and i have not the id attribute of the created model here. And im not getting the id attribute it too when i log this.model
This is because the request sent to the server when you call Collection.create is asynchronous, the Javascript code will continue to execute before the server receives and responds to the request.
If you want to have the Model updated with the ID coming back from the server, you can specify {wait: true} in the Collection.create call. This will mean that the Collection will not have the Model added straight away, but instead only when the server responds (successfully).
In this case you should not run the fetch immediately afterwards, as it will also need to wait for the create operation to complete. You should setup any following actions to trigger when the create operation has completed. Here is an example:
var model = collection.create({field: 'abc'}, {wait: true});
model.once('sync', function() { window.alert(model.id); });
Backbone's create
Convenience to create a new instance of a model within a collection.
Equivalent to instantiating a model with a hash of attributes, saving
the model to the server, and adding the model to the set after being
successfully created.
There's no need to fetch a collection after a create, the model id and any other field are automatically merged within its attributes hash.
Fetch after model creation
While mikeapr4 is not wrong, his example could be improved.
The { wait: true } is unnecessary if the only problem comes from the fetch, not from the model already being inside the collection.
Also, once should be avoided as it's the "old" way, and instead listenToOnce should be used. See Difference between ListenTo and on.
If you really want to fetch once a model is created, using events is overkill here, and instead, using the success callback is best:
save: function() {
// ..snip...
this.model.shifts.create({ /* ...snip... */ }, {
context: this,
success: this.onModelCreated
});
},
onModelCreated: function() {
// the model is now created and its attributes are up-to-date
this.model.shifts.fetch();
}
Other notes on your code
There are no sync option in Backbone. Only a "sync" event and a sync function.
Avoid using the global jQuery selector (like $('.class-name')) and instead, whenever the element is within the view's element, use this.$('.class-name').
Also, cache the jQuery element to avoid the costly search of the find method.
Like $("#start") could be cache and reused. Only reset the cached elements when re-rendering.
The Backbone .render function should return this by convention.
Then, your rendering call should look like:
this.$container.html(this.render().el); // el is enough
I want to display a simple list of languages.
class Language extends Backbone.Model
defaults:
id: 1
language: 'N/A'
class LanguageList extends Backbone.Collection
model: Language
url: '/languages'
languages = new LanguageList
class LanguageListView extends Backbone.View
el: $ '#here'
initialize: ->
_.bindAll #
#render()
render: ->
languages.fetch()
console.log languages.models
list_view = new LanguageListView
languages.models appears empty, although I checked that the request came in and languages were fetched. Am I missing something?
Thanks.
The fetch call is asynchronous:
fetch collection.fetch([options])
Fetch the default set of models for this collection from the server, resetting the collection when they arrive. The options hash takes success and error callbacks which will be passed (collection, response) as arguments. When the model data returns from the server, the collection will reset.
The result is that your console.log languages.models is getting called before the languages.fetch() call has gotten anything back from the server.
So your render should look more like this:
render: ->
languages.fetch
success: -> console.log languages.models
# # Render should always return #
That should get you something on the console.
It would make more sense to call languages.fetch in initialize and bind #render to the collection's reset event; then you could put things on the page when the collection is ready.
Also, _.bindAll # is rarely needed with CoffeeScript. You should create the relevant methods with => instead.
Following Backbone/Marionette Controller and Collection won't fetch.
define(["jquery", "backbone","models/Poi"],
function($, Backbone, Poi) {
// Creates a new Backbone Poi class object
var PoiCollection = Backbone.Collection.extend({
model:Poi,
parse: function (response) {
console.log(response);
// Return people object which is the array from response
return response;
}
});
// Returns the Poi class
return PoiCollection;
}
);
define(['App', 'backbone', 'marionette', 'views/MapView', 'views/DesktopHeaderView', 'views/DesktopFooterView', 'models/Poi'],
function (App, Backbone, Marionette, MapView, DesktopHeaderView, DesktopFooterView, Poi) {
return Backbone.Marionette.Controller.extend({
initialize: function (options) {
App.headerRegion.show(new DesktopHeaderView());
App.mainRegion.show(new MapView());
App.footerRegion.show(new DesktopFooterView());
},
//gets mapped to in AppRouter's appRoutes
index: function () {
console.log("Ajax::list of POI");
var p = new Poi();
p.fetch({
success: function (data) {
console.log("data");
}
});
console.log(p);
}
});
});
I have no Idea where to look to debug this. The Network tab tells me that the data was fetched, but the success method is never called.
Thanks
I think your fetch call itself looks OK, but two other apparent bugs could be affecting that call:
1) Your log message in the index function says "list of Poi", but you're using a (single) Poi instance -- should that be PoiCollection instead? I'm assuming the Poi model (not shown above) is for a single item.
2) There's no url property in the PoiCollection, so if you did fetch a PoiCollection instead, that call would fail because PoiCollection doesn't know what URL to use. The most common pattern with Collection + related Model is to put an url only in the Collection, and no url in the single Model for the Collection's individual items (Poi in this case). Backbone will construct the corresponding individual-model URLs as needed based on the parent Collection's url. I think getting the url straightened out will help here.
Finally, one more thing: the fist parameter passed to the fetch call's success function is the Model or Collection instance itself, not the raw data object. That's not relevant for the current success code you have now (you're only logging a static string), but it will be relevant as soon as you try using that parameter. :-)
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'
});
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.
I want to display a simple list of languages.
class Language extends Backbone.Model
defaults:
id: 1
language: 'N/A'
class LanguageList extends Backbone.Collection
model: Language
url: '/languages'
languages = new LanguageList
class LanguageListView extends Backbone.View
el: $ '#here'
initialize: ->
_.bindAll #
#render()
render: ->
languages.fetch()
console.log languages.models
list_view = new LanguageListView
languages.models appears empty, although I checked that the request came in and languages were fetched. Am I missing something?
Thanks.
The fetch call is asynchronous:
fetch collection.fetch([options])
Fetch the default set of models for this collection from the server, resetting the collection when they arrive. The options hash takes success and error callbacks which will be passed (collection, response) as arguments. When the model data returns from the server, the collection will reset.
The result is that your console.log languages.models is getting called before the languages.fetch() call has gotten anything back from the server.
So your render should look more like this:
render: ->
languages.fetch
success: -> console.log languages.models
# # Render should always return #
That should get you something on the console.
It would make more sense to call languages.fetch in initialize and bind #render to the collection's reset event; then you could put things on the page when the collection is ready.
Also, _.bindAll # is rarely needed with CoffeeScript. You should create the relevant methods with => instead.