[#145,view/refactor][m]: switch to a proper sidebar setup where views can have both a main element and a sidebar element and this added to central sidebar.
* this fixes functional and visual bugs with current (new) setup where fields where in separate in RHS sidebar * Refactored map and graph view to put menu/controls in the sidebar (this is quite nice!)
This commit is contained in:
parent
e73be69499
commit
ef74f13163
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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: ' \
|
||||
<div class="editor"> \
|
||||
<form class="form-stacked"> \
|
||||
<div class="clearfix"> \
|
||||
<label>Graph Type</label> \
|
||||
<div class="input editor-type"> \
|
||||
<select> \
|
||||
<option value="lines-and-points">Lines and Points</option> \
|
||||
<option value="lines">Lines</option> \
|
||||
<option value="points">Points</option> \
|
||||
<option value="bars">Bars</option> \
|
||||
</select> \
|
||||
</div> \
|
||||
<label>Group Column (x-axis)</label> \
|
||||
<div class="input editor-group"> \
|
||||
<select> \
|
||||
<option value="">Please choose ...</option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
<div class="editor-series-group"> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="editor-buttons"> \
|
||||
<button class="btn editor-add">Add Series</button> \
|
||||
</div> \
|
||||
<div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
|
||||
<button class="editor-save">Save</button> \
|
||||
<input type="hidden" class="editor-id" value="chart-1" /> \
|
||||
</div> \
|
||||
</form> \
|
||||
</div> \
|
||||
<div class="panel graph"> \
|
||||
<div class="js-temp-notice alert alert-block"> \
|
||||
<h3 class="alert-heading">Hey there!</h3> \
|
||||
@ -68,26 +34,6 @@ my.Graph = Backbone.View.extend({
|
||||
</div> \
|
||||
</div> \
|
||||
',
|
||||
templateSeriesEditor: ' \
|
||||
<div class="editor-series js-series-{{seriesIndex}}"> \
|
||||
<label>Series <span>{{seriesName}} (y-axis)</span> \
|
||||
[<a href="#remove" class="action-remove-series">Remove</a>] \
|
||||
</label> \
|
||||
<div class="input"> \
|
||||
<select> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
</div> \
|
||||
',
|
||||
|
||||
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: ' \
|
||||
<div class="editor"> \
|
||||
<form class="form-stacked"> \
|
||||
<div class="clearfix"> \
|
||||
<label>Graph Type</label> \
|
||||
<div class="input editor-type"> \
|
||||
<select> \
|
||||
<option value="lines-and-points">Lines and Points</option> \
|
||||
<option value="lines">Lines</option> \
|
||||
<option value="points">Points</option> \
|
||||
<option value="bars">Bars</option> \
|
||||
</select> \
|
||||
</div> \
|
||||
<label>Group Column (x-axis)</label> \
|
||||
<div class="input editor-group"> \
|
||||
<select> \
|
||||
<option value="">Please choose ...</option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
<div class="editor-series-group"> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="editor-buttons"> \
|
||||
<button class="btn editor-add">Add Series</button> \
|
||||
</div> \
|
||||
<div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
|
||||
<button class="editor-save">Save</button> \
|
||||
<input type="hidden" class="editor-id" value="chart-1" /> \
|
||||
</div> \
|
||||
</form> \
|
||||
</div> \
|
||||
',
|
||||
templateSeriesEditor: ' \
|
||||
<div class="editor-series js-series-{{seriesIndex}}"> \
|
||||
<label>Series <span>{{seriesName}} (y-axis)</span> \
|
||||
[<a href="#remove" class="action-remove-series">Remove</a>] \
|
||||
</label> \
|
||||
<div class="input"> \
|
||||
<select> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
</div> \
|
||||
',
|
||||
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.
|
||||
|
||||
319
src/view.map.js
319
src/view.map.js
@ -24,68 +24,11 @@ this.recline.View = this.recline.View || {};
|
||||
// }
|
||||
// </pre>
|
||||
my.Map = Backbone.View.extend({
|
||||
|
||||
tagName: 'div',
|
||||
className: 'recline-map',
|
||||
|
||||
template: ' \
|
||||
<div class="editor"> \
|
||||
<form class="form-stacked"> \
|
||||
<div class="clearfix"> \
|
||||
<div class="editor-field-type"> \
|
||||
<label class="radio"> \
|
||||
<input type="radio" id="editor-field-type-latlon" name="editor-field-type" value="latlon" checked="checked"/> \
|
||||
Latitude / Longitude fields</label> \
|
||||
<label class="radio"> \
|
||||
<input type="radio" id="editor-field-type-geom" name="editor-field-type" value="geom" /> \
|
||||
GeoJSON field</label> \
|
||||
</div> \
|
||||
<div class="editor-field-type-latlon"> \
|
||||
<label>Latitude field</label> \
|
||||
<div class="input editor-lat-field"> \
|
||||
<select> \
|
||||
<option value=""></option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
<label>Longitude field</label> \
|
||||
<div class="input editor-lon-field"> \
|
||||
<select> \
|
||||
<option value=""></option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="editor-field-type-geom" style="display:none"> \
|
||||
<label>Geometry field (GeoJSON)</label> \
|
||||
<div class="input editor-geom-field"> \
|
||||
<select> \
|
||||
<option value=""></option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="editor-buttons"> \
|
||||
<button class="btn editor-update-map">Update</button> \
|
||||
</div> \
|
||||
<div class="editor-options" > \
|
||||
<label class="checkbox"> \
|
||||
<input type="checkbox" id="editor-auto-zoom" checked="checked" /> \
|
||||
Auto zoom to features</label> \
|
||||
</div> \
|
||||
<input type="hidden" class="editor-id" value="map-1" /> \
|
||||
</div> \
|
||||
</form> \
|
||||
</div> \
|
||||
<div class="panel map"> \
|
||||
</div> \
|
||||
<div class="panel map"></div> \
|
||||
',
|
||||
|
||||
// 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: ' \
|
||||
<form class="form-stacked"> \
|
||||
<div class="clearfix"> \
|
||||
<div class="editor-field-type"> \
|
||||
<label class="radio"> \
|
||||
<input type="radio" id="editor-field-type-latlon" name="editor-field-type" value="latlon" checked="checked"/> \
|
||||
Latitude / Longitude fields</label> \
|
||||
<label class="radio"> \
|
||||
<input type="radio" id="editor-field-type-geom" name="editor-field-type" value="geom" /> \
|
||||
GeoJSON field</label> \
|
||||
</div> \
|
||||
<div class="editor-field-type-latlon"> \
|
||||
<label>Latitude field</label> \
|
||||
<div class="input editor-lat-field"> \
|
||||
<select> \
|
||||
<option value=""></option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
<label>Longitude field</label> \
|
||||
<div class="input editor-lon-field"> \
|
||||
<select> \
|
||||
<option value=""></option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="editor-field-type-geom" style="display:none"> \
|
||||
<label>Geometry field (GeoJSON)</label> \
|
||||
<div class="input editor-geom-field"> \
|
||||
<select> \
|
||||
<option value=""></option> \
|
||||
{{#fields}} \
|
||||
<option value="{{id}}">{{label}}</option> \
|
||||
{{/fields}} \
|
||||
</select> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="editor-buttons"> \
|
||||
<button class="btn editor-update-map">Update</button> \
|
||||
</div> \
|
||||
<div class="editor-options" > \
|
||||
<label class="checkbox"> \
|
||||
<input type="checkbox" id="editor-auto-zoom" checked="checked" /> \
|
||||
Auto zoom to features</label> \
|
||||
</div> \
|
||||
<input type="hidden" class="editor-id" value="map-1" /> \
|
||||
</div> \
|
||||
</form> \
|
||||
',
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
|
||||
@ -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){
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user