I'm trying to make a small backbone.js app, but struggle with the order of things.
In my html file, I have two script blocks in the header:
<script type="text/template" id="model-template">
<a href="#"><%= title %></a>
</script>
<script type="text/javascript">
jQuery(function(){
window.model.fetch();
});
</script>
In my app.js, I have defined a simple model, view and a router.
(function($) {
window.MyModel = Backbone.Model.extend({
url: '/mymodel'
});
window.mymodel = new MyModel();
$(document).ready(function() {
window.MyModelView = Backbone.View.extend({
template: _.template($('#mymodel-template').html()),
initialize: function () {
_.bindAll(this, 'render');
},
render: function () {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
});
window.MyApp = Backbone.Router.extend({
routes: {
'': 'home'
},
initialize: function () {
this.myModelView = new MyModelView({
model: window.mymodel
});
},
home: function() {
var $body = $('body');
$body.empty();
$body.append(this.myModelView.render().el);
}
});
$(function() {
window.App = new MyApp();
Backbone.history.start({pushState: true});
});
})(jQuery);
The application is served by a simple sinatra application. The url /mymodel
serves a static json file:
{
"title": "My Model",
}
When loading the application, I get an error in the javascript console:
Uncaught ReferenceError: title is not defined
The problem seems to be, that the view renders itself before the model is fetched from the server. Why is that?
Yesterday, I followed the first two backbone.js screencasts from PeepCode. I have tried to compare my application with the one that came out of the screencasts, but cant't see a reason for why my application want work.
Any suggestions?
In this case you should bootstrap your model data so that it's available on page load.
Instead of
window.model.fetch();
put something like this in (if using a .erb)
<script>
window.model = new MyModel(<%= @model.to_json %>);
</script>
Otherwise you need to render the view once the model is fetched e.g.
bind the view to render when the model changes
initialize: function () {
_.bindAll(this, 'render');
this.model.on("change", this.render);
},
or handle the success of the model.fetch and render the view
window.model.fetch({
success: function (model, response) {
window.MyApp.myModelView.render();
}
});
You can also take advantage of the deferred object http://api.jquery.com/category/deferred-object/ that Backbone.Model.fetch()
returns, like this:
window.MyModel = Backbone.Model.extend({
url: '/mymodel'
});
//when the model is fetched, store a reference to the jQuery deferred object
window.MyModelFetch = window.MyModel.fetch();
window.MyModelView = Backbone.View.extend({
template: _.template($('#mymodel-template').html()),
initialize: function () {
_.bindAll(this, 'render');
},
render: function () {
//reference the deferred object and use 'done' method
//to register a callback after complete
window.MyModelFetch.done(function(){
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
}
});
You may want to create an extension of the backbone model that stores a deferred object on your model that you can reference, like this:
Backbone.DeferrableModel = Backbone.Model.extend({
fetch: function(){
this.fetching = Backbone.Model.prototype.fetch.apply(this, arguments);
return this.fetching;
}
});
Then in your view render method, you can just say this:
render: function () {
//the deferred reference is now directly referenced from your model
this.model.fetching.done(function(){
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
}
It can be very handy to use an extended model and follow this pattern throughout your backbone application.
Resets the model's state from the server. Useful if the model has never been populated with data, or if you'd like to ensure that you have the latest server state. A "change" event will be triggered if the server's state differs from the current attributes. Accepts success and error callbacks in the options hash, which are passed (model, response) as arguments.
In this case, you'll want to render the view in the success callback.
model.fetch({
error: function () {
},
success: function (model, response) { // model is ready now
// do view stuff here
}
});
Clearly from the previous answers, you know that you need to render on the fetch success callback, however I think you problem is a bit more than that. Namely, your home route is used to build myModelView immediately rather than when its data loads. This is happening because you are calling render() in order to append to body. Instead, try and initialize the view with an existing element. This way you can delay the call for render until fetch has completed:
window.MyApp = Backbone.Router.extend({
routes: {
'': 'home'
},
initialize: function () {
},
home: function() {
var $body = $(document.body).empty();
var myModelEl = $("<div></div>").appendTo($body);
this.myModelView = new MyModelView({
model: window.mymodel,
el: myModelEl
});
}
});
Now, you haven't called render() yet, but your view is successfully attached to the DOM as you intended.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With