I am not sure if I am using Models and Collections correctly. If I'm not I would really appreciate any guidance or advice into what I am doing wrong.
I have set up a Model and a Collection. The Collection has a url which is executed using the .fetch() method. I pass the Collection to the View where I log the results to the console. When I console.log(this.model) in the View I see the attributes nested a few levels deep. I would like to see the attributes in the console.log. The .toJSON() method doe not seem to work.
Here's a Fiddle to my current code: http://jsfiddle.net/Gacgc/
Here is the JS:
(function () {
var DimensionsModel = Backbone.Model.extend();
var setHeader = function (xhr) {
xhr.setRequestHeader('JsonStub-User-Key', '0bb5822a-58f7-41cc-b8a7-17b4a30cd9d7');
xhr.setRequestHeader('JsonStub-Project-Key', '9e508c89-b7ac-400d-b414-b7d0dd35a42a');
};
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata'
});
var dimensionsCollection = new DimensionsCollection();
var DimensionsView = Backbone.View.extend({
el: '.js-container',
initialize: function (options) {
this.model.fetch({beforeSend: setHeader});
_.bindAll(this, 'render');
this.model.bind('reset', this.render());
return this;
},
template: _.template( $('#dimensions-template').html() ),
render: function () {
console.log( this.model.toJSON() ); //Why does this return an empty array???
return this;
}
});
var myView = new DimensionsView({model: dimensionsCollection});
}());
Is this what you're looking for?
If you're passing a collection to the view you should assign it to the collection property:
// It's a collection. Backbone views have a collection
// property. We should totally use that!
var myView = new DimensionsView({collection: dimensionsCollection});
When you attempt to bind the reset event to your view's render function, you're actually invoking the function immediately (by including the braces):
// Omit the braces to assign the function definition rather than invoke
// it directly (and immediately)
this.model.bind('reset', this.render);
But that's beside the point, because backbone's collection doesn't trigger a reset event (see documentation). One approach would be to assign the view's render function to the success parameter of the options object you pass to your collection:
var self = this;
this.collection.fetch({
beforeSend: setHeader,
success: function() {
self.render();
}
});
Finally, you need a parse function in your collection to pull the dimensions array out of the JSON you're loading:
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata',
parse: function(response) {
return response.dimensions;
}
});
Related
I'm new at reading Backbone js and I have some serious problems with passing arguments in Backbone js.
var Song = Backbone.Model.extend();
var Songs = Backbone.Collection.extend({
model: Song
});
var SongView = Backbone.View.extend({
el: "li",
render: function() {
this.$el.html(this.model.get("title"));
return this;
}
});
var SongsView = Backbone.View.extend({
el: "ul",
initialize: function() {
this.model.on("add", this.onSongAdded, this);
},
onSongAdded: function(song) { // when object is added to a collection add event is triggerd
// the handler for this event get an argument which is the object that was just added
//in this case it refers to a song model so we simply pass it to our songView which is responsible for rendering a song an then we use jquery append method
// to append it to our list
var songView = new SongView({
model: Song
});
this.$el.append(songView.render().$el);
},
render: function() {
var self = this;
this.model.each(function(song) { //
var songView = new SongView({
model: Song
});
self.$el.append(songView.render().$el);
});
}
});
var songs = new Songs([
new Song({
title: "1"
}),
new Song({
title: "2"
}),
new Song({
title: "3"
})
]);
var song_1 = new Song({
title: "hello"
});
var songsView = new SongsView({
el: "#songs",
model: Songs
});
songsView.render();
as you can see I have this function: onSongAdded
we have some built-in events such as add that get 3 arguments like this:
add(collection, model , options)
how can I use these arguments in my code?
can you help me?
el option is for pointing the view to an element already existing in DOM. Your item views should be creating new <li> elements so you should be using tagName option instead.
In your collection view constructor you've defined el option and you're passing a different el option while instantiating it. If #songs is a <uL> in DOM, then no use in defining el: "ul", in constructor.
Also, there is no need to manually instantiate models, you can just pass objects into collections and collection will do it internally. And don't pass collection as model, pass it as collection.
var Text = Backbone.Model.extend({});
Texts = Backbone.Collection.extend({
model: Text,
url: '/data.json',
});
var TextsView = Backbone.View.extend({
initialize: function() {
_.bindAll(this);
this.render();
},
el: "#Texts",
template: _.template($('#TextTemplate').html()),
render: function(e){
_.each(this.model.models, function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
return this;
}
})
var Texts = new Texts();
Texts.fetch();
var TextView = new TextsView({collection: Texts});
this gives me Uncaught TypeError: Cannot read property 'models' of undefined and does not display anything on the page.
This this.model.models should be this.collection
In your render method in your view, you should use this.collection.each instead of _.each function.
render: function(e){
this.collection.each(function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
return this;
}
If you want to use _.each function, then you will need to access the models array directly in your collection as #dfsq pointed out. This can be done by using this.collection.models.
render: function(e){
_.each(this.collection.models, function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
return this;
}
EDIT 2
Here are some reasons your fetch call may not be working. First check that you are using a web server, since ajax requests may be blocked for security reasons using file system. I know this is blocked in Chrome unless you change a certain setting. Not sure about Firefox.
The second reason is that the fetch call is asynchronous. This means that mostly likely your data will not be loaded when you run initialize
This means you'll need to make the following adjustments. First you need to add a listener to the add event of your collection so that anytime an item is added, your view will be notified.
initialize: function() {
_.bindAll(this);
this.render();
// Listen to the `add` event in your collection
this.listenTo(this.collection,"add", this.renderText);
},
Next we need to add a function to your view that will render a single item
renderText: function(Text) {
var TextTemplate = this.template(Text.toJSON());
this.$el.append(TextTemplate);
}
Also to answer your other question about the user of this in the each loop. The last parameter in the each function is the scope you want to use in the inside the callback function that executes. So if you use this as the second parameter, it allows you to access your viewing using this.
this.collection.each(function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
If you don't add this, then you'd need to do this:
var view = this;
this.collection.each(function(Text){
var TextTemplate = view.template(Text.toJSON());
$(view.el).append(TextTemplate);
});
I need help in trying to get attributes out of my model with backbone.js
Below is what I have tried so far. I connect to a REST URL and pull back data in json. I now want to display some of that data within the view. However, when I try print/console out club_url I get an undefined error. If I print out the test object itself I can see the value in the attributes section of the object.
Can someone tell me where I'm going wrong?
(function ($) {
var Model = Backbone.Model.extend({
urlRoot: '/api/test/',
initialize: function () {
this.club_url = this.club_url
}
});
var thisCollection = Backbone.Collection.extend({
urlRoot: '/api/test/',
model: Model
});
var PanelView = Backbone.View.extend({
el: '#reward_view',
initialize: function () {
_.bindAll(this, 'render');
this.collection = new thisCollection();
this.collection.bind('add', this.appendItem);
this.render();
},
render: function () {
var test = new thisCollection;
test.fetch();
console.log(test.get('club_url'))
return this;
}
});
var listView = new PanelView();
})(jQuery);
As another test I tried was to init something like this in the view
this.model = new Model()
this.model.fetch()
but then in the render function I did this:
this.model.get('club_url')
however this did not work either!
The fetching of data is async operation. So, I guess that you should wait for an event before to get club_url. I.e. something like that:
render: function () {
var test = new thisCollection;
test.fetch({
success: function(collection, response) {
console.log(test.get('club_url'))
}
});
return this;
}
Ok, so I am working on a method to override the fetch method on a model. I want to be able to pass it a list of URL's and have it do a fetch on each one, apply some processing to the results, then update its own attributes when they have all completed. Here's the basic design:
A Parent "wrapper" Model called AllVenues has a custom fetch function which reads a list of URL's it is given when it is instantiated
For each URL, it creates a Child Model and calls fetch on it specifying that URL as well as a success callback.
The AllVenues instance also has a property progress which it needs to update inside the success callback, so that it will know when all Child fetch's are complete.
And that's the part I'm having problems with. When the Child Model fetch completes, the success callback has no context of the Parent Model which originally called it. I've kind of hacked it because I have access to the Module and have stored the Parent Model in a variable, but this doesn't seem right to me. The Parent Model executed the Child's fetch so it should be able to pass the context along somehow. I don't want to hardcode the reference in there.
TL;DR
Here's my jsFiddle illustrating the problem. The interesting part starts on line 13. http://jsfiddle.net/tonicboy/64XpZ/5/
The full code:
// Define the app and a region to show content
// -------------------------------------------
var App = new Marionette.Application();
App.addRegions({
"mainRegion": "#main"
});
App.module("SampleModule", function (Mod, App, Backbone, Marionette, $, _) {
var MainView = Marionette.ItemView.extend({
template: "#sample-template"
});
var AllVenues = Backbone.Model.extend({
progress: 0,
join: function (model) {
this.progress++;
// do some processing of each model
if (this.progress === this.urls.length) this.finish();
},
finish: function() {
// do something when all models have completed
this.progress = 0;
console.log("FINISHED!");
},
fetch: function() {
successCallback = function(model) {
console.log("Returning from the fetch for a model");
Mod.controller.model.join(model);
};
_.bind(successCallback, this);
$.each(this.urls, function(key, val) {
var venue = new Backbone.Model();
venue.url = val;
venue.fetch({
success: successCallback
});
});
}
});
var Venue = Backbone.Model.extend({
toJSON: function () {
return _.clone(this.attributes.response);
}
});
var Controller = Marionette.Controller.extend({
initialize: function (options) {
this.region = options.region;
this.model = options.model;
this.listenTo(this.model, 'change', this.renderRegion);
},
show: function () {
this.model.fetch();
},
renderRegion: function () {
var view = new MainView({
model: this.model
});
this.region.show(view);
}
});
Mod.addInitializer(function () {
var allVenues = new AllVenues();
allVenues.urls = [
'https://api.foursquare.com/v2/venues/4a27485af964a52071911fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
'https://api.foursquare.com/v2/venues/4afc4d3bf964a520512122e3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
'https://api.foursquare.com/v2/venues/49cfde17f964a520d85a1fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811'
];
Mod.controller = new Controller({
region: App.mainRegion,
model: allVenues
});
Mod.controller.show();
});
});
App.start();
I think you're misunderstanding how _.bind works. _.bind returns the bound function, it doesn't modify it in place. In truth, the documentation could be a bit clearer on this.
So this:
_.bind(successCallback, this);
is pointless as you're ignoring the bound function that _.bind is returning. I think you want to say this:
var successCallback = _.bind(function(model) {
console.log("Returning from the fetch for a model");
Mod.controller.model.join(model);
}, this);
Also note that I added a missing var, presumably you don't want successCallback to be global.
I have a Backbone Collection that I'm trying to render in the View. The JSON data seems correct, however I can't access the values from within the view.
Here's the basic collection:
define(['backbone', 'BaseModel'], function(Backbone, BaseModel) {
var BaseCollection = Backbone.Collection.extend({
model: BaseModel,
url: "/collection/get?json=true",
initialize: function() {}
});
return BaseCollection;
});
Here's the View:
define(['backbone', 'BaseCollection'], function(Backbone, BaseCollection) {
var BaseView = Backbone.View.extend({
el: $('#baseContainer'),
template: _.template($('#baseTemplate').html()),
initialize: function() {
_.bindAll(this);
this.collection = new BaseCollection();
this.collection.bind('all', this.render, this);
this.collection.fetch();
},
render: function() {
//This returns 3 objects which is correct based on the JSON data being returned from the server
console.log(this.collection.toJSON());
var html = this.template(this.collection.toJSON());
this.$el.html(html);
return this;
},
});
return BaseView;
});
I think I need to iterate through this.render for each model within the collection. But, I'm not sure, because it shouldn't 'render' until it completes all iterations.
Any suggestions would be great! Thank you!
You need to give your template access to the models via name. When you do this:
var html = this.template(this.collection.toJSON());
You end up passing an array to the template function, which normally expects a context object (name/value pairs). Try this:
var html = this.template({collection: this.collection});
Then in your template you can iterate through them using the collection.each iterator function or any of the underscore utility methods for iteration/filtering/map/etc. I also recommend NOT using toJSON when giving your template access to the collection as it makes your data dumber and harder to work with. toJSON is best left for when you are making HTTP requests.