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.
Related
Here is my headache. I'm developing a modularized javascript app and everything seems pretty neat except for one detail: I just can figure out how to load my initialize function only after my models have been fetched from the server with this Backbone.collection.fetch() method.
Please have a look into this file and possibly the whole project if you will, I appreciate a lot your collaboration.
fetch has a success callback, as Reed Spool pointed out. Here's how you could use it:
var View = Backbone.View.extend({
initialize: function () {
// Preserve *this* in closures
var that = this;
this.collection.fetch({
success: function(data){
console.log(data);
that.render();
},
error: function(){
console.log('error');
}
});
},
render: function() {
//render whatever you need from your fetched collection
}
});
If I'm hearin you right, I think you need to use a "Success" callback function in fetch. See here #2 http://chilipepperdesign.com/2013/01/15/backbone-js-bind-callback-to-successful-model-fetch/
If it's decoupling function timing in modularized code you're worried about, as your question's header suggests, I suggest using Backbone's Events. See the Event Aggregator, or Pub-Sub design patterns.
The conventional way to get around this using Backbone, is to use events. I've based this off the various blog posts of MarionetteJS founder Derek Bailey, so I'll add references when I find the relevant ones later...
Your view would do this:
var view = Backbone.View.extend({
...
_actuallyInitialize: function () {
...
}
initialize: function (attr, options) {
...
this.listenTo(this.model, 'data:fetched', this._actuallyInitialize);
...
}
});
And the code that is fetching your model, would do this:
var jqXHR = model.fetch();
jqXHR.done((function (bindModel) {
return function (response, textStatus, jqXHR) {
...
bindModel.trigger('data:fetched');
};
}(model)));
This way, you maintain the high-level modular approach, decouple the code (any number of views can update themselves on fetch now), and maintain readability. Your Model does stuff to itself, and your View does stuff to itself. Pretty neat, in my opinion, and one of the patterns I love using in Backbone. Thanks for the Q.
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.
I just got started in Javascript, backbone, and jquery. My coworker was trying to explain the this concept and contexts in Javascript. He was saying this type of pattern is common, but there might be better ways to do it. I was wondering if it is in fact the "standard" pattern or if there was a better way.
Basically, the pattern is, when I'm in my class that is handling what's being viewed on screen (for us, it's a combination of MVC and backbone), I need to fetch my data to populate the page. But before we fetch, we do
var _this = this;
Since the ajax call we do is asynch, he was saying that if in our success handler, if we did
this.model.property = // set some property from the callback
this would not be what we want and we need to do
_this.model.property = // something
I guess I was wondering if this is pretty standard. I feel like after a week of Javascript, I'm doing it every time I fetch data from the server, and sometimes before I try to format my page with underscore's _.each since I guess 'this' changes in my _.each block as well. Thanks!
Scope is one of the most tricky parts of javascript, basically, that is what your friend is describing. Fully understanding scope is the key to being awesome at javascript. Check out some of these links for a full understanding of what is occuring:
Mozilla Article on Functions and Scope
Another good article
Anyways, I hope you like javascript, its personally my favorite language, especially after node.js found its way into my life :)
Edit:
To actually answer your question: What is happening is that the context is becoming lost in the async mode, and so the scope of this changes. Saving it off allows you to reference it later. Hope this helps!
You can bind function to context, so during async call you don't have to use annoying
var _this = this
//or
var self = this;
You can use underscore.js bind function:
//this - current context
var func = function ({ this.doSomething(); });
var bindedFunction = _.bind(func, this);
So when you call bindedFunction it will be bound to the currentContext.
If you are using backbone, usually I bind callback functions, so they'r bound to the object:
var MyView = Backbone.View.extend({
initialize: function (args) {
_.bindAll(this, 'onDataFetched');
},
fetchData: function () {
$.ajax({
//... some code
success: this.onDataFetched
});
},
onDataFetched: function(result) {
//Do something usefull here
//this = MyView instance
}
});
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 have been using Backbone on a new project and so far have loved it, but I have come to a blocker that I can't seem to get around.
Without trying to explain my whole domain model, I have found that when you save a model, the response comes back from the server and gets parsed again, creating new sub objects and therefore breaking and event bindings I had previously put on the object.
For instance, if I save ContentCollection (its a Backbone.Model not a collection) when it comes back from the server, the response gets parsed and creates a new Collection in this.contentItems, which breaks all the binding I had on this.contentItems. Is there any way to get around this? Tell backbone not to parse the response somehow? Grab the bindings off the original list, and then re-attach them to the new list?
App.ContentCollection = Backbone.Model.extend({
urlRoot: '/collection',
initialize: function() {
},
parse: function(resp, xhr) {
this.contentItems = new App.ContentList(resp.items)
this.subscriptions = new App.SubscriptionList(resp.subscriptions)
return resp
},
remove: function(model){
this.contentItems.remove(model)
this.save({'removeContentId':model.attributes.id})
},
setPrimaryContent: function(model){
this.save({'setPrimaryContent':model.attributes.id})
}
})
Has anyone run into this before?
I think the issue here is the way you're using the parse() method. Backbone just expects this method to take a server response and return a hash of attributes - not to change the object in any way. So Backbone calls this.parse() within save(), not expecting there to be any side-effects - but the way you've overridden .parse(), you're changing the model when the function is called.
The way I've dealt with this use case in the past is to initialize the collections when you first call fetch(), something like:
App.ContentCollection = Backbone.Model.extend({
initialize: function() {
this.bind('change', initCollections, this);
},
initCollections: function() {
this.contentItems = new App.ContentList(resp.items);
this.subscriptions = new App.SubscriptionList(resp.subscriptions);
// now you probably want to unbind it,
// so it only gets called once
this.unbind('change', initCollections, this)
},
// etc
});