I have configured a simple backbone Model and View using an underscore template. The exact same configuration is used for two separate APIs.
API 1 works as expected.
To reproduce the problem, comment out the url for API 1 and uncomment the url for API 2.
As you can see I have normalized the response data for both apis, the exact same data structure is returned for both apis. However, the render method for API 2 is not called. To make matters even more strange, on rare occasions render does get called by API 2.
What am I missing here?
// Model
var Quote = Backbone.Model.extend({
// API 1
//urlRoot: 'http://quotes.stormconsultancy.co.uk/quotes/1.json',
// API 2
urlRoot: 'http://quotes.rest/qod.json',
parse: function (data){
try{
data = data['contents'].quotes[0];
}
catch(e){
}
var rd = {author:data.author, quote:data.quote}
console.log("parsed", typeof rd, rd);
return rd;
},
// UPDATE as suggested by cory
initialize: function() {
this.on('all', function(eventName) {
console.log('QuoteModel: ' + eventName);
});
}
});
// View
var QuoteView = Backbone.View.extend({
initialize: function() {
this.template = _.template($('#quote-template').html());
this.listenTo(this.model, 'change', this.render);
},
render: function(){
console.log("render", this.model.attributes)
this.$el.html(this.template(this.model.attributes));
}
});
var quoteM = new Quote();
quoteM.fetch();
$(document).ready(function() {
var quoteV = new QuoteView({
el: $('#quote'),
model: quoteM
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<script type="text/html" id="quote-template">
<p>The author is : <%= author %></p>
<p>The content is : <%= quote %></p>
</script>
<div id="quote"></div>
You have a race condition, where you fetch before creating the view.
So if the fetch finishes before the document is ready, the change event gets triggered before the view has started listening to the model.
$(document).ready(function() {
var quoteM = new Quote();
var quoteV = new QuoteView({
el: $('#quote'),
model: quoteM
});
// fetch after
quoteM.fetch();
});
var API_DOMAIN = "http://quotes.rest/";
// Reusable model
var Quote = Backbone.Model.extend({});
// reusable quotes collection
var QuoteCollection = Backbone.Collection.extend({
model: Quote,
// simple generic parse
parse: function(response) {
return response.contents.quotes;
},
});
// View
var QuoteView = Backbone.View.extend({
// GOOD: gets called once
template: _.template($('#quote-template').html()),
initialize: function() {
// BAD: gets called for every view
// this.template = _.template($('#quote-template').html());
this.listenTo(this.model, 'change', this.render);
},
render: function() {
console.log("render", this.model.attributes)
this.$el.html(this.template(this.model.toJSON()));
// Backbone standard for chaining
return this;
}
});
$(function() {
var quoteV,
collection = new QuoteCollection();
collection.fetch({
url: API_DOMAIN + 'qod.json',
success: function(collection, response, options) {
// only create the view when you need it
quoteV = new QuoteView({
el: $('#quote'),
model: collection.first()
});
// manually render to be 100% in control. The event now only
// serves if the model really changes.
quoteV.render();
}
});
});
<div id="quote"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<script type="text/html" id="quote-template">
<p>The author is :
<%= author %>
</p>
<p>The content is :
<%= quote %>
</p>
</script>