mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
644 lines
20 KiB
JavaScript
644 lines
20 KiB
JavaScript
// Additional dependencies: jQuery, underscore.
|
|
define(['mvc/ui/ui-modal', 'mvc/ui/ui-frames'], function(Modal, Frames) {
|
|
|
|
/**
|
|
* Dataset metedata.
|
|
*/
|
|
var DatasetMetadata = Backbone.Model.extend({});
|
|
|
|
/**
|
|
* A dataset. In Galaxy, datasets are associated with a history, so
|
|
* this object is also known as a HistoryDatasetAssociation.
|
|
*/
|
|
var Dataset = Backbone.Model.extend({
|
|
defaults: {
|
|
id: '',
|
|
type: '',
|
|
name: '',
|
|
hda_ldda: 'hda',
|
|
metadata: null
|
|
},
|
|
|
|
initialize: function() {
|
|
this._set_metadata();
|
|
|
|
// Update metadata on change.
|
|
this.on('change', this._set_metadata, this);
|
|
},
|
|
|
|
_set_metadata: function() {
|
|
var metadata = new DatasetMetadata();
|
|
|
|
// Move metadata from dataset attributes to metadata object.
|
|
_.each(_.keys(this.attributes), function(k) {
|
|
if (k.indexOf('metadata_') === 0) {
|
|
// Found metadata.
|
|
var new_key = k.split('metadata_')[1];
|
|
metadata.set(new_key, this.attributes[k]);
|
|
delete this.attributes[k];
|
|
}
|
|
}, this);
|
|
|
|
// Because this is an internal change, silence it.
|
|
this.set('metadata', metadata, { 'silent': true });
|
|
},
|
|
|
|
/**
|
|
* Returns dataset metadata for a given attribute.
|
|
*/
|
|
get_metadata: function(attribute) {
|
|
return this.attributes.metadata.get(attribute);
|
|
},
|
|
|
|
urlRoot: galaxy_config.root + "api/datasets"
|
|
});
|
|
|
|
/**
|
|
* 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.
|
|
this.attributes.chunk_index = (this.attributes.first_data_chunk ? 1 : 0);
|
|
},
|
|
|
|
/**
|
|
* 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 a base for table-based, dynamic view of a tabular dataset.
|
|
* Do not instantiate directly; use either TopLevelTabularDatasetChunkedView
|
|
* or EmbeddedTabularDatasetChunkedView.
|
|
*/
|
|
var TabularDatasetChunkedView = Backbone.View.extend({
|
|
|
|
/**
|
|
* Initialize view and, importantly, set a scroll element.
|
|
*/
|
|
initialize: function(options) {
|
|
// Row count for rendering.
|
|
this.row_count = 0;
|
|
|
|
// CSS colors used in table.
|
|
this.header_color = '#AAA';
|
|
this.dark_row_color = '#DDD';
|
|
|
|
// load trackster button
|
|
new TabularButtonTracksterView({
|
|
model : options.model,
|
|
$el : this.$el
|
|
});
|
|
},
|
|
|
|
render: function() {
|
|
// 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'),
|
|
header_row = $('<tr/>').css('background-color', this.header_color).appendTo(data_table);
|
|
if (column_names) {
|
|
header_row.append('<th>' + column_names.join('</th><th>') + '</th>');
|
|
}
|
|
|
|
// 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,
|
|
// Flag to ensure that only one chunk is loaded at a time.
|
|
loading_chunk = false;
|
|
|
|
// Set up chunk loading when scrolling using the scrolling element.
|
|
this.scroll_elt.scroll(function() {
|
|
// If not already loading a chunk and have scrolled to the bottom of this element, get next chunk.
|
|
if ( !loading_chunk && self.scrolled_to_bottom() ) {
|
|
loading_chunk = true;
|
|
$.when(self.model.get_next_chunk()).then(function(result) {
|
|
if (result) {
|
|
self._renderChunk(result);
|
|
loading_chunk = false;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
$('#loading_indicator').ajaxStart(function(){
|
|
$(this).show();
|
|
}).ajaxStop(function(){
|
|
$(this).hide();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns true if user has scrolled to the bottom of the view.
|
|
*/
|
|
scrolled_to_bottom: function() {
|
|
return false;
|
|
},
|
|
|
|
// -- Helper functions. --
|
|
|
|
_renderCell: function(cell_contents, index, colspan) {
|
|
var $cell = $('<td>').text(cell_contents);
|
|
var column_types = this.model.get_metadata('column_types');
|
|
if (colspan !== undefined) {
|
|
$cell.attr('colspan', colspan).addClass('stringalign');
|
|
} else if (column_types) {
|
|
if (index < column_types.length) {
|
|
if (column_types[index] === 'str' || column_types === 'list') {
|
|
/* Left align all str columns, right align the rest */
|
|
$cell.addClass('stringalign');
|
|
}
|
|
}
|
|
}
|
|
return $cell;
|
|
},
|
|
|
|
_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 (this.row_count % 2 !== 0) {
|
|
row.css('background-color', this.dark_row_color);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
this.row_count++;
|
|
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);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Tabular view that is placed at the top level of page. Scrolling occurs
|
|
* view top-level elements outside of view.
|
|
*/
|
|
var TopLevelTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
|
|
|
|
initialize: function(options) {
|
|
TabularDatasetChunkedView.prototype.initialize.call(this, options);
|
|
|
|
// Scrolling happens in top-level elements.
|
|
scroll_elt = _.find(this.$el.parents(), function(p) {
|
|
return $(p).css('overflow') === 'auto';
|
|
});
|
|
|
|
// If no scrolling element found, use window.
|
|
if (!scroll_elt) { scroll_elt = window; }
|
|
|
|
// Wrap scrolling element for easy access.
|
|
this.scroll_elt = $(scroll_elt);
|
|
},
|
|
|
|
/**
|
|
* Returns true if user has scrolled to the bottom of the view.
|
|
*/
|
|
scrolled_to_bottom: function() {
|
|
return (this.$el.height() - this.scroll_elt.scrollTop() - this.scroll_elt.height() <= 0);
|
|
}
|
|
|
|
});
|
|
|
|
/**
|
|
* Tabular view tnat is embedded in a page. Scrolling occurs in view's el.
|
|
*/
|
|
var EmbeddedTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
|
|
|
|
initialize: function(options) {
|
|
TabularDatasetChunkedView.prototype.initialize.call(this, options);
|
|
|
|
// Because view is embedded, set up div to do scrolling.
|
|
this.scroll_elt = this.$el.css({
|
|
position: 'relative',
|
|
overflow: 'scroll',
|
|
height: this.options.height || '500px'
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns true if user has scrolled to the bottom of the view.
|
|
*/
|
|
scrolled_to_bottom: function() {
|
|
return this.$el.scrollTop() + this.$el.innerHeight() >= this.el.scrollHeight;
|
|
}
|
|
|
|
});
|
|
|
|
// button for trackster visualization
|
|
var TabularButtonTracksterView = Backbone.View.extend({
|
|
|
|
// gene region columns
|
|
col: {
|
|
chrom : null,
|
|
start : null,
|
|
end : null
|
|
},
|
|
|
|
// url for trackster
|
|
url_viz : null,
|
|
|
|
// dataset id
|
|
dataset_id : null,
|
|
|
|
// database key
|
|
genome_build: null,
|
|
|
|
// data type
|
|
data_type : null,
|
|
|
|
// backbone initialize
|
|
initialize: function (options) {
|
|
// check if environment is available
|
|
var Galaxy = parent.Galaxy;
|
|
|
|
// link galaxy modal or create one
|
|
if (Galaxy && Galaxy.modal) {
|
|
this.modal = Galaxy.modal;
|
|
}
|
|
|
|
// link galaxy frames
|
|
if (Galaxy && Galaxy.frame) {
|
|
this.frame = Galaxy.frame;
|
|
}
|
|
|
|
// check
|
|
if (!this.modal || !this.frame) {
|
|
return;
|
|
}
|
|
|
|
// model/metadata
|
|
var model = options.model;
|
|
var metadata = model.get('metadata');
|
|
|
|
// check for datatype
|
|
if (!model.get('data_type')) {
|
|
return;
|
|
}
|
|
|
|
// get data type
|
|
this.data_type = model.get('data_type');
|
|
|
|
// check for bed-file format
|
|
if (this.data_type == 'bed')
|
|
{
|
|
// verify that metadata exists
|
|
if (metadata.get('chromCol') && metadata.get('startCol') && metadata.get('endCol'))
|
|
{
|
|
// read in columns
|
|
this.col.chrom = metadata.get('chromCol') - 1;
|
|
this.col.start = metadata.get('startCol') - 1;
|
|
this.col.end = metadata.get('endCol') - 1;
|
|
} else {
|
|
console.log('TabularButtonTrackster : Bed-file metadata incomplete.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check for vcf-file format
|
|
if (this.data_type == 'vcf')
|
|
{
|
|
// search array
|
|
function search (str, array) {
|
|
for (var j = 0; j < array.length; j++)
|
|
if (array[j].match(str)) return j;
|
|
return -1;
|
|
};
|
|
|
|
// load
|
|
this.col.chrom = search('Chrom', metadata.get('column_names'));
|
|
this.col.start = search('Pos', metadata.get('column_names'));
|
|
this.col.end = null;
|
|
|
|
// verify that metadata exists
|
|
if (this.col.chrom == -1 || this.col.start == -1) {
|
|
console.log('TabularButtonTrackster : VCF-file metadata incomplete.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check
|
|
if(this.col.chrom === undefined) {
|
|
return;
|
|
}
|
|
|
|
// get dataset id
|
|
if (model.id) {
|
|
this.dataset_id = model.id;
|
|
} else {
|
|
console.log('TabularButtonTrackster : Dataset identification is missing.');
|
|
return;
|
|
}
|
|
|
|
// get url
|
|
if (model.get('url_viz')) {
|
|
this.url_viz = model.get('url_viz');
|
|
} else {
|
|
console.log('TabularButtonTrackster : Url for visualization controller is missing.');
|
|
return;
|
|
}
|
|
|
|
// get genome_build / database key
|
|
if (model.get('genome_build')) {
|
|
this.genome_build = model.get('genome_build');
|
|
}
|
|
|
|
// create the icon
|
|
var btn_viz = new IconButtonView({
|
|
model : new IconButton({
|
|
title : 'Visualize',
|
|
icon_class : 'chart_curve',
|
|
id : 'btn_viz'
|
|
})
|
|
});
|
|
|
|
// set element
|
|
this.setElement(options.$el);
|
|
|
|
// add to element
|
|
this.$el.append(btn_viz.render().$el);
|
|
|
|
// hide the button
|
|
this.hide();
|
|
},
|
|
|
|
// backbone events
|
|
events:
|
|
{
|
|
'mouseover tr' : 'show',
|
|
'mouseleave' : 'hide'
|
|
},
|
|
|
|
// show button
|
|
show: function (e) {
|
|
// is numeric
|
|
function is_numeric(n) {
|
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
|
};
|
|
|
|
// check
|
|
if(this.col.chrom === null)
|
|
return;
|
|
|
|
// get selected data line
|
|
var row = $(e.target).parent();
|
|
|
|
// verify that location has been found
|
|
var chrom = row.children().eq(this.col.chrom).html();
|
|
var start = row.children().eq(this.col.start).html();
|
|
|
|
// end is optional
|
|
var end = this.col.end ? row.children().eq(this.col.end).html() : start;
|
|
|
|
// double check location
|
|
if (!chrom.match("^#") && chrom !== "" && is_numeric(start)) {
|
|
|
|
// get target gene region
|
|
var btn_viz_pars = {
|
|
dataset_id : this.dataset_id,
|
|
gene_region : chrom + ":" + start + "-" + end
|
|
};
|
|
|
|
// get button position
|
|
var offset = row.offset();
|
|
var left = offset.left - 10;
|
|
var top = offset.top - $(window).scrollTop() + 3;
|
|
|
|
// update css
|
|
$('#btn_viz').css({'position': 'fixed', 'top': top + 'px', 'left': left + 'px'});
|
|
$('#btn_viz').off('click');
|
|
$('#btn_viz').click(this.create_trackster_action(this.url_viz, btn_viz_pars, this.genome_build));
|
|
|
|
// show the button
|
|
$('#btn_viz').show();
|
|
} else {
|
|
// hide the button
|
|
$('#btn_viz').hide();
|
|
}
|
|
},
|
|
|
|
// hide button
|
|
hide: function () {
|
|
this.$el.find('#btn_viz').hide();
|
|
},
|
|
|
|
// create action
|
|
create_trackster_action : function (vis_url, dataset_params, dbkey) {
|
|
// link this
|
|
var self = this;
|
|
|
|
// create function
|
|
return function() {
|
|
var listTracksParams = {};
|
|
if (dbkey) {
|
|
listTracksParams[ 'f-dbkey' ] = dbkey;
|
|
}
|
|
$.ajax({
|
|
url: vis_url + '/list_tracks?' + $.param( listTracksParams ),
|
|
dataType: 'html',
|
|
error: function() {
|
|
// show error message
|
|
self.modal.show({
|
|
title : 'Something went wrong!',
|
|
body : 'Unfortunately we could not add this dataset to the track browser. Please try again or contact us.',
|
|
buttons : {
|
|
'Cancel': function(){
|
|
self.modal.hide();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
success: function(table_html) {
|
|
self.modal.show({
|
|
title : 'View Data in a New or Saved Visualization',
|
|
buttons :{
|
|
'Cancel': function(){
|
|
self.modal.hide();
|
|
},
|
|
'View in saved visualization': function(){
|
|
// show modal with saved visualizations
|
|
self.modal.show(
|
|
{
|
|
title : 'Add Data to Saved Visualization',
|
|
body : table_html,
|
|
buttons : {
|
|
'Cancel': function(){
|
|
self.modal.hide();
|
|
},
|
|
'Add to visualization': function(){
|
|
// hide
|
|
self.modal.hide();
|
|
|
|
// search selected fields
|
|
self.modal.$el.find('input[name=id]:checked').each(function(){
|
|
// get visualization id
|
|
var vis_id = $(this).val();
|
|
dataset_params.id = vis_id;
|
|
|
|
// add widget
|
|
self.frame.add({
|
|
title : 'Trackster',
|
|
type : 'url',
|
|
content : vis_url + '/trackster?' + $.param(dataset_params)
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
'View in new visualization': function(){
|
|
// hide
|
|
self.modal.hide();
|
|
|
|
// add widget
|
|
self.frame.add({
|
|
title : 'Trackster',
|
|
type : 'url',
|
|
content : vis_url + '/trackster?' + $.param(dataset_params)
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return false;
|
|
};
|
|
}
|
|
});
|
|
|
|
// -- Utility functions. --
|
|
|
|
/**
|
|
* Create a model, attach it to a view, render view, and attach it to a parent element.
|
|
*/
|
|
var createModelAndView = function(model, view, model_config, parent_elt) {
|
|
// Create model, view.
|
|
var a_view = new view({
|
|
model: new model(model_config)
|
|
});
|
|
|
|
// Render view and add to parent element.
|
|
a_view.render();
|
|
if (parent_elt) {
|
|
parent_elt.append(a_view.$el);
|
|
}
|
|
|
|
return a_view;
|
|
};
|
|
|
|
/**
|
|
* Create a tabular dataset chunked view (and requisite tabular dataset model)
|
|
* and appends to parent_elt.
|
|
*/
|
|
var createTabularDatasetChunkedView = function(options) {
|
|
// Create and set model.
|
|
options.model = new TabularDataset(options.dataset_config);
|
|
|
|
var parent_elt = options.parent_elt;
|
|
var embedded = options.embedded;
|
|
|
|
// Clean up options so that only needed options are passed to view.
|
|
delete options.embedded;
|
|
delete options.parent_elt;
|
|
delete options.dataset_config;
|
|
|
|
// Create and set up view.
|
|
var view = (embedded ? new EmbeddedTabularDatasetChunkedView(options) :
|
|
new TopLevelTabularDatasetChunkedView(options));
|
|
view.render();
|
|
|
|
if (parent_elt) {
|
|
parent_elt.append(view.$el);
|
|
}
|
|
|
|
return view;
|
|
};
|
|
|
|
return {
|
|
Dataset: Dataset,
|
|
TabularDataset: TabularDataset,
|
|
DatasetCollection: DatasetCollection,
|
|
TabularDatasetChunkedView: TabularDatasetChunkedView,
|
|
createTabularDatasetChunkedView: createTabularDatasetChunkedView
|
|
};
|
|
|
|
});
|