Merge branch 'master' into gh-pages

This commit is contained in:
Rufus Pollock 2012-05-18 00:55:12 +01:00
commit 1c93e1f67c
8 changed files with 307 additions and 93 deletions

9
_includes/data.js Normal file
View File

@ -0,0 +1,9 @@
var data = [
{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}
];

View File

@ -97,36 +97,37 @@
<div class="hero-unit">
<h1>Welcome to the Recline Data Explorer</h1>
<p>Recline allows you to explore and work with data in your browser and then share with others</p>
In basic operation it's much like a spreadsheet - though it's
feature set is a little different. In particular, the Data
Explorer provides:
<ul>
<li>Data grid / spreadsheet</li>
<li>Data editing including programmatic data transformation in javascript</li>
<li>Visualizations includes graphs and maps</li>
<li>Import and export from a variety of sources including online sources such as online Excel and CSV files, Google docs and
the <a href="http://datahub.io/">DataHub</a> and offline sources like CSV files on your local machine.</li>
<li>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.</li>
</ul>
<div class="row">
<div class="span4">
<h3>View the demo</h3>
<p>Take a look at a local demo dataset.</p>
<p><a class="btn btn-primary" href="?url=demo">View the demo dataset &raquo;</a></p>
</div>
<div class="span4">
<h3>Read the tutorial</h3>
<p>Take a look at the tutorial for using the data explorer:</p>
<a class="btn btn-primary" href="#tutorial">Read the tutorial &raquo;</a>
</div>
<div class="span4">
<h3>Import some data</h3>
<p>Starting working with some data straight away. You can import some data <strong>using the menu at the top right</strong> of this page.</p>
</div>
</div>
<div class="row-fluid">
<div class="span4">
<div class="well">
<h3>View the demo</h3>
<p>Try out the demo using a local example dataset.</p>
<p><a class="btn btn-primary" href="?url=demo#explorer">View the demo dataset &raquo;</a></p>
</div>
</div>
<div class="span4">
<div class="well">
<h3>Features</h3>
<ul>
<li>Data grid</li>
<li>Data editing including programmatic data transformation in javascript</li>
<li>Visualizations includes graphs and maps</li>
<li>Import and export from a variety of sources including online sources such as online Excel and CSV files, Google docs and
the <a href="http://datahub.io/">DataHub</a> and offline sources like CSV files on your local machine.</li>
<li>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.</li>
</ul>
</div>
</div>
<div class="span4">
<div class="well">
<h3>Get started</h3>
<p>Get started straight away for example by importing some data from an external source <strong>using the menu at the top right</strong> of this page.</p>
</div>
</div>
</div>
</div>
</div>
<div class="page-explorer backbone-page">
<div class="data-explorer-here"></div>
@ -153,7 +154,7 @@
<select name="backend_type">
<option value="csv">CSV</option>
<option vlaue="excel">Excel</option>
<option value="gdocs">Google Spreadsheet</option>
<option value="gdoc">Google Spreadsheet</option>
<option value="elasticsearch">ElasticSearch</option>
</select>
</div>

View File

@ -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 {

View File

@ -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

View File

@ -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 %}
<!-- you do not have to use bootstrap but we use it by default -->
<link rel="stylesheet" href="vendor/bootstrap/2.0.2/css/bootstrap.css">
<link rel="stylesheet" href="vendor/bootstrap/2.0.2/css/bootstrap.css" />
<!-- CSS for relevant view components - here we just have grid -->
<link rel="stylesheet" href="css/grid.css" />{% endhighlight %}
<link rel="stylesheet" href="css/grid.css" />{% endhighlight %}
3. Include the relevant Javascript files somewhere on the page (preferably before body close tag):
{% highlight html %}
<!-- you do not have to use bootstrap but we use it by default -->
<link rel="stylesheet" href="vendor/bootstrap/2.0.2/css/bootstrap.css">
<!-- 3rd party dependencies -->
<script type="text/javascript" src="vendor/jquery/1.7.1/jquery.js"></script>
<script type="text/javascript" src="vendor/underscore/1.1.6/underscore.js"></script>
<script type="text/javascript" src="vendor/backbone/0.5.1/backbone.js"></script>
<script type="text/javascript" src="vendor/jquery.mustache.js"></script>
<!-- note that we could include individual components rather than whole of recline -->
<!-- note that we could include individual components rather than whole of recline e.g.
<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/memory.js"></script>
<script type="text/javascript" src="src/view-grid.js"></script>
-->
<script type="text/javascript" src="recline.js"></script>{% endhighlight %}
4. Create a div to hold the Recline view(s):
{% highlight html %}
<div id="recline-grid"></div>{% endhighlight %}
<div id="mygrid"></div>{% 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 `<div id="recline-grid"></div>` we created earlier:
Let's create a data grid view to display the dataset we have just created, binding the view to the `<div id="mygrid"></div>` 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:
<div id="recline-grid" class="recline-read-only">&nbsp;</div>
<div id="mygrid" class="recline-read-only" style="margin-bottom: 30px; margin-top: -20px;">&nbsp;</div>
<script type="text/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 %}
var dataset = recline.Backend.createDataset(data);
var $el = $('#recline-grid');
var $el = $('#mygrid');
var grid = new recline.View.Grid({
model: dataset,
});
@ -96,3 +94,83 @@ $el.append(grid.el);
grid.render();
</script>
### 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 %}
<div id="mygraph"></div>
{% 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:
<div id="mygraph" style="margin-bottom: 30px;">&nbsp;</div>
<script type="text/javascript">
var $el = $('#mygraph');
var graph = new recline.View.Graph({
model: dataset
});
$el.append(graph.el);
graph.render();
</script>
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:
<div id="mygraph2" style="margin-bottom: 30px;">&nbsp;</div>
<script type="text/javascript">
var $el = $('#mygraph2');
var graph = new recline.View.Graph({
model: dataset,
state: {
graphType: "lines-and-points",
group: "x",
series: ["y", "z"]
}
});
$el.append(graph.el);
graph.render();
graph.redraw();
</script>
<div class="alert alert-info">
<strong>State</strong>: 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 <a href="../docs/view.html">Views
documentation</a> as well as the documentation of individual views such as the
<a href="../docs/view-graph.html">Graph View</a>.
</div>

View File

@ -612,7 +612,7 @@ my.composeQueryString = function(queryParams) {
if (typeof(value) === 'object') {
value = JSON.stringify(value);
}
items.push(key + '=' + value);
items.push(key + '=' + encodeURIComponent(value));
});
queryString += items.join('&');
return queryString;
@ -1177,8 +1177,9 @@ my.Grid = Backbone.View.extend({
// ======================================================
// #### Templating
template: ' \
<div class="table-container"> \
<table class="recline-grid table-striped table-condensed" cellspacing="0"> \
<thead> \
<thead class="fixed-header"> \
<tr> \
{{#notEmpty}} \
<th class="column-header"> \
@ -1191,7 +1192,7 @@ my.Grid = Backbone.View.extend({
</th> \
{{/notEmpty}} \
{{#fields}} \
<th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}"> \
<th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}" style="width: {{width}}px;"> \
<div class="btn-group column-header-menu"> \
<a class="btn dropdown-toggle" data-toggle="dropdown"><i class="icon-cog"></i><span class="caret"></span></a> \
<ul class="dropdown-menu data-table-menu pull-right"> \
@ -1210,17 +1211,24 @@ my.Grid = Backbone.View.extend({
<span class="column-header-name">{{label}}</span> \
</th> \
{{/fields}} \
<th class="last-header" style="width: {{lastHeaderWidth}}px; padding: 0; margin: 0;"></th> \
</tr> \
</thead> \
<tbody></tbody> \
<tbody class="scroll-content"></tbody> \
</table> \
</div> \
',
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 = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").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({
</div> \
</td> \
{{#cells}} \
<td data-field="{{field}}"> \
<td data-field="{{field}}" style="width: {{width}}px; max-width: {{width}}px;"> \
<div class="data-table-cell-content"> \
<a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
<div class="data-table-cell-value">{{{value}}}</div> \
@ -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 = ' \
<div class="alert alert-{{category}} fade in" data-alert="alert"><a class="close" data-dismiss="alert" href="#">×</a> \
{{message}} \
{{#loader}} \
if (tmplData.loader) {
var _template = ' \
<div class="alert alert-info alert-loader"> \
{{message}} \
<span class="notification-loader">&nbsp;</span> \
{{/loader}} \
</div>';
var _templated = $.mustache(_template, tmplData);
</div>';
} else {
var _template = ' \
<div class="alert alert-{{category}} fade in" data-alert="alert"><a class="close" data-dismiss="alert" href="#">×</a> \
{{message}} \
</div>';
}
var _templated = $($.mustache(_template, tmplData));
_templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages'));
if (!flash.persist) {
setTimeout(function() {
@ -2460,7 +2498,9 @@ my.DataExplorer = Backbone.View.extend({
// Clear all existing notifications
clearNotifications: function() {
var $notifications = $('.recline-data-explorer .alert-messages .alert');
$notifications.remove();
$notifications.fadeOut(1500, function() {
$(this).remove();
});
}
});

View File

@ -56,7 +56,7 @@ my.composeQueryString = function(queryParams) {
if (typeof(value) === 'object') {
value = JSON.stringify(value);
}
items.push(key + '=' + value);
items.push(key + '=' + encodeURIComponent(value));
});
queryString += items.join('&');
return queryString;

View File

@ -130,8 +130,9 @@ my.Grid = Backbone.View.extend({
// ======================================================
// #### Templating
template: ' \
<div class="table-container"> \
<table class="recline-grid table-striped table-condensed" cellspacing="0"> \
<thead> \
<thead class="fixed-header"> \
<tr> \
{{#notEmpty}} \
<th class="column-header"> \
@ -144,7 +145,7 @@ my.Grid = Backbone.View.extend({
</th> \
{{/notEmpty}} \
{{#fields}} \
<th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}"> \
<th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}" style="width: {{width}}px;"> \
<div class="btn-group column-header-menu"> \
<a class="btn dropdown-toggle" data-toggle="dropdown"><i class="icon-cog"></i><span class="caret"></span></a> \
<ul class="dropdown-menu data-table-menu pull-right"> \
@ -163,17 +164,24 @@ my.Grid = Backbone.View.extend({
<span class="column-header-name">{{label}}</span> \
</th> \
{{/fields}} \
<th class="last-header" style="width: {{lastHeaderWidth}}px; padding: 0; margin: 0;"></th> \
</tr> \
</thead> \
<tbody></tbody> \
<tbody class="scroll-content"></tbody> \
</table> \
</div> \
',
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() {
@ -181,6 +189,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) {
@ -193,8 +215,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 = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight };
$c.remove();
return dim;
}
});
@ -231,7 +270,7 @@ my.GridRow = Backbone.View.extend({
</div> \
</td> \
{{#cells}} \
<td data-field="{{field}}"> \
<td data-field="{{field}}" style="width: {{width}}px; max-width: {{width}}px;"> \
<div class="data-table-cell-content"> \
<a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
<div class="data-table-cell-value">{{{value}}}</div> \
@ -251,6 +290,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)
};
});