[#19,routing][m]: introduce Backbone routing and use it for navigation between grid and graph views.

Some substantial other related changes:

* Switched DataExplorer view to have element it attaches to passed in rather than being created internally (this was important for FlotGraph to set up correctly).
  * Also refactored DataExplorer setup somewhat (e.g. got rid of draw function which wasn't needed -- merging necessary parts with initialize)
* Substantial refactoring of FlotGraph so that we correctly work around flot's delicateness regarding rendering (element must not be hidden etc -- see comments in code)
  * Also generally cleaner code
This commit is contained in:
Rufus Pollock
2012-01-10 00:07:21 +00:00
parent 973e59fbdd
commit f176979f5b
2 changed files with 93 additions and 59 deletions

View File

@@ -4,9 +4,9 @@ $(function() {
window.$container = $('.data-explorer-here'); window.$container = $('.data-explorer-here');
var dataset = demoDataset(); var dataset = demoDataset();
window.dataExplorer = new recline.View.DataExplorer({ window.dataExplorer = new recline.View.DataExplorer({
model: dataset el: window.$container
, model: dataset
}); });
window.$container.append(window.dataExplorer.el);
setupLoadFromWebstore(function(dataset) { setupLoadFromWebstore(function(dataset) {
window.dataExplorer.remove(); window.dataExplorer.remove();
window.dataExplorer = null; window.dataExplorer = null;

View File

@@ -7,6 +7,10 @@ var my = {};
// The primary view for the entire application. // The primary view for the entire application.
// //
// It should be initialized with a recline.Model.Dataset object and an existing
// dom element to attach to (the existing DOM element is important for
// rendering of FlotGraph subview).
//
// To pass in configuration options use the config key in initialization hash // To pass in configuration options use the config key in initialization hash
// e.g. // e.g.
// //
@@ -22,12 +26,11 @@ var my = {};
// //
// All other views as contained in this one. // All other views as contained in this one.
my.DataExplorer = Backbone.View.extend({ my.DataExplorer = Backbone.View.extend({
tagName: 'div',
className: 'data-explorer',
template: ' \ template: ' \
<div class="data-explorer"> \
<div class="header"> \ <div class="header"> \
<ul class="navigation"> \ <ul class="navigation"> \
<li class="active"><a href="#datatable" class="btn">Grid</a> \ <li class="active"><a href="#grid" class="btn">Grid</a> \
<li><a href="#graph" class="btn">Graph</a></li> \ <li><a href="#graph" class="btn">Graph</a></li> \
</ul> \ </ul> \
<div class="pagination"> \ <div class="pagination"> \
@@ -48,14 +51,15 @@ my.DataExplorer = Backbone.View.extend({
<img src="images/small-spinner.gif" class="notification-loader"><span class="notification-message">Loading...</span> \ <img src="images/small-spinner.gif" class="notification-loader"><span class="notification-message">Loading...</span> \
</div> \ </div> \
</div> \ </div> \
</div> \
', ',
events: { events: {
'click .navigation li a': 'navChange',
'submit form.display-count': 'onDisplayCountUpdate' 'submit form.display-count': 'onDisplayCountUpdate'
}, },
initialize: function(options) { initialize: function(options) {
var self = this;
this.el = $(this.el); this.el = $(this.el);
this.config = options.config || {}; this.config = options.config || {};
_.extend(this.config, { _.extend(this.config, {
@@ -65,35 +69,34 @@ my.DataExplorer = Backbone.View.extend({
if (this.config.readOnly) { if (this.config.readOnly) {
this.setReadOnly(); this.setReadOnly();
} }
this.draw(); // Hash of 'page' views (i.e. those for whole page) keyed by page name
this.pageViews = {
grid: new my.DataTable({
model: this.model
})
, graph: new my.FlotGraph({
model: this.model
})
};
// this must be called after pageViews are created
this.render();
this.router = new Backbone.Router();
this.setupRouting();
Backbone.history.start();
// retrieve basic data like headers etc
// note this.model and dataset returned are the same
this.model.fetch().then(function(dataset) {
// initialize of dataTable calls render
self.model.getDocuments(self.config.displayCount);
});
}, },
onDisplayCountUpdate: function(e) { onDisplayCountUpdate: function(e) {
e.preventDefault(); e.preventDefault();
this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val()); this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val());
this.draw(); this.model.getDocuments(this.config.displayCount);
},
draw: function() {
var self = this;
this.el.empty();
// retrieve basic data like headers etc
// note this.model and dataset returned are the same
this.model.fetch().then(function(dataset) {
self.render();
self.$dataViewContainer = self.el.find('.data-view-container');
// initialize of dataTable calls render
self.dataTable = new my.DataTable({
model: dataset
});
self.flotGraph = new my.FlotGraph({
model: dataset
});
self.flotGraph.el.hide();
self.$dataViewContainer.append(self.dataTable.el)
self.$dataViewContainer.append(self.flotGraph.el);
self.model.getDocuments(self.config.displayCount);
});
}, },
setReadOnly: function() { setReadOnly: function() {
@@ -105,25 +108,40 @@ my.DataExplorer = Backbone.View.extend({
tmplData.displayCount = this.config.displayCount; tmplData.displayCount = this.config.displayCount;
var template = $.mustache(this.template, tmplData); var template = $.mustache(this.template, tmplData);
$(this.el).html(template); $(this.el).html(template);
var $dataViewContainer = this.el.find('.data-view-container');
_.each(this.pageViews, function(view, pageName) {
$dataViewContainer.append(view.el)
});
}, },
navChange: function(e) { setupRouting: function() {
// TODO: really ugly and will not scale to more widgets ... var self = this;
var widgetToShow = $(e.target).attr('href').slice(1); this.router.route('', 'grid', function() {
self.updateNav('grid');
});
this.router.route('grid', 'grid', function() {
self.updateNav('grid');
});
this.router.route('graph', 'graph', function() {
self.updateNav('graph');
// we have to call here due to fact plot cannot draw if hidden
// see comments in FlotGraph.redraw
self.pageViews['graph'].redraw();
});
},
updateNav: function(pageName) {
this.el.find('.navigation li').removeClass('active'); this.el.find('.navigation li').removeClass('active');
$(e.target).parent().addClass('active'); var $el = this.el.find('.navigation li a[href=#' + pageName + ']');
if (widgetToShow == 'datatable') { $el.parent().addClass('active');
this.flotGraph.el.hide(); // show the specific page
this.dataTable.el.show(); _.each(this.pageViews, function(view, pageViewName) {
} else if (widgetToShow == 'graph') { if (pageViewName === pageName) {
this.flotGraph.el.show(); view.el.show();
this.dataTable.el.hide(); } else {
// Have to call this here view.el.hide();
// If you attempt to render with flot when graph is hidden / invisible flot will complain with }
// Invalid dimensions for plot, width = 0, height = 0 });
// (Could hack this by moving plot left -1000 or similar ...)
this.flotGraph.createPlot();
}
} }
}); });
@@ -641,15 +659,18 @@ my.FlotGraph = Backbone.View.extend({
initialize: function(options, chart) { initialize: function(options, chart) {
var self = this; var self = this;
this.el = $(this.el); this.el = $(this.el);
_.bindAll(this, 'render'); _.bindAll(this, 'render', 'redraw');
this.model.currentDocuments.bind('add', this.render); // we need the model.headers to render properly
this.model.currentDocuments.bind('reset', this.render); this.model.bind('change', this.render);
this.model.currentDocuments.bind('add', this.redraw);
this.model.currentDocuments.bind('reset', this.redraw);
this.chart = chart; this.chart = chart;
this.chartConfig = { this.chartConfig = {
group: null, group: null,
series: [], series: [],
graphType: 'line' graphType: 'line'
}; };
this.render();
}, },
toTemplateJSON: function() { toTemplateJSON: function() {
@@ -671,6 +692,29 @@ my.FlotGraph = Backbone.View.extend({
onEditorSubmit: function(e) { onEditorSubmit: function(e) {
var select = this.el.find('.editor-group select'); var select = this.el.find('.editor-group select');
this._getEditorData(); this._getEditorData();
this.redraw();
},
redraw: function() {
// There appear to be issues generating a Flot graph if either:
// * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with
//
// Uncaught Invalid dimensions for plot, width = 0, height = 0
// * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value'
var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]);
if (!this.plot && (!areWeVisible || this.model.currentDocuments.length == 0)) {
return
}
// create this.plot and cache it
if (!this.plot) {
// only lines for the present
options = {
id: 'line',
name: 'Line Chart'
};
this.plot = $.plot(this.$graph, this.createSeries(), options);
}
this.plot.setData(this.createSeries()); this.plot.setData(this.createSeries());
this.plot.resize(); this.plot.resize();
this.plot.setupGrid(); this.plot.setupGrid();
@@ -686,16 +730,6 @@ my.FlotGraph = Backbone.View.extend({
this.chartConfig.group = this.el.find('.editor-group select').val(); this.chartConfig.group = this.el.find('.editor-group select').val();
}, },
createPlot: function () {
// only lines for the present
options = {
id: 'line',
name: 'Line Chart'
};
this.plot = $.plot(this.$graph, this.createSeries(), options);
return this;
},
createSeries: function () { createSeries: function () {
var self = this; var self = this;
var series = []; var series = [];