// Additional dependencies: jQuery, underscore. define(['mvc/ui/ui-modal', 'mvc/ui/ui-frames', 'mvc/ui/icon-button'], function(Modal, Frames, mod_icon_btn) { /** * 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() { // Metadata can be passed in as a model or a set of attributes; if it's // already a model, there's no need to set metadata. if (!this.get('metadata')) { 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); this.attributes.chunk_url = galaxy_config.root + 'dataset/display?dataset_id=' + this.id; this.attributes.url_viz = galaxy_config.root + 'visualization'; }, /** * 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; this.loading_chunk = false; // load trackster button new TabularButtonTracksterView({ model : options.model, $el : this.$el }); }, expand_to_container: function(){ if (this.$el.height() < this.scroll_elt.height()){ this.attempt_to_fetch(); } }, attempt_to_fetch: function( func ){ var self = this; if ( !this.loading_chunk && this.scrolled_to_bottom() ) { this.loading_chunk = true; this.loading_indicator.show(); $.when(self.model.get_next_chunk()).then(function(result) { if (result) { self._renderChunk(result); self.loading_chunk = false; } self.loading_indicator.hide(); self.expand_to_container(); }); } }, render: function() { // Add loading indicator. this.loading_indicator = $('
').attr('id', 'loading_indicator'); this.$el.append(this.loading_indicator); // Add data table and header. var data_table = $('').attr({ id: 'content_table', cellpadding: 0 }); this.$el.append(data_table); var column_names = this.model.get_metadata('column_names'), header_container = $('').appendTo(data_table), header_row = $('').appendTo(header_container); if (column_names) { header_row.append(''); } else { for (var j = 1; j <= this.model.get_metadata('columns'); j++) { header_row.append(''); } } // Render first chunk. var self = this, first_chunk = this.model.get('first_data_chunk'); if (first_chunk) { // First chunk is bootstrapped, so render now. this._renderChunk(first_chunk); } else { // No bootstrapping, so get first chunk and then render. $.when(self.model.get_next_chunk()).then(function(result) { self._renderChunk(result); }); } // -- Show new chunks during scrolling. -- // Set up chunk loading when scrolling using the scrolling element. this.scroll_elt.scroll(function(){ self.attempt_to_fetch(); }); }, /** * 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 = $(''), num_columns = this.model.get_metadata('columns'); if (this.row_count % 2 !== 0) { row.addClass('dark_row'); } 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($('
' + column_names.join('') + '' + j + '').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[index] === '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 = $('
')); } 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) { if (line !== ''){ 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 file_ext : 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('file_ext')) { return; } // get data type this.file_ext = model.get('file_ext'); // check for bed-file format if (this.file_ext == '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.file_ext == '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 mod_icon_btn.IconButtonView({ model : new mod_icon_btn.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) { // If no model, create and set model from dataset config. if (!options.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); // If we're sticking this in another element, once it's appended check // to make sure we've filled enough space. // Without this, the scroll elements don't work. view.expand_to_container(); } return view; }; return { Dataset: Dataset, TabularDataset: TabularDataset, DatasetCollection: DatasetCollection, TabularDatasetChunkedView: TabularDatasetChunkedView, createTabularDatasetChunkedView: createTabularDatasetChunkedView }; });