I'm developing a small project in an attempt to learn backbone.js and I'm using AMD (recommended in a tutorial). I have a child view for an "Athlete Control", basically a view I plan to reuse on other pages/views. The click event on the child view (AthleteView) doesn't fire.
The AthleteView is rendered in a div within the TimerView and the TimerView is rendered in a div with id "page" on the index page. I've included everything below, as I suspect it could how I'm setting things up that is the problem.
Main View:
define([
'jquery',
'underscore',
'backbone',
'views/SettingsView',
'views/AthleteControlView',
'text!templates/timerTemplate.html',
'timer'
], function($, _, Backbone, SettingsView, AthleteView, timerTemplate){
var TimerView = Backbone.View.extend({
el: $("#page"),
render: function(){
this.$el.html(timerTemplate);
var settingsView = new SettingsView();
settingsView.render();
var athleteView = new AthleteView();
athleteView.render();
console.log('rendered Timer');
}
});
return TimerView;
});
Child View:
define([
'jquery',
'underscore',
'backbone',
'text!templates/AthleteControlTemplate.html',
'jqueryui'
], function($, _, Backbone, keypadTemplate){
var AthleteView = Backbone.View.extend({
el: $("#AthleteControl"),
events: {
'click': 'clickHandler'
},
render: function(){
$("#AthleteControl").html(keypadTemplate);
console.log('rendered AthleteControl');
},
clickHandler: function()
{
alert('click');
console.log('click');
}
});
return AthleteView;
});
ROUTER.JS
define([
'jquery',
'underscore',
'backbone',
'views/TimerView'
], function($, _, Backbone, TimerView) {
var AppRouter = Backbone.Router.extend({
routes: {
'*actions': 'defaultAction'
}
});
var initialize = function(){
var app_router = new AppRouter;
app_router.on('route:defaultAction', function (actions) {
var timerView = new TimerView();
timerView.render();
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
Index.html
<html>
<head>
<script data-main="js/main" src="js/libs/require/require.js"></script>
</head>
<body>
<div id="page">
Loading....
</div>
<div id="footer"></div>
</div>
</body>
</html>
TIMER TEMPLATE:
<div class = "Controls">
<button id="Start">Start</button>
<button id="Stop">Stop</button>
<button id="Reset">Reset</button>
</div>
<div id="AthleteControl"></div>
So the id="AthleteControl"
element comes from inside timerTemplate.html
. That means that $('#AthleteControl")
won't find anything when this code is executed:
var AthleteView = Backbone.View.extend({
el: $("#AthleteControl"),
That means that AthleteView
will end up behaving as though you said:
var AthleteView = Backbone.View.extend({
el: undefined,
If you don't specify a view's el
explicitly, the view will create an empty <div>
as the el
. Things like:
el: $("some-selector")
are almost always a mistake due to the timing problems. Instead, just use the selector string and let Backbone convert it to a DOM node when it needs to:
el: "some-selector"
The quick fix should be to un-jQuery the el
s:
el: '#page'
el: '#AthleteControl'
As far as the "let the views create their own el
s" is concerned, you let the views create their own DOM nodes to use as their el
rather than trying to bind views to el
s that are already on the page. This approach:
view.remove()
call rather than manually cleaning up the event bindings and HTML. If you have subviews then you'd override remove
in your view to call remove
on the subviews before chaining to the default remove
implementation.This approach (without the AMD/require.js stuff for clarity) would look more like the following.
HTML:
<script id="timerTemplate" type="text/x-underscore">
<div class="Controls">
<button id="Start">Start</button>
<button id="Stop">Stop</button>
<button id="Reset">Reset</button>
</div>
</script>
<div id="page">
</div>
JavaScript:
var AthleteView = Backbone.View.extend({
id: 'AthleteControl',
render: function() {
this.$el.html('Athlete control stuff goes here.');
return this; // This is standard practice, you'll see why below.
}
});
var TimerView = Backbone.View.extend({
render: function () {
var tmpl = _.template($('#timerTemplate').html());
this.$el.html(tmpl());
var athleteView = new AthleteView();
this.$el.append(athleteView.render().el);
return this;
}
});
var timer_view = new TimerView;
$('#page').html(timer_view.render().el);
Note the return this
in the render
methods and note how this.$el.html
and this.$el.append
are used to build and combine the views.