diff --git a/css/graph.css b/css/graph.css
index d88168c4..363d1fe9 100644
--- a/css/graph.css
+++ b/css/graph.css
@@ -1,6 +1,5 @@
.recline-graph .graph {
height: 500px;
- margin-right: 200px;
}
.recline-graph .legend table {
@@ -18,25 +17,3 @@
margin: auto;
}
-/**********************************************************
- * Editor
- *********************************************************/
-
-.recline-graph .editor {
- float: right;
- width: 200px;
- padding-left: 0px;
-}
-
-.recline-graph .editor form {
- padding-left: 4px;
-}
-
-.recline-graph .editor select {
- width: 100%;
-}
-
-.recline-graph .editor-hide-info p {
- display: none;
-}
-
diff --git a/css/multiview.css b/css/multiview.css
index 2404e304..a73a8f0f 100644
--- a/css/multiview.css
+++ b/css/multiview.css
@@ -1,10 +1,12 @@
.recline-data-explorer .data-view-container {
display: block;
+ margin-right: 225px;
}
.recline-data-explorer .data-view-sidebar {
float: right;
margin-left: 8px;
+ width: 220px;
}
.recline-data-explorer .header .navigation {
diff --git a/src/view.graph.js b/src/view.graph.js
index e7059cac..c67dc293 100644
--- a/src/view.graph.js
+++ b/src/view.graph.js
@@ -21,44 +21,10 @@ this.recline.View = this.recline.View || {};
// NB: should *not* provide an el argument to the view but must let the view
// generate the element itself (you can then append view.el to the DOM.
my.Graph = Backbone.View.extend({
-
tagName: "div",
className: "recline-graph",
template: ' \
-
\
\
\
Hey there!
\
@@ -68,26 +34,6 @@ my.Graph = Backbone.View.extend({
\
\
',
- templateSeriesEditor: ' \
- \
-
\
-
\
- \
-
\
-
\
- ',
-
- events: {
- 'change form select': 'onEditorSubmit',
- 'click .editor-add': '_onAddSeries',
- 'click .action-remove-series': 'removeSeries'
- },
initialize: function(options) {
var self = this;
@@ -114,6 +60,15 @@ my.Graph = Backbone.View.extend({
options.state
);
this.state = new recline.Model.ObjectState(stateData);
+ this.editor = new my.GraphControls({
+ model: this.model,
+ state: this.state.toJSON()
+ });
+ this.editor.state.bind('change', function() {
+ self.state.set(self.editor.state.toJSON());
+ self.redraw();
+ });
+ this.elSidebar = this.editor.el;
this.render();
},
@@ -123,56 +78,9 @@ my.Graph = Backbone.View.extend({
var htmls = Mustache.render(this.template, tmplData);
$(this.el).html(htmls);
this.$graph = this.el.find('.panel.graph');
-
- // set up editor from state
- if (this.state.get('graphType')) {
- this._selectOption('.editor-type', this.state.get('graphType'));
- }
- if (this.state.get('group')) {
- this._selectOption('.editor-group', this.state.get('group'));
- }
- // ensure at least one series box shows up
- var tmpSeries = [""];
- if (this.state.get('series').length > 0) {
- tmpSeries = this.state.get('series');
- }
- _.each(tmpSeries, function(series, idx) {
- self.addSeries(idx);
- self._selectOption('.editor-series.js-series-' + idx, series);
- });
return this;
},
- // Private: Helper function to select an option from a select list
- //
- _selectOption: function(id,value){
- var options = this.el.find(id + ' select > option');
- if (options) {
- options.each(function(opt){
- if (this.value == value) {
- $(this).attr('selected','selected');
- return false;
- }
- });
- }
- },
-
- onEditorSubmit: function(e) {
- var select = this.el.find('.editor-group select');
- var $editor = this;
- var $series = this.el.find('.editor-series select');
- var series = $series.map(function () {
- return $(this).val();
- });
- var updatedState = {
- series: $.makeArray(series),
- group: this.el.find('.editor-group select').val(),
- graphType: this.el.find('.editor-type select').val()
- };
- this.state.set(updatedState);
- this.redraw();
- },
-
redraw: function() {
// There appear to be issues generating a Flot graph if either:
@@ -187,6 +95,8 @@ my.Graph = Backbone.View.extend({
}
// check we have something to plot
if (this.state.get('group') && this.state.get('series')) {
+ // faff around with width because flot draws axes *outside* of the element width which means graph can get push down as it hits element next to it
+ this.$graph.width(this.el.width() - 20);
var series = this.createSeries();
var options = this.getGraphOptions(this.state.attributes.graphType);
this.plot = $.plot(this.$graph, series, options);
@@ -362,6 +272,128 @@ my.Graph = Backbone.View.extend({
series.push({data: points, label: field});
});
return series;
+ }
+});
+
+my.GraphControls = Backbone.View.extend({
+ className: "editor",
+ template: ' \
+ \
+',
+ templateSeriesEditor: ' \
+ \
+
\
+
\
+ \
+
\
+
\
+ ',
+ events: {
+ 'change form select': 'onEditorSubmit',
+ 'click .editor-add': '_onAddSeries',
+ 'click .action-remove-series': 'removeSeries'
+ },
+
+ initialize: function(options) {
+ var self = this;
+ this.el = $(this.el);
+ _.bindAll(this, 'render');
+ this.model.fields.bind('reset', this.render);
+ this.model.fields.bind('add', this.render);
+ this.state = new recline.Model.ObjectState(options.state);
+ this.render();
+ },
+
+ render: function() {
+ var self = this;
+ var tmplData = this.model.toTemplateJSON();
+ var htmls = Mustache.render(this.template, tmplData);
+ this.el.html(htmls);
+
+ // set up editor from state
+ if (this.state.get('graphType')) {
+ this._selectOption('.editor-type', this.state.get('graphType'));
+ }
+ if (this.state.get('group')) {
+ this._selectOption('.editor-group', this.state.get('group'));
+ }
+ // ensure at least one series box shows up
+ var tmpSeries = [""];
+ if (this.state.get('series').length > 0) {
+ tmpSeries = this.state.get('series');
+ }
+ _.each(tmpSeries, function(series, idx) {
+ self.addSeries(idx);
+ self._selectOption('.editor-series.js-series-' + idx, series);
+ });
+ return this;
+ },
+
+ // Private: Helper function to select an option from a select list
+ //
+ _selectOption: function(id,value){
+ var options = this.el.find(id + ' select > option');
+ if (options) {
+ options.each(function(opt){
+ if (this.value == value) {
+ $(this).attr('selected','selected');
+ return false;
+ }
+ });
+ }
+ },
+
+ onEditorSubmit: function(e) {
+ var select = this.el.find('.editor-group select');
+ var $editor = this;
+ var $series = this.el.find('.editor-series select');
+ var series = $series.map(function () {
+ return $(this).val();
+ });
+ var updatedState = {
+ series: $.makeArray(series),
+ group: this.el.find('.editor-group select').val(),
+ graphType: this.el.find('.editor-type select').val()
+ };
+ this.state.set(updatedState);
},
// Public: Adds a new empty series select box to the editor.
diff --git a/src/view.map.js b/src/view.map.js
index 79b19492..eb8f9631 100644
--- a/src/view.map.js
+++ b/src/view.map.js
@@ -24,68 +24,11 @@ this.recline.View = this.recline.View || {};
// }
//
my.Map = Backbone.View.extend({
-
tagName: 'div',
className: 'recline-map',
template: ' \
- \
- \
- \
- \
-
\
+ \
',
// These are the default (case-insensitive) names of field that are used if found.
@@ -94,23 +37,12 @@ my.Map = Backbone.View.extend({
longitudeFieldNames: ['lon','longitude'],
geometryFieldNames: ['geom','the_geom','geometry','spatial','location'],
- // Define here events for UI elements
- events: {
- 'click .editor-update-map': 'onEditorSubmit',
- 'change .editor-field-type': 'onFieldTypeChange',
- 'change #editor-auto-zoom': 'onAutoZoomChange'
- },
-
initialize: function(options) {
var self = this;
this.el = $(this.el);
// Listen to changes in the fields
this.model.fields.bind('change', function() {
- self._setupGeometryField();
- });
- this.model.fields.bind('add', this.render);
- this.model.fields.bind('reset', function(){
self._setupGeometryField()
self.render()
});
@@ -143,39 +75,36 @@ my.Map = Backbone.View.extend({
var stateData = _.extend({
geomField: null,
lonField: null,
- latField: null
+ latField: null,
+ autoZoom: true
},
options.state
);
this.state = new recline.Model.ObjectState(stateData);
+ this.menu = new my.MapMenu({
+ model: this.model,
+ state: this.state.toJSON()
+ });
+ this.menu.state.bind('change', function() {
+ self.state.set(self.menu.state.toJSON());
+ self.redraw();
+ });
+ this.elSidebar = this.menu.el;
- this.autoZoom = true;
this.mapReady = false;
this.render();
+ this.redraw();
},
// ### Public: Adds the necessary elements to the page.
//
// Also sets up the editor fields and the map if necessary.
render: function() {
-
var self = this;
htmls = Mustache.render(this.template, this.model.toTemplateJSON());
-
$(this.el).html(htmls);
this.$map = this.el.find('.panel.map');
-
- if (this.geomReady && this.model.fields.length){
- if (this.state.get('geomField')){
- this._selectOption('editor-geom-field',this.state.get('geomField'));
- $('#editor-field-type-geom').attr('checked','checked').change();
- } else{
- this._selectOption('editor-lon-field',this.state.get('lonField'));
- this._selectOption('editor-lat-field',this.state.get('latField'));
- $('#editor-field-type-latlon').attr('checked','checked').change();
- }
- }
return this;
},
@@ -191,14 +120,14 @@ my.Map = Backbone.View.extend({
var self = this;
action = action || 'refresh';
// try to set things up if not already
- if (!self.geomReady){
+ if (!self._geomReady()){
self._setupGeometryField();
}
if (!self.mapReady){
self._setupMap();
}
- if (this.geomReady && this.mapReady){
+ if (this._geomReady() && this.mapReady){
if (action == 'reset' || action == 'refresh'){
this.features.clearLayers();
this._add(this.model.currentRecords.models);
@@ -207,7 +136,7 @@ my.Map = Backbone.View.extend({
} else if (action == 'remove' && doc){
this._remove(doc);
}
- if (this.autoZoom){
+ if (this.state.get('autoZoom')){
if (this.visible){
this._zoomToFeatures();
} else {
@@ -217,51 +146,8 @@ my.Map = Backbone.View.extend({
}
},
- //
- // UI Event handlers
- //
-
- // Public: Update map with user options
- //
- // Right now the only configurable option is what field(s) contains the
- // location information.
- //
- onEditorSubmit: function(e){
- e.preventDefault();
- if ($('#editor-field-type-geom').attr('checked')){
- this.state.set({
- geomField: $('.editor-geom-field > select > option:selected').val(),
- lonField: null,
- latField: null
- });
- } else {
- this.state.set({
- geomField: null,
- lonField: $('.editor-lon-field > select > option:selected').val(),
- latField: $('.editor-lat-field > select > option:selected').val()
- });
- }
- this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
- this.redraw();
-
- return false;
- },
-
- // Public: Shows the relevant select lists depending on the location field
- // type selected.
- //
- onFieldTypeChange: function(e){
- if (e.target.value == 'geom'){
- $('.editor-field-type-geom').show();
- $('.editor-field-type-latlon').hide();
- } else {
- $('.editor-field-type-geom').hide();
- $('.editor-field-type-latlon').show();
- }
- },
-
- onAutoZoomChange: function(e){
- this.autoZoom = !this.autoZoom;
+ _geomReady: function() {
+ return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
},
// Private: Add one or n features to the map
@@ -341,7 +227,7 @@ my.Map = Backbone.View.extend({
// Private: Return a GeoJSON geomtry extracted from the record fields
//
_getGeometryFromRecord: function(doc){
- if (this.geomReady){
+ if (this._geomReady()){
if (this.state.get('geomField')){
var value = doc.get(this.state.get('geomField'));
if (typeof(value) === 'string'){
@@ -380,16 +266,14 @@ my.Map = Backbone.View.extend({
//
// If not found, the user can define them via the UI form.
_setupGeometryField: function(){
- var geomField, latField, lonField;
- this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
// should not overwrite if we have already set this (e.g. explicitly via state)
- if (!this.geomReady) {
+ if (!this._geomReady()) {
this.state.set({
geomField: this._checkField(this.geometryFieldNames),
latField: this._checkField(this.latitudeFieldNames),
lonField: this._checkField(this.longitudeFieldNames)
});
- this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
+ this.menu.state.set(this.state.toJSON());
}
},
@@ -427,7 +311,6 @@ my.Map = Backbone.View.extend({
// on [OpenStreetMap](http://openstreetmap.org).
//
_setupMap: function(){
-
this.map = new L.Map(this.$map.get(0));
var mapUrl = "http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png";
@@ -483,8 +366,166 @@ my.Map = Backbone.View.extend({
});
}
}
+});
- });
+my.MapMenu = Backbone.View.extend({
+ className: 'editor',
+
+ template: ' \
+ \
+',
+
+ // Define here events for UI elements
+ events: {
+ 'click .editor-update-map': 'onEditorSubmit',
+ 'change .editor-field-type': 'onFieldTypeChange',
+ 'change #editor-auto-zoom': 'onAutoZoomChange'
+ },
+
+ initialize: function(options) {
+ var self = this;
+ this.el = $(this.el);
+ _.bindAll(this, 'render');
+ this.model.fields.bind('change', this.render);
+ this.state = new recline.Model.ObjectState(options.state);
+ this.state.bind('change', this.render);
+ this.render();
+ },
+
+ // ### Public: Adds the necessary elements to the page.
+ //
+ // Also sets up the editor fields and the map if necessary.
+ render: function() {
+ var self = this;
+ htmls = Mustache.render(this.template, this.model.toTemplateJSON());
+ $(this.el).html(htmls);
+
+ if (this._geomReady() && this.model.fields.length){
+ if (this.state.get('geomField')){
+ this._selectOption('editor-geom-field',this.state.get('geomField'));
+ $('#editor-field-type-geom').attr('checked','checked').change();
+ } else{
+ this._selectOption('editor-lon-field',this.state.get('lonField'));
+ this._selectOption('editor-lat-field',this.state.get('latField'));
+ $('#editor-field-type-latlon').attr('checked','checked').change();
+ }
+ }
+ return this;
+ },
+
+ _geomReady: function() {
+ return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
+ },
+
+ // ## UI Event handlers
+ //
+
+ // Public: Update map with user options
+ //
+ // Right now the only configurable option is what field(s) contains the
+ // location information.
+ //
+ onEditorSubmit: function(e){
+ e.preventDefault();
+ if (this.el.find('#editor-field-type-geom').attr('checked')){
+ this.state.set({
+ geomField: this.el.find('.editor-geom-field > select > option:selected').val(),
+ lonField: null,
+ latField: null
+ });
+ } else {
+ this.state.set({
+ geomField: null,
+ lonField: this.el.find('.editor-lon-field > select > option:selected').val(),
+ latField: this.el.find('.editor-lat-field > select > option:selected').val()
+ });
+ }
+ return false;
+ },
+
+ // Public: Shows the relevant select lists depending on the location field
+ // type selected.
+ //
+ onFieldTypeChange: function(e){
+ if (e.target.value == 'geom'){
+ this.el.find('.editor-field-type-geom').show();
+ this.el.find('.editor-field-type-latlon').hide();
+ } else {
+ this.el.find('.editor-field-type-geom').hide();
+ this.el.find('.editor-field-type-latlon').show();
+ }
+ },
+
+ onAutoZoomChange: function(e){
+ this.state.set({autoZoom: !this.state.get('autoZoom')});
+ },
+
+ // Private: Helper function to select an option from a select list
+ //
+ _selectOption: function(id,value){
+ var options = this.el.find('.' + id + ' > select > option');
+ if (options){
+ options.each(function(opt){
+ if (this.value == value) {
+ $(this).attr('selected','selected');
+ return false;
+ }
+ });
+ }
+ }
+});
})(jQuery, recline.View);
diff --git a/src/view.multiview.js b/src/view.multiview.js
index dd90d641..96447891 100644
--- a/src/view.multiview.js
+++ b/src/view.multiview.js
@@ -200,28 +200,40 @@ my.MultiView = Backbone.View.extend({
tmplData.views = this.pageViews;
var template = Mustache.render(this.template, tmplData);
$(this.el).html(template);
+
+ // now create and append other views
var $dataViewContainer = this.el.find('.data-view-container');
+ var $dataSidebar = this.el.find('.data-view-sidebar');
+
+ // the main views
_.each(this.pageViews, function(view, pageName) {
$dataViewContainer.append(view.view.el);
+ if (view.view.elSidebar) {
+ $dataSidebar.append(view.view.elSidebar);
+ }
});
+
var pager = new recline.View.Pager({
model: this.model.queryState
});
this.el.find('.recline-results-info').after(pager.el);
+
var queryEditor = new recline.View.QueryEditor({
model: this.model.queryState
});
this.el.find('.query-editor-here').append(queryEditor.el);
+
var filterEditor = new recline.View.FilterEditor({
model: this.model.queryState
});
this.$filterEditor = filterEditor.el;
this.el.find('.header').append(filterEditor.el);
+
var fieldsView = new recline.View.Fields({
model: this.model
});
this.$fieldsView = fieldsView.el;
- this.el.find('.data-view-sidebar').append(fieldsView.el);
+ $dataSidebar.append(fieldsView.el);
},
updateNav: function(pageName) {
@@ -232,9 +244,15 @@ my.MultiView = Backbone.View.extend({
_.each(this.pageViews, function(view, idx) {
if (view.id === pageName) {
view.view.el.show();
+ if (view.view.elSidebar) {
+ view.view.elSidebar.show();
+ }
view.view.trigger('view:show');
} else {
view.view.el.hide();
+ if (view.view.elSidebar) {
+ view.view.elSidebar.hide();
+ }
view.view.trigger('view:hide');
}
});
diff --git a/test/view.graph.test.js b/test/view.graph.test.js
index 3b015f12..13c48efc 100644
--- a/test/view.graph.test.js
+++ b/test/view.graph.test.js
@@ -8,7 +8,7 @@ test('basics', function () {
$('.fixtures').append(view.el);
equal(view.state.get('graphType'), 'lines-and-points');
// view will auto render ...
- assertPresent('.editor', view.el);
+ assertPresent('.editor', view.elSidebar);
view.remove();
});
@@ -27,9 +27,9 @@ test('initialize', function () {
deepEqual(view.state.get('series'), ['y', 'z']);
// check we have updated editor with state info
- equal(view.el.find('.editor-type select').val(), 'lines');
- equal(view.el.find('.editor-group select').val(), 'x');
- var out = _.map(view.el.find('.editor-series select'), function($el) {
+ equal(view.elSidebar.find('.editor-type select').val(), 'lines');
+ equal(view.elSidebar.find('.editor-group select').val(), 'x');
+ var out = _.map(view.elSidebar.find('.editor-series select'), function($el) {
return $($el).val();
});
deepEqual(out, ['y', 'z']);
@@ -51,3 +51,20 @@ test('dates in graph view', function () {
view.remove();
});
+
+test('GraphControls basics', function () {
+ var dataset = Fixture.getDataset();
+ var view = new recline.View.GraphControls({
+ model: dataset,
+ state: {
+ graphType: 'bars',
+ series: []
+ }
+ });
+ $('.fixtures').append(view.el);
+ equal(view.state.get('graphType'), 'bars');
+ // view will auto render ...
+ assertPresent('.editor', view.el);
+ view.remove();
+});
+
diff --git a/test/view.map.test.js b/test/view.map.test.js
index 61e4d5a3..f5d26406 100644
--- a/test/view.map.test.js
+++ b/test/view.map.test.js
@@ -31,7 +31,7 @@ test('basics', function () {
//Fire query, otherwise the map won't be initialized
dataset.query();
- assertPresent('.editor',view.el);
+ assertPresent('.editor-field-type', view.elSidebar);
// Check that the Leaflet map was set up
assertPresent('.leaflet-container',view.el);
@@ -42,6 +42,21 @@ test('basics', function () {
view.remove();
});
+test('_setupGeometryField', function () {
+ var dataset = Fixture.getDataset();
+ var view = new recline.View.Map({
+ model: dataset
+ });
+ var exp = {
+ geomField: null,
+ lonField: 'lon',
+ latField: 'lat',
+ autoZoom: true
+ };
+ deepEqual(view.state.toJSON(), exp);
+ deepEqual(view.menu.state.toJSON(), exp);
+});
+
test('Lat/Lon geom fields', function () {
var dataset = Fixture.getDataset();
var view = new recline.View.Map({
@@ -138,6 +153,15 @@ test('Popup', function () {
view.remove();
});
+test('MapMenu', function () {
+ var dataset = Fixture.getDataset();
+ var controls = new recline.View.MapMenu({
+ model: dataset,
+ state: {}
+ });
+ assertPresent('.editor-field-type', controls.el);
+});
+
var _getFeaturesCount = function(features){
var cnt = 0;
features._iterateLayers(function(layer){