Recline allows you to explore and work with data in your browser and then share with others
- In basic operation it's much like a spreadsheet - though it's
- feature set is a little different. In particular, the Data
- Explorer provides:
-
-
Data grid / spreadsheet
-
Data editing including programmatic data transformation in javascript
-
Visualizations includes graphs and maps
-
Import and export from a variety of sources including online sources such as online Excel and CSV files, Google docs and
- the DataHub and offline sources like CSV files on your local machine.
-
Use online or offline - because the app is built in pure javascript and html you can use it anywhere there's a modern web browser. Using offline is as easy and downloading this web page to your local machine.
Data editing including programmatic data transformation in javascript
+
Visualizations includes graphs and maps
+
Import and export from a variety of sources including online sources such as online Excel and CSV files, Google docs and
+ the DataHub and offline sources like CSV files on your local machine.
+
Use online or offline - because the app is built in pure javascript and html you can use it anywhere there's a modern web browser. Using offline is as easy and downloading this web page to your local machine.
+
+
+
+
+
+
Get started
+
Get started straight away for example by importing some data from an external source using the menu at the top right of this page.
+
@@ -153,7 +154,7 @@
diff --git a/app/js/app.js b/app/js/app.js
index 4070c841..145ec05b 100755
--- a/app/js/app.js
+++ b/app/js/app.js
@@ -21,7 +21,7 @@ var ExplorerApp = Backbone.View.extend({
this.router.route(/explorer/, 'explorer', this.viewExplorer);
Backbone.history.start();
- var state = recline.Util.parseQueryString(window.location.search);
+ var state = recline.Util.parseQueryString(decodeURIComponent(window.location.search));
if (state) {
_.each(state, function(value, key) {
try {
diff --git a/css/grid.css b/css/grid.css
index c0439459..a8ff0625 100644
--- a/css/grid.css
+++ b/css/grid.css
@@ -2,20 +2,22 @@
* (Data) Grid
*********************************************************/
+table.recline-grid {
+ table-layout: fixed;
+ width: 100%;
+}
+
.recline-grid .btn-group .dropdown-toggle {
padding: 1px 3px;
line-height: auto;
}
-.recline-grid {
- border: 1px solid #ccc;
- width: 100%;
-}
-
.recline-grid td, .recline-grid th {
border-left: 1px solid #ccc;
padding: 3px 4px;
text-align: left;
+ word-wrap: break-word;
+ white-space: normal;
}
.recline-grid td {
@@ -26,6 +28,14 @@
width: 20px;
}
+.recline-grid tbody tr:last-child {
+ border-bottom: 1px solid #ccc;
+}
+
+.recline-grid tbody td:last-child {
+ border-right: 1px solid #ccc;
+}
+
/* direct borrowing from twitter buttons */
.recline-grid th,
.transform-column-view .expression-preview-table-wrapper th
@@ -53,6 +63,42 @@
transition: 0.1s linear all;
}
+/**********************************************************
+ * Fixed Header - http://www.imaputz.com/cssStuff/bigFourVersion.html
+ *********************************************************/
+
+div.table-container {
+ overflow: auto;
+}
+
+/* Reset overflow value to hidden for all non-IE browsers. */
+html>body div.table-container {
+ overflow: hidden;
+}
+
+/* set table header to a fixed position. WinIE 6.x only */
+/* In WinIE 6.x, any element with a position property set to relative and is a child of */
+/* an element that has an overflow property set, the relative value translates into fixed. */
+/* Ex: parent element DIV with a class of table-container has an overflow property set to auto */
+thead.fixed-header tr {
+ position: relative
+}
+
+/* set THEAD element to have block level attributes. All other non-IE browsers */
+/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */
+html>body thead.fixed-header tr {
+ display: block
+}
+
+/* define the table content to be scrollable */
+/* set TBODY element to have block level attributes. All other non-IE browsers */
+/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */
+/* induced side effect is that child TDs no longer accept width: auto */
+tbody.scroll-content {
+ display: block;
+ max-height: 500px;
+ overflow: auto;
+}
/**********************************************************
* Data Table Menus
diff --git a/example-quickstart.markdown b/example-quickstart.markdown
index e87ab283..266d8e6c 100644
--- a/example-quickstart.markdown
+++ b/example-quickstart.markdown
@@ -20,41 +20,42 @@ Before writing any code with Recline, you need to do the following preparation s
2. Include the relevant CSS in the head section of your document:
{% highlight html %}
-
+
- {% endhighlight %}
+{% endhighlight %}
3. Include the relevant Javascript files somewhere on the page (preferably before body close tag):
{% highlight html %}
-
-
-
+
{% endhighlight %}
4. Create a div to hold the Recline view(s):
{% highlight html %}
- {% endhighlight %}
+ {% endhighlight %}
You're now ready to start working with Recline.
### Creating a Dataset
-We are going to be working with the following set of data:
+Here's some example data We are going to work with:
{% highlight javascript %}
-var data = [
- {id: 0, x: 1, y: 2, z: 3, country: 'UK', label: 'first'},
- {id: 1, x: 2, y: 4, z: 6, country: 'UK', label: 'second'},
- {id: 2, x: 3, y: 6, z: 9, country: 'US', label: 'third'}
- ];
+{% include data.js %}
{% endhighlight %}
-Here we have 3 documents / rows each of which is a javascript object containing keys and values (note that all values here are 'simple' but there is no reason you cannot have full objects as values.
+In this data we have 6 documents / rows. Each document is a javascript object
+containing keys and values (note that all values here are 'simple' but there is
+no reason you cannot have objects as values allowing you to nest data.
We can now create a recline Dataset object (and memory backend) from this raw data:
@@ -67,28 +68,25 @@ Note that behind the scenes Recline will create a Memory backend for this datase
### Setting up the Grid
-Let's create a data grid view to display the dataset we have just created, binding the view to the `` we created earlier:
+Let's create a data grid view to display the dataset we have just created, binding the view to the `` we created earlier:
{% highlight javascript %}
+var $el = $('#mygrid');
var grid = new recline.View.Grid({
- model: dataset,
- el: $('#recline-grid')
+ model: dataset
});
+$el.append(grid.el);
grid.render();
{% endhighlight %}
And hey presto:
-
+
+### Creating a Graph
+
+Let's create a graph view to display a line graph for this dataset.
+
+First, create a new div for the graph:
+
+{% highlight html %}
+
+{% endhighlight %}
+
+Now let's create the graph, we will use the same dataset we had earlier:
+
+{% highlight javascript %}
+var $el = $('#mygraph');
+var graph = new recline.View.Graph({
+ model: dataset
+});
+$el.append(grid.el);
+graph.render();
+{% endhighlight %}
+
+And ... we have a graph view -- with instructions on how to use the controls to
+create a graph -- but no graph. Go ahead and play around with the controls to
+create a graph of your choosing:
+
+
+
+
+
+But I wanted to create a graph not a graph editor. Can we do that? Yes you can!
+All you need to do is set the 'state' of the graph view:
+
+{% highlight javascript %}
+var $el = $('#mygraph');
+var graph = new recline.View.Graph({
+ model: dataset,
+ state: {
+ group: "date",
+ series: ["x", "z"]
+ }
+});
+$el.append(grid.el);
+graph.render();
+graph.redraw();
+{% endhighlight %}
+
+We would get this rendered graph:
+
+
+
+
+
+
+State: The concept of a state is a common feature of Recline views being an object
+which stores information about the state and configuration of a given view. You
+can read more about it in the general Views
+documentation as well as the documentation of individual views such as the
+Graph View.
+
\
',
toTemplateJSON: function() {
+ var self = this;
var modelData = this.model.toJSON();
modelData.notEmpty = ( this.fields.length > 0 );
// TODO: move this sort of thing into a toTemplateJSON method on Dataset?
- modelData.fields = _.map(this.fields, function(field) { return field.toJSON(); });
+ modelData.fields = _.map(this.fields, function(field) {
+ return field.toJSON();
+ });
+ // last header width = scroll bar - border (2px) */
+ modelData.lastHeaderWidth = this.scrollbarDimensions.width - 2;
return modelData;
},
render: function() {
@@ -1228,6 +1236,20 @@ my.Grid = Backbone.View.extend({
this.fields = this.model.fields.filter(function(field) {
return _.indexOf(self.state.get('hiddenFields'), field.id) == -1;
});
+ this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions
+ var numFields = this.fields.length;
+ // compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar)
+ var fullWidth = self.el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width;
+ var width = parseInt(Math.max(50, fullWidth / numFields));
+ var remainder = fullWidth - numFields * width;
+ _.each(this.fields, function(field, idx) {
+ // add the remainder to the first field width so we make up full col
+ if (idx == 0) {
+ field.set({width: width+remainder});
+ } else {
+ field.set({width: width});
+ }
+ });
var htmls = $.mustache(this.template, this.toTemplateJSON());
this.el.html(htmls);
this.model.currentDocuments.forEach(function(doc) {
@@ -1240,8 +1262,25 @@ my.Grid = Backbone.View.extend({
});
newView.render();
});
+ // hide extra header col if no scrollbar to avoid unsightly overhang
+ var $tbody = this.el.find('tbody')[0];
+ if ($tbody.scrollHeight <= $tbody.offsetHeight) {
+ this.el.find('th.last-header').hide();
+ }
this.el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0));
return this;
+ },
+
+ // ### _scrollbarSize
+ //
+ // Measure width of a vertical scrollbar and height of a horizontal scrollbar.
+ //
+ // @return: { width: pixelWidth, height: pixelHeight }
+ _scrollbarSize: function() {
+ var $c = $("").appendTo("body");
+ var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight };
+ $c.remove();
+ return dim;
}
});
@@ -1278,7 +1317,7 @@ my.GridRow = Backbone.View.extend({
\
\
{{#cells}} \
-
\
+
\
\
\
{{{value}}}
\
@@ -1298,6 +1337,7 @@ my.GridRow = Backbone.View.extend({
var cellData = this._fields.map(function(field) {
return {
field: field.id,
+ width: field.get('width'),
value: doc.getFieldValue(field)
};
});
@@ -1795,7 +1835,7 @@ my.Map = Backbone.View.extend({
// on [OpenStreetMap](http://openstreetmap.org).
//
_setupMap: function(){
- var self = this;
+
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";
@@ -1835,14 +1875,6 @@ my.Map = Backbone.View.extend({
this.map.setView(new L.LatLng(0, 0), 2);
- var popup = new L.Popup();
- this.map.on('click', function(e) {
- var latlngStr = '(' + e.latlng.lat.toFixed(3) + ', ' + e.latlng.lng.toFixed(3) + ')';
- popup.setLatLng(e.latlng);
- popup.setContent("You clicked the map at " + latlngStr);
- self.map.openPopup(popup);
- });
-
this.mapReady = true;
},
@@ -2043,10 +2075,11 @@ my.ColumnTransform = Backbone.View.extend({
// # Recline Views
//
-// Recline Views are Backbone Views and in keeping with normal Backbone views
-// are Widgets / Components displaying something in the DOM. Like all Backbone
-// views they have a pointer to a model or a collection and is bound to an
-// element.
+// 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:
//
@@ -2262,12 +2295,11 @@ my.DataExplorer = Backbone.View.extend({
}
this.model.bind('query:start', function() {
- self.notify({message: 'Loading data', loader: true});
+ self.notify({loader: true, persist: true});
});
this.model.bind('query:done', function() {
self.clearNotifications();
self.el.find('.doc-count').text(self.model.docCount || 'Unknown');
- self.notify({message: 'Data loaded', category: 'success'});
});
this.model.bind('query:fail', function(error) {
self.clearNotifications();
@@ -2432,19 +2464,25 @@ my.DataExplorer = Backbone.View.extend({
// * loader: if true show loading spinner
notify: function(flash) {
var tmplData = _.extend({
- message: '',
- category: 'warning'
+ message: 'Loading',
+ category: 'warning',
+ loader: false
},
flash
);
- var _template = ' \
-
× \
- {{message}} \
- {{#loader}} \
+ if (tmplData.loader) {
+ var _template = ' \
+
\
+ {{message}} \
\
- {{/loader}} \
-
';
- var _templated = $.mustache(_template, tmplData);
+