Merge branch 'master' into gh-pages
This commit is contained in:
@@ -58,12 +58,12 @@
|
|||||||
<script type="text/javascript" src="../src/backend/gdocs.js"></script>
|
<script type="text/javascript" src="../src/backend/gdocs.js"></script>
|
||||||
<script type="text/javascript" src="../src/backend/csv.js"></script>
|
<script type="text/javascript" src="../src/backend/csv.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../src/view-grid.js"></script>
|
<script type="text/javascript" src="../src/view.grid.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-slickgrid.js"></script>
|
<script type="text/javascript" src="../src/view.slickgrid.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-transform-dialog.js"></script>
|
<script type="text/javascript" src="../src/view.transform.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-graph.js"></script>
|
<script type="text/javascript" src="../src/view.graph.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-map.js"></script>
|
<script type="text/javascript" src="../src/view.map.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-timeline.js"></script>
|
<script type="text/javascript" src="../src/view.timeline.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.pager.js"></script>
|
<script type="text/javascript" src="../src/widget.pager.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.filtereditor.js"></script>
|
<script type="text/javascript" src="../src/widget.filtereditor.js"></script>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.recline-graph .graph {
|
.recline-graph .graph {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
margin-right: 200px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.recline-graph .legend table {
|
.recline-graph .legend table {
|
||||||
@@ -18,25 +17,3 @@
|
|||||||
margin: auto;
|
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 {
|
.recline-data-explorer .data-view-container {
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-right: 225px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recline-data-explorer .data-view-sidebar {
|
.recline-data-explorer .data-view-sidebar {
|
||||||
float: right;
|
float: right;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recline-data-explorer .header .navigation {
|
.recline-data-explorer .header .navigation {
|
||||||
@@ -94,22 +96,25 @@
|
|||||||
* Filter Editor
|
* Filter Editor
|
||||||
*********************************************************/
|
*********************************************************/
|
||||||
|
|
||||||
.recline-filter-editor .filter-term .input-append a {
|
.recline-filter-editor {
|
||||||
margin-left: -5px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recline-facet-viewer .facet-summary label {
|
.recline-filter-editor .filter-term a {
|
||||||
display: inline;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recline-filter-editor input,
|
||||||
|
.recline-filter-editor select
|
||||||
|
{
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************
|
/**********************************************************
|
||||||
* Fields Widget
|
* Fields Widget
|
||||||
*********************************************************/
|
*********************************************************/
|
||||||
|
|
||||||
.recline-fields-view {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recline-fields-view .fields-list {
|
.recline-fields-view .fields-list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,19 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
|
|||||||
if (results.error) {
|
if (results.error) {
|
||||||
dfd.reject(results.error);
|
dfd.reject(results.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename duplicate fieldIds as each field name needs to be
|
||||||
|
// unique.
|
||||||
|
var seen = {};
|
||||||
|
_.map(results.fields, function(fieldId, index) {
|
||||||
|
if (fieldId in seen) {
|
||||||
|
seen[fieldId] += 1;
|
||||||
|
results.fields[index] = fieldId + "("+seen[fieldId]+")";
|
||||||
|
} else {
|
||||||
|
seen[fieldId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
dataset.fields.reset(_.map(results.fields, function(fieldId) {
|
dataset.fields.reset(_.map(results.fields, function(fieldId) {
|
||||||
return {id: fieldId};
|
return {id: fieldId};
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
|
|||||||
var value = rawdoc[field.id];
|
var value = rawdoc[field.id];
|
||||||
if (value !== null) { value = value.toString(); }
|
if (value !== null) { value = value.toString(); }
|
||||||
// TODO regexes?
|
// TODO regexes?
|
||||||
foundmatch = foundmatch || (value === term);
|
foundmatch = foundmatch || (value.toLowerCase() === term.toLowerCase());
|
||||||
// TODO: early out (once we are true should break to spare unnecessary testing)
|
// TODO: early out (once we are true should break to spare unnecessary testing)
|
||||||
// if (foundmatch) return true;
|
// if (foundmatch) return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ my.Query = Backbone.Model.extend({
|
|||||||
addTermFilter: function(fieldId, value) {
|
addTermFilter: function(fieldId, value) {
|
||||||
var filters = this.get('filters');
|
var filters = this.get('filters');
|
||||||
var filter = { term: {} };
|
var filter = { term: {} };
|
||||||
filter.term[fieldId] = value;
|
filter.term[fieldId] = value || '';
|
||||||
filters.push(filter);
|
filters.push(filter);
|
||||||
this.set({filters: filters});
|
this.set({filters: filters});
|
||||||
// change does not seem to be triggered automatically
|
// change does not seem to be triggered automatically
|
||||||
|
|||||||
@@ -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
|
// 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.
|
// generate the element itself (you can then append view.el to the DOM.
|
||||||
my.Graph = Backbone.View.extend({
|
my.Graph = Backbone.View.extend({
|
||||||
|
|
||||||
tagName: "div",
|
tagName: "div",
|
||||||
className: "recline-graph",
|
className: "recline-graph",
|
||||||
|
|
||||||
template: ' \
|
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="panel graph"> \
|
||||||
<div class="js-temp-notice alert alert-block"> \
|
<div class="js-temp-notice alert alert-block"> \
|
||||||
<h3 class="alert-heading">Hey there!</h3> \
|
<h3 class="alert-heading">Hey there!</h3> \
|
||||||
@@ -68,26 +34,6 @@ my.Graph = Backbone.View.extend({
|
|||||||
</div> \
|
</div> \
|
||||||
</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) {
|
initialize: function(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -114,6 +60,15 @@ my.Graph = Backbone.View.extend({
|
|||||||
options.state
|
options.state
|
||||||
);
|
);
|
||||||
this.state = new recline.Model.ObjectState(stateData);
|
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();
|
this.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -123,56 +78,9 @@ my.Graph = Backbone.View.extend({
|
|||||||
var htmls = Mustache.render(this.template, tmplData);
|
var htmls = Mustache.render(this.template, tmplData);
|
||||||
$(this.el).html(htmls);
|
$(this.el).html(htmls);
|
||||||
this.$graph = this.el.find('.panel.graph');
|
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;
|
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() {
|
redraw: function() {
|
||||||
// There appear to be issues generating a Flot graph if either:
|
// 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
|
// check we have something to plot
|
||||||
if (this.state.get('group') && this.state.get('series')) {
|
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 series = this.createSeries();
|
||||||
var options = this.getGraphOptions(this.state.attributes.graphType);
|
var options = this.getGraphOptions(this.state.attributes.graphType);
|
||||||
this.plot = $.plot(this.$graph, series, options);
|
this.plot = $.plot(this.$graph, series, options);
|
||||||
@@ -362,6 +272,128 @@ my.Graph = Backbone.View.extend({
|
|||||||
series.push({data: points, label: field});
|
series.push({data: points, label: field});
|
||||||
});
|
});
|
||||||
return series;
|
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.
|
// Public: Adds a new empty series select box to the editor.
|
||||||
@@ -24,82 +24,18 @@ this.recline.View = this.recline.View || {};
|
|||||||
// }
|
// }
|
||||||
// </pre>
|
// </pre>
|
||||||
my.Map = Backbone.View.extend({
|
my.Map = Backbone.View.extend({
|
||||||
|
|
||||||
tagName: 'div',
|
tagName: 'div',
|
||||||
className: 'recline-map',
|
className: 'recline-map',
|
||||||
|
|
||||||
template: ' \
|
template: ' \
|
||||||
<div class="editor"> \
|
<div class="panel map"></div> \
|
||||||
<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> \
|
|
||||||
',
|
',
|
||||||
|
|
||||||
// These are the default (case-insensitive) names of field that are used if found.
|
// These are the default (case-insensitive) names of field that are used if found.
|
||||||
// If not found, the user will need to define the fields via the editor.
|
// If not found, the user will need to define the fields via the editor.
|
||||||
latitudeFieldNames: ['lat','latitude'],
|
latitudeFieldNames: ['lat','latitude'],
|
||||||
longitudeFieldNames: ['lon','longitude'],
|
longitudeFieldNames: ['lon','longitude'],
|
||||||
geometryFieldNames: ['geom','the_geom','geometry','spatial','location'],
|
geometryFieldNames: ['geojson', '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) {
|
initialize: function(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -107,10 +43,6 @@ my.Map = Backbone.View.extend({
|
|||||||
|
|
||||||
// Listen to changes in the fields
|
// Listen to changes in the fields
|
||||||
this.model.fields.bind('change', function() {
|
this.model.fields.bind('change', function() {
|
||||||
self._setupGeometryField();
|
|
||||||
});
|
|
||||||
this.model.fields.bind('add', this.render);
|
|
||||||
this.model.fields.bind('reset', function(){
|
|
||||||
self._setupGeometryField()
|
self._setupGeometryField()
|
||||||
self.render()
|
self.render()
|
||||||
});
|
});
|
||||||
@@ -129,7 +61,7 @@ my.Map = Backbone.View.extend({
|
|||||||
// to display properly
|
// to display properly
|
||||||
if (self.map){
|
if (self.map){
|
||||||
self.map.invalidateSize();
|
self.map.invalidateSize();
|
||||||
if (self._zoomPending && self.autoZoom) {
|
if (self._zoomPending && self.state.get('autoZoom')) {
|
||||||
self._zoomToFeatures();
|
self._zoomToFeatures();
|
||||||
self._zoomPending = false;
|
self._zoomPending = false;
|
||||||
}
|
}
|
||||||
@@ -143,39 +75,36 @@ my.Map = Backbone.View.extend({
|
|||||||
var stateData = _.extend({
|
var stateData = _.extend({
|
||||||
geomField: null,
|
geomField: null,
|
||||||
lonField: null,
|
lonField: null,
|
||||||
latField: null
|
latField: null,
|
||||||
|
autoZoom: true
|
||||||
},
|
},
|
||||||
options.state
|
options.state
|
||||||
);
|
);
|
||||||
this.state = new recline.Model.ObjectState(stateData);
|
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.mapReady = false;
|
||||||
this.render();
|
this.render();
|
||||||
|
this.redraw();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ### Public: Adds the necessary elements to the page.
|
// ### Public: Adds the necessary elements to the page.
|
||||||
//
|
//
|
||||||
// Also sets up the editor fields and the map if necessary.
|
// Also sets up the editor fields and the map if necessary.
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
htmls = Mustache.render(this.template, this.model.toTemplateJSON());
|
htmls = Mustache.render(this.template, this.model.toTemplateJSON());
|
||||||
|
|
||||||
$(this.el).html(htmls);
|
$(this.el).html(htmls);
|
||||||
this.$map = this.el.find('.panel.map');
|
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;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -191,14 +120,14 @@ my.Map = Backbone.View.extend({
|
|||||||
var self = this;
|
var self = this;
|
||||||
action = action || 'refresh';
|
action = action || 'refresh';
|
||||||
// try to set things up if not already
|
// try to set things up if not already
|
||||||
if (!self.geomReady){
|
if (!self._geomReady()){
|
||||||
self._setupGeometryField();
|
self._setupGeometryField();
|
||||||
}
|
}
|
||||||
if (!self.mapReady){
|
if (!self.mapReady){
|
||||||
self._setupMap();
|
self._setupMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.geomReady && this.mapReady){
|
if (this._geomReady() && this.mapReady){
|
||||||
if (action == 'reset' || action == 'refresh'){
|
if (action == 'reset' || action == 'refresh'){
|
||||||
this.features.clearLayers();
|
this.features.clearLayers();
|
||||||
this._add(this.model.currentRecords.models);
|
this._add(this.model.currentRecords.models);
|
||||||
@@ -207,7 +136,7 @@ my.Map = Backbone.View.extend({
|
|||||||
} else if (action == 'remove' && doc){
|
} else if (action == 'remove' && doc){
|
||||||
this._remove(doc);
|
this._remove(doc);
|
||||||
}
|
}
|
||||||
if (this.autoZoom){
|
if (this.state.get('autoZoom')){
|
||||||
if (this.visible){
|
if (this.visible){
|
||||||
this._zoomToFeatures();
|
this._zoomToFeatures();
|
||||||
} else {
|
} else {
|
||||||
@@ -217,51 +146,8 @@ my.Map = Backbone.View.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
_geomReady: function() {
|
||||||
// UI Event handlers
|
return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField')));
|
||||||
//
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Private: Add one or n features to the map
|
// 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
|
// Private: Return a GeoJSON geomtry extracted from the record fields
|
||||||
//
|
//
|
||||||
_getGeometryFromRecord: function(doc){
|
_getGeometryFromRecord: function(doc){
|
||||||
if (this.geomReady){
|
if (this._geomReady()){
|
||||||
if (this.state.get('geomField')){
|
if (this.state.get('geomField')){
|
||||||
var value = doc.get(this.state.get('geomField'));
|
var value = doc.get(this.state.get('geomField'));
|
||||||
if (typeof(value) === 'string'){
|
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.
|
// If not found, the user can define them via the UI form.
|
||||||
_setupGeometryField: function(){
|
_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)
|
// should not overwrite if we have already set this (e.g. explicitly via state)
|
||||||
if (!this.geomReady) {
|
if (!this._geomReady()) {
|
||||||
this.state.set({
|
this.state.set({
|
||||||
geomField: this._checkField(this.geometryFieldNames),
|
geomField: this._checkField(this.geometryFieldNames),
|
||||||
latField: this._checkField(this.latitudeFieldNames),
|
latField: this._checkField(this.latitudeFieldNames),
|
||||||
lonField: this._checkField(this.longitudeFieldNames)
|
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).
|
// on [OpenStreetMap](http://openstreetmap.org).
|
||||||
//
|
//
|
||||||
_setupMap: function(){
|
_setupMap: function(){
|
||||||
|
|
||||||
this.map = new L.Map(this.$map.get(0));
|
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";
|
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);
|
})(jQuery, recline.View);
|
||||||
|
|
||||||
@@ -200,28 +200,46 @@ my.MultiView = Backbone.View.extend({
|
|||||||
tmplData.views = this.pageViews;
|
tmplData.views = this.pageViews;
|
||||||
var template = Mustache.render(this.template, tmplData);
|
var template = Mustache.render(this.template, tmplData);
|
||||||
$(this.el).html(template);
|
$(this.el).html(template);
|
||||||
|
|
||||||
|
// now create and append other views
|
||||||
var $dataViewContainer = this.el.find('.data-view-container');
|
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) {
|
_.each(this.pageViews, function(view, pageName) {
|
||||||
$dataViewContainer.append(view.view.el);
|
$dataViewContainer.append(view.view.el);
|
||||||
|
if (view.view.elSidebar) {
|
||||||
|
$dataSidebar.append(view.view.elSidebar);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var pager = new recline.View.Pager({
|
var pager = new recline.View.Pager({
|
||||||
model: this.model.queryState
|
model: this.model.queryState
|
||||||
});
|
});
|
||||||
this.el.find('.recline-results-info').after(pager.el);
|
this.el.find('.recline-results-info').after(pager.el);
|
||||||
|
|
||||||
var queryEditor = new recline.View.QueryEditor({
|
var queryEditor = new recline.View.QueryEditor({
|
||||||
model: this.model.queryState
|
model: this.model.queryState
|
||||||
});
|
});
|
||||||
this.el.find('.query-editor-here').append(queryEditor.el);
|
this.el.find('.query-editor-here').append(queryEditor.el);
|
||||||
|
|
||||||
var filterEditor = new recline.View.FilterEditor({
|
var filterEditor = new recline.View.FilterEditor({
|
||||||
model: this.model.queryState
|
model: this.model
|
||||||
});
|
});
|
||||||
this.$filterEditor = filterEditor.el;
|
this.$filterEditor = filterEditor.el;
|
||||||
this.el.find('.header').append(filterEditor.el);
|
$dataSidebar.append(filterEditor.el);
|
||||||
|
// are there actually any filters to show?
|
||||||
|
if (this.model.get('filters') && this.model.get('filters').length > 0) {
|
||||||
|
this.$filterEditor.show();
|
||||||
|
} else {
|
||||||
|
this.$filterEditor.hide();
|
||||||
|
}
|
||||||
|
|
||||||
var fieldsView = new recline.View.Fields({
|
var fieldsView = new recline.View.Fields({
|
||||||
model: this.model
|
model: this.model
|
||||||
});
|
});
|
||||||
this.$fieldsView = fieldsView.el;
|
this.$fieldsView = fieldsView.el;
|
||||||
this.el.find('.data-view-sidebar').append(fieldsView.el);
|
$dataSidebar.append(fieldsView.el);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateNav: function(pageName) {
|
updateNav: function(pageName) {
|
||||||
@@ -232,9 +250,15 @@ my.MultiView = Backbone.View.extend({
|
|||||||
_.each(this.pageViews, function(view, idx) {
|
_.each(this.pageViews, function(view, idx) {
|
||||||
if (view.id === pageName) {
|
if (view.id === pageName) {
|
||||||
view.view.el.show();
|
view.view.el.show();
|
||||||
|
if (view.view.elSidebar) {
|
||||||
|
view.view.elSidebar.show();
|
||||||
|
}
|
||||||
view.view.trigger('view:show');
|
view.view.trigger('view:show');
|
||||||
} else {
|
} else {
|
||||||
view.view.el.hide();
|
view.view.el.hide();
|
||||||
|
if (view.view.elSidebar) {
|
||||||
|
view.view.elSidebar.hide();
|
||||||
|
}
|
||||||
view.view.trigger('view:hide');
|
view.view.trigger('view:hide');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,8 +63,12 @@ my.Fields = Backbone.View.extend({
|
|||||||
this.el = $(this.el);
|
this.el = $(this.el);
|
||||||
_.bindAll(this, 'render');
|
_.bindAll(this, 'render');
|
||||||
|
|
||||||
this.model.fields.bind('all', function() {
|
// TODO: this is quite restrictive in terms of when it is re-run
|
||||||
|
// e.g. a change in type will not trigger a re-run atm.
|
||||||
|
// being more liberal (e.g. binding to all) can lead to being called a lot (e.g. for change:width)
|
||||||
|
this.model.fields.bind('reset', function(action) {
|
||||||
self.model.fields.each(function(field) {
|
self.model.fields.each(function(field) {
|
||||||
|
field.facets.unbind('all', self.render);
|
||||||
field.facets.bind('all', self.render);
|
field.facets.bind('all', self.render);
|
||||||
});
|
});
|
||||||
// fields can get reset or changed in which case we need to recalculate
|
// fields can get reset or changed in which case we need to recalculate
|
||||||
|
|||||||
@@ -8,49 +8,55 @@ this.recline.View = this.recline.View || {};
|
|||||||
my.FilterEditor = Backbone.View.extend({
|
my.FilterEditor = Backbone.View.extend({
|
||||||
className: 'recline-filter-editor well',
|
className: 'recline-filter-editor well',
|
||||||
template: ' \
|
template: ' \
|
||||||
<a class="close js-hide" href="#">×</a> \
|
<div class="filters"> \
|
||||||
<div class="row filters"> \
|
<h3>Filters</h3> \
|
||||||
<div class="span1"> \
|
<a href="#" class="js-add-filter">Add filter</a> \
|
||||||
<h3>Filters</h3> \
|
<form class="form-stacked js-add" style="display: none;"> \
|
||||||
</div> \
|
<fieldset> \
|
||||||
<div class="span11"> \
|
<label>Filter type</label> \
|
||||||
<form class="form-horizontal"> \
|
<select class="filterType"> \
|
||||||
<div class="row"> \
|
<option value="term">Term (text) filter</option> \
|
||||||
<div class="span6"> \
|
</select> \
|
||||||
{{#termFilters}} \
|
<label>Field</label> \
|
||||||
<div class="control-group filter-term filter" data-filter-id={{id}}> \
|
<select class="fields"> \
|
||||||
<label class="control-label" for="">{{label}}</label> \
|
{{#fields}} \
|
||||||
<div class="controls"> \
|
<option value="{{id}}">{{label}}</option> \
|
||||||
<div class="input-append"> \
|
{{/fields}} \
|
||||||
<input type="text" value="{{value}}" name="{{fieldId}}" class="span4" data-filter-field="{{fieldId}}" data-filter-id="{{id}}" data-filter-type="term" /> \
|
</select> \
|
||||||
<a class="btn js-remove-filter"><i class="icon-remove"></i></a> \
|
<button type="submit" class="btn">Add</button> \
|
||||||
</div> \
|
</fieldset> \
|
||||||
</div> \
|
</form> \
|
||||||
</div> \
|
<form class="form-stacked js-edit"> \
|
||||||
{{/termFilters}} \
|
{{#termFilters}} \
|
||||||
</div> \
|
<div class="control-group filter-term filter" data-filter-id={{id}}> \
|
||||||
<div class="span4"> \
|
<label class="control-label" for="">{{label}}</label> \
|
||||||
<p>To add a filter use the column menu in the grid view.</p> \
|
<div class="controls"> \
|
||||||
<button type="submit" class="btn">Update</button> \
|
<input type="text" value="{{value}}" name="{{fieldId}}" data-filter-field="{{fieldId}}" data-filter-id="{{id}}" data-filter-type="term" /> \
|
||||||
|
<a class="js-remove-filter" href="#">×</a> \
|
||||||
</div> \
|
</div> \
|
||||||
</form> \
|
</div> \
|
||||||
</div> \
|
{{/termFilters}} \
|
||||||
|
{{#termFilters.length}} \
|
||||||
|
<button type="submit" class="btn">Update</button> \
|
||||||
|
{{/termFilters.length}} \
|
||||||
|
</form> \
|
||||||
</div> \
|
</div> \
|
||||||
',
|
',
|
||||||
events: {
|
events: {
|
||||||
'click .js-hide': 'onHide',
|
|
||||||
'click .js-remove-filter': 'onRemoveFilter',
|
'click .js-remove-filter': 'onRemoveFilter',
|
||||||
'submit form': 'onTermFiltersUpdate'
|
'click .js-add-filter': 'onAddFilterShow',
|
||||||
|
'submit form.js-edit': 'onTermFiltersUpdate',
|
||||||
|
'submit form.js-add': 'onAddFilter'
|
||||||
},
|
},
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
this.el = $(this.el);
|
this.el = $(this.el);
|
||||||
_.bindAll(this, 'render');
|
_.bindAll(this, 'render');
|
||||||
this.model.bind('change', this.render);
|
this.model.queryState.bind('change', this.render);
|
||||||
this.model.bind('change:filters:new-blank', this.render);
|
this.model.queryState.bind('change:filters:new-blank', this.render);
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
var tmplData = $.extend(true, {}, this.model.toJSON());
|
var tmplData = $.extend(true, {}, this.model.queryState.toJSON());
|
||||||
// we will use idx in list as there id ...
|
// we will use idx in list as there id ...
|
||||||
tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
|
tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
|
||||||
filter.id = idx;
|
filter.id = idx;
|
||||||
@@ -68,29 +74,38 @@ my.FilterEditor = Backbone.View.extend({
|
|||||||
value: filter.term[fieldId]
|
value: filter.term[fieldId]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
tmplData.fields = this.model.fields.toJSON();
|
||||||
var out = Mustache.render(this.template, tmplData);
|
var out = Mustache.render(this.template, tmplData);
|
||||||
this.el.html(out);
|
this.el.html(out);
|
||||||
// are there actually any facets to show?
|
|
||||||
if (this.model.get('filters').length > 0) {
|
|
||||||
this.el.show();
|
|
||||||
} else {
|
|
||||||
this.el.hide();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onHide: function(e) {
|
onAddFilterShow: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.el.hide();
|
var $target = $(e.target);
|
||||||
|
$target.hide();
|
||||||
|
this.el.find('form.js-add').show();
|
||||||
|
},
|
||||||
|
onAddFilter: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $target = $(e.target);
|
||||||
|
$target.hide();
|
||||||
|
var filterType = $target.find('select.filterType').val();
|
||||||
|
var field = $target.find('select.fields').val();
|
||||||
|
if (filterType === 'term') {
|
||||||
|
this.model.queryState.addTermFilter(field);
|
||||||
|
}
|
||||||
|
// trigger render explicitly as queryState change will not be triggered (as blank value for filter)
|
||||||
|
this.render();
|
||||||
},
|
},
|
||||||
onRemoveFilter: function(e) {
|
onRemoveFilter: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var $target = $(e.target);
|
var $target = $(e.target);
|
||||||
var filterId = $target.closest('.filter').attr('data-filter-id');
|
var filterId = $target.closest('.filter').attr('data-filter-id');
|
||||||
this.model.removeFilter(filterId);
|
this.model.queryState.removeFilter(filterId);
|
||||||
},
|
},
|
||||||
onTermFiltersUpdate: function(e) {
|
onTermFiltersUpdate: function(e) {
|
||||||
var self = this;
|
var self = this;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var filters = self.model.get('filters');
|
var filters = self.model.queryState.get('filters');
|
||||||
var $form = $(e.target);
|
var $form = $(e.target);
|
||||||
_.each($form.find('input'), function(input) {
|
_.each($form.find('input'), function(input) {
|
||||||
var $input = $(input);
|
var $input = $(input);
|
||||||
@@ -99,8 +114,8 @@ my.FilterEditor = Backbone.View.extend({
|
|||||||
var fieldId = $input.attr('data-filter-field');
|
var fieldId = $input.attr('data-filter-field');
|
||||||
filters[filterIndex].term[fieldId] = value;
|
filters[filterIndex].term[fieldId] = value;
|
||||||
});
|
});
|
||||||
self.model.set({filters: filters});
|
self.model.queryState.set({filters: filters});
|
||||||
self.model.trigger('change');
|
self.model.queryState.trigger('change');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
14
test/base.js
14
test/base.js
@@ -7,17 +7,17 @@ var Fixture = {
|
|||||||
{id: 'y'},
|
{id: 'y'},
|
||||||
{id: 'z'},
|
{id: 'z'},
|
||||||
{id: 'country'},
|
{id: 'country'},
|
||||||
{id: 'label'},
|
{id: 'title'},
|
||||||
{id: 'lat'},
|
{id: 'lat'},
|
||||||
{id: 'lon'}
|
{id: 'lon'}
|
||||||
];
|
];
|
||||||
var documents = [
|
var documents = [
|
||||||
{id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', label: 'first', lat:52.56, lon:13.40},
|
{id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', title: 'first', lat:52.56, lon:13.40},
|
||||||
{id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', label: 'second', lat:54.97, lon:-1.60},
|
{id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', title: 'second', lat:54.97, lon:-1.60},
|
||||||
{id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', label: 'third', lat:40.00, lon:-75.5},
|
{id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', title: 'third', lat:40.00, lon:-75.5},
|
||||||
{id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', label: 'fourth', lat:57.27, lon:-6.20},
|
{id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', title: 'fourth', lat:57.27, lon:-6.20},
|
||||||
{id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', label: 'fifth', lat:51.58, lon:0},
|
{id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', title: 'fifth', lat:51.58, lon:0},
|
||||||
{id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', label: 'sixth', lat:51.04, lon:7.9}
|
{id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', title: 'sixth', lat:51.04, lon:7.9}
|
||||||
];
|
];
|
||||||
var dataset = recline.Backend.Memory.createDataset(documents, fields);
|
var dataset = recline.Backend.Memory.createDataset(documents, fields);
|
||||||
return dataset;
|
return dataset;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
<script type="text/javascript" src="base.js"></script>
|
<script type="text/javascript" src="base.js"></script>
|
||||||
|
|
||||||
|
<!-- model and backend tests -->
|
||||||
<script type="text/javascript" src="../src/model.js"></script>
|
<script type="text/javascript" src="../src/model.js"></script>
|
||||||
<script type="text/javascript" src="../src/backend/base.js"></script>
|
<script type="text/javascript" src="../src/backend/base.js"></script>
|
||||||
<script type="text/javascript" src="../src/backend/memory.js"></script>
|
<script type="text/javascript" src="../src/backend/memory.js"></script>
|
||||||
@@ -34,31 +35,34 @@
|
|||||||
<script type="text/javascript" src="../src/backend/gdocs.js"></script>
|
<script type="text/javascript" src="../src/backend/gdocs.js"></script>
|
||||||
<script type="text/javascript" src="../src/backend/elasticsearch.js"></script>
|
<script type="text/javascript" src="../src/backend/elasticsearch.js"></script>
|
||||||
<script type="text/javascript" src="../src/backend/csv.js"></script>
|
<script type="text/javascript" src="../src/backend/csv.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="model.test.js"></script>
|
<script type="text/javascript" src="model.test.js"></script>
|
||||||
<script type="text/javascript" src="backend.test.js"></script>
|
<script type="text/javascript" src="backend.test.js"></script>
|
||||||
<script type="text/javascript" src="backend/memory.test.js"></script>
|
<script type="text/javascript" src="backend/memory.test.js"></script>
|
||||||
<script type="text/javascript" src="backend.elasticsearch.test.js"></script>
|
<script type="text/javascript" src="backend/elasticsearch.test.js"></script>
|
||||||
<script type="text/javascript" src="backend/csv.test.js"></script>
|
<script type="text/javascript" src="backend/csv.test.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../src/view-grid.js"></script>
|
<!-- views and view tests -->
|
||||||
<script type="text/javascript" src="../src/view-slickgrid.js"></script>
|
<script type="text/javascript" src="../src/view.grid.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-transform-dialog.js"></script>
|
<script type="text/javascript" src="../src/view.slickgrid.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-graph.js"></script>
|
<script type="text/javascript" src="../src/view.transform.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-map.js"></script>
|
<script type="text/javascript" src="../src/view.graph.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-timeline.js"></script>
|
<script type="text/javascript" src="../src/view.map.js"></script>
|
||||||
|
<script type="text/javascript" src="../src/view.timeline.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.pager.js"></script>
|
<script type="text/javascript" src="../src/widget.pager.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.filtereditor.js"></script>
|
<script type="text/javascript" src="../src/widget.filtereditor.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.fields.js"></script>
|
<script type="text/javascript" src="../src/widget.fields.js"></script>
|
||||||
<script type="text/javascript" src="../src/view.multiview.js"></script>
|
<script type="text/javascript" src="../src/view.multiview.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="view-grid.test.js"></script>
|
<script type="text/javascript" src="view.grid.test.js"></script>
|
||||||
<script type="text/javascript" src="view-slickgrid.test.js"></script>
|
<script type="text/javascript" src="view.slickgrid.test.js"></script>
|
||||||
<script type="text/javascript" src="view-graph.test.js"></script>
|
<script type="text/javascript" src="view.graph.test.js"></script>
|
||||||
<script type="text/javascript" src="view-map.test.js"></script>
|
<script type="text/javascript" src="view.map.test.js"></script>
|
||||||
<script type="text/javascript" src="view-timeline.test.js"></script>
|
<script type="text/javascript" src="view.timeline.test.js"></script>
|
||||||
<script type="text/javascript" src="view.multiview.test.js"></script>
|
<script type="text/javascript" src="view.multiview.test.js"></script>
|
||||||
<script type="text/javascript" src="util.test.js"></script>
|
<script type="text/javascript" src="util.test.js"></script>
|
||||||
|
<script type="text/javascript" src="widget.filtereditor.test.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 id="qunit-header">Qunit Tests</h1>
|
<h1 id="qunit-header">Qunit Tests</h1>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ test('basics', function () {
|
|||||||
$('.fixtures').append(view.el);
|
$('.fixtures').append(view.el);
|
||||||
equal(view.state.get('graphType'), 'lines-and-points');
|
equal(view.state.get('graphType'), 'lines-and-points');
|
||||||
// view will auto render ...
|
// view will auto render ...
|
||||||
assertPresent('.editor', view.el);
|
assertPresent('.editor', view.elSidebar);
|
||||||
view.remove();
|
view.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,9 +27,9 @@ test('initialize', function () {
|
|||||||
deepEqual(view.state.get('series'), ['y', 'z']);
|
deepEqual(view.state.get('series'), ['y', 'z']);
|
||||||
|
|
||||||
// check we have updated editor with state info
|
// check we have updated editor with state info
|
||||||
equal(view.el.find('.editor-type select').val(), 'lines');
|
equal(view.elSidebar.find('.editor-type select').val(), 'lines');
|
||||||
equal(view.el.find('.editor-group select').val(), 'x');
|
equal(view.elSidebar.find('.editor-group select').val(), 'x');
|
||||||
var out = _.map(view.el.find('.editor-series select'), function($el) {
|
var out = _.map(view.elSidebar.find('.editor-series select'), function($el) {
|
||||||
return $($el).val();
|
return $($el).val();
|
||||||
});
|
});
|
||||||
deepEqual(out, ['y', 'z']);
|
deepEqual(out, ['y', 'z']);
|
||||||
@@ -51,3 +51,20 @@ test('dates in graph view', function () {
|
|||||||
|
|
||||||
view.remove();
|
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
|
//Fire query, otherwise the map won't be initialized
|
||||||
dataset.query();
|
dataset.query();
|
||||||
|
|
||||||
assertPresent('.editor',view.el);
|
assertPresent('.editor-field-type', view.elSidebar);
|
||||||
|
|
||||||
// Check that the Leaflet map was set up
|
// Check that the Leaflet map was set up
|
||||||
assertPresent('.leaflet-container',view.el);
|
assertPresent('.leaflet-container',view.el);
|
||||||
@@ -42,6 +42,21 @@ test('basics', function () {
|
|||||||
view.remove();
|
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 () {
|
test('Lat/Lon geom fields', function () {
|
||||||
var dataset = Fixture.getDataset();
|
var dataset = Fixture.getDataset();
|
||||||
var view = new recline.View.Map({
|
var view = new recline.View.Map({
|
||||||
@@ -138,6 +153,15 @@ test('Popup', function () {
|
|||||||
view.remove();
|
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 _getFeaturesCount = function(features){
|
||||||
var cnt = 0;
|
var cnt = 0;
|
||||||
features._iterateLayers(function(layer){
|
features._iterateLayers(function(layer){
|
||||||
@@ -27,7 +27,7 @@ test('state', function () {
|
|||||||
var view = new recline.View.SlickGrid({
|
var view = new recline.View.SlickGrid({
|
||||||
model: dataset,
|
model: dataset,
|
||||||
state: {
|
state: {
|
||||||
hiddenColumns:['x','lat','label'],
|
hiddenColumns:['x','lat','title'],
|
||||||
columnsOrder:['lon','id','z','date', 'y', 'country'],
|
columnsOrder:['lon','id','z','date', 'y', 'country'],
|
||||||
columnsSort:{column:'country',direction:'desc'},
|
columnsSort:{column:'country',direction:'desc'},
|
||||||
columnsWidth:[
|
columnsWidth:[
|
||||||
41
test/widget.filtereditor.test.js
Normal file
41
test/widget.filtereditor.test.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
module("Widget - Filter Editor");
|
||||||
|
|
||||||
|
test('basics', function () {
|
||||||
|
var dataset = Fixture.getDataset();
|
||||||
|
var view = new recline.View.FilterEditor({
|
||||||
|
model: dataset
|
||||||
|
});
|
||||||
|
$('.fixtures').append(view.el);
|
||||||
|
assertPresent('.js-add-filter', view.elSidebar);
|
||||||
|
var $addForm = view.el.find('form.js-add');
|
||||||
|
ok(!$addForm.is(":visible"));
|
||||||
|
view.el.find('.js-add-filter').click();
|
||||||
|
ok(!view.el.find('.js-add-filter').is(":visible"));
|
||||||
|
ok($addForm.is(":visible"));
|
||||||
|
|
||||||
|
// submit the form
|
||||||
|
$addForm.find('select.fields').val('country');
|
||||||
|
$addForm.submit();
|
||||||
|
|
||||||
|
// now check we have new filter
|
||||||
|
ok(!$addForm.is(":visible"));
|
||||||
|
$editForm = view.el.find('form.js-edit');
|
||||||
|
equal($editForm.find('.filter-term').length, 1)
|
||||||
|
equal(_.keys(dataset.queryState.attributes.filters[0].term)[0], 'country');
|
||||||
|
|
||||||
|
// now set filter value and apply
|
||||||
|
$editForm.find('input').val('UK');
|
||||||
|
$editForm.submit();
|
||||||
|
equal(dataset.queryState.attributes.filters[0].term.country, 'UK');
|
||||||
|
equal(dataset.currentRecords.length, 3);
|
||||||
|
|
||||||
|
// now remove filter
|
||||||
|
$editForm.find('.js-remove-filter').click();
|
||||||
|
// hmmm, not working yet but works by eye!
|
||||||
|
// $editForm = view.el.find('form.js-edit');
|
||||||
|
// equal($editForm.find('.filter-term').length, 0)
|
||||||
|
// equal(dataset.currentRecords.length, 6);
|
||||||
|
|
||||||
|
view.remove();
|
||||||
|
});
|
||||||
|
|
||||||
Reference in New Issue
Block a user