In Backbone.js, how can i retrieve a veriable that is returned by a model function after the fetch has completed? As I understand the model cannot communicate with the view, so how can the view listen in on the model specific function fetch?
Thanks!
My attempt to do this can be seen below (if you see code mistakes, don't mind them, I wrote this as an example of what I'm trying to do):
var ScheduleModel = Backbone.Model.extend({
urlRoot: '/api/schedule/1',
getSubjectData: function(){
this.fetch({
success: function(data, scheduleData){
return scheduleData;
}
});
}
});
var ScheduleView = Backbone.View.extend({
initialize: function(){
console.log(this.model.getSubjectData());
}
});
You can listen to several model events with listenTo:
http://backbonejs.org/#Events-listenTo
since model.fetch triggers the 'change' event (http://backbonejs.org/#Model-fetch) somewhat similar in your view code:
var ScheduleView = Backbone.View.extend({
initialize: function(){
console.log(this.model.getSubjectData());
this.listenTo(this.model, 'change', this.doSmthng);
},
doSmthng: function () {
// ....
}
});
should fire the doSmthng models' fetch is done.
You can do fetch inside view like this.
var ScheduleView = Backbone.View.extend({
initialize: function(){
this.model.fetch({success: function() {
//you can do your stuff here.
//Try to get model data using `model.get`.
}});
}
}
and,
As I understand the model cannot communicate with the view.
This is wrong. You can set like this inside your view.
this.model.view = this;
and you can access view in your model as like this.
this.view
But in my apps i am not doing this. Accessing view inside model will collapse the purpose of backbone.
Related
I have a controller:
var layout = new LayoutView();
App.holder1.show(layout);
var my_view = new myView({id: options})
layout.holder1.show();
console.log(my_view.model.get('name')) <---- I want this
I want to get my_view.model.get('name') however, the issue is I get undefined. I have console.log the model and it is populated ok, however I think it's because it's not fully loaded yet when I try the get.
This is my current thisView:
var thisView = Backbone.Marionette.ItemView.extend({
initialize: function (options) {
this.model.fetch();
},
model: new myModel(),
template: testExampleTemplate,
});
return thisView;
You'll have the object populated only after the success callback function:
initialize: function (options) {
this.model.fetch({
success: function(model){
console.log(model.get('name'));
};
});
}
Listen for an event. "change" or "reset" will work.
viewInstance.model.on("change", function(){
viewInstance.model.get("nameOfAttribute");
// do something
});
http://backbonejs.org/#Events-catalog
There's a few ways to approach this. First, you could listen for a change event from the model in the view, and do whatever it is you need when the change event fires. If you need to do something no matter what, you have a couple of options: you could write an implementation for you model's parse method that fires an event your view listens for and does something in response, or you can do something in the success callback for the fetch method itself (passed as an option to fetch). I can provide an example if I understand better which approach makes sense for your situation.
is it possible to pass the questions variable into the view render?
Ive attempted calling this.render inside the success on the fetch however I got an error, presumably it's because this. is not at the correct scope.
app.AppView = Backbone.View.extend({
initialize: function(){
var inputs = new app.Form();
inputs.fetch({
success: function() {
var questions = inputs.get(0).toJSON().Questions;
app.validate = new Validate(questions);
app.validate.questions();
}, // End Success()
error: function(err){
console.log("Couldn't GET the service " + err);
}
}); // End Input.fetch()
this.render();
}, // End Initialize
render: function(){
el: $('#finder')
var template = _.template( $("#form_template").html(), {} );
this.$el.html(template);
}
The success callback is called with a different this object than your View instance.
The easiest way to fix it is to add something like this before you call inputs.fetch:
var self = this;
And then inside the success callback:
self.render();
I'm not quite sure what you're trying to achieve, but if your problem is calling render from the success callback, you have two options, Function#bind or assigning a self variable.
For more information about "self" variable, see var self = this? . An example:
var self = this;
inputs.fetch({
success: function () {
self.render();
},
...
});
You should probably do some reading on JavaScript scopes, for example "Effective Javascript" or search the topic ( for example this MDN article ) online to get a better idea what happens there.
For Function#bind(), see the MDN article about it. With Backbone I suggest you use Underscore/LoDash's _.bind instead though, to make sure it works even where Function#bind() is not supported.
As for more high-level concepts, the fetching operation looks like it belongs to the model or router level and instead you should assign the questions variable as the model of your view. Ideally views don't do data processing / fetching, they're just given a model that has the methods necessary to perform any data transformations you might need.
The views shouldn't even need to worry about where the model comes from, this is normally handled by a router in case of single page applications or some initialization code on the page.
I'm working on a toy backbone.js application, a library application to perform CRUD operations on a library. Here is the book model and the library collection (of books)
var Book = Backbone.Model.extend({
url: function() {
return '/api/books' + this.get('id');
}
});
var Library = Backbone.Collection.extend({
model : Book,
url : '/api/books'
});
This seems pretty straightforward. Next, I obviously want to be able to show the book, so I have a BookView...
var BookView = Backbone.View.extend({
tagName: 'li',
render: function() {
this.$el.html(this.model.get('author'));
$('#list-of-books').append(this.$el);
}
});
all the render method does is append an li to the end of an unordered list with an id of list-of-books that is in my html.
Next, unsurprisingly, if I add the following code, I get a list with one item (the name of the author of the book with id=4)
var a_book = new Book();
a_book.url = '/api/books/4';
a_book.fetch({
success: function() {
var bookView = new BookView({ model: a_book });
bookView.render();
}
});
Here's where I don't understand. I add the following code and nothing happens:
var some_books = new Library();
some_books.fetch();
some_books.forEach(function(book) {
alert('why is this function not being run');
var view = new BookView({ model: book });
view.render();
});
For some reason, I can't even get that loop code to run, I don't even see that alert pop out to the screen. Let me know if you understand what's wrong with this code and how I can make the list render properly. Thanks
You are calling .render inside fetch. Hence the execution stops over there itself. For loop wont run after fetch as it has returned already.
.fetch() is an asynchronous call, so you may not have the collection data populated when you are calling .forEach().
You should either call the rendering code in the success callback or (even better) render the view on events (reset,...) on the collection.
BTW, you have a typo in the url() function:
url: function() {
return '/api/books/' + this.get('id');
}
Why are you defining the url function for the Book?, since you use the model attribute in the Library collection, url for the Book will be set correctly by default.
The following code works but I would like to improve readability and accessibility avoiding to write callbacks.
I need to render my view when fetch is performed on my collection.
Here the working code:
var MyView = Backbone.View.extends({
initialize: function()
{
var that = this;
MyCollection.fetch({
success: function () {
that.render();
}
});
},
....
});
Here my attempt which does not work:
var MyView = Backbone.View.extends({
initialize: function()
{
MyCollection.fetch();
MyCollection.bind('change', this.render);
},
....
});
Looks like you need to set the context for the call to bind. Like this:
MyCollection.bind('change', this.render, this);
One excellent thing about Coffeescript is that it takes care of these things much more cleanly.
ETA: the change event isn’t triggered on fetch, it’s only triggered when one of the models in the collection changes. reset is, though. Also, you’re binding to the event after triggering the fetch, not sure if that’s what you intend.
Aside: seems confusing to me that you’re capitalising the MyCollection member, makes it easily mixed up with a class.
I'd like to find a way to raise a backbone.js "event" without something having changed in the model or in the dom.
For instance, I'm loading the Facebook SDK asynchronously. I'm subscribed to the auth.login event, and would like to send a message to my view that the user has logged in so it can re-render itself appropriately.
My view looks similar to this:
window.CreateItemView = Backbone.View.extend({
el: $('#content'),
initialize: function() {
this.render();
},
render: function() {
// do something
return this;
},
setSignedRequest: function(signedRequest) {
//do something with signedRequest
}
});
In my facebook code, I do this:
FB.Event.subscribe('auth.login', function(response){
if (response.status === 'connected') {
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
window.signedRequest = response.authResponse.signedRequest;
if (window.view && window.view.setSignedRequest) {
window.view.setSignedRequest(window.signedRequest);
}
}
});
However, while window.view exists, it cannot see the setSignedRequest method. I've ensured that my scripts are loading in the correct order. Strangely I have this same code on a different page albeit a different View object and it works fine. I haven't seen any difference that would account for this.
A better solution would be to raise some sort of event and have the view listen for it. However, I don't want to utilize the change event on the model as the signedRequest shouldn't be a property of the model. Is there a better way of accomplishing this?
Backbone.View is extended with Backbone.Events which means you can easily trigger custom events and pass whatever data you want
var View = Backbone.View.extend({
initialize: function() {
this.on('customEvent', this.doSomething, this);
}
doSomething: function(someData) {
// this!
}
});
var view = new View();
view.trigger('customEvent', "someDataHere");
though I don't know why you can't see the method on the view - it should work - Are you 100% sure you are instantiating the view correctly? and that window.view is instance of CreateItemView ?
If you want to do that outside your model and subscribe to the event on your view you could do the following:
var SomeObject = {...};
_.extend(SomeObject, Backbone.Events);
Then, you can subscribe to events on SomeObject
SomeObject.on('myevent', someFunc, this);
or trigger the event
SomeObject.trigger('myevent', data);