Refactor tabular chunked display into Backbone and use for displaying tabular data.

This commit is contained in:
Jeremy Goecks
2013-02-04 12:37:32 -05:00
parent 9d6d64684b
commit e4ff2da11b
3 changed files with 186 additions and 87 deletions
+164 -1
View File
@@ -1,3 +1,4 @@
// Additional dependencies: jQuery, underscore.
define(["libs/backbone/backbone-relational"], function() {
/**
@@ -46,13 +47,175 @@ var Dataset = Backbone.RelationalModel.extend({
urlRoot: galaxy_paths.get('datasets_url')
});
/**
* A tabular dataset. This object extends dataset to provide incremental chunked data.
*/
var TabularDataset = Dataset.extend({
defaults: _.extend({}, Dataset.prototype.defaults, {
chunk_url: null,
first_data_chunk: null,
chunk_index: -1,
at_eof: false
}),
initialize: function(options) {
Dataset.prototype.initialize.call(this);
// If first data chunk is available, next
// chunk is 1.
chunk_index = (this.attributes.first_data_chunk ? 1 : 0);
},
/**
* Set first data chunk; useful when initializing on the server side.
*/
set_first_chunk: function(chunk) {
this.attributes.first_data_chunk = chunk;
this.attributes.chunk_index = 1;
},
/**
* Returns a jQuery Deferred object that resolves to the next data chunk or null if at EOF.
*/
get_next_chunk: function() {
// If already at end of file, do nothing.
if (this.attributes.at_eof) {
return null;
}
// Get next chunk.
var self = this,
next_chunk = $.Deferred();
$.getJSON(this.attributes.chunk_url, {
chunk: self.attributes.chunk_index++
}).success(function(chunk) {
var rval;
if (chunk.ck_data !== '') {
// Found chunk.
rval = chunk;
}
else {
// At EOF.
self.attributes.at_eof = true;
rval = null;
}
next_chunk.resolve(rval);
});
return next_chunk;
}
});
var DatasetCollection = Backbone.Collection.extend({
model: Dataset
});
/**
* Provides table-based, dynamic view of a tabular dataset.
*/
var TabularDatasetChunkedView = Backbone.View.extend({
initialize: function(options) {},
render: function() {
// Add loading indicator div.
this.$el.append( $('<div/>').attr('id', 'loading_indicator') );
// Add data table and header.
var data_table = $('<table/>').attr({
id: 'content_table',
cellpadding: 0
});
this.$el.append(data_table);
var column_names = this.model.get_metadata('column_names');
if (column_names) {
data_table.append('<tr><th>' + column_names.join('</th><th>') + '</th></tr>');
}
// Add first chunk.
var first_chunk = this.model.get('first_data_chunk');
if (first_chunk) {
this._renderChunk(first_chunk);
}
// Show new chunks during scrolling.
var self = this;
$(window).scroll(function() {
if ($(window).scrollTop() === $(document).height() - $(window).height()) {
$.when(self.model.get_next_chunk()).then(function(result) {
if (result) {
self._renderChunk(result);
}
});
}
});
$('#loading_indicator').ajaxStart(function(){
$(this).show();
}).ajaxStop(function(){
$(this).hide();
});
},
// -- Helper functions. --
_renderCell: function(cell_contents, index, colspan) {
var column_types = this.model.get_metadata('column_types');
if (colspan !== undefined) {
return $('<td>').attr('colspan', colspan).addClass('stringalign').text(cell_contents);
}
else if (column_types[index] === 'str' || column_types === 'list') {
/* Left align all str columns, right align the rest */
return $('<td>').addClass('stringalign').text(cell_contents);
}
else {
return $('<td>').text(cell_contents);
}
},
_renderRow: function(line) {
// Check length of cells to ensure this is a complete row.
var cells = line.split('\t'),
row = $('<tr>'),
num_columns = this.model.get_metadata('columns');
if (cells.length === num_columns) {
_.each(cells, function(cell_contents, index) {
row.append(this._renderCell(cell_contents, index));
}, this);
}
else if (cells.length > num_columns) {
// SAM file or like format with optional metadata included.
_.each(cells.slice(0, num_columns - 1), function(cell_contents, index) {
row.append(this._renderCell(cell_contents, index));
}, this);
row.append(this._renderCell(cells.slice(num_columns - 1).join('\t'), num_columns - 1));
}
else if (num_columns > 5 && cells.length === num_columns - 1 ) {
// SAM file or like format with optional metadata missing.
_.each(cells, function(cell_contents, index) {
row.append(this._renderCell(cell_contents, index));
}, this);
row.append($('<td>'));
}
else {
// Comment line, just return the one cell.
row.append(this._renderCell(line, 0, num_columns));
}
return row;
},
_renderChunk: function(chunk) {
var data_table = this.$el.find('table');
_.each(chunk.ck_data.split('\n'), function(line, index) {
data_table.append(this._renderRow(line));
}, this);
}
});
return {
Dataset: Dataset,
DatasetCollection: DatasetCollection
TabularDataset: TabularDataset,
DatasetCollection: DatasetCollection,
TabularDatasetChunkedView: TabularDatasetChunkedView
};
});
+1 -1
View File
@@ -1 +1 @@
define(["libs/backbone/backbone-relational"],function(){var a=Backbone.RelationalModel.extend({});var b=Backbone.RelationalModel.extend({defaults:{id:"",type:"",name:"",hda_ldda:"hda",metadata:null},initialize:function(){var d=new a();_.each(_.keys(this.attributes),function(e){if(e.indexOf("metadata_")===0){var f=e.split("metadata_")[1];d.set(f,this.attributes[e]);delete this.attributes[e]}},this);this.set("metadata",d)},get_metadata:function(d){return this.attributes.metadata.get(d)},urlRoot:galaxy_paths.get("datasets_url")});var c=Backbone.Collection.extend({model:b});return{Dataset:b,DatasetCollection:c}});
define(["libs/backbone/backbone-relational"],function(){var b=Backbone.RelationalModel.extend({});var c=Backbone.RelationalModel.extend({defaults:{id:"",type:"",name:"",hda_ldda:"hda",metadata:null},initialize:function(){var f=new b();_.each(_.keys(this.attributes),function(g){if(g.indexOf("metadata_")===0){var h=g.split("metadata_")[1];f.set(h,this.attributes[g]);delete this.attributes[g]}},this);this.set("metadata",f)},get_metadata:function(f){return this.attributes.metadata.get(f)},urlRoot:galaxy_paths.get("datasets_url")});var a=c.extend({defaults:_.extend({},c.prototype.defaults,{chunk_url:null,first_data_chunk:null,chunk_index:-1,at_eof:false}),initialize:function(f){c.prototype.initialize.call(this);chunk_index=(this.attributes.first_data_chunk?1:0)},set_first_chunk:function(f){this.attributes.first_data_chunk=f;this.attributes.chunk_index=1},get_next_chunk:function(){if(this.attributes.at_eof){return null}var f=this,g=$.Deferred();$.getJSON(this.attributes.chunk_url,{chunk:f.attributes.chunk_index++}).success(function(h){var i;if(h.ck_data!==""){i=h}else{f.attributes.at_eof=true;i=null}g.resolve(i)});return g}});var e=Backbone.Collection.extend({model:c});var d=Backbone.View.extend({initialize:function(f){},render:function(){this.$el.append($("<div/>").attr("id","loading_indicator"));var i=$("<table/>").attr({id:"content_table",cellpadding:0});this.$el.append(i);var f=this.model.get_metadata("column_names");if(f){i.append("<tr><th>"+f.join("</th><th>")+"</th></tr>")}var h=this.model.get("first_data_chunk");if(h){this._renderChunk(h)}var g=this;$(window).scroll(function(){if($(window).scrollTop()===$(document).height()-$(window).height()){$.when(g.model.get_next_chunk()).then(function(j){if(j){g._renderChunk(j)}})}});$("#loading_indicator").ajaxStart(function(){$(this).show()}).ajaxStop(function(){$(this).hide()})},_renderCell:function(h,f,i){var g=this.model.get_metadata("column_types");if(i!==undefined){return $("<td>").attr("colspan",i).addClass("stringalign").text(h)}else{if(g[f]==="str"||g==="list"){return $("<td>").addClass("stringalign").text(h)}else{return $("<td>").text(h)}}},_renderRow:function(f){var g=f.split("\t"),i=$("<tr>"),h=this.model.get_metadata("columns");if(g.length===h){_.each(g,function(k,j){i.append(this._renderCell(k,j))},this)}else{if(g.length>h){_.each(g.slice(0,h-1),function(k,j){i.append(this._renderCell(k,j))},this);i.append(this._renderCell(g.slice(h-1).join("\t"),h-1))}else{if(h>5&&g.length===h-1){_.each(g,function(k,j){i.append(this._renderCell(k,j))},this);i.append($("<td>"))}else{i.append(this._renderCell(f,0,h))}}}return i},_renderChunk:function(f){var g=this.$el.find("table");_.each(f.ck_data.split("\n"),function(h,i){g.append(this._renderRow(h))},this)}});return{Dataset:c,TabularDataset:a,DatasetCollection:e,TabularDatasetChunkedView:d}});
@@ -5,91 +5,31 @@
<%def name="javascripts()">
${parent.javascripts()}
${h.js( "libs/require" )}
<script type="text/javascript">
var DATASET_URL = "${h.url_for( controller='/dataset', action='display', dataset_id=trans.security.encode_id( dataset.id ))}";
var COLUMN_NUMBER = ${column_number};
var COLUMN_TYPES = ${column_types};
var COLUMN_NAMES = ${column_names};
require.config({
baseUrl: "${h.url_for('/static/scripts')}",
shim: {
"libs/underscore": { exports: "_" },
"libs/backbone/backbone": { exports: "Backbone" },
"libs/backbone/backbone-relational": ["libs/backbone/backbone"]
}
});
var chunk = ${chunk};
var current_chunk = 0;
require([ 'mvc/data' ], function(data) {
function renderCell(cell_contents, index, colspan){
if (colspan !== undefined){
return $('<td>').attr('colspan', colspan).addClass('stringalign').text(cell_contents);
}
else if (COLUMN_TYPES[index] == 'str' || COLUMN_TYPES[index] == 'list'){
/* Left align all str columns, right align the rest */
return $('<td>').addClass('stringalign').text(cell_contents);;
}
else{
return $('<td>').text(cell_contents);
}
}
function renderRow(line){
/* Check length of cells to ensure this is a complete row. */
var cells = line.split('\t');
var row = $('<tr>');
if (cells.length == COLUMN_NUMBER){
$.each(cells, function(index, cell_contents){
row.append(renderCell(cell_contents, index));
});
}
else if(cells.length > COLUMN_NUMBER){
/* SAM file or like format with optional metadata included */
$.each(cells.slice(0, COLUMN_NUMBER -1), function(index, cell_contents){
row.append(renderCell(cell_contents, index));
});
row.append(renderCell(cells.slice(COLUMN_NUMBER -1).join('\t'), COLUMN_NUMBER-1));
}
else if(COLUMN_NUMBER > 5 && cells.length == COLUMN_NUMBER - 1 ){
/* SAM file or like format with optional metadata missing */
$.each(cells, function(index, cell_contents){
row.append(renderCell(cell_contents, index));
});
row.append($('<td>'));
}
else{
/* Comment line, just return the one cell*/
row.append(renderCell(line, 0, COLUMN_NUMBER));
}
return row;
}
function renderChunk(chunk){
var table = $('#content_table');
if (chunk.ck_data == ""){
current_chunk = -1;
}
else if(chunk.ck_index === current_chunk + 1){
if (current_chunk === 0 && COLUMN_NAMES){
table.append('<tr><th>' + COLUMN_NAMES.join('</th><th>') + '</th></tr>');
}
var lines = chunk.ck_data.split('\n');
$.each(lines, function(index, line){
table.append(renderRow(line));
});
current_chunk = chunk.ck_index;
}
}
$(document).ready(function(){
renderChunk(chunk);
$(window).scroll(function(){
if ($(window).scrollTop() == $(document).height() - $(window).height()){
if (current_chunk !== -1){
$.getJSON(DATASET_URL,
{chunk: current_chunk},
function(result){renderChunk(result)});
}
}
});
$('#loading_indicator').ajaxStart(function(){
$(this).show();
}).ajaxStop(function(){
$(this).hide();
// Set up dataset and attributes.
var dataset = new data.TabularDataset( ${h.to_json_string( dataset.get_api_value() )} );
dataset.set('chunk_url',
"${h.url_for( controller='/dataset', action='display', dataset_id=trans.security.encode_id( dataset.id ))}");
dataset.set_first_chunk(${chunk})
// Set up, render, and add view.
var dataset_view = new data.TabularDatasetChunkedView({
model: dataset
});
dataset_view.render();
$('body').append(dataset_view.$el);
});
</script>
</%def>
@@ -97,7 +37,3 @@
<%def name="stylesheets()">
${parent.stylesheets()}
</%def>
<div id="loading_indicator" ></div>
<table id="content_table" cellpadding="0">
</table>