[#124,refactor,view][s]: ([m] in effect) rename DataExplorer view to MultiView and split contents view.js into separate files - fixes #124.

This commit is contained in:
Rufus Pollock 2012-05-31 21:36:52 +01:00
parent c1c1881660
commit 53327a7a1e
10 changed files with 275 additions and 25 deletions

View File

@ -58,13 +58,16 @@
<script type="text/javascript" src="../src/backend/elasticsearch.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/view.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-transform-dialog.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-transform-dialog.js"></script>
<script type="text/javascript" src="../src/view-timeline.js"></script>
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
<script type="text/javascript" src="../src/widget.facetviewer.js"></script>
<script type="text/javascript" src="../src/view.multiview.js"></script>
<!-- non-library javascript specific to this demo -->
<script type="text/javascript" src="js/app.js"></script>

View File

@ -108,7 +108,7 @@ var ExplorerApp = Backbone.View.extend({
}
];
this.dataExplorer = new recline.View.DataExplorer({
this.dataExplorer = new recline.View.MultiView({
model: dataset,
el: $el,
state: state,

100
library-view.markdown Normal file
View File

@ -0,0 +1,100 @@
---
layout: container
title: Library - Views
---
<div class="page-header">
<h1>
Recline Views
</h1>
</div>
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 a parent
recline-read-only class in order 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:
<pre>
initialize: {
model: {a recline.Model.Dataset instance}
// el: {do not specify - instead view should create}
state: {(optional) Object / Hash specifying initial state}
...
}
</pre>
Note: Dataset Views in core Recline have a common layout on disk as follows,
where ViewName is the named of View class:
<pre>
src/view-{lower-case-ViewName}.js
css/{lower-case-ViewName}.css
test/view-{lower-case-ViewName}.js
</pre>
### 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.DataExplorer`.
### 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 DataExplorer view.
### Writing your own Views
See the existing Views.

View File

@ -112,7 +112,7 @@ title: Library - Home
<p>Complementing the model are various Views (you can
also easily write your own). Each view holds a pointer to a Dataset:</p>
<ul>
<li>DataExplorer: the parent view which manages the overall app and sets up
<li>MultiView: the parent view which manages the overall app and sets up
sub views.</li>
<li>Grid: the data grid view.</li>
<li>Graph: a simple graphing view using <a
@ -138,7 +138,7 @@ title: Library - Home
<h4>Models and Views (Widgets)</h4>
<ul>
<li><a href="docs/model.html">Models</a></li>
<li><a href="docs/view.html">DataExplorer View (plus common view code)</a></li>
<li><a href="docs/view.multiview.html">MultiView View (plus common view code)</a></li>
<li><a href="docs/view-grid.html">(Data) Grid View</a></li>
<li><a href="docs/view-graph.html">Graph View (based on Flot)</a></li>
<li><a href="docs/view-map.html">Map View (based on Leaflet)</a></li>

2
make
View File

@ -13,7 +13,7 @@ def docs():
print("** Building docs")
docco_executable = os.environ.get('DOCCO_EXECUTABLE','docco')
cmd = '%s src/model.js src/view.js src/view-grid.js src/view-graph.js src/view-map.js' % (docco_executable)
cmd = '%s src/*.js' % (docco_executable)
os.system(cmd)
if os.path.exists('/tmp/recline-docs'):
shutil.rmtree('/tmp/recline-docs')

View File

@ -70,7 +70,7 @@
//
// 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.DataExplorer`.
// state from across multiple components see `recline.View.MultiView`.
//
// ### Flash Messages / Notifications
//
@ -85,7 +85,7 @@
//
// 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 DataExplorer view.
// an example of such behaviour see the MultiView view.
//
// ### Writing your own Views
//
@ -98,12 +98,12 @@ this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
// ## DataExplorer
// ## MultiView
//
// The primary view for the entire application. Usage:
// Manage multiple views together along with query editor etc. Usage:
//
// <pre>
// var myExplorer = new model.recline.DataExplorer({
// var myExplorer = new model.recline.MultiView({
// model: {{recline.Model.Dataset instance}}
// el: {{an existing dom element}}
// views: {{dataset views}}
@ -120,7 +120,7 @@ this.recline.View = this.recline.View || {};
// Graph).
//
// **views**: (optional) the dataset views (Grid, Graph etc) for
// DataExplorer to show. This is an array of view hashes. If not provided
// MultiView to show. This is an array of view hashes. If not provided
// initialize with (recline.View.)Grid, Graph, and Map views (with obvious id
// and labels!).
//
@ -161,8 +161,8 @@ this.recline.View = this.recline.View || {};
// Note that at present we do *not* serialize information about the actual set
// of views in use -- e.g. those specified by the views argument -- but instead
// expect either that the default views are fine or that the client to have
// initialized the DataExplorer with the relevant views themselves.
my.DataExplorer = Backbone.View.extend({
// initialized the MultiView with the relevant views themselves.
my.MultiView = Backbone.View.extend({
template: ' \
<div class="recline-data-explorer"> \
<div class="alert-messages"></div> \
@ -453,12 +453,12 @@ my.DataExplorer = Backbone.View.extend({
}
});
// ### DataExplorer.restore
// ### MultiView.restore
//
// Restore a DataExplorer instance from a serialized state including the associated dataset
my.DataExplorer.restore = function(state) {
// Restore a MultiView instance from a serialized state including the associated dataset
my.MultiView.restore = function(state) {
var dataset = recline.Model.Dataset.restore(state);
var explorer = new my.DataExplorer({
var explorer = new my.MultiView({
model: dataset,
state: state
});

80
src/widget.facetviewer.js Normal file
View File

@ -0,0 +1,80 @@
/*jshint multistr:true */
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
my.FacetViewer = Backbone.View.extend({
className: 'recline-facet-viewer well',
template: ' \
<a class="close js-hide" href="#">&times;</a> \
<div class="facets row"> \
<div class="span1"> \
<h3>Facets</h3> \
</div> \
{{#facets}} \
<div class="facet-summary span2 dropdown" data-facet="{{id}}"> \
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#"><i class="icon-chevron-down"></i> {{id}} {{label}}</a> \
<ul class="facet-items dropdown-menu"> \
{{#terms}} \
<li><a class="facet-choice js-facet-filter" data-value="{{term}}">{{term}} ({{count}})</a></li> \
{{/terms}} \
{{#entries}} \
<li><a class="facet-choice js-facet-filter" data-value="{{time}}">{{term}} ({{count}})</a></li> \
{{/entries}} \
</ul> \
</div> \
{{/facets}} \
</div> \
',
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);

65
src/widget.queryeditor.js Normal file
View File

@ -0,0 +1,65 @@
/*jshint multistr:true */
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
my.QueryEditor = Backbone.View.extend({
className: 'recline-query-editor',
template: ' \
<form action="" method="GET" class="form-inline"> \
<div class="input-prepend text-query"> \
<span class="add-on"><i class="icon-search"></i></span> \
<input type="text" name="q" value="{{q}}" class="span2" placeholder="Search data ..." class="search-query" /> \
</div> \
<div class="pagination"> \
<ul> \
<li class="prev action-pagination-update"><a href="">&laquo;</a></li> \
<li class="active"><a><input name="from" type="text" value="{{from}}" /> &ndash; <input name="to" type="text" value="{{to}}" /> </a></li> \
<li class="next action-pagination-update"><a href="">&raquo;</a></li> \
</ul> \
</div> \
<button type="submit" class="btn">Go &raquo;</button> \
</form> \
',
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);
}
});
})(jQuery, recline.View);

View File

@ -41,20 +41,22 @@
<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="../src/view.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-transform-dialog.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-timeline.js"></script>
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
<script type="text/javascript" src="../src/widget.facetviewer.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-slickgrid.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-timeline.test.js"></script>
<script type="text/javascript" src="view.test.js"></script>
<script type="text/javascript" src="view.multiview.test.js"></script>
<script type="text/javascript" src="util.test.js"></script>
</head>
<body>

View File

@ -6,7 +6,7 @@ test('basic explorer functionality', function () {
var $el = $('<div class="test-view-explorer-basic" />');
$('.fixtures .data-explorer-here').append($el);
var dataset = Fixture.getDataset();
var explorer = new recline.View.DataExplorer({
var explorer = new recline.View.MultiView({
model: dataset,
el: $el
});
@ -21,7 +21,7 @@ test('get State', function () {
var dataset = Fixture.getDataset();
var url = 'xyz';
dataset.set({url: url});
var explorer = new recline.View.DataExplorer({
var explorer = new recline.View.MultiView({
model: dataset,
el: $el
});
@ -41,7 +41,7 @@ test('initialize state', function () {
var $el = $('<div class="test-view-explorer-init-state" />');
$('.fixtures .data-explorer-here').append($el);
var dataset = Fixture.getDataset();
var explorer = new recline.View.DataExplorer({
var explorer = new recline.View.MultiView({
model: dataset,
el: $el,
state: {
@ -74,11 +74,11 @@ test('initialize state', function () {
test('restore (from serialized state)', function() {
var dataset = Fixture.getDataset();
var explorer = new recline.View.DataExplorer({
var explorer = new recline.View.MultiView({
model: dataset,
});
var state = explorer.state.toJSON();
var explorerNew = recline.View.DataExplorer.restore(state);
var explorerNew = recline.View.MultiView.restore(state);
var out = explorerNew.state.toJSON();
equal(out.backend, state.backend);
});