diff --git a/app/index.html b/app/index.html index 4b16c85a..d38b71b2 100644 --- a/app/index.html +++ b/app/index.html @@ -66,6 +66,7 @@ + diff --git a/src/view.multiview.js b/src/view.multiview.js index 78f74372..7f818a91 100644 --- a/src/view.multiview.js +++ b/src/view.multiview.js @@ -1,98 +1,5 @@ /*jshint multistr:true */ -// # Recline Views -// -// Recline Views are instances of Backbone Views and they act as 'WUI' (web -// user interface) component displaying some model object in the DOM. Like all -// Backbone views they have a pointer to a model (or a collection) and have an -// associated DOM-style element (usually this element will be bound into the -// page at some point). -// -// Views provided by core Recline are crudely divided into two types: -// -// * Dataset Views: a View intended for displaying a recline.Model.Dataset -// in some fashion. Examples are the Grid, Graph and Map views. -// * Widget Views: a widget used for displaying some specific (and -// smaller) aspect of a dataset or the application. Examples are -// QueryEditor and FilterEditor which both provide a way for editing (a -// part of) a `recline.Model.Query` associated to a Dataset. -// -// ## Dataset View -// -// These views are just Backbone views with a few additional conventions: -// -// 1. The model passed to the View should always be a recline.Model.Dataset instance -// 2. Views should generate their own root element rather than having it passed -// in. -// 3. Views should apply a css class named 'recline-{view-name-lower-cased} to -// the root element (and for all CSS for this view to be qualified using this -// CSS class) -// 4. Read-only mode: CSS for this view should respect/utilize -// recline-read-only class to trigger read-only behaviour (this class will -// usually be set on some parent element of the view's root element. -// 5. State: state (configuration) information for the view should be stored on -// an attribute named state that is an instance of a Backbone Model (or, more -// speficially, be an instance of `recline.Model.ObjectState`). In addition, -// a state attribute may be specified in the Hash passed to a View on -// iniitialization and this information should be used to set the initial -// state of the view. -// -// Example of state would be the set of fields being plotted in a graph -// view. -// -// More information about State can be found below. -// -// To summarize some of this, the initialize function for a Dataset View should -// look like: -// -//
-//    initialize: {
-//        model: {a recline.Model.Dataset instance}
-//        // el: {do not specify - instead view should create}
-//        state: {(optional) Object / Hash specifying initial state}
-//        ...
-//    }
-// 
-// -// Note: Dataset Views in core Recline have a common layout on disk as -// follows, where ViewName is the named of View class: -// -//
-// src/view-{lower-case-ViewName}.js
-// css/{lower-case-ViewName}.css
-// test/view-{lower-case-ViewName}.js
-// 
-// -// ### State -// -// State information exists in order to support state serialization into the -// url or elsewhere and reloading of application from a stored state. -// -// State is available not only for individual views (as described above) but -// for the dataset (e.g. the current query). For an example of pulling together -// state from across multiple components see `recline.View.MultiView`. -// -// ### Flash Messages / Notifications -// -// To send 'flash messages' or notifications the convention is that views -// should fire an event named `recline:flash` with a payload that is a -// flash object with the following attributes (all optional): -// -// * message: message to show. -// * category: warning (default), success, error -// * persist: if true alert is persistent, o/w hidden after 3s (default=false) -// * loader: if true show a loading message -// -// Objects or views wishing to bind to flash messages may then subscribe to -// these events and take some action such as displaying them to the user. For -// an example of such behaviour see the MultiView view. -// -// ### Writing your own Views -// -// See the existing Views. -// -// ---- - // Standard JS module setup this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; @@ -292,16 +199,16 @@ my.MultiView = Backbone.View.extend({ _.each(this.pageViews, function(view, pageName) { $dataViewContainer.append(view.view.el); }); - var queryEditor = new my.QueryEditor({ + var queryEditor = new recline.View.QueryEditor({ model: this.model.queryState }); this.el.find('.query-editor-here').append(queryEditor.el); - var filterEditor = new my.FilterEditor({ + var filterEditor = new recline.View.FilterEditor({ model: this.model.queryState }); this.$filterEditor = filterEditor.el; this.el.find('.header').append(filterEditor.el); - var facetViewer = new my.FacetViewer({ + var facetViewer = new recline.View.FacetViewer({ model: this.model }); this.$facetViewer = facetViewer.el; @@ -465,231 +372,5 @@ my.MultiView.restore = function(state) { return explorer; } -my.QueryEditor = Backbone.View.extend({ - className: 'recline-query-editor', - template: ' \ -
\ -
\ - \ - \ -
\ - \ - \ -
\ - ', - - events: { - 'submit form': 'onFormSubmit', - 'click .action-pagination-update': 'onPaginationUpdate' - }, - - initialize: function() { - _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.bind('change', this.render); - this.render(); - }, - onFormSubmit: function(e) { - e.preventDefault(); - var query = this.el.find('.text-query input').val(); - var newFrom = parseInt(this.el.find('input[name="from"]').val()); - var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom; - this.model.set({size: newSize, from: newFrom, q: query}); - }, - onPaginationUpdate: function(e) { - e.preventDefault(); - var $el = $(e.target); - var newFrom = 0; - if ($el.parent().hasClass('prev')) { - newFrom = this.model.get('from') - Math.max(0, this.model.get('size')); - } else { - newFrom = this.model.get('from') + this.model.get('size'); - } - this.model.set({from: newFrom}); - }, - render: function() { - var tmplData = this.model.toJSON(); - tmplData.to = this.model.get('from') + this.model.get('size'); - var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); - } -}); - -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.

\ - \ -
\ - \ -
\ -
\ - ', - events: { - 'click .js-hide': 'onHide', - 'click .js-remove-filter': 'onRemoveFilter', - 'submit form': 'onTermFiltersUpdate' - }, - 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.render(); - }, - render: function() { - var tmplData = $.extend(true, {}, this.model.toJSON()); - // we will use idx in list as there id ... - tmplData.filters = _.map(tmplData.filters, function(filter, idx) { - filter.id = idx; - return filter; - }); - tmplData.termFilters = _.filter(tmplData.filters, function(filter) { - return filter.term !== undefined; - }); - tmplData.termFilters = _.map(tmplData.termFilters, function(filter) { - var fieldId = _.keys(filter.term)[0]; - return { - id: filter.id, - fieldId: fieldId, - label: fieldId, - value: filter.term[fieldId] - }; - }); - 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) { - e.preventDefault(); - this.el.hide(); - }, - onRemoveFilter: function(e) { - e.preventDefault(); - var $target = $(e.target); - var filterId = $target.closest('.filter').attr('data-filter-id'); - this.model.removeFilter(filterId); - }, - onTermFiltersUpdate: function(e) { - var self = this; - e.preventDefault(); - var filters = self.model.get('filters'); - var $form = $(e.target); - _.each($form.find('input'), function(input) { - var $input = $(input); - var filterIndex = parseInt($input.attr('data-filter-id')); - var value = $input.val(); - var fieldId = $input.attr('data-filter-field'); - filters[filterIndex].term[fieldId] = value; - }); - self.model.set({filters: filters}); - self.model.trigger('change'); - } -}); - -my.FacetViewer = Backbone.View.extend({ - className: 'recline-facet-viewer well', - template: ' \ - × \ -
\ -
\ -

Facets

\ -
\ - {{#facets}} \ - \ - {{/facets}} \ -
\ - ', - - events: { - 'click .js-hide': 'onHide', - 'click .js-facet-filter': 'onFacetFilter' - }, - initialize: function(model) { - _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.facets.bind('all', this.render); - this.model.fields.bind('all', this.render); - this.render(); - }, - render: function() { - var tmplData = { - facets: this.model.facets.toJSON(), - fields: this.model.fields.toJSON() - }; - tmplData.facets = _.map(tmplData.facets, function(facet) { - if (facet._type === 'date_histogram') { - facet.entries = _.map(facet.entries, function(entry) { - entry.term = new Date(entry.time).toDateString(); - return entry; - }); - } - return facet; - }); - var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); - // are there actually any facets to show? - if (this.model.facets.length > 0) { - this.el.show(); - } else { - this.el.hide(); - } - }, - onHide: function(e) { - e.preventDefault(); - this.el.hide(); - }, - onFacetFilter: function(e) { - var $target= $(e.target); - var fieldId = $target.closest('.facet-summary').attr('data-facet'); - var value = $target.attr('data-value'); - this.model.queryState.addTermFilter(fieldId, value); - } -}); - - })(jQuery, recline.View); diff --git a/src/widget.filtereditor.js b/src/widget.filtereditor.js new file mode 100644 index 00000000..1564e3ab --- /dev/null +++ b/src/widget.filtereditor.js @@ -0,0 +1,109 @@ +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + +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.

\ + \ +
\ + \ +
\ +
\ + ', + events: { + 'click .js-hide': 'onHide', + 'click .js-remove-filter': 'onRemoveFilter', + 'submit form': 'onTermFiltersUpdate' + }, + 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.render(); + }, + render: function() { + var tmplData = $.extend(true, {}, this.model.toJSON()); + // we will use idx in list as there id ... + tmplData.filters = _.map(tmplData.filters, function(filter, idx) { + filter.id = idx; + return filter; + }); + tmplData.termFilters = _.filter(tmplData.filters, function(filter) { + return filter.term !== undefined; + }); + tmplData.termFilters = _.map(tmplData.termFilters, function(filter) { + var fieldId = _.keys(filter.term)[0]; + return { + id: filter.id, + fieldId: fieldId, + label: fieldId, + value: filter.term[fieldId] + }; + }); + 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) { + e.preventDefault(); + this.el.hide(); + }, + onRemoveFilter: function(e) { + e.preventDefault(); + var $target = $(e.target); + var filterId = $target.closest('.filter').attr('data-filter-id'); + this.model.removeFilter(filterId); + }, + onTermFiltersUpdate: function(e) { + var self = this; + e.preventDefault(); + var filters = self.model.get('filters'); + var $form = $(e.target); + _.each($form.find('input'), function(input) { + var $input = $(input); + var filterIndex = parseInt($input.attr('data-filter-id')); + var value = $input.val(); + var fieldId = $input.attr('data-filter-field'); + filters[filterIndex].term[fieldId] = value; + }); + self.model.set({filters: filters}); + self.model.trigger('change'); + } +}); + + +})(jQuery, recline.View); + diff --git a/test/index.html b/test/index.html index 6a328121..b38518d8 100644 --- a/test/index.html +++ b/test/index.html @@ -48,6 +48,7 @@ +