Reputation: 1
I'm working on a single-page application with Backbone.js v0.5.5, and on one 'page' of the app I have a Master View (audioOnDemand) and two Secondary Views (audioPlay and audioNav). I wanted the secondary views to be routable, so that the user could, as an example, go to '#audio_ondemand', '#audio_ondemand/id/:id', or '#audio_ondemand/page/:page' and everything would load. In order to make this work, I ended up having to put some jQuery code in my controllers/routers, which felt very hacky. Which is why I'm here, to see if there's a better way.
Here's my controllers/routers.
audio_ondemand: function(splat) {
this._audioondemandview = new AudioOnDemandView();
this._audioondemandview.render();
if (splat) {
var id_patt = /id\/(\d*)/i;
var id = id_patt.exec(splat);
id = (id) ? id[1] : null;
//console.log("id is: "+id);
var page_patt = /^page\/(\d*)/i;
var page = page_patt.exec(splat);
}
page = (page) ? page[1] : 1;
//console.log("page is: "+page);
if (id) {
setTimeout('app.play_audio("'+id+'");', 500);
}
setTimeout('app.audio_nav("'+page+'");', 500);
},
audio_nav: function(page) {
var nav_holder = $('#pagination-nav');
if (nav_holder.length == 0) {
app.audio_ondemand('page/'+page);
} else {
var audios = this._audios;
var nav = new AudioNavView({el: nav_holder, audios: audios, page: page});
nav.render();
}
},
play_audio: function(id) {
var audio_holder = $('#audio-player');
if (audio_holder.length == 0) {
app.audio_ondemand('id/'+id);
} else {
var audio = this._audios.getByUid(id);
this._audioview = new AudioView({el: $('#audio-player'), model: audio});
this._audioview.render();
}
}
The views don't do anything particularly special, but here's my 'master' template (using jQuery Templates):
<script id="audioOnDemandTmpl" type="text/x-jquery-tmpl">
<div class="sub-header">
<strong>Audio</strong> On Demand
</div>
<div id="currently-playing">
<div id="currently-header">Currently Playing</div>
<div id="current-holder"></div>
</div>
<div id="audio-player"></div>
<div id="pagination-nav"></div>
<div id="audios-holder"></div>
</script>
I would assume by now you get the dilemma. The master view has to be rendered before the secondary views, or else the secondary views don't have the proper DOM elements to attach to. But if I want the secondary views to be routable, I have to do the 'hacky' jQuery check to see if the master view has been rendered or not.
Appreciate any thoughts or suggestions, and let me know if you need more details!
UPDATE: Because it might help give the idea better, here's the navigation view and templates.
<script id="audioNavTmpl" type="text/x-jquery-tmpl">
{{if prev > 0}}<a href="#audio_ondemand/page/${prev}">Prev</a> | {{/if}}${current}{{if next}} | <a href="#audio_ondemand/page/${next}">Next</a>{{/if}}
</script>
<script id="audioTmpl" type="text/x-jquery-tmpl">
<div class="audio-holder">
<div class="title-author">
<div class="title"><a href="#audio_ondemand/id/${attributes.uid}">${attributes.title}</a></div>
<div class="author">${attributes.author}</div>
</div>
<div class="topic-date">
<div class="topic"><strong>${attributes.topic}</strong></div>
<div class="date">${attributes.date}</div>
</div>
</div>
</script>
var AudioNavView = Backbone.View.extend({
audioNavTemplate: $('#audioNavTmpl').template(),
audioTemplate: $('#audioTmpl').template(),
initialize: function(options) {
this.audios = options.audios.models;
var temp_page = parseInt(options.page);
this.page = [
{
prev: temp_page - 1,
current: temp_page,
next: temp_page + 1
}
];
this.slice_start = (temp_page - 1) * 11;
this.slice_end = temp_page * 11;
this.audios = this.audios.slice(this.slice_start, this.slice_end);
if (this.audios.length <= 11) {
this.page[0]["next"] = false;
}
},
render: function() {
//console.log(this.page);
this.el.empty();
var sg = this;
$('#audios-holder').fadeOut('fast', function() {
$.tmpl(sg.audioNavTemplate, sg.page).appendTo(sg.el);
$(this).empty();
$.tmpl(sg.audioTemplate, sg.audios).appendTo($(this));
$(this).fadeIn('fast');
});
return this;
}
});
How should I set this up better, so that if someone goes straight from '#home' to '#audio_ondemand/page/2' the router makes sure to load the audioOnDemandView before the audioNavView?
Upvotes: 0
Views: 1651
Reputation: 57512
As far as I can tell, your nested views are only 1 level deep (i.e. a master view with sub-views, but not any sub-sub-views), so you can solve your problem by rendering your master view in the initialize method of your Router and then rendering any sub-views in the route callbacks. Here's a very simply example. I'm using underscore's templating engine, but the idea is the same with jQuery's templating engine.
Templates:
<script id="master-view" type="text/html">
Hi, this is a master view. <a href="#subview">Render sub-view now.</a>
<div id="subview-container"></div>
</script>
<script id="sub-view" type="text/html">
Hi, this is a sub view.
</script>
Backbone Views and Router
var MasterView = Backbone.View.extend({
_template: _.template($('#master-view').html()),
render: function () {
$(this.el).html(this._template());
return this;
}
});
var SubView = Backbone.View.extend({
_template: _.template($('#sub-view').html()),
render: function () {
$(this.el).html(this._template());
return this;
}
});
var MyRouter = Backbone.Router.extend({
initialize: function () {
var view = new MasterView();
$('body').append(view.render().el);
},
routes: {
'': 'home',
'subview': 'subview'
},
home: function () {
// probably don't have to do anything
},
subview: function () {
// render subview here
var view = new SubView({ el: $('#subview-container')[0] });
view.render();
}
});
new MyRouter();
Backbone.history.start();
When you point the browser to the page you'll first only see the master view. Click the link and you'll see the url hash change to #subview and the subview will be rendered. You can then reload the page (with the url hash still at #subview) and the sub-view will be rendered after the master view correctly.
Upvotes: 1