PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/static/scripts/mvc/data.js

https://bitbucket.org/galaxy/galaxy-central
JavaScript | 467 lines | 308 code | 62 blank | 97 comment | 36 complexity | 8ce3acec996fc9bc8965c26fe3f0fd2b MD5 | raw file
Possible License(s): CC-BY-3.0
  1. // Additional dependencies: jQuery, underscore.
  2. define(["libs/backbone/backbone-relational"], function() {
  3. /**
  4. * Dataset metedata.
  5. */
  6. var DatasetMetadata = Backbone.RelationalModel.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.RelationalModel.extend({
  12. defaults: {
  13. id: '',
  14. type: '',
  15. name: '',
  16. hda_ldda: 'hda',
  17. metadata: null
  18. },
  19. initialize: function() {
  20. // Set metadata.
  21. // FIXME: pass back a metadata dict and then Backbone-relational
  22. // can be used unpack metadata automatically.
  23. this._set_metadata();
  24. // Update metadata on change.
  25. this.on('change', this._set_metadata, this);
  26. },
  27. _set_metadata: function() {
  28. var metadata = new DatasetMetadata();
  29. // Move metadata from dataset attributes to metadata object.
  30. _.each(_.keys(this.attributes), function(k) {
  31. if (k.indexOf('metadata_') === 0) {
  32. // Found metadata.
  33. var new_key = k.split('metadata_')[1];
  34. metadata.set(new_key, this.attributes[k]);
  35. delete this.attributes[k];
  36. }
  37. }, this);
  38. // Because this is an internal change, silence it.
  39. this.set('metadata', metadata, { 'silent': true });
  40. },
  41. /**
  42. * Returns dataset metadata for a given attribute.
  43. */
  44. get_metadata: function(attribute) {
  45. return this.attributes.metadata.get(attribute);
  46. },
  47. urlRoot: galaxy_paths.get('datasets_url')
  48. });
  49. /**
  50. * A tabular dataset. This object extends dataset to provide incremental chunked data.
  51. */
  52. var TabularDataset = Dataset.extend({
  53. defaults: _.extend({}, Dataset.prototype.defaults, {
  54. chunk_url: null,
  55. first_data_chunk: null,
  56. chunk_index: -1,
  57. at_eof: false
  58. }),
  59. initialize: function(options) {
  60. Dataset.prototype.initialize.call(this);
  61. // If first data chunk is available, next chunk is 1.
  62. this.attributes.chunk_index = (this.attributes.first_data_chunk ? 1 : 0);
  63. },
  64. /**
  65. * Returns a jQuery Deferred object that resolves to the next data chunk or null if at EOF.
  66. */
  67. get_next_chunk: function() {
  68. // If already at end of file, do nothing.
  69. if (this.attributes.at_eof) {
  70. return null;
  71. }
  72. // Get next chunk.
  73. var self = this,
  74. next_chunk = $.Deferred();
  75. $.getJSON(this.attributes.chunk_url, {
  76. chunk: self.attributes.chunk_index++
  77. }).success(function(chunk) {
  78. var rval;
  79. if (chunk.ck_data !== '') {
  80. // Found chunk.
  81. rval = chunk;
  82. }
  83. else {
  84. // At EOF.
  85. self.attributes.at_eof = true;
  86. rval = null;
  87. }
  88. next_chunk.resolve(rval);
  89. });
  90. return next_chunk;
  91. }
  92. });
  93. var DatasetCollection = Backbone.Collection.extend({
  94. model: Dataset
  95. });
  96. /**
  97. * Provides table-based, dynamic view of a tabular dataset.
  98. * NOTE: view's el must be in DOM already and provided when
  99. * createing the view so that scrolling event can be attached
  100. * to the correct container.
  101. */
  102. var TabularDatasetChunkedView = Backbone.View.extend({
  103. initialize: function(options)
  104. {
  105. // load trackster button
  106. (new TabularButtonTracksterView(options)).render();
  107. },
  108. render: function()
  109. {
  110. // Add data table and header.
  111. var data_table = $('<table/>').attr({
  112. id: 'content_table',
  113. cellpadding: 0
  114. });
  115. this.$el.append(data_table);
  116. var column_names = this.model.get_metadata('column_names');
  117. if (column_names) {
  118. data_table.append('<tr><th>' + column_names.join('</th><th>') + '</th></tr>');
  119. }
  120. // Add first chunk.
  121. var first_chunk = this.model.get('first_data_chunk');
  122. if (first_chunk) {
  123. this._renderChunk(first_chunk);
  124. }
  125. // -- Show new chunks during scrolling. --
  126. var self = this,
  127. // Element that does the scrolling.
  128. scroll_elt = _.find(this.$el.parents(), function(p) {
  129. return $(p).css('overflow') === 'auto';
  130. }),
  131. // Flag to ensure that only one chunk is loaded at a time.
  132. loading_chunk = false;
  133. // If no scrolling element found, use window.
  134. if (!scroll_elt) { scroll_elt = window; }
  135. // Wrap scrolling element for easy access.
  136. scroll_elt = $(scroll_elt);
  137. // Set up chunk loading when scrolling using the scrolling element.
  138. scroll_elt.scroll(function() {
  139. // If not already loading a chunk and have scrolled to the bottom of this element, get next chunk.
  140. if ( !loading_chunk && (self.$el.height() - scroll_elt.scrollTop() - scroll_elt.height() <= 0) ) {
  141. loading_chunk = true;
  142. $.when(self.model.get_next_chunk()).then(function(result) {
  143. if (result) {
  144. self._renderChunk(result);
  145. loading_chunk = false;
  146. }
  147. });
  148. }
  149. });
  150. $('#loading_indicator').ajaxStart(function(){
  151. $(this).show();
  152. }).ajaxStop(function(){
  153. $(this).hide();
  154. });
  155. },
  156. // -- Helper functions. --
  157. _renderCell: function(cell_contents, index, colspan) {
  158. var column_types = this.model.get_metadata('column_types');
  159. if (colspan !== undefined) {
  160. return $('<td>').attr('colspan', colspan).addClass('stringalign').text(cell_contents);
  161. }
  162. else if (column_types[index] === 'str' || column_types === 'list') {
  163. /* Left align all str columns, right align the rest */
  164. return $('<td>').addClass('stringalign').text(cell_contents);
  165. }
  166. else {
  167. return $('<td>').text(cell_contents);
  168. }
  169. },
  170. _renderRow: function(line) {
  171. // Check length of cells to ensure this is a complete row.
  172. var cells = line.split('\t'),
  173. row = $('<tr>'),
  174. num_columns = this.model.get_metadata('columns');
  175. if (cells.length === num_columns) {
  176. _.each(cells, function(cell_contents, index) {
  177. row.append(this._renderCell(cell_contents, index));
  178. }, this);
  179. }
  180. else if (cells.length > num_columns) {
  181. // SAM file or like format with optional metadata included.
  182. _.each(cells.slice(0, num_columns - 1), function(cell_contents, index) {
  183. row.append(this._renderCell(cell_contents, index));
  184. }, this);
  185. row.append(this._renderCell(cells.slice(num_columns - 1).join('\t'), num_columns - 1));
  186. }
  187. else if (num_columns > 5 && cells.length === num_columns - 1 ) {
  188. // SAM file or like format with optional metadata missing.
  189. _.each(cells, function(cell_contents, index) {
  190. row.append(this._renderCell(cell_contents, index));
  191. }, this);
  192. row.append($('<td>'));
  193. }
  194. else {
  195. // Comment line, just return the one cell.
  196. row.append(this._renderCell(line, 0, num_columns));
  197. }
  198. return row;
  199. },
  200. _renderChunk: function(chunk) {
  201. var data_table = this.$el.find('table');
  202. _.each(chunk.ck_data.split('\n'), function(line, index) {
  203. data_table.append(this._renderRow(line));
  204. }, this);
  205. }
  206. });
  207. // button for trackster visualization
  208. var TabularButtonTracksterView = Backbone.View.extend(
  209. {
  210. // gene region columns
  211. col: {
  212. chrom : null,
  213. start : null,
  214. end : null
  215. },
  216. // url for trackster
  217. url_viz : null,
  218. // dataset id
  219. dataset_id : null,
  220. // database key
  221. genome_build: null,
  222. // backbone initialize
  223. initialize: function (options)
  224. {
  225. // verify that metadata exists
  226. var metadata = options.model.attributes.metadata.attributes;
  227. if (typeof metadata.chromCol === "undefined" || typeof metadata.startCol === "undefined" || typeof metadata.endCol === "undefined")
  228. console.log("TabularButtonTrackster : Metadata for column identification is missing.");
  229. else {
  230. // read in columns
  231. this.col.chrom = metadata.chromCol - 1;
  232. this.col.start = metadata.startCol - 1;
  233. this.col.end = metadata.endCol - 1;
  234. }
  235. // check
  236. if(this.col.chrom === null)
  237. return;
  238. // get dataset id
  239. if (typeof options.model.attributes.id === "undefined")
  240. console.log("TabularButtonTrackster : Dataset identification is missing.");
  241. else
  242. this.dataset_id = options.model.attributes.id;
  243. // get url
  244. if (typeof options.model.attributes.url_viz === "undefined")
  245. console.log("TabularButtonTrackster : Url for visualization controller is missing.");
  246. else
  247. this.url_viz = options.model.attributes.url_viz;
  248. // get genome_build / database key
  249. if (typeof options.model.attributes.genome_build !== "undefined")
  250. this.genome_build = options.model.attributes.genome_build;
  251. },
  252. // backbone events
  253. events:
  254. {
  255. 'mouseover tr' : 'btn_viz_show',
  256. 'mouseleave' : 'btn_viz_hide'
  257. },
  258. // show button
  259. btn_viz_show: function (e)
  260. {
  261. // check
  262. if(this.col.chrom === null)
  263. return;
  264. // get selected data line
  265. var row = $(e.target).parent();
  266. // verify that location has been found
  267. var chrom = row.children().eq(this.col.chrom).html();
  268. var start = row.children().eq(this.col.start).html();
  269. var end = row.children().eq(this.col.end).html();
  270. if (chrom !== "" && start !== "" && end !== "")
  271. {
  272. // get target gene region
  273. var btn_viz_pars = {
  274. dataset_id : this.dataset_id,
  275. gene_region : chrom + ":" + start + "-" + end
  276. };
  277. // get button position
  278. var offset = row.offset();
  279. var left = offset.left - 10;
  280. var top = offset.top;
  281. // update css
  282. $('#btn_viz').css({'position': 'fixed', 'top': top + 'px', 'left': left + 'px'});
  283. $('#btn_viz').off('click');
  284. $('#btn_viz').click(this.create_trackster_action(this.url_viz, btn_viz_pars, this.genome_build));
  285. // show the button
  286. $('#btn_viz').show();
  287. }
  288. },
  289. // hide button
  290. btn_viz_hide: function ()
  291. {
  292. // hide button from screen
  293. $('#btn_viz').hide();
  294. },
  295. // create action
  296. create_trackster_action : function (vis_url, dataset_params, dbkey) {
  297. return function() {
  298. var listTracksParams = {};
  299. if (dbkey){
  300. // list_tracks seems to use 'f-dbkey' (??)
  301. listTracksParams[ 'f-dbkey' ] = dbkey;
  302. }
  303. $.ajax({
  304. url: vis_url + '/list_tracks?' + $.param( listTracksParams ),
  305. dataType: "html",
  306. error: function() { alert( ( "Could not add this dataset to browser" ) + '.' ); },
  307. success: function(table_html) {
  308. var parent = window.parent;
  309. parent.show_modal( ( "View Data in a New or Saved Visualization" ), "", {
  310. "Cancel": function() {
  311. parent.hide_modal();
  312. },
  313. "View in saved visualization": function() {
  314. // Show new modal with saved visualizations.
  315. parent.show_modal( ( "Add Data to Saved Visualization" ), table_html, {
  316. "Cancel": function() {
  317. parent.hide_modal();
  318. },
  319. "Add to visualization": function() {
  320. $(parent.document).find('input[name=id]:checked').each(function() {
  321. var vis_id = $(this).val();
  322. dataset_params.id = vis_id;
  323. // add widget
  324. parent.frame_manager.frame_new(
  325. {
  326. title : "Trackster",
  327. type : "url",
  328. content : vis_url + "/trackster?" + $.param(dataset_params)
  329. });
  330. // hide
  331. parent.hide_modal();
  332. });
  333. }
  334. });
  335. },
  336. "View in new visualization": function() {
  337. var url = vis_url + "/trackster?" + $.param(dataset_params);
  338. // add widget
  339. parent.frame_manager.frame_new(
  340. {
  341. title : "Trackster",
  342. type : "url",
  343. content : url
  344. });
  345. // hide
  346. parent.hide_modal();
  347. }
  348. });
  349. }
  350. });
  351. return false;
  352. };
  353. },
  354. // render frame
  355. render: function()
  356. {
  357. // render the icon from template
  358. var btn_viz = new IconButtonView({ model : new IconButton({
  359. title : 'Visualize',
  360. icon_class : 'chart_curve',
  361. id : 'btn_viz'
  362. })});
  363. // add it to the screen
  364. this.$el.append(btn_viz.render().$el);
  365. // hide the button
  366. $('#btn_viz').hide();
  367. }
  368. });
  369. // -- Utility functions. --
  370. /**
  371. * Create a model, attach it to a view, render view, and attach it to a parent element.
  372. */
  373. var createModelAndView = function(model, view, model_config, parent_elt) {
  374. // Create model, view.
  375. var a_view = new view({
  376. model: new model(model_config)
  377. });
  378. // Render view and add to parent element.
  379. a_view.render();
  380. if (parent_elt) {
  381. parent_elt.append(a_view.$el);
  382. }
  383. return a_view;
  384. };
  385. /**
  386. * Create a tabular dataset chunked view (and requisite tabular dataset model)
  387. * and appends to parent_elt.
  388. */
  389. var createTabularDatasetChunkedView = function(dataset_config, parent_elt) {
  390. // Create view element and add to parent.
  391. var view_div = $('<div/>').appendTo(parent_elt);
  392. // default viewer
  393. return new TabularDatasetChunkedView({
  394. el: view_div,
  395. model: new TabularDataset(dataset_config)
  396. }).render();
  397. };
  398. return {
  399. Dataset: Dataset,
  400. TabularDataset: TabularDataset,
  401. DatasetCollection: DatasetCollection,
  402. TabularDatasetChunkedView: TabularDatasetChunkedView,
  403. createTabularDatasetChunkedView: createTabularDatasetChunkedView
  404. };
  405. });