diff --git a/app/index.html b/app/index.html index 11d576c8..c07eec7f 100644 --- a/app/index.html +++ b/app/index.html @@ -58,12 +58,12 @@ - - - - - - + + + + + + diff --git a/css/graph.css b/css/graph.css index d88168c4..363d1fe9 100644 --- a/css/graph.css +++ b/css/graph.css @@ -1,6 +1,5 @@ .recline-graph .graph { height: 500px; - margin-right: 200px; } .recline-graph .legend table { @@ -18,25 +17,3 @@ margin: auto; } -/********************************************************** - * Editor - *********************************************************/ - -.recline-graph .editor { - float: right; - width: 200px; - padding-left: 0px; -} - -.recline-graph .editor form { - padding-left: 4px; -} - -.recline-graph .editor select { - width: 100%; -} - -.recline-graph .editor-hide-info p { - display: none; -} - diff --git a/css/multiview.css b/css/multiview.css index 2404e304..96afa99f 100644 --- a/css/multiview.css +++ b/css/multiview.css @@ -1,10 +1,12 @@ .recline-data-explorer .data-view-container { display: block; + margin-right: 225px; } .recline-data-explorer .data-view-sidebar { float: right; margin-left: 8px; + width: 220px; } .recline-data-explorer .header .navigation { @@ -94,22 +96,25 @@ * Filter Editor *********************************************************/ -.recline-filter-editor .filter-term .input-append a { - margin-left: -5px; +.recline-filter-editor { + padding: 8px; } -.recline-facet-viewer .facet-summary label { - display: inline; +.recline-filter-editor .filter-term a { + font-size: 18px; } +.recline-filter-editor input, +.recline-filter-editor select +{ + width: 175px; +} + + /********************************************************** * Fields Widget *********************************************************/ -.recline-fields-view { - width: 200px; -} - .recline-fields-view .fields-list { padding: 0; } diff --git a/src/backend/dataproxy.js b/src/backend/dataproxy.js index a2731f00..eb305189 100644 --- a/src/backend/dataproxy.js +++ b/src/backend/dataproxy.js @@ -56,6 +56,19 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; if (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) { return {id: fieldId}; }) diff --git a/src/backend/memory.js b/src/backend/memory.js index f42aede6..daf78dbf 100644 --- a/src/backend/memory.js +++ b/src/backend/memory.js @@ -110,7 +110,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; var value = rawdoc[field.id]; if (value !== null) { value = value.toString(); } // 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) // if (foundmatch) return true; }); diff --git a/src/model.js b/src/model.js index 11eca39f..2628903e 100644 --- a/src/model.js +++ b/src/model.js @@ -425,7 +425,7 @@ my.Query = Backbone.Model.extend({ addTermFilter: function(fieldId, value) { var filters = this.get('filters'); var filter = { term: {} }; - filter.term[fieldId] = value; + filter.term[fieldId] = value || ''; filters.push(filter); this.set({filters: filters}); // change does not seem to be triggered automatically diff --git a/src/view-graph.js b/src/view.graph.js similarity index 92% rename from src/view-graph.js rename to src/view.graph.js index e7059cac..c67dc293 100644 --- a/src/view-graph.js +++ b/src/view.graph.js @@ -21,44 +21,10 @@ this.recline.View = this.recline.View || {}; // NB: should *not* provide an el argument to the view but must let the view // generate the element itself (you can then append view.el to the DOM. my.Graph = Backbone.View.extend({ - tagName: "div", className: "recline-graph", template: ' \ -
\ -
\ -
\ - \ -
\ - \ -
\ - \ -
\ - \ -
\ -
\ -
\ -
\ -
\ - \ -
\ - \ -
\ -
\
\
\

Hey there!

\ @@ -68,26 +34,6 @@ my.Graph = Backbone.View.extend({
\
\ ', - templateSeriesEditor: ' \ -
\ - \ -
\ - \ -
\ -
\ - ', - - events: { - 'change form select': 'onEditorSubmit', - 'click .editor-add': '_onAddSeries', - 'click .action-remove-series': 'removeSeries' - }, initialize: function(options) { var self = this; @@ -114,6 +60,15 @@ my.Graph = Backbone.View.extend({ options.state ); this.state = new recline.Model.ObjectState(stateData); + this.editor = new my.GraphControls({ + model: this.model, + state: this.state.toJSON() + }); + this.editor.state.bind('change', function() { + self.state.set(self.editor.state.toJSON()); + self.redraw(); + }); + this.elSidebar = this.editor.el; this.render(); }, @@ -123,56 +78,9 @@ my.Graph = Backbone.View.extend({ var htmls = Mustache.render(this.template, tmplData); $(this.el).html(htmls); this.$graph = this.el.find('.panel.graph'); - - // set up editor from state - if (this.state.get('graphType')) { - this._selectOption('.editor-type', this.state.get('graphType')); - } - if (this.state.get('group')) { - this._selectOption('.editor-group', this.state.get('group')); - } - // ensure at least one series box shows up - var tmpSeries = [""]; - if (this.state.get('series').length > 0) { - tmpSeries = this.state.get('series'); - } - _.each(tmpSeries, function(series, idx) { - self.addSeries(idx); - self._selectOption('.editor-series.js-series-' + idx, series); - }); return this; }, - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = this.el.find(id + ' select > option'); - if (options) { - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - }, - - onEditorSubmit: function(e) { - var select = this.el.find('.editor-group select'); - var $editor = this; - var $series = this.el.find('.editor-series select'); - var series = $series.map(function () { - return $(this).val(); - }); - var updatedState = { - series: $.makeArray(series), - group: this.el.find('.editor-group select').val(), - graphType: this.el.find('.editor-type select').val() - }; - this.state.set(updatedState); - this.redraw(); - }, - redraw: function() { // There appear to be issues generating a Flot graph if either: @@ -187,6 +95,8 @@ my.Graph = Backbone.View.extend({ } // check we have something to plot if (this.state.get('group') && this.state.get('series')) { + // faff around with width because flot draws axes *outside* of the element width which means graph can get push down as it hits element next to it + this.$graph.width(this.el.width() - 20); var series = this.createSeries(); var options = this.getGraphOptions(this.state.attributes.graphType); this.plot = $.plot(this.$graph, series, options); @@ -362,6 +272,128 @@ my.Graph = Backbone.View.extend({ series.push({data: points, label: field}); }); return series; + } +}); + +my.GraphControls = Backbone.View.extend({ + className: "editor", + template: ' \ +
\ +
\ +
\ + \ +
\ + \ +
\ + \ +
\ + \ +
\ +
\ +
\ +
\ +
\ + \ +
\ + \ +
\ +
\ +', + templateSeriesEditor: ' \ +
\ + \ +
\ + \ +
\ +
\ + ', + events: { + 'change form select': 'onEditorSubmit', + 'click .editor-add': '_onAddSeries', + 'click .action-remove-series': 'removeSeries' + }, + + initialize: function(options) { + var self = this; + this.el = $(this.el); + _.bindAll(this, 'render'); + this.model.fields.bind('reset', this.render); + this.model.fields.bind('add', this.render); + this.state = new recline.Model.ObjectState(options.state); + this.render(); + }, + + render: function() { + var self = this; + var tmplData = this.model.toTemplateJSON(); + var htmls = Mustache.render(this.template, tmplData); + this.el.html(htmls); + + // set up editor from state + if (this.state.get('graphType')) { + this._selectOption('.editor-type', this.state.get('graphType')); + } + if (this.state.get('group')) { + this._selectOption('.editor-group', this.state.get('group')); + } + // ensure at least one series box shows up + var tmpSeries = [""]; + if (this.state.get('series').length > 0) { + tmpSeries = this.state.get('series'); + } + _.each(tmpSeries, function(series, idx) { + self.addSeries(idx); + self._selectOption('.editor-series.js-series-' + idx, series); + }); + return this; + }, + + // Private: Helper function to select an option from a select list + // + _selectOption: function(id,value){ + var options = this.el.find(id + ' select > option'); + if (options) { + options.each(function(opt){ + if (this.value == value) { + $(this).attr('selected','selected'); + return false; + } + }); + } + }, + + onEditorSubmit: function(e) { + var select = this.el.find('.editor-group select'); + var $editor = this; + var $series = this.el.find('.editor-series select'); + var series = $series.map(function () { + return $(this).val(); + }); + var updatedState = { + series: $.makeArray(series), + group: this.el.find('.editor-group select').val(), + graphType: this.el.find('.editor-type select').val() + }; + this.state.set(updatedState); }, // Public: Adds a new empty series select box to the editor. diff --git a/src/view-grid.js b/src/view.grid.js similarity index 100% rename from src/view-grid.js rename to src/view.grid.js diff --git a/src/view-map.js b/src/view.map.js similarity index 83% rename from src/view-map.js rename to src/view.map.js index 79b19492..624ef29d 100644 --- a/src/view-map.js +++ b/src/view.map.js @@ -24,82 +24,18 @@ this.recline.View = this.recline.View || {}; // } // my.Map = Backbone.View.extend({ - tagName: 'div', className: 'recline-map', template: ' \ -
\ -
\ -
\ -
\ - \ - \ -
\ -
\ - \ -
\ - \ -
\ - \ -
\ - \ -
\ -
\ - \ -
\ -
\ - \ -
\ -
\ - \ -
\ - \ -
\ - \ - \ -
\ -
\ +
\ ', // These are the default (case-insensitive) names of field that are used if found. // If not found, the user will need to define the fields via the editor. latitudeFieldNames: ['lat','latitude'], 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' - }, + geometryFieldNames: ['geojson', 'geom','the_geom','geometry','spatial','location'], initialize: function(options) { var self = this; @@ -107,10 +43,6 @@ my.Map = Backbone.View.extend({ // 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() }); @@ -129,7 +61,7 @@ my.Map = Backbone.View.extend({ // to display properly if (self.map){ self.map.invalidateSize(); - if (self._zoomPending && self.autoZoom) { + if (self._zoomPending && self.state.get('autoZoom')) { self._zoomToFeatures(); self._zoomPending = false; } @@ -143,39 +75,36 @@ my.Map = Backbone.View.extend({ var stateData = _.extend({ geomField: null, lonField: null, - latField: null + latField: null, + autoZoom: true }, options.state ); this.state = new recline.Model.ObjectState(stateData); + this.menu = new my.MapMenu({ + model: this.model, + state: this.state.toJSON() + }); + this.menu.state.bind('change', function() { + self.state.set(self.menu.state.toJSON()); + self.redraw(); + }); + this.elSidebar = this.menu.el; - this.autoZoom = true; this.mapReady = false; this.render(); + this.redraw(); }, // ### Public: Adds the necessary elements to the page. // // Also sets up the editor fields and the map if necessary. render: function() { - var self = this; htmls = Mustache.render(this.template, this.model.toTemplateJSON()); - $(this.el).html(htmls); this.$map = this.el.find('.panel.map'); - - if (this.geomReady && this.model.fields.length){ - if (this.state.get('geomField')){ - this._selectOption('editor-geom-field',this.state.get('geomField')); - $('#editor-field-type-geom').attr('checked','checked').change(); - } else{ - this._selectOption('editor-lon-field',this.state.get('lonField')); - this._selectOption('editor-lat-field',this.state.get('latField')); - $('#editor-field-type-latlon').attr('checked','checked').change(); - } - } return this; }, @@ -191,14 +120,14 @@ my.Map = Backbone.View.extend({ var self = this; action = action || 'refresh'; // try to set things up if not already - if (!self.geomReady){ + if (!self._geomReady()){ self._setupGeometryField(); } if (!self.mapReady){ self._setupMap(); } - if (this.geomReady && this.mapReady){ + if (this._geomReady() && this.mapReady){ if (action == 'reset' || action == 'refresh'){ this.features.clearLayers(); this._add(this.model.currentRecords.models); @@ -207,7 +136,7 @@ my.Map = Backbone.View.extend({ } else if (action == 'remove' && doc){ this._remove(doc); } - if (this.autoZoom){ + if (this.state.get('autoZoom')){ if (this.visible){ this._zoomToFeatures(); } else { @@ -217,51 +146,8 @@ my.Map = Backbone.View.extend({ } }, - // - // UI Event handlers - // - - // Public: Update map with user options - // - // Right now the only configurable option is what field(s) contains the - // location information. - // - onEditorSubmit: function(e){ - e.preventDefault(); - if ($('#editor-field-type-geom').attr('checked')){ - this.state.set({ - geomField: $('.editor-geom-field > select > option:selected').val(), - lonField: null, - latField: null - }); - } else { - this.state.set({ - geomField: null, - lonField: $('.editor-lon-field > select > option:selected').val(), - latField: $('.editor-lat-field > select > option:selected').val() - }); - } - this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); - this.redraw(); - - return false; - }, - - // Public: Shows the relevant select lists depending on the location field - // type selected. - // - onFieldTypeChange: function(e){ - if (e.target.value == 'geom'){ - $('.editor-field-type-geom').show(); - $('.editor-field-type-latlon').hide(); - } else { - $('.editor-field-type-geom').hide(); - $('.editor-field-type-latlon').show(); - } - }, - - onAutoZoomChange: function(e){ - this.autoZoom = !this.autoZoom; + _geomReady: function() { + return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); }, // Private: Add one or n features to the map @@ -341,7 +227,7 @@ my.Map = Backbone.View.extend({ // Private: Return a GeoJSON geomtry extracted from the record fields // _getGeometryFromRecord: function(doc){ - if (this.geomReady){ + if (this._geomReady()){ if (this.state.get('geomField')){ var value = doc.get(this.state.get('geomField')); if (typeof(value) === 'string'){ @@ -380,16 +266,14 @@ my.Map = Backbone.View.extend({ // // If not found, the user can define them via the UI form. _setupGeometryField: function(){ - var geomField, latField, lonField; - this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); // should not overwrite if we have already set this (e.g. explicitly via state) - if (!this.geomReady) { + if (!this._geomReady()) { this.state.set({ geomField: this._checkField(this.geometryFieldNames), latField: this._checkField(this.latitudeFieldNames), lonField: this._checkField(this.longitudeFieldNames) }); - this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); + this.menu.state.set(this.state.toJSON()); } }, @@ -427,7 +311,6 @@ my.Map = Backbone.View.extend({ // on [OpenStreetMap](http://openstreetmap.org). // _setupMap: function(){ - this.map = new L.Map(this.$map.get(0)); var mapUrl = "http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"; @@ -483,8 +366,166 @@ my.Map = Backbone.View.extend({ }); } } +}); - }); +my.MapMenu = Backbone.View.extend({ + className: 'editor', + + template: ' \ +
\ +
\ +
\ + \ + \ +
\ +
\ + \ +
\ + \ +
\ + \ +
\ + \ +
\ +
\ + \ +
\ +
\ + \ +
\ +
\ + \ +
\ + \ + \ +
\ +', + + // Define here events for UI elements + events: { + 'click .editor-update-map': 'onEditorSubmit', + 'change .editor-field-type': 'onFieldTypeChange', + 'change #editor-auto-zoom': 'onAutoZoomChange' + }, + + initialize: function(options) { + var self = this; + this.el = $(this.el); + _.bindAll(this, 'render'); + this.model.fields.bind('change', this.render); + this.state = new recline.Model.ObjectState(options.state); + this.state.bind('change', this.render); + this.render(); + }, + + // ### Public: Adds the necessary elements to the page. + // + // Also sets up the editor fields and the map if necessary. + render: function() { + var self = this; + htmls = Mustache.render(this.template, this.model.toTemplateJSON()); + $(this.el).html(htmls); + + if (this._geomReady() && this.model.fields.length){ + if (this.state.get('geomField')){ + this._selectOption('editor-geom-field',this.state.get('geomField')); + $('#editor-field-type-geom').attr('checked','checked').change(); + } else{ + this._selectOption('editor-lon-field',this.state.get('lonField')); + this._selectOption('editor-lat-field',this.state.get('latField')); + $('#editor-field-type-latlon').attr('checked','checked').change(); + } + } + return this; + }, + + _geomReady: function() { + return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); + }, + + // ## UI Event handlers + // + + // Public: Update map with user options + // + // Right now the only configurable option is what field(s) contains the + // location information. + // + onEditorSubmit: function(e){ + e.preventDefault(); + if (this.el.find('#editor-field-type-geom').attr('checked')){ + this.state.set({ + geomField: this.el.find('.editor-geom-field > select > option:selected').val(), + lonField: null, + latField: null + }); + } else { + this.state.set({ + geomField: null, + lonField: this.el.find('.editor-lon-field > select > option:selected').val(), + latField: this.el.find('.editor-lat-field > select > option:selected').val() + }); + } + return false; + }, + + // Public: Shows the relevant select lists depending on the location field + // type selected. + // + onFieldTypeChange: function(e){ + if (e.target.value == 'geom'){ + this.el.find('.editor-field-type-geom').show(); + this.el.find('.editor-field-type-latlon').hide(); + } else { + this.el.find('.editor-field-type-geom').hide(); + this.el.find('.editor-field-type-latlon').show(); + } + }, + + onAutoZoomChange: function(e){ + this.state.set({autoZoom: !this.state.get('autoZoom')}); + }, + + // Private: Helper function to select an option from a select list + // + _selectOption: function(id,value){ + var options = this.el.find('.' + id + ' > select > option'); + if (options){ + options.each(function(opt){ + if (this.value == value) { + $(this).attr('selected','selected'); + return false; + } + }); + } + } +}); })(jQuery, recline.View); diff --git a/src/view.multiview.js b/src/view.multiview.js index dd90d641..1401dc36 100644 --- a/src/view.multiview.js +++ b/src/view.multiview.js @@ -200,28 +200,46 @@ 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 + model: this.model }); 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({ 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 +250,15 @@ my.MultiView = Backbone.View.extend({ _.each(this.pageViews, function(view, idx) { if (view.id === pageName) { view.view.el.show(); + if (view.view.elSidebar) { + view.view.elSidebar.show(); + } view.view.trigger('view:show'); } else { view.view.el.hide(); + if (view.view.elSidebar) { + view.view.elSidebar.hide(); + } view.view.trigger('view:hide'); } }); diff --git a/src/view-slickgrid.js b/src/view.slickgrid.js similarity index 100% rename from src/view-slickgrid.js rename to src/view.slickgrid.js diff --git a/src/view-timeline.js b/src/view.timeline.js similarity index 100% rename from src/view-timeline.js rename to src/view.timeline.js diff --git a/src/view-transform-dialog.js b/src/view.transform.js similarity index 100% rename from src/view-transform-dialog.js rename to src/view.transform.js diff --git a/src/widget.fields.js b/src/widget.fields.js index f4894273..598d4cf3 100644 --- a/src/widget.fields.js +++ b/src/widget.fields.js @@ -63,8 +63,12 @@ my.Fields = Backbone.View.extend({ this.el = $(this.el); _.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) { + field.facets.unbind('all', self.render); field.facets.bind('all', self.render); }); // fields can get reset or changed in which case we need to recalculate diff --git a/src/widget.filtereditor.js b/src/widget.filtereditor.js index 1564e3ab..a53a8b69 100644 --- a/src/widget.filtereditor.js +++ b/src/widget.filtereditor.js @@ -8,49 +8,55 @@ this.recline.View = this.recline.View || {}; my.FilterEditor = Backbone.View.extend({ className: 'recline-filter-editor well', template: ' \ - × \ -
\ -
\ -

Filters

\ -
\ -
\ -
\ -
\ -
\ - {{#termFilters}} \ -
\ - \ -
\ -
\ - \ - \ -
\ -
\ -
\ - {{/termFilters}} \ -
\ -
\ -

To add a filter use the column menu in the grid view.

\ - \ +
\ +

Filters

\ + Add filter \ + \ +
\ + \ + \ + \ + \ + \ +
\ + \ +
\ + {{#termFilters}} \ +
\ + \ +
\ + \ + × \
\ - \ -
\ +
\ + {{/termFilters}} \ + {{#termFilters.length}} \ + \ + {{/termFilters.length}} \ + \
\ ', events: { - 'click .js-hide': 'onHide', '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() { this.el = $(this.el); _.bindAll(this, 'render'); - this.model.bind('change', this.render); - this.model.bind('change:filters:new-blank', this.render); + this.model.queryState.bind('change', this.render); + this.model.queryState.bind('change:filters:new-blank', this.render); this.render(); }, 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 ... tmplData.filters = _.map(tmplData.filters, function(filter, idx) { filter.id = idx; @@ -68,29 +74,38 @@ my.FilterEditor = Backbone.View.extend({ value: filter.term[fieldId] }; }); + tmplData.fields = this.model.fields.toJSON(); var out = Mustache.render(this.template, tmplData); 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(); - 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) { e.preventDefault(); var $target = $(e.target); var filterId = $target.closest('.filter').attr('data-filter-id'); - this.model.removeFilter(filterId); + this.model.queryState.removeFilter(filterId); }, onTermFiltersUpdate: function(e) { var self = this; e.preventDefault(); - var filters = self.model.get('filters'); + var filters = self.model.queryState.get('filters'); var $form = $(e.target); _.each($form.find('input'), function(input) { var $input = $(input); @@ -99,8 +114,8 @@ my.FilterEditor = Backbone.View.extend({ var fieldId = $input.attr('data-filter-field'); filters[filterIndex].term[fieldId] = value; }); - self.model.set({filters: filters}); - self.model.trigger('change'); + self.model.queryState.set({filters: filters}); + self.model.queryState.trigger('change'); } }); diff --git a/test/backend.elasticsearch.test.js b/test/backend/elasticsearch.test.js similarity index 100% rename from test/backend.elasticsearch.test.js rename to test/backend/elasticsearch.test.js diff --git a/test/base.js b/test/base.js index 7163131b..bd279965 100644 --- a/test/base.js +++ b/test/base.js @@ -7,17 +7,17 @@ var Fixture = { {id: 'y'}, {id: 'z'}, {id: 'country'}, - {id: 'label'}, + {id: 'title'}, {id: 'lat'}, {id: 'lon'} ]; 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: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', label: '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: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', label: '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: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', label: 'sixth', lat:51.04, lon:7.9} + {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', title: 'second', lat:54.97, lon:-1.60}, + {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', title: 'fourth', lat:57.27, lon:-6.20}, + {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', title: 'sixth', lat:51.04, lon:7.9} ]; var dataset = recline.Backend.Memory.createDataset(documents, fields); return dataset; diff --git a/test/index.html b/test/index.html index 26fed5dd..2e3aa91c 100644 --- a/test/index.html +++ b/test/index.html @@ -27,6 +27,7 @@ + @@ -34,31 +35,34 @@ + - + - - - - - - + + + + + + + - - - - - + + + + + +

Qunit Tests

diff --git a/test/view-graph.test.js b/test/view.graph.test.js similarity index 65% rename from test/view-graph.test.js rename to test/view.graph.test.js index 3b015f12..13c48efc 100644 --- a/test/view-graph.test.js +++ b/test/view.graph.test.js @@ -8,7 +8,7 @@ test('basics', function () { $('.fixtures').append(view.el); equal(view.state.get('graphType'), 'lines-and-points'); // view will auto render ... - assertPresent('.editor', view.el); + assertPresent('.editor', view.elSidebar); view.remove(); }); @@ -27,9 +27,9 @@ test('initialize', function () { deepEqual(view.state.get('series'), ['y', 'z']); // check we have updated editor with state info - equal(view.el.find('.editor-type select').val(), 'lines'); - equal(view.el.find('.editor-group select').val(), 'x'); - var out = _.map(view.el.find('.editor-series select'), function($el) { + equal(view.elSidebar.find('.editor-type select').val(), 'lines'); + equal(view.elSidebar.find('.editor-group select').val(), 'x'); + var out = _.map(view.elSidebar.find('.editor-series select'), function($el) { return $($el).val(); }); deepEqual(out, ['y', 'z']); @@ -51,3 +51,20 @@ test('dates in graph view', function () { view.remove(); }); + +test('GraphControls basics', function () { + var dataset = Fixture.getDataset(); + var view = new recline.View.GraphControls({ + model: dataset, + state: { + graphType: 'bars', + series: [] + } + }); + $('.fixtures').append(view.el); + equal(view.state.get('graphType'), 'bars'); + // view will auto render ... + assertPresent('.editor', view.el); + view.remove(); +}); + diff --git a/test/view-grid.test.js b/test/view.grid.test.js similarity index 100% rename from test/view-grid.test.js rename to test/view.grid.test.js diff --git a/test/view-map.test.js b/test/view.map.test.js similarity index 86% rename from test/view-map.test.js rename to test/view.map.test.js index 61e4d5a3..f5d26406 100644 --- a/test/view-map.test.js +++ b/test/view.map.test.js @@ -31,7 +31,7 @@ test('basics', function () { //Fire query, otherwise the map won't be initialized dataset.query(); - assertPresent('.editor',view.el); + assertPresent('.editor-field-type', view.elSidebar); // Check that the Leaflet map was set up assertPresent('.leaflet-container',view.el); @@ -42,6 +42,21 @@ test('basics', function () { view.remove(); }); +test('_setupGeometryField', function () { + var dataset = Fixture.getDataset(); + var view = new recline.View.Map({ + model: dataset + }); + var exp = { + geomField: null, + lonField: 'lon', + latField: 'lat', + autoZoom: true + }; + deepEqual(view.state.toJSON(), exp); + deepEqual(view.menu.state.toJSON(), exp); +}); + test('Lat/Lon geom fields', function () { var dataset = Fixture.getDataset(); var view = new recline.View.Map({ @@ -138,6 +153,15 @@ test('Popup', function () { view.remove(); }); +test('MapMenu', function () { + var dataset = Fixture.getDataset(); + var controls = new recline.View.MapMenu({ + model: dataset, + state: {} + }); + assertPresent('.editor-field-type', controls.el); +}); + var _getFeaturesCount = function(features){ var cnt = 0; features._iterateLayers(function(layer){ diff --git a/test/view-slickgrid.test.js b/test/view.slickgrid.test.js similarity index 98% rename from test/view-slickgrid.test.js rename to test/view.slickgrid.test.js index 6e5e7017..94d4d9d7 100644 --- a/test/view-slickgrid.test.js +++ b/test/view.slickgrid.test.js @@ -27,7 +27,7 @@ test('state', function () { var view = new recline.View.SlickGrid({ model: dataset, state: { - hiddenColumns:['x','lat','label'], + hiddenColumns:['x','lat','title'], columnsOrder:['lon','id','z','date', 'y', 'country'], columnsSort:{column:'country',direction:'desc'}, columnsWidth:[ diff --git a/test/view-timeline.test.js b/test/view.timeline.test.js similarity index 100% rename from test/view-timeline.test.js rename to test/view.timeline.test.js diff --git a/test/widget.filtereditor.test.js b/test/widget.filtereditor.test.js new file mode 100644 index 00000000..627db158 --- /dev/null +++ b/test/widget.filtereditor.test.js @@ -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(); +}); +