PageRenderTime 59ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/static/scripts/mvc/data.js

https://bitbucket.org/remy_d1/galaxy-central-manageapi
JavaScript | 674 lines | 431 code | 101 blank | 142 comment | 67 complexity | c770f3072b96c50b03a0dcda8fb84889 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. // Additional dependencies: jQuery, underscore.
  2. define(['mvc/ui/ui-modal', 'mvc/ui/ui-frames'], function(Modal, Frames) {
  3. /**
  4. * Dataset metedata.
  5. */
  6. var DatasetMetadata = Backbone.Model.extend({});
  7. /**
  8. * A dataset. In Galaxy, datasets are associated with a history, so
  9. * this object is also known as a HistoryDatasetAssociation.
  10. */
  11. var Dataset = Backbone.Model.extend({
  12. defaults: {
  13. id: '',
  14. type: '',
  15. name: '',
  16. hda_ldda: 'hda',
  17. metadata: null
  18. },
  19. initialize: function() {
  20. // Metadata can be passed in as a model or a set of attributes; if it's
  21. // already a model, there's no need to set metadata.
  22. if (!this.get('metadata')) {
  23. this._set_metadata();
  24. }
  25. // Update metadata on change.
  26. this.on('change', this._set_metadata, this);
  27. },
  28. _set_metadata: function() {
  29. var metadata = new DatasetMetadata();
  30. // Move metadata from dataset attributes to metadata object.
  31. _.each(_.keys(this.attributes), function(k) {
  32. if (k.indexOf('metadata_') === 0) {
  33. // Found metadata.
  34. var new_key = k.split('metadata_')[1];
  35. metadata.set(new_key, this.attributes[k]);
  36. delete this.attributes[k];
  37. }
  38. }, this);
  39. // Because this is an internal change, silence it.
  40. this.set('metadata', metadata, { 'silent': true });
  41. },
  42. /**
  43. * Returns dataset metadata for a given attribute.
  44. */
  45. get_metadata: function(attribute) {
  46. return this.attributes.metadata.get(attribute);
  47. },
  48. urlRoot: galaxy_config.root + "api/datasets"
  49. });
  50. /**
  51. * A tabular dataset. This object extends dataset to provide incremental chunked data.
  52. */
  53. var TabularDataset = Dataset.extend({
  54. defaults: _.extend({}, Dataset.prototype.defaults, {
  55. chunk_url: null,
  56. first_data_chunk: null,
  57. chunk_index: -1,
  58. at_eof: false
  59. }),
  60. initialize: function(options) {
  61. Dataset.prototype.initialize.call(this);
  62. // If first data chunk is available, next chunk is 1.
  63. this.attributes.chunk_index = (this.attributes.first_data_chunk ? 1 : 0);
  64. this.attributes.chunk_url = galaxy_config.root + 'dataset/display?dataset_id=' + this.id;
  65. this.attributes.url_viz = galaxy_config.root + 'visualization';
  66. },
  67. /**
  68. * Returns a jQuery Deferred object that resolves to the next data chunk or null if at EOF.
  69. */
  70. get_next_chunk: function() {
  71. // If already at end of file, do nothing.
  72. if (this.attributes.at_eof) {
  73. return null;
  74. }
  75. // Get next chunk.
  76. var self = this,
  77. next_chunk = $.Deferred();
  78. $.getJSON(this.attributes.chunk_url, {
  79. chunk: self.attributes.chunk_index++
  80. }).success(function(chunk) {
  81. var rval;
  82. if (chunk.ck_data !== '') {
  83. // Found chunk.
  84. rval = chunk;
  85. }
  86. else {
  87. // At EOF.
  88. self.attributes.at_eof = true;
  89. rval = null;
  90. }
  91. next_chunk.resolve(rval);
  92. });
  93. return next_chunk;
  94. }
  95. });
  96. var DatasetCollection = Backbone.Collection.extend({
  97. model: Dataset
  98. });
  99. /**
  100. * Provides a base for table-based, dynamic view of a tabular dataset.
  101. * Do not instantiate directly; use either TopLevelTabularDatasetChunkedView
  102. * or EmbeddedTabularDatasetChunkedView.
  103. */
  104. var TabularDatasetChunkedView = Backbone.View.extend({
  105. /**
  106. * Initialize view and, importantly, set a scroll element.
  107. */
  108. initialize: function(options) {
  109. // Row count for rendering.
  110. this.row_count = 0;
  111. this.loading_chunk = false;
  112. // CSS colors used in table.
  113. this.header_color = '#AAA';
  114. this.dark_row_color = '#DDD';
  115. // load trackster button
  116. new TabularButtonTracksterView({
  117. model : options.model,
  118. $el : this.$el
  119. });
  120. },
  121. expand_to_container: function(){
  122. if (this.$el.height() < this.scroll_elt.height()){
  123. this.attempt_to_fetch();
  124. }
  125. },
  126. attempt_to_fetch: function( func ){
  127. var self = this;
  128. if ( !this.loading_chunk && this.scrolled_to_bottom() ) {
  129. this.loading_chunk = true;
  130. this.loading_indicator.show();
  131. $.when(self.model.get_next_chunk()).then(function(result) {
  132. if (result) {
  133. self._renderChunk(result);
  134. self.loading_chunk = false;
  135. }
  136. self.loading_indicator.hide();
  137. self.expand_to_container();
  138. });
  139. }
  140. },
  141. render: function() {
  142. // Add loading indicator.
  143. this.loading_indicator = $('<div/>').attr('id', 'loading_indicator');
  144. this.$el.append(this.loading_indicator);
  145. // Add data table and header.
  146. var data_table = $('<table/>').attr({
  147. id: 'content_table',
  148. cellpadding: 0
  149. });
  150. this.$el.append(data_table);
  151. var column_names = this.model.get_metadata('column_names'),
  152. header_row = $('<tr/>').css('background-color', this.header_color).appendTo(data_table);
  153. if (column_names) {
  154. header_row.append('<th>' + column_names.join('</th><th>') + '</th>');
  155. }
  156. // Render first chunk.
  157. var self = this,
  158. first_chunk = this.model.get('first_data_chunk');
  159. if (first_chunk) {
  160. // First chunk is bootstrapped, so render now.
  161. this._renderChunk(first_chunk);
  162. }
  163. else {
  164. // No bootstrapping, so get first chunk and then render.
  165. $.when(self.model.get_next_chunk()).then(function(result) {
  166. self._renderChunk(result);
  167. });
  168. }
  169. // -- Show new chunks during scrolling. --
  170. // Set up chunk loading when scrolling using the scrolling element.
  171. this.scroll_elt.scroll(function(){
  172. self.attempt_to_fetch();
  173. });
  174. },
  175. /**
  176. * Returns true if user has scrolled to the bottom of the view.
  177. */
  178. scrolled_to_bottom: function() {
  179. return false;
  180. },
  181. // -- Helper functions. --
  182. _renderCell: function(cell_contents, index, colspan) {
  183. var $cell = $('<td>').text(cell_contents);
  184. var column_types = this.model.get_metadata('column_types');
  185. if (colspan !== undefined) {
  186. $cell.attr('colspan', colspan).addClass('stringalign');
  187. } else if (column_types) {
  188. if (index < column_types.length) {
  189. if (column_types[index] === 'str' || column_types[index] === 'list') {
  190. /* Left align all str columns, right align the rest */
  191. $cell.addClass('stringalign');
  192. }
  193. }
  194. }
  195. return $cell;
  196. },
  197. _renderRow: function(line) {
  198. // Check length of cells to ensure this is a complete row.
  199. var cells = line.split('\t'),
  200. row = $('<tr>'),
  201. num_columns = this.model.get_metadata('columns');
  202. if (this.row_count % 2 !== 0) {
  203. row.css('background-color', this.dark_row_color);
  204. }
  205. if (cells.length === num_columns) {
  206. _.each(cells, function(cell_contents, index) {
  207. row.append(this._renderCell(cell_contents, index));
  208. }, this);
  209. }
  210. else if (cells.length > num_columns) {
  211. // SAM file or like format with optional metadata included.
  212. _.each(cells.slice(0, num_columns - 1), function(cell_contents, index) {
  213. row.append(this._renderCell(cell_contents, index));
  214. }, this);
  215. row.append(this._renderCell(cells.slice(num_columns - 1).join('\t'), num_columns - 1));
  216. }
  217. else if (num_columns > 5 && cells.length === num_columns - 1 ) {
  218. // SAM file or like format with optional metadata missing.
  219. _.each(cells, function(cell_contents, index) {
  220. row.append(this._renderCell(cell_contents, index));
  221. }, this);
  222. row.append($('<td>'));
  223. }
  224. else {
  225. // Comment line, just return the one cell.
  226. row.append(this._renderCell(line, 0, num_columns));
  227. }
  228. this.row_count++;
  229. return row;
  230. },
  231. _renderChunk: function(chunk) {
  232. var data_table = this.$el.find('table');
  233. _.each(chunk.ck_data.split('\n'), function(line, index) {
  234. if (line !== ''){
  235. data_table.append(this._renderRow(line));
  236. }
  237. }, this);
  238. }
  239. });
  240. /**
  241. * Tabular view that is placed at the top level of page. Scrolling occurs
  242. * view top-level elements outside of view.
  243. */
  244. var TopLevelTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
  245. initialize: function(options) {
  246. TabularDatasetChunkedView.prototype.initialize.call(this, options);
  247. // Scrolling happens in top-level elements.
  248. scroll_elt = _.find(this.$el.parents(), function(p) {
  249. return $(p).css('overflow') === 'auto';
  250. });
  251. // If no scrolling element found, use window.
  252. if (!scroll_elt) { scroll_elt = window; }
  253. // Wrap scrolling element for easy access.
  254. this.scroll_elt = $(scroll_elt);
  255. },
  256. /**
  257. * Returns true if user has scrolled to the bottom of the view.
  258. */
  259. scrolled_to_bottom: function() {
  260. return (this.$el.height() - this.scroll_elt.scrollTop() - this.scroll_elt.height() <= 0);
  261. }
  262. });
  263. /**
  264. * Tabular view tnat is embedded in a page. Scrolling occurs in view's el.
  265. */
  266. var EmbeddedTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
  267. initialize: function(options) {
  268. TabularDatasetChunkedView.prototype.initialize.call(this, options);
  269. // Because view is embedded, set up div to do scrolling.
  270. this.scroll_elt = this.$el.css({
  271. position: 'relative',
  272. overflow: 'scroll',
  273. height: this.options.height || '500px'
  274. });
  275. },
  276. /**
  277. * Returns true if user has scrolled to the bottom of the view.
  278. */
  279. scrolled_to_bottom: function() {
  280. return this.$el.scrollTop() + this.$el.innerHeight() >= this.el.scrollHeight;
  281. }
  282. });
  283. // button for trackster visualization
  284. var TabularButtonTracksterView = Backbone.View.extend({
  285. // gene region columns
  286. col: {
  287. chrom : null,
  288. start : null,
  289. end : null
  290. },
  291. // url for trackster
  292. url_viz : null,
  293. // dataset id
  294. dataset_id : null,
  295. // database key
  296. genome_build: null,
  297. // data type
  298. file_ext : null,
  299. // backbone initialize
  300. initialize: function (options) {
  301. // check if environment is available
  302. var Galaxy = parent.Galaxy;
  303. // link galaxy modal or create one
  304. if (Galaxy && Galaxy.modal) {
  305. this.modal = Galaxy.modal;
  306. }
  307. // link galaxy frames
  308. if (Galaxy && Galaxy.frame) {
  309. this.frame = Galaxy.frame;
  310. }
  311. // check
  312. if (!this.modal || !this.frame) {
  313. return;
  314. }
  315. // model/metadata
  316. var model = options.model;
  317. var metadata = model.get('metadata');
  318. // check for datatype
  319. if (!model.get('file_ext')) {
  320. return;
  321. }
  322. // get data type
  323. this.file_ext = model.get('file_ext');
  324. // check for bed-file format
  325. if (this.file_ext == 'bed')
  326. {
  327. // verify that metadata exists
  328. if (metadata.get('chromCol') && metadata.get('startCol') && metadata.get('endCol'))
  329. {
  330. // read in columns
  331. this.col.chrom = metadata.get('chromCol') - 1;
  332. this.col.start = metadata.get('startCol') - 1;
  333. this.col.end = metadata.get('endCol') - 1;
  334. } else {
  335. console.log('TabularButtonTrackster : Bed-file metadata incomplete.');
  336. return;
  337. }
  338. }
  339. // check for vcf-file format
  340. if (this.file_ext == 'vcf')
  341. {
  342. // search array
  343. function search (str, array) {
  344. for (var j = 0; j < array.length; j++)
  345. if (array[j].match(str)) return j;
  346. return -1;
  347. };
  348. // load
  349. this.col.chrom = search('Chrom', metadata.get('column_names'));
  350. this.col.start = search('Pos', metadata.get('column_names'));
  351. this.col.end = null;
  352. // verify that metadata exists
  353. if (this.col.chrom == -1 || this.col.start == -1) {
  354. console.log('TabularButtonTrackster : VCF-file metadata incomplete.');
  355. return;
  356. }
  357. }
  358. // check
  359. if(this.col.chrom === undefined) {
  360. return;
  361. }
  362. // get dataset id
  363. if (model.id) {
  364. this.dataset_id = model.id;
  365. } else {
  366. console.log('TabularButtonTrackster : Dataset identification is missing.');
  367. return;
  368. }
  369. // get url
  370. if (model.get('url_viz')) {
  371. this.url_viz = model.get('url_viz');
  372. } else {
  373. console.log('TabularButtonTrackster : Url for visualization controller is missing.');
  374. return;
  375. }
  376. // get genome_build / database key
  377. if (model.get('genome_build')) {
  378. this.genome_build = model.get('genome_build');
  379. }
  380. // create the icon
  381. var btn_viz = new IconButtonView({
  382. model : new IconButton({
  383. title : 'Visualize',
  384. icon_class : 'chart_curve',
  385. id : 'btn_viz'
  386. })
  387. });
  388. // set element
  389. this.setElement(options.$el);
  390. // add to element
  391. this.$el.append(btn_viz.render().$el);
  392. // hide the button
  393. this.hide();
  394. },
  395. // backbone events
  396. events:
  397. {
  398. 'mouseover tr' : 'show',
  399. 'mouseleave' : 'hide'
  400. },
  401. // show button
  402. show: function (e) {
  403. // is numeric
  404. function is_numeric(n) {
  405. return !isNaN(parseFloat(n)) && isFinite(n);
  406. };
  407. // check
  408. if(this.col.chrom === null)
  409. return;
  410. // get selected data line
  411. var row = $(e.target).parent();
  412. // verify that location has been found
  413. var chrom = row.children().eq(this.col.chrom).html();
  414. var start = row.children().eq(this.col.start).html();
  415. // end is optional
  416. var end = this.col.end ? row.children().eq(this.col.end).html() : start;
  417. // double check location
  418. if (!chrom.match("^#") && chrom !== "" && is_numeric(start)) {
  419. // get target gene region
  420. var btn_viz_pars = {
  421. dataset_id : this.dataset_id,
  422. gene_region : chrom + ":" + start + "-" + end
  423. };
  424. // get button position
  425. var offset = row.offset();
  426. var left = offset.left - 10;
  427. var top = offset.top - $(window).scrollTop() + 3;
  428. // update css
  429. $('#btn_viz').css({'position': 'fixed', 'top': top + 'px', 'left': left + 'px'});
  430. $('#btn_viz').off('click');
  431. $('#btn_viz').click(this.create_trackster_action(this.url_viz, btn_viz_pars, this.genome_build));
  432. // show the button
  433. $('#btn_viz').show();
  434. } else {
  435. // hide the button
  436. $('#btn_viz').hide();
  437. }
  438. },
  439. // hide button
  440. hide: function () {
  441. this.$el.find('#btn_viz').hide();
  442. },
  443. // create action
  444. create_trackster_action : function (vis_url, dataset_params, dbkey) {
  445. // link this
  446. var self = this;
  447. // create function
  448. return function() {
  449. var listTracksParams = {};
  450. if (dbkey) {
  451. listTracksParams[ 'f-dbkey' ] = dbkey;
  452. }
  453. $.ajax({
  454. url: vis_url + '/list_tracks?' + $.param( listTracksParams ),
  455. dataType: 'html',
  456. error: function() {
  457. // show error message
  458. self.modal.show({
  459. title : 'Something went wrong!',
  460. body : 'Unfortunately we could not add this dataset to the track browser. Please try again or contact us.',
  461. buttons : {
  462. 'Cancel': function(){
  463. self.modal.hide();
  464. }
  465. }
  466. });
  467. },
  468. success: function(table_html) {
  469. self.modal.show({
  470. title : 'View Data in a New or Saved Visualization',
  471. buttons :{
  472. 'Cancel': function(){
  473. self.modal.hide();
  474. },
  475. 'View in saved visualization': function(){
  476. // show modal with saved visualizations
  477. self.modal.show(
  478. {
  479. title : 'Add Data to Saved Visualization',
  480. body : table_html,
  481. buttons : {
  482. 'Cancel': function(){
  483. self.modal.hide();
  484. },
  485. 'Add to visualization': function(){
  486. // hide
  487. self.modal.hide();
  488. // search selected fields
  489. self.modal.$el.find('input[name=id]:checked').each(function(){
  490. // get visualization id
  491. var vis_id = $(this).val();
  492. dataset_params.id = vis_id;
  493. // add widget
  494. self.frame.add({
  495. title : 'Trackster',
  496. type : 'url',
  497. content : vis_url + '/trackster?' + $.param(dataset_params)
  498. });
  499. });
  500. }
  501. }
  502. });
  503. },
  504. 'View in new visualization': function(){
  505. // hide
  506. self.modal.hide();
  507. // add widget
  508. self.frame.add({
  509. title : 'Trackster',
  510. type : 'url',
  511. content : vis_url + '/trackster?' + $.param(dataset_params)
  512. });
  513. }
  514. }
  515. });
  516. }
  517. });
  518. return false;
  519. };
  520. }
  521. });
  522. // -- Utility functions. --
  523. /**
  524. * Create a model, attach it to a view, render view, and attach it to a parent element.
  525. */
  526. var createModelAndView = function(model, view, model_config, parent_elt) {
  527. // Create model, view.
  528. var a_view = new view({
  529. model: new model(model_config)
  530. });
  531. // Render view and add to parent element.
  532. a_view.render();
  533. if (parent_elt) {
  534. parent_elt.append(a_view.$el);
  535. }
  536. return a_view;
  537. };
  538. /**
  539. * Create a tabular dataset chunked view (and requisite tabular dataset model)
  540. * and appends to parent_elt.
  541. */
  542. var createTabularDatasetChunkedView = function(options) {
  543. // If no model, create and set model from dataset config.
  544. if (!options.model) {
  545. options.model = new TabularDataset(options.dataset_config);
  546. }
  547. var parent_elt = options.parent_elt;
  548. var embedded = options.embedded;
  549. // Clean up options so that only needed options are passed to view.
  550. delete options.embedded;
  551. delete options.parent_elt;
  552. delete options.dataset_config;
  553. // Create and set up view.
  554. var view = (embedded ? new EmbeddedTabularDatasetChunkedView(options) :
  555. new TopLevelTabularDatasetChunkedView(options));
  556. view.render();
  557. if (parent_elt) {
  558. parent_elt.append(view.$el);
  559. // If we're sticking this in another element, once it's appended check
  560. // to make sure we've filled enough space.
  561. // Without this, the scroll elements don't work.
  562. view.expand_to_container();
  563. }
  564. return view;
  565. };
  566. return {
  567. Dataset: Dataset,
  568. TabularDataset: TabularDataset,
  569. DatasetCollection: DatasetCollection,
  570. TabularDatasetChunkedView: TabularDatasetChunkedView,
  571. createTabularDatasetChunkedView: createTabularDatasetChunkedView
  572. };
  573. });