I my Backbone app I have two interdependent subviews within the main App view: one which displays a music score rendered using
Vexflow (Javascript music notation package), and another below which displays an analysis of the score, also using Vexflow but with some extra objects (text, lines, clickable elements, etc).
I have a problem, however, in that a lot of the data I need for the analysis view doesn't come into existence until the score view has been rendered (this is due to the way Vexflow renders things). For example, the x coordinate of a musical note is only available
after the note has been drawn (the same isn't true of the y coordinate). Below is a schematic my app view set up:
var AppView = Backbone.View.extend({
//...
initialize: function() {
this.scoreView = new ScoreView();
this.analysisView = new AnalysisView({
data: this.getAnalysisData()
});
},
render: function() {
this.scoreView.render();
this.analysisView.render();
return this;
},
getAnalysisData: function() {
// Performs anaysis of this.scoreView,
// and returns result.
}
});
My work around is to move the analysis view setup into the render method, after the score view has been rendered. I dislike doing this, as the
getAnalysisData method can be quite expensive, and I believe the render method should be reserved simply for rendering things, not processing.
So I'm wondering if - since there doesn't seem to be a Vexflow solution - there is a Backbone pattern that might fix this. I am familiar with the 'pub/sub' event aggregator pattern for decoupling views, as in:
this.vent = _.extend({}, Backbone.Events);
So on this pattern the analysis view render method subscribes to an event fired after the score view is rendered. I'm not sure how this would alter my code, however. Or perhaps use
listenTo, like this:
// Score subview.
var ScoreView = Backbone.View.extend({
initialize: function() {
this.data = "Some data";
},
render: function() {
alert('score');
this.trigger('render');
}
});
// Analysis subview.
var AnalysisView = Backbone.View.extend({
initialize: function(options) {
this.data = options.data;
},
render: function() {
alert(this.data);
return this;
}
});
// Main view.
var AppView = Backbone.View.extend({
el: "#some-div",
initialize: function() {
this.scoreView = new ScoreView();
var view = this;
this.listenTo(this.scoreView, 'render', this.doAnalysis); // <- listen to 'render' event.
},
render: function() {
this.scoreView.render();
return this;
},
doAnalysis: function() {
this.analysisView = new AnalysisView({
data: this.getAnalysisData()
});
this.analysisView.render();
},
getAnalysisData: function() {
return this.scoreView.data;
}
});
Of course, the analysis step is still effectively being done
during the render process, but this seems a better pattern. It seems more like the Backbone way of doing things. Am I right? Or am I missing something?
I don't
necessarily have to create the analysis view in the
doAnalysis, I could still do that in the main view
initialize (at the moment I'm not). But
doAnalysis has to run after the score view has rendered, otherwise it cannot access the relevant score geometry information.